diff options
Diffstat (limited to 'spec/scripts')
-rw-r--r-- | spec/scripts/changed-feature-flags_spec.rb | 168 | ||||
-rw-r--r-- | spec/scripts/generate_rspec_pipeline_spec.rb | 30 | ||||
-rw-r--r-- | spec/scripts/pipeline/average_reports_spec.rb | 140 | ||||
-rw-r--r-- | spec/scripts/pipeline/create_test_failure_issues_spec.rb | 188 |
4 files changed, 165 insertions, 361 deletions
diff --git a/spec/scripts/changed-feature-flags_spec.rb b/spec/scripts/changed-feature-flags_spec.rb deleted file mode 100644 index f1e381b0656..00000000000 --- a/spec/scripts/changed-feature-flags_spec.rb +++ /dev/null @@ -1,168 +0,0 @@ -# frozen_string_literal: true - -require 'fast_spec_helper' -require 'tmpdir' - -load File.expand_path('../../scripts/changed-feature-flags', __dir__) - -RSpec.describe 'scripts/changed-feature-flags' do - describe GetFeatureFlagsFromFiles do - let!(:feature_flag_definition1) do - file = File.open(File.join(ff_dir, "#{file_name1}.yml"), 'w+') - file.write(<<~YAML) - --- - name: foo_flag - default_enabled: true - YAML - file.rewind - file - end - - let!(:feature_flag_definition2) do - file = File.open(File.join(ff_dir, "#{file_name2}.yml"), 'w+') - file.write(<<~YAML) - --- - name: bar_flag - default_enabled: false - YAML - file.rewind - file - end - - let!(:feature_flag_diff1) do - FileUtils.mkdir_p(File.join(diffs_dir, ff_sub_dir)) - file = File.open(File.join(diffs_dir, ff_sub_dir, "#{file_name1}.yml.diff"), 'w+') - file.write(<<~YAML) - @@ -5,4 +5,4 @@ - name: foo_flag - -default_enabled: false - +default_enabled: true - YAML - file.rewind - file - end - - let!(:feature_flag_diff2) do - FileUtils.mkdir_p(File.join(diffs_dir, ff_sub_dir)) - file = File.open(File.join(diffs_dir, ff_sub_dir, "#{file_name2}.yml.diff"), 'w+') - file.write(<<~YAML) - @@ -0,0 +0,0 @@ - name: bar_flag - -default_enabled: true - +default_enabled: false - YAML - file.rewind - file - end - - let!(:deleted_feature_flag_diff) do - FileUtils.mkdir_p(File.join(diffs_dir, ff_sub_dir)) - file = File.open(File.join(diffs_dir, ff_sub_dir, "foobar_ff_#{SecureRandom.hex(8)}.yml.deleted.diff"), 'w+') - file.write(<<~YAML) - @@ -0,0 +0,0 @@ - -name: foobar_flag - -default_enabled: true - YAML - file.rewind - file - end - - before do - allow(Dir).to receive(:pwd).and_return(Dir.tmpdir) - end - - after do - feature_flag_definition1.close - feature_flag_definition2.close - feature_flag_diff1.close - feature_flag_diff2.close - deleted_feature_flag_diff.close - FileUtils.rm_r(ff_dir) - FileUtils.rm_r(diffs_dir) - end - - describe '.extracted_flags' do - let(:file_name1) { "foo_ff_#{SecureRandom.hex(8)}" } - let(:file_name2) { "bar_ff_#{SecureRandom.hex(8)}" } - let(:ff_dir) { FileUtils.mkdir_p(File.join(Dir.tmpdir, ff_sub_dir)) } - let(:diffs_dir) { FileUtils.mkdir_p(File.join(Dir.tmpdir, 'diffs')).first } - - shared_examples 'extract feature flags' do - it 'returns feature flags on their own' do - subject = described_class.new({ files: diffs_dir }) - - expect(subject.extracted_flags.split(',')).to include('foo_flag', 'bar_flag') - end - - it 'returns feature flags and their state as enabled' do - subject = described_class.new({ files: diffs_dir, state: 'enabled' }) - - expect(subject.extracted_flags.split(',')).to include('foo_flag=enabled', 'bar_flag=enabled') - end - - it 'returns feature flags and their state as disabled' do - subject = described_class.new({ files: diffs_dir, state: 'disabled' }) - - expect(subject.extracted_flags.split(',')).to include('foo_flag=disabled', 'bar_flag=disabled') - end - - it 'does not return feature flags when there are mixed deleted and non-deleted definition files' do - subject = described_class.new({ files: diffs_dir, state: 'deleted' }) - - expect(subject.extracted_flags).to eq('') - end - end - - context 'with definition files in the development directory' do - let(:ff_sub_dir) { %w[feature_flags development] } - - it_behaves_like 'extract feature flags' - end - - context 'with definition files in the ops directory' do - let(:ff_sub_dir) { %w[feature_flags ops] } - - it_behaves_like 'extract feature flags' - end - - context 'with definition files in the experiment directory' do - let(:ff_sub_dir) { %w[feature_flags experiment] } - - it 'ignores the files' do - subject = described_class.new({ files: diffs_dir }) - - expect(subject.extracted_flags).to eq('') - end - end - - context 'with only deleted definition files' do - let(:ff_sub_dir) { %w[feature_flags development] } - - before do - feature_flag_diff1.close - feature_flag_diff2.close - FileUtils.rm_r(feature_flag_diff1) - FileUtils.rm_r(feature_flag_diff2) - end - - it 'returns feature flags and their state as deleted' do - subject = described_class.new({ files: diffs_dir, state: 'deleted' }) - - expect(subject.extracted_flags).to eq('foobar_flag=deleted') - end - - it 'does not return feature flags when the desired state is enabled' do - subject = described_class.new({ files: diffs_dir, state: 'enabled' }) - - expect(subject.extracted_flags).to eq('') - end - - it 'does not return feature flags when the desired state is disabled' do - subject = described_class.new({ files: diffs_dir, state: 'disabled' }) - - expect(subject.extracted_flags).to eq('') - end - end - end - end -end diff --git a/spec/scripts/generate_rspec_pipeline_spec.rb b/spec/scripts/generate_rspec_pipeline_spec.rb index 91b5739cf63..894c33968b8 100644 --- a/spec/scripts/generate_rspec_pipeline_spec.rb +++ b/spec/scripts/generate_rspec_pipeline_spec.rb @@ -9,7 +9,7 @@ RSpec.describe GenerateRspecPipeline, :silence_stdout, feature_category: :toolin describe '#generate!' do let!(:rspec_files) { Tempfile.new(['rspec_files_path', '.txt']) } let(:rspec_files_content) do - "spec/migrations/a_spec.rb spec/migrations/b_spec.rb " \ + "spec/migrations/a_spec.rb spec/migrations/b_spec.rb spec/migrations/c_spec.rb spec/migrations/d_spec.rb " \ "spec/lib/gitlab/background_migration/a_spec.rb spec/lib/gitlab/background_migration/b_spec.rb " \ "spec/models/a_spec.rb spec/models/b_spec.rb " \ "spec/controllers/a_spec.rb spec/controllers/b_spec.rb " \ @@ -63,8 +63,13 @@ RSpec.describe GenerateRspecPipeline, :silence_stdout, feature_category: :toolin let(:knapsack_report_content) do <<~JSON { - "spec/migrations/a_spec.rb": 360.3, - "spec/migrations/b_spec.rb": 180.1, + "spec/migrations/a_spec.rb": 620.3, + "spec/migrations/b_spec.rb": 610.1, + "spec/migrations/c_spec.rb": 20.1, + "spec/migrations/d_spec.rb": 20.1, + "spec/migrations/e_spec.rb": 20.1, + "spec/migrations/f_spec.rb": 20.1, + "spec/migrations/g_spec.rb": 20.1, "spec/lib/gitlab/background_migration/a_spec.rb": 60.5, "spec/lib/gitlab/background_migration/b_spec.rb": 180.3, "spec/models/a_spec.rb": 360.2, @@ -123,7 +128,7 @@ RSpec.describe GenerateRspecPipeline, :silence_stdout, feature_category: :toolin expect(File.read("#{pipeline_template.path}.yml")) .to eq( - "rspec migration:\n parallel: 2\nrspec background_migration:\n parallel: 2\n" \ + "rspec migration:\n parallel: 4\nrspec background_migration:\n parallel: 2\n" \ "rspec unit:\n parallel: 2\nrspec integration:\n parallel: 2\n" \ "rspec system:\n parallel: 2" ) @@ -164,12 +169,27 @@ RSpec.describe GenerateRspecPipeline, :silence_stdout, feature_category: :toolin expect(File.read("#{pipeline_template.path}.yml")) .to eq( - "rspec migration:\n parallel: 2\nrspec background_migration:\n" \ + "rspec migration:\n parallel: 4\nrspec background_migration:\n" \ "rspec unit:\n parallel: 2\nrspec integration:\n" \ "rspec system:\n parallel: 2" ) end + context 'and RSpec files have a high duration' do + let(:rspec_files_content) do + "spec/migrations/a_spec.rb spec/migrations/b_spec.rb" + end + + it 'generates the pipeline config with parallelization based on Knapsack' do + subject.generate! + + expect(File.read("#{pipeline_template.path}.yml")) + .to eq( + "rspec migration:\n parallel: 2" + ) + end + end + context 'and Knapsack report does not contain valid JSON' do let(:knapsack_report_content) { "#{super()}," } diff --git a/spec/scripts/pipeline/average_reports_spec.rb b/spec/scripts/pipeline/average_reports_spec.rb new file mode 100644 index 00000000000..2eee8d34fd5 --- /dev/null +++ b/spec/scripts/pipeline/average_reports_spec.rb @@ -0,0 +1,140 @@ +# frozen_string_literal: true + +require 'fast_spec_helper' +require 'tempfile' +require 'json' +require_relative '../../../scripts/pipeline/average_reports' + +RSpec.describe AverageReports, feature_category: :tooling do + let(:initial_report) do + { + 'spec/frontend/fixtures/analytics.rb' => 1, + 'spec/frontend/fixtures/runner_instructions.rb' => 0.8074841039997409, + 'ee/spec/frontend/fixtures/analytics/value_streams_test_stage.rb' => 50.35115972699987, + 'ee/spec/frontend/fixtures/merge_requests.rb' => 19.16644390500005, + 'old' => 123 + } + end + + let(:new_report) do + { + 'spec/frontend/fixtures/analytics.rb' => 2, + 'spec/frontend/fixtures/runner_instructions.rb' => 0, + 'ee/spec/frontend/fixtures/analytics/value_streams_test_stage.rb' => 0, + 'ee/spec/frontend/fixtures/merge_requests.rb' => 0, + 'new' => 234 + } + end + + let(:new_report_2) do + { + 'spec/frontend/fixtures/analytics.rb' => 3, + 'new' => 468 + } + end + + let(:initial_report_file) do + Tempfile.new('temp_initial_report.json').tap do |f| + # rubocop:disable Gitlab/Json + f.write(JSON.dump(initial_report)) + # rubocop:enable Gitlab/Json + f.close + end + end + + let(:new_report_file_1) do |_f| + Tempfile.new('temp_new_report1.json').tap do |f| + # rubocop:disable Gitlab/Json + f.write(JSON.dump(new_report)) + # rubocop:enable Gitlab/Json + f.close + end + end + + let(:new_report_file_2) do |_f| + Tempfile.new('temp_new_report2.json').tap do |f| + # rubocop:disable Gitlab/Json + f.write(JSON.dump(new_report_2)) + # rubocop:enable Gitlab/Json + f.close + end + end + + before do + allow(subject).to receive(:puts) + end + + after do + initial_report_file.unlink + new_report_file_1.unlink + new_report_file_2.unlink + end + + describe 'execute' do + context 'with 1 new report' do + subject do + described_class.new( + initial_report_file: initial_report_file.path, + new_report_files: [new_report_file_1.path] + ) + end + + it 'returns average durations' do + results = subject.execute + + expect(results['spec/frontend/fixtures/analytics.rb']).to be_within(0.01).of(1.5) + expect(results['spec/frontend/fixtures/runner_instructions.rb']).to be_within(0.01).of(0.4) + expect(results['ee/spec/frontend/fixtures/analytics/value_streams_test_stage.rb']).to be_within(0.01).of(25.17) + expect(results['ee/spec/frontend/fixtures/merge_requests.rb']).to be_within(0.01).of(9.58) + expect(results['new']).to be_within(0.01).of(234) + + # excludes entry missing from the new report + expect(results['old']).to be_nil + end + end + + context 'with 2 new reports' do + subject do + described_class.new( + initial_report_file: initial_report_file.path, + new_report_files: [new_report_file_1.path, new_report_file_2.path] + ) + end + + it 'returns average durations' do + results = subject.execute + + expect(subject).to have_received(:puts).with("Updating #{initial_report_file.path} with 2 new reports...") + expect(subject).to have_received(:puts).with("Updated 5 data points from #{new_report_file_1.path}") + expect(subject).to have_received(:puts).with("Updated 2 data points from #{new_report_file_2.path}") + + expect(results['spec/frontend/fixtures/analytics.rb']).to be_within(0.01).of(2) + expect(results['new']).to be_within(0.01).of(351) + + # retains entry present in one of the new reports + expect(results['spec/frontend/fixtures/runner_instructions.rb']).to be_within(0.01).of(0.4) + expect(results['ee/spec/frontend/fixtures/analytics/value_streams_test_stage.rb']).to be_within(0.01).of(25.17) + expect(results['ee/spec/frontend/fixtures/merge_requests.rb']).to be_within(0.01).of(9.58) + + # excludes entry missing from both of the new reports + expect(results['old']).to be_nil + end + end + + context 'when some of the new report files do not exist' do + subject do + described_class.new( + initial_report_file: initial_report_file.path, + new_report_files: [new_report_file_1.path, 'file_does_not_exist.json'] + ) + end + + it 'ignores the nil file and only process 1 new report' do + subject.execute + + expect(subject).to have_received(:puts).with("Updating #{initial_report_file.path} with 1 new reports...") + expect(subject).to have_received(:puts).with("Updated 5 data points from #{new_report_file_1.path}") + end + end + end +end diff --git a/spec/scripts/pipeline/create_test_failure_issues_spec.rb b/spec/scripts/pipeline/create_test_failure_issues_spec.rb deleted file mode 100644 index 2a5910f5238..00000000000 --- a/spec/scripts/pipeline/create_test_failure_issues_spec.rb +++ /dev/null @@ -1,188 +0,0 @@ -# frozen_string_literal: true - -# rubocop:disable RSpec/VerifiedDoubles - -require 'fast_spec_helper' -require 'active_support/testing/time_helpers' -require 'rspec-parameterized' - -require_relative '../../../scripts/pipeline/create_test_failure_issues' - -RSpec.describe CreateTestFailureIssues, feature_category: :tooling do - describe CreateTestFailureIssue do - include ActiveSupport::Testing::TimeHelpers - - let(:server_host) { 'example.com' } - let(:project_path) { 'group/project' } - - let(:env) do - { - 'CI_SERVER_HOST' => server_host, - 'CI_PROJECT_PATH' => project_path, - 'CI_PIPELINE_URL' => "https://#{server_host}/#{project_path}/-/pipelines/1234" - } - end - - let(:api_token) { 'api_token' } - let(:creator) { described_class.new(project: project_path, api_token: api_token) } - let(:test_name) { 'The test description' } - let(:test_file) { 'spec/path/to/file_spec.rb' } - let(:test_file_content) do - <<~CONTENT - # comment - - RSpec.describe Foo, feature_category: :source_code_management do - end - - CONTENT - end - - let(:test_file_stub) { double(read: test_file_content) } - let(:failed_test) do - { - 'name' => test_name, - 'file' => test_file, - 'job_url' => "https://#{server_host}/#{project_path}/-/jobs/5678" - } - end - - let(:categories_mapping) do - { - 'source_code_management' => { - 'group' => 'source_code', - 'label' => 'Category:Source Code Management' - } - } - end - - let(:groups_mapping) do - { - 'source_code' => { - 'label' => 'group::source_code' - } - } - end - - let(:test_hash) { Digest::SHA256.hexdigest(failed_test['file'] + failed_test['name'])[0...12] } - let(:latest_format_issue_title) { "#{failed_test['file']} [test-hash:#{test_hash}]" } - let(:latest_format_issue_description) do - <<~DESCRIPTION - ### Test description - - `#{failed_test['name']}` - - ### Test file path - - [`#{failed_test['file']}`](https://#{server_host}/#{project_path}/-/blob/master/#{failed_test['file']}) - - <!-- Don't add anything after the report list since it's updated automatically --> - ### Reports (1) - - #{failed_test_report_line} - DESCRIPTION - end - - around do |example| - freeze_time { example.run } - end - - before do - stub_env(env) - allow(creator).to receive(:puts) - end - - describe '#upsert' do - let(:expected_search_payload) do - { - state: :opened, - search: test_hash, - in: :title, - per_page: 1 - } - end - - let(:find_issue_stub) { double('FindIssues') } - let(:issue_stub) { double('Issue', title: latest_format_issue_title, web_url: 'issue_web_url') } - - let(:failed_test_report_line) do - "1. #{Time.new.utc.strftime('%F')}: #{failed_test['job_url']} (#{env['CI_PIPELINE_URL']})" - end - - before do - allow(File).to receive(:open).and_call_original - allow(File).to receive(:open).with(File.expand_path(File.join('..', '..', '..', test_file), __dir__)) - .and_return(test_file_stub) - - allow(FindIssues).to receive(:new).with(project: project_path, api_token: api_token).and_return(find_issue_stub) - - allow(creator).to receive(:categories_mapping).and_return(categories_mapping) - allow(creator).to receive(:groups_mapping).and_return(groups_mapping) - end - - context 'when no issues are found' do - let(:create_issue_stub) { double('CreateIssue') } - let(:expected_create_payload) do - { - title: latest_format_issue_title, - description: latest_format_issue_description, - labels: described_class::DEFAULT_LABELS.map { |label| "wip-#{label}" } + [ - "wip-#{categories_mapping['source_code_management']['label']}", - "wip-#{groups_mapping['source_code']['label']}" - ], - weight: 1 - } - end - - before do - allow(find_issue_stub).to receive(:execute).with(expected_search_payload).and_return([]) - end - - it 'calls CreateIssue#execute(payload)' do - expect(CreateIssue).to receive(:new).with(project: project_path, api_token: api_token) - .and_return(create_issue_stub) - expect(create_issue_stub).to receive(:execute).with(expected_create_payload).and_return(issue_stub) - - creator.upsert(failed_test) - end - end - - context 'when issues are found' do - let(:issue_stub) do - double('Issue', iid: 42, title: issue_title, description: issue_description, web_url: 'issue_web_url') - end - - before do - allow(find_issue_stub).to receive(:execute).with(expected_search_payload).and_return([issue_stub]) - end - - # This shared example can be useful if we want to test migration to a new format in the future - shared_examples 'existing issue update' do - let(:update_issue_stub) { double('UpdateIssue') } - let(:expected_update_payload) do - { - description: latest_format_issue_description.sub(/^### Reports.*$/, '### Reports (2)') + - "\n#{failed_test_report_line}", - weight: 2 - } - end - - it 'calls UpdateIssue#execute(payload)' do - expect(UpdateIssue).to receive(:new).with(project: project_path, api_token: api_token) - .and_return(update_issue_stub) - expect(update_issue_stub).to receive(:execute).with(42, **expected_update_payload) - - creator.upsert(failed_test) - end - end - - context 'when issue already has the latest format' do - let(:issue_description) { latest_format_issue_description } - let(:issue_title) { latest_format_issue_title } - - it_behaves_like 'existing issue update' - end - end - end - end -end -# rubocop:enable RSpec/VerifiedDoubles |