mirror of
https://gitlab.com/gitlab-org/gitlab-foss.git
synced 2025-08-01 16:04:19 +00:00
416 lines
12 KiB
Ruby
416 lines
12 KiB
Ruby
# frozen_string_literal: true
|
|
|
|
require 'fast_spec_helper'
|
|
require 'tempfile'
|
|
require_relative '../../../scripts/setup/tests-metadata'
|
|
|
|
# rubocop:disable Gitlab/Json, Lint/MissingCopEnableDirective -- It's not intended to have extra dependency
|
|
|
|
RSpec.describe TestsMetadata, feature_category: :tooling do # rubocop:disable RSpec/SpecFilePathFormat -- We use dashes in scripts
|
|
subject(:metadata) do
|
|
described_class.new(
|
|
mode: mode,
|
|
knapsack_report_path: knapsack_report_path,
|
|
flaky_report_path: flaky_report_path,
|
|
fast_quarantine_path: fast_quarantine_path,
|
|
average_knapsack: average_knapsack)
|
|
end
|
|
|
|
let(:average_knapsack) { true }
|
|
let(:knapsack_report_path) { 'knapsack_report/path' }
|
|
let(:flaky_report_path) { 'flaky_report/path' }
|
|
let(:fast_quarantine_path) { 'fast_quarantine/path' }
|
|
let(:aborted) { StandardError.new }
|
|
|
|
describe '#main' do
|
|
context 'when mode is retrieve' do
|
|
let(:mode) { 'retrieve' }
|
|
|
|
it 'calls prepare_directories and retrieve' do
|
|
expect(metadata).to receive(:prepare_directories)
|
|
expect(metadata).to receive(:retrieve)
|
|
expect(metadata).not_to receive(:update)
|
|
expect(metadata).not_to receive(:verify)
|
|
|
|
metadata.main
|
|
end
|
|
end
|
|
|
|
context 'when mode is update' do
|
|
let(:mode) { 'update' }
|
|
|
|
it 'calls prepare_directories and retrieve and update' do
|
|
expect(metadata).to receive(:prepare_directories)
|
|
expect(metadata).to receive(:retrieve)
|
|
expect(metadata).to receive(:update)
|
|
expect(metadata).not_to receive(:verify)
|
|
|
|
metadata.main
|
|
end
|
|
end
|
|
|
|
context 'when mode is verify' do
|
|
let(:mode) { 'verify' }
|
|
|
|
it 'calls prepare_directories and retrieve and update' do
|
|
expect(metadata).not_to receive(:prepare_directories)
|
|
expect(metadata).not_to receive(:retrieve)
|
|
expect(metadata).not_to receive(:update)
|
|
expect(metadata).to receive(:verify)
|
|
|
|
metadata.main
|
|
end
|
|
end
|
|
end
|
|
|
|
describe '#prepare_directories' do
|
|
let(:mode) { 'retrieve' }
|
|
|
|
it 'prepares the directories' do
|
|
expect(FileUtils).to receive(:mkdir_p).with([
|
|
File.dirname(knapsack_report_path),
|
|
File.dirname(flaky_report_path),
|
|
File.dirname(fast_quarantine_path)
|
|
])
|
|
|
|
metadata.__send__(:prepare_directories)
|
|
end
|
|
end
|
|
|
|
shared_context 'with fake reports' do
|
|
let(:knapsack_report_path) { knapsack_report_file.path }
|
|
let(:flaky_report_path) { flaky_report_file.path }
|
|
let(:fast_quarantine_path) { fast_quarantine_file.path }
|
|
|
|
let(:knapsack_report_file) { tempfile_write('knapsack', knapsack_report) }
|
|
let(:flaky_report_file) { tempfile_write('flaky', flaky_report) }
|
|
let(:fast_quarantine_file) { tempfile_write('fast_quarantine', fast_quarantine_report) }
|
|
|
|
let(:knapsack_report) { json_report }
|
|
let(:flaky_report) { json_report }
|
|
let(:fast_quarantine_report) { text_report }
|
|
|
|
let(:json_report) { '{"valid":"json"}' }
|
|
let(:text_report) { 'This is an apple' }
|
|
|
|
after do
|
|
[knapsack_report_file, flaky_report_file, fast_quarantine_file]
|
|
.each(&:unlink)
|
|
end
|
|
|
|
def tempfile_write(path, content)
|
|
file = Tempfile.new(path)
|
|
file.write(content)
|
|
file.close
|
|
file
|
|
end
|
|
end
|
|
|
|
describe '#retrieve' do
|
|
include_context 'with fake reports'
|
|
|
|
let(:mode) { 'retrieve' }
|
|
let(:expect_curl) { true }
|
|
let(:curl_knapsack_return) { true }
|
|
let(:curl_flaky_report_return) { true }
|
|
let(:curl_fast_quarantine_return) { true }
|
|
|
|
before do
|
|
expect_system_curl
|
|
end
|
|
|
|
def expect_system_curl
|
|
expect_system_curl_with(%W[
|
|
curl --fail --location -o #{knapsack_report_path} https://gitlab-org.gitlab.io/gitlab/#{knapsack_report_path}
|
|
], curl_knapsack_return)
|
|
|
|
expect_system_curl_with(%W[
|
|
curl --fail --location -o #{flaky_report_path} https://gitlab-org.gitlab.io/gitlab/#{flaky_report_path}
|
|
], curl_flaky_report_return)
|
|
|
|
expect_system_curl_with(%W[
|
|
curl --fail --location -o #{fast_quarantine_path} https://gitlab-org.gitlab.io/quality/engineering-productivity/fast-quarantine/#{fast_quarantine_path}
|
|
], curl_fast_quarantine_return)
|
|
end
|
|
|
|
def expect_system_curl_with(arguments, curl_return)
|
|
to =
|
|
if expect_curl
|
|
:to
|
|
else
|
|
:not_to
|
|
end
|
|
|
|
expectation =
|
|
expect(metadata).public_send(to, receive(:system)).with(*arguments) # rubocop:disable RSpec/MissingExpectationTargetMethod -- it's dynamic
|
|
|
|
expectation.and_return(curl_return) if expect_curl
|
|
end
|
|
|
|
it 'downloads the metadata and parse it respectively' do
|
|
metadata.__send__(:retrieve)
|
|
|
|
expect(File.read(knapsack_report_path)).to eq(json_report)
|
|
expect(File.read(flaky_report_path)).to eq(json_report)
|
|
expect(File.read(fast_quarantine_path)).to eq(text_report)
|
|
end
|
|
|
|
context 'when JSON report we download is invalid' do
|
|
let(:json_report) { 'This is a bad JSON' }
|
|
|
|
it 'writes a fallback JSON file instead of using invalid JSON' do
|
|
metadata.__send__(:retrieve)
|
|
|
|
expect(File.read(knapsack_report_path)).to eq(described_class::FALLBACK_JSON)
|
|
expect(File.read(flaky_report_path)).to eq(described_class::FALLBACK_JSON)
|
|
expect(File.read(fast_quarantine_path)).to eq(text_report)
|
|
end
|
|
end
|
|
|
|
context 'when fast quarantine report failed to download' do
|
|
let(:curl_fast_quarantine_return) { false }
|
|
|
|
it 'writes a fallback file with fallback content' do
|
|
metadata.__send__(:retrieve)
|
|
|
|
expect(File.read(fast_quarantine_path)).to eq('')
|
|
end
|
|
end
|
|
|
|
context 'when it is update mode' do
|
|
let(:mode) { 'update' }
|
|
let(:expect_curl) { false }
|
|
|
|
it 'does not download tests metadata via curl' do # rubocop:disable RSpec/NoExpectationExample -- set in before already, see expect_system_curl_with
|
|
metadata.__send__(:retrieve)
|
|
end
|
|
end
|
|
end
|
|
|
|
describe '#update' do
|
|
let(:mode) { 'update' }
|
|
|
|
it 'updates all reports' do
|
|
expect(metadata).to receive(:update_knapsack_report)
|
|
expect(metadata).to receive(:update_flaky_report)
|
|
expect(metadata).to receive(:prune_flaky_report)
|
|
|
|
metadata.__send__(:update)
|
|
end
|
|
end
|
|
|
|
describe '#update_knapsack_report' do
|
|
include_context 'with fake reports'
|
|
|
|
let(:mode) { 'update' }
|
|
let(:knapsack_report_dir) { File.dirname(knapsack_report_path) }
|
|
|
|
let(:individual_knapsack_reports) do
|
|
%W[
|
|
#{knapsack_report_dir}/rspec-0.json
|
|
#{knapsack_report_dir}/rspec-1.json
|
|
]
|
|
end
|
|
|
|
before do
|
|
allow(Dir).to receive(:[]).with("#{knapsack_report_dir}/rspec*.json")
|
|
.and_return(individual_knapsack_reports)
|
|
end
|
|
|
|
it 'updates knapsack report' do
|
|
expect(metadata).to receive(:system).with(
|
|
'scripts/pipeline/average_reports.rb',
|
|
'-i', knapsack_report_path,
|
|
'-n', individual_knapsack_reports.join(',')
|
|
).and_return(true)
|
|
|
|
metadata.__send__(:update_knapsack_report)
|
|
end
|
|
|
|
context 'when scripts/pipeline/average_reports.rb failed' do
|
|
it 'aborts the process' do
|
|
expect(metadata).to receive(:system).with(
|
|
'scripts/pipeline/average_reports.rb',
|
|
'-i', knapsack_report_path,
|
|
'-n', individual_knapsack_reports.join(',')
|
|
).and_return(false)
|
|
|
|
expect(metadata).to receive(:abort)
|
|
|
|
metadata.__send__(:update_knapsack_report)
|
|
end
|
|
end
|
|
|
|
context 'when average_knapsack is false' do
|
|
let(:average_knapsack) { false }
|
|
|
|
it 'uses scripts/merge-reports to merge reports instead' do
|
|
expect(metadata).to receive(:system).with(
|
|
'scripts/merge-reports',
|
|
knapsack_report_path,
|
|
individual_knapsack_reports.join(' ')
|
|
).and_return(true)
|
|
|
|
metadata.__send__(:update_knapsack_report)
|
|
end
|
|
|
|
context 'when scripts/merge-reports failed' do
|
|
it 'aborts the process' do
|
|
expect(metadata).to receive(:system).with(
|
|
'scripts/merge-reports',
|
|
knapsack_report_path,
|
|
individual_knapsack_reports.join(' ')
|
|
).and_return(false)
|
|
|
|
expect(metadata).to receive(:abort)
|
|
|
|
metadata.__send__(:update_knapsack_report)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
describe '#update_flaky_report' do
|
|
include_context 'with fake reports'
|
|
|
|
let(:mode) { 'update' }
|
|
let(:flaky_report_dir) { File.dirname(flaky_report_path) }
|
|
|
|
let(:individual_flaky_reports) do
|
|
%W[
|
|
#{flaky_report_dir}/all_0.json
|
|
#{flaky_report_dir}/all_1.json
|
|
]
|
|
end
|
|
|
|
before do
|
|
allow(Dir).to receive(:[]).with("#{flaky_report_dir}/all_*.json")
|
|
.and_return(individual_flaky_reports)
|
|
end
|
|
|
|
it 'updates flaky report' do
|
|
expect(metadata).to receive(:system).with(
|
|
'scripts/merge-reports',
|
|
flaky_report_path,
|
|
individual_flaky_reports.join(' ')
|
|
).and_return(true)
|
|
|
|
metadata.__send__(:update_flaky_report)
|
|
end
|
|
|
|
context 'when scripts/merge-reports failed' do
|
|
it 'aborts the process' do
|
|
expect(metadata).to receive(:system).with(
|
|
'scripts/merge-reports',
|
|
flaky_report_path,
|
|
individual_flaky_reports.join(' ')
|
|
).and_return(false)
|
|
|
|
expect(metadata).to receive(:abort).and_raise(aborted)
|
|
|
|
expect do
|
|
metadata.__send__(:update_flaky_report)
|
|
end.to raise_error(aborted)
|
|
end
|
|
end
|
|
end
|
|
|
|
describe '#prune_flaky_report' do
|
|
include_context 'with fake reports'
|
|
|
|
let(:mode) { 'update' }
|
|
let(:flaky_report_dir) { File.dirname(flaky_report_path) }
|
|
|
|
it 'prunes flaky report' do
|
|
expect(metadata).to receive(:system).with(
|
|
'scripts/flaky_examples/prune-old-flaky-examples',
|
|
flaky_report_path
|
|
).and_return(true)
|
|
|
|
metadata.__send__(:prune_flaky_report)
|
|
end
|
|
|
|
context 'when scripts/flaky_examples/prune-old-flaky-examples failed' do
|
|
it 'aborts the process' do
|
|
expect(metadata).to receive(:system).with(
|
|
'scripts/flaky_examples/prune-old-flaky-examples',
|
|
flaky_report_path
|
|
).and_return(false)
|
|
|
|
expect(metadata).to receive(:abort)
|
|
|
|
metadata.__send__(:prune_flaky_report)
|
|
end
|
|
end
|
|
end
|
|
|
|
describe '#verify' do
|
|
include_context 'with fake reports'
|
|
|
|
shared_examples 'fail verification and abort' do
|
|
it 'calls abort to fail the verification' do
|
|
expect(metadata).to receive(:abort).and_raise(aborted)
|
|
|
|
expect do
|
|
metadata.__send__(:verify)
|
|
end.to raise_error(aborted)
|
|
end
|
|
end
|
|
|
|
let(:mode) { 'verify' }
|
|
|
|
let(:knapsack_report) { JSON.dump({ __FILE__ => 123.456 }) }
|
|
|
|
let(:flaky_report) do
|
|
<<~JSON
|
|
{
|
|
"fa1659e83e918bab8cb80518ea5f80f4": {
|
|
"first_flaky_at": "2023-12-07 14:21:34 +0000",
|
|
"last_flaky_at": "2024-09-02 22:18:36 +0000",
|
|
"last_flaky_job": "https://gitlab.com/gitlab-org/gitlab/-/jobs/7724274859",
|
|
"last_attempts_count": 2,
|
|
"flaky_reports": 954,
|
|
"feature_category": "integrations",
|
|
"example_id": "./spec/features/projects/integrations/user_activates_issue_tracker_spec.rb[1:4:1:2:1]",
|
|
"file": "./spec/features/projects/integrations/user_activates_issue_tracker_spec.rb",
|
|
"line": 49,
|
|
"description": "User activates issue tracker behaves like external issue tracker activation user sets and activates the integration when the connection test fails activates the integration"
|
|
}
|
|
}
|
|
JSON
|
|
end
|
|
|
|
let(:fast_quarantine_report) do
|
|
<<~TEXT
|
|
qa/specs/features/ee/browser_ui/3_create/remote_development/workspace_actions_spec.rb
|
|
spec/features/work_items/work_item_detail_spec.rb:67
|
|
TEXT
|
|
end
|
|
|
|
it 'validates reports are valid' do
|
|
expect { metadata.__send__(:verify) }.to output("OK\n").to_stdout
|
|
end
|
|
|
|
context 'when knapsack report has type error' do
|
|
let(:knapsack_report) { JSON.dump({ __FILE__ => '123.456' }) }
|
|
|
|
it_behaves_like 'fail verification and abort'
|
|
end
|
|
|
|
context 'when knapsack report is not valid JSON' do
|
|
let(:knapsack_report) { { __FILE__ => 123.456 }.to_s }
|
|
|
|
it_behaves_like 'fail verification and abort'
|
|
end
|
|
|
|
context 'when flaky report is not valid JSON' do
|
|
let(:flaky_report) { 'This is an apple' }
|
|
|
|
it_behaves_like 'fail verification and abort'
|
|
end
|
|
|
|
context 'when fast quarantine report is not valid',
|
|
skip: 'This is not possible because it is always considered valid'
|
|
end
|
|
end
|