From 264fc6e1b02dc53380c3ba2f7cdcbbab82a6b546 Mon Sep 17 00:00:00 2001 From: David Dieulivol <ddieulivol@gitlab.com> Date: Mon, 24 Apr 2023 20:18:17 +0000 Subject: [PATCH] Move mappings base class to a module We need to reuse the methods from Mappings::Base class outside of the Mappings module, so we extract the content of that class to a module that can be included anywhere. --- .../find_files_using_feature_flags_spec.rb | 122 ++++++++++++++++++ .../predictive_tests_helper_spec.rb} | 13 +- tooling/lib/tooling/find_changes.rb | 4 +- .../tooling/find_files_using_feature_flags.rb | 48 +++++++ tooling/lib/tooling/find_tests.rb | 4 +- .../predictive_tests_helper.rb} | 6 +- .../mappings/graphql_base_type_mappings.rb | 6 +- .../mappings/js_to_system_specs_mappings.rb | 6 +- .../mappings/partial_to_views_mappings.rb | 6 +- .../tooling/mappings/view_to_js_mappings.rb | 6 +- .../mappings/view_to_system_specs_mappings.rb | 6 +- tooling/lib/tooling/predictive_tests.rb | 2 + 12 files changed, 209 insertions(+), 20 deletions(-) create mode 100644 spec/tooling/lib/tooling/find_files_using_feature_flags_spec.rb rename spec/tooling/lib/tooling/{mappings/base_spec.rb => helpers/predictive_tests_helper_spec.rb} (72%) create mode 100644 tooling/lib/tooling/find_files_using_feature_flags.rb rename tooling/lib/tooling/{mappings/base.rb => helpers/predictive_tests_helper.rb} (88%) diff --git a/spec/tooling/lib/tooling/find_files_using_feature_flags_spec.rb b/spec/tooling/lib/tooling/find_files_using_feature_flags_spec.rb new file mode 100644 index 0000000000000..f553d34768fec --- /dev/null +++ b/spec/tooling/lib/tooling/find_files_using_feature_flags_spec.rb @@ -0,0 +1,122 @@ +# frozen_string_literal: true + +require 'tempfile' +require_relative '../../../../tooling/lib/tooling/find_files_using_feature_flags' + +RSpec.describe Tooling::FindFilesUsingFeatureFlags, feature_category: :tooling do + attr_accessor :changed_files_file + + let(:changed_files_pathname) { changed_files_file.path } + let(:instance) { described_class.new(changed_files_pathname: changed_files_pathname) } + let(:changed_files_content) { '' } + + around do |example| + self.changed_files_file = Tempfile.new('changed_files_file') + + # See https://ruby-doc.org/stdlib-1.9.3/libdoc/tempfile/rdoc/ + # Tempfile.html#class-Tempfile-label-Explicit+close + begin + example.run + ensure + changed_files_file.close + changed_files_file.unlink + end + end + + before do + allow(File).to receive(:exist?).and_call_original + allow(File).to receive(:read).and_call_original + + File.write(changed_files_pathname, changed_files_content) + end + + describe '#execute' do + subject { instance.execute } + + let(:valid_ff_pathname_1) { 'config/feature_flags/development/my_feature_flag.yml' } + let(:valid_ff_pathname_2) { 'config/feature_flags/development/my_other_feature_flag.yml' } + let(:changed_files_content) { "#{valid_ff_pathname_1} #{valid_ff_pathname_2}" } + let(:ruby_files) { [] } + + before do + allow(File).to receive(:exist?).with(valid_ff_pathname_1).and_return(true) + allow(File).to receive(:exist?).with(valid_ff_pathname_2).and_return(true) + allow(Dir).to receive(:[]).with('**/*.rb').and_return(ruby_files) + end + + context 'when no ruby files are using the modified feature flag' do + let(:ruby_files) { [] } + + it 'does not add anything to the input file' do + expect { subject }.not_to change { File.read(changed_files_pathname) } + end + end + + context 'when some ruby files are using the modified feature flags' do + let(:matching_ruby_file_1) { 'first-ruby-file' } + let(:matching_ruby_file_2) { 'second-ruby-file' } + let(:not_matching_ruby_file) { 'third-ruby-file' } + let(:ruby_files) { [matching_ruby_file_1, matching_ruby_file_2, not_matching_ruby_file] } + + before do + allow(File).to receive(:read).with(matching_ruby_file_1).and_return('my_feature_flag') + allow(File).to receive(:read).with(matching_ruby_file_2).and_return('my_other_feature_flag') + allow(File).to receive(:read).with(not_matching_ruby_file).and_return('other text') + end + + it 'add the matching ruby files to the input file' do + expect { subject }.to change { File.read(changed_files_pathname) } + .from(changed_files_content) + .to("#{changed_files_content} #{matching_ruby_file_1} #{matching_ruby_file_2}") + end + end + end + + describe '#filter_files' do + subject { instance.filter_files } + + let(:changed_files_content) { path_to_file } + + context 'when the file does not exist on disk' do + let(:path_to_file) { "config/other_feature_flags_folder/feature.yml" } + + before do + allow(File).to receive(:exist?).with(path_to_file).and_return(false) + end + + it 'returns an empty array' do + expect(subject).to be_empty + end + end + + context 'when the file exists on disk' do + before do + allow(File).to receive(:exist?).with(path_to_file).and_return(true) + end + + context 'when the file is not in the features folder' do + let(:path_to_file) { "config/other_folder/development/feature.yml" } + + it 'returns an empty array' do + expect(subject).to be_empty + end + end + + context 'when the filename does not have the correct extension' do + let(:path_to_file) { "config/feature_flags/development/feature.rb" } + + it 'returns an empty array' do + expect(subject).to be_empty + end + end + + context 'when the ruby file uses a valid feature flag file' do + let(:path_to_file) { "config/feature_flags/development/feature.yml" } + + it 'returns the file' do + expect(subject).to match_array(path_to_file) + end + end + end + end +end diff --git a/spec/tooling/lib/tooling/mappings/base_spec.rb b/spec/tooling/lib/tooling/helpers/predictive_tests_helper_spec.rb similarity index 72% rename from spec/tooling/lib/tooling/mappings/base_spec.rb rename to spec/tooling/lib/tooling/helpers/predictive_tests_helper_spec.rb index 935f833fa8b09..48a5866ac568c 100644 --- a/spec/tooling/lib/tooling/mappings/base_spec.rb +++ b/spec/tooling/lib/tooling/helpers/predictive_tests_helper_spec.rb @@ -1,12 +1,19 @@ # frozen_string_literal: true -require_relative '../../../../../tooling/lib/tooling/mappings/view_to_js_mappings' +require 'tempfile' +require_relative '../../../../../tooling/lib/tooling/helpers/predictive_tests_helper' + +class MockClass # rubocop:disable Gitlab/NamespacedClass + include Tooling::Helpers::PredictiveTestsHelper +end + +RSpec.describe Tooling::Helpers::PredictiveTestsHelper, feature_category: :tooling do + let(:instance) { MockClass.new } -RSpec.describe Tooling::Mappings::Base, feature_category: :tooling do describe '#folders_for_available_editions' do let(:base_folder_path) { 'app/views' } - subject { described_class.new.folders_for_available_editions(base_folder_path) } + subject { instance.folders_for_available_editions(base_folder_path) } context 'when FOSS' do before do diff --git a/tooling/lib/tooling/find_changes.rb b/tooling/lib/tooling/find_changes.rb index d8373d1124581..25381e1a89446 100755 --- a/tooling/lib/tooling/find_changes.rb +++ b/tooling/lib/tooling/find_changes.rb @@ -2,11 +2,11 @@ # frozen_string_literal: true require 'gitlab' -require_relative 'helpers/file_handler' +require_relative 'helpers/predictive_tests_helper' module Tooling class FindChanges - include Helpers::FileHandler + include Helpers::PredictiveTestsHelper def initialize( from:, diff --git a/tooling/lib/tooling/find_files_using_feature_flags.rb b/tooling/lib/tooling/find_files_using_feature_flags.rb new file mode 100644 index 0000000000000..27cace604089b --- /dev/null +++ b/tooling/lib/tooling/find_files_using_feature_flags.rb @@ -0,0 +1,48 @@ +# frozen_string_literal: true + +require 'test_file_finder' +require_relative 'helpers/predictive_tests_helper' + +module Tooling + class FindFilesUsingFeatureFlags + include Helpers::PredictiveTestsHelper + + def initialize(changed_files_pathname:, feature_flags_base_folder: 'config/feature_flags') + @changed_files_pathname = changed_files_pathname + @changed_files = read_array_from_file(changed_files_pathname) + @feature_flags_base_folders = folders_for_available_editions(feature_flags_base_folder) + end + + def execute + ff_union_regexp = Regexp.union(feature_flag_filenames) + + files_using_modified_feature_flags = ruby_files.select do |ruby_file| + ruby_file if ff_union_regexp.match?(File.read(ruby_file)) + end + + write_array_to_file(changed_files_pathname, files_using_modified_feature_flags.uniq) + end + + def filter_files + @_filter_files ||= changed_files.select do |filename| + filename.start_with?(*feature_flags_base_folders) && + File.basename(filename).end_with?('.yml') && + File.exist?(filename) + end + end + + private + + def feature_flag_filenames + filter_files.map do |feature_flag_pathname| + File.basename(feature_flag_pathname).delete_suffix('.yml') + end + end + + def ruby_files + Dir["**/*.rb"].reject { |pathname| pathname.start_with?('vendor') } + end + + attr_reader :changed_files, :changed_files_pathname, :feature_flags_base_folders + end +end diff --git a/tooling/lib/tooling/find_tests.rb b/tooling/lib/tooling/find_tests.rb index f26c1eacdc7e3..bf7a608878b67 100644 --- a/tooling/lib/tooling/find_tests.rb +++ b/tooling/lib/tooling/find_tests.rb @@ -1,11 +1,11 @@ # frozen_string_literal: true require 'test_file_finder' -require_relative 'helpers/file_handler' +require_relative 'helpers/predictive_tests_helper' module Tooling class FindTests - include Helpers::FileHandler + include Helpers::PredictiveTestsHelper def initialize(changed_files_pathname, predictive_tests_pathname) @predictive_tests_pathname = predictive_tests_pathname diff --git a/tooling/lib/tooling/mappings/base.rb b/tooling/lib/tooling/helpers/predictive_tests_helper.rb similarity index 88% rename from tooling/lib/tooling/mappings/base.rb rename to tooling/lib/tooling/helpers/predictive_tests_helper.rb index 27a9a0925b04f..b8e5a30024e5f 100644 --- a/tooling/lib/tooling/mappings/base.rb +++ b/tooling/lib/tooling/helpers/predictive_tests_helper.rb @@ -5,9 +5,9 @@ # Returns system specs files that are related to the JS files that were changed in the MR. module Tooling - module Mappings - class Base - include Helpers::FileHandler + module Helpers + module PredictiveTestsHelper + include FileHandler # Input: A folder # Output: An array of folders, each prefixed with a GitLab edition diff --git a/tooling/lib/tooling/mappings/graphql_base_type_mappings.rb b/tooling/lib/tooling/mappings/graphql_base_type_mappings.rb index 569a82781634b..80aa99efc9639 100644 --- a/tooling/lib/tooling/mappings/graphql_base_type_mappings.rb +++ b/tooling/lib/tooling/mappings/graphql_base_type_mappings.rb @@ -2,13 +2,15 @@ require 'active_support/inflector' -require_relative 'base' +require_relative '../helpers/predictive_tests_helper' require_relative '../../../../lib/gitlab_edition' # If a GraphQL type class changed, we try to identify the other GraphQL types that potentially include this type. module Tooling module Mappings - class GraphqlBaseTypeMappings < Base + class GraphqlBaseTypeMappings + include Helpers::PredictiveTestsHelper + # Checks for the implements keyword, and graphql_base_types the class name GRAPHQL_IMPLEMENTS_REGEXP = /implements[( ]([\w:]+)[)]?$/ diff --git a/tooling/lib/tooling/mappings/js_to_system_specs_mappings.rb b/tooling/lib/tooling/mappings/js_to_system_specs_mappings.rb index b2fca3a765a1c..bc2cd259fdcca 100644 --- a/tooling/lib/tooling/mappings/js_to_system_specs_mappings.rb +++ b/tooling/lib/tooling/mappings/js_to_system_specs_mappings.rb @@ -2,13 +2,15 @@ require 'active_support/inflector' -require_relative 'base' +require_relative '../helpers/predictive_tests_helper' require_relative '../../../../lib/gitlab_edition' # Returns system specs files that are related to the JS files that were changed in the MR. module Tooling module Mappings - class JsToSystemSpecsMappings < Base + class JsToSystemSpecsMappings + include Helpers::PredictiveTestsHelper + def initialize( changed_files_pathname, predictive_tests_pathname, js_base_folder: 'app/assets/javascripts', system_specs_base_folder: 'spec/features') diff --git a/tooling/lib/tooling/mappings/partial_to_views_mappings.rb b/tooling/lib/tooling/mappings/partial_to_views_mappings.rb index 8b0a5ed4ecd48..931cacea77f21 100644 --- a/tooling/lib/tooling/mappings/partial_to_views_mappings.rb +++ b/tooling/lib/tooling/mappings/partial_to_views_mappings.rb @@ -1,12 +1,14 @@ # frozen_string_literal: true -require_relative 'base' +require_relative '../helpers/predictive_tests_helper' require_relative '../../../../lib/gitlab_edition' # Returns view files that include the potential rails partials from the changed files passed as input. module Tooling module Mappings - class PartialToViewsMappings < Base + class PartialToViewsMappings + include Helpers::PredictiveTestsHelper + def initialize(changed_files_pathname, views_with_partials_pathname, view_base_folder: 'app/views') @views_with_partials_pathname = views_with_partials_pathname @changed_files = read_array_from_file(changed_files_pathname) diff --git a/tooling/lib/tooling/mappings/view_to_js_mappings.rb b/tooling/lib/tooling/mappings/view_to_js_mappings.rb index f2098d6acd570..b78c354f9d2e4 100644 --- a/tooling/lib/tooling/mappings/view_to_js_mappings.rb +++ b/tooling/lib/tooling/mappings/view_to_js_mappings.rb @@ -1,12 +1,14 @@ # frozen_string_literal: true -require_relative 'base' +require_relative '../helpers/predictive_tests_helper' require_relative '../../../../lib/gitlab_edition' # Returns JS files that are related to the Rails views files that were changed in the MR. module Tooling module Mappings - class ViewToJsMappings < Base + class ViewToJsMappings + include Helpers::PredictiveTestsHelper + # The HTML attribute value pattern we're looking for to match an HTML file to a JS file. HTML_ATTRIBUTE_VALUE_REGEXP = /js-[-\w]+/.freeze diff --git a/tooling/lib/tooling/mappings/view_to_system_specs_mappings.rb b/tooling/lib/tooling/mappings/view_to_system_specs_mappings.rb index 6d840dcbd7166..1542c81774549 100644 --- a/tooling/lib/tooling/mappings/view_to_system_specs_mappings.rb +++ b/tooling/lib/tooling/mappings/view_to_system_specs_mappings.rb @@ -1,12 +1,14 @@ # frozen_string_literal: true -require_relative 'base' +require_relative '../helpers/predictive_tests_helper' require_relative '../../../../lib/gitlab_edition' # Returns system specs files that are related to the Rails views files that were changed in the MR. module Tooling module Mappings - class ViewToSystemSpecsMappings < Base + class ViewToSystemSpecsMappings + include Helpers::PredictiveTestsHelper + def initialize(changed_files_pathname, predictive_tests_pathname, view_base_folder: 'app/views') @predictive_tests_pathname = predictive_tests_pathname @changed_files = read_array_from_file(changed_files_pathname) diff --git a/tooling/lib/tooling/predictive_tests.rb b/tooling/lib/tooling/predictive_tests.rb index 503426b552096..1ad63e111e3d1 100644 --- a/tooling/lib/tooling/predictive_tests.rb +++ b/tooling/lib/tooling/predictive_tests.rb @@ -2,6 +2,7 @@ require_relative 'find_changes' require_relative 'find_tests' +require_relative 'find_files_using_feature_flags' require_relative 'mappings/graphql_base_type_mappings' require_relative 'mappings/js_to_system_specs_mappings' require_relative 'mappings/partial_to_views_mappings' @@ -36,6 +37,7 @@ def execute from: :api, changed_files_pathname: rspec_changed_files_path ).execute + Tooling::FindFilesUsingFeatureFlags.new(changed_files_pathname: rspec_changed_files_path).execute Tooling::FindTests.new(rspec_changed_files_path, rspec_matching_tests_path).execute Tooling::Mappings::PartialToViewsMappings.new( rspec_changed_files_path, rspec_views_including_partials_path).execute -- GitLab