Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'spec/scripts')
-rw-r--r--spec/scripts/changed-feature-flags_spec.rb168
-rw-r--r--spec/scripts/generate_rspec_pipeline_spec.rb30
-rw-r--r--spec/scripts/pipeline/average_reports_spec.rb140
-rw-r--r--spec/scripts/pipeline/create_test_failure_issues_spec.rb188
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