diff options
Diffstat (limited to 'qa/spec')
-rw-r--r-- | qa/spec/runtime/feature_spec.rb | 48 | ||||
-rw-r--r-- | qa/spec/scenario/test/instance/reliable_spec.rb | 7 | ||||
-rw-r--r-- | qa/spec/spec_helper.rb | 12 | ||||
-rw-r--r-- | qa/spec/specs/allure_report_spec.rb | 19 | ||||
-rw-r--r-- | qa/spec/specs/helpers/context_selector_spec.rb | 18 | ||||
-rw-r--r-- | qa/spec/support/formatters/test_stats_formatter_spec.rb | 26 | ||||
-rw-r--r-- | qa/spec/support/repeater_spec.rb | 114 | ||||
-rw-r--r-- | qa/spec/support/retrier_spec.rb | 71 | ||||
-rw-r--r-- | qa/spec/support/shared_contexts/packages_registry_shared_context.rb | 7 | ||||
-rw-r--r-- | qa/spec/support/waiter_spec.rb | 35 | ||||
-rw-r--r-- | qa/spec/tools/reliable_report_spec.rb | 145 |
11 files changed, 381 insertions, 121 deletions
diff --git a/qa/spec/runtime/feature_spec.rb b/qa/spec/runtime/feature_spec.rb index 39c20dd3070..88f5cd5be93 100644 --- a/qa/spec/runtime/feature_spec.rb +++ b/qa/spec/runtime/feature_spec.rb @@ -175,6 +175,20 @@ RSpec.describe QA::Runtime::Feature do expect(described_class.enabled?(feature_flag)).to be_truthy end + it 'raises an error when the scope is unknown' do + expect(QA::Runtime::API::Request) + .to receive(:new) + .with(api_client, "/features") + .and_return(request) + expect(described_class) + .to receive(:get) + .and_return( + Struct.new(:code, :body) + .new(200, %([{ "name": "a_flag", "state": "conditional", "gates": { "key": "groups", "value": ["foo"] } }]))) + + expect { described_class.enabled?(feature_flag, scope: 'foo') }.to raise_error(QA::Runtime::Feature::UnknownScopeError) + end + context 'when a project scope is provided' do it_behaves_like 'checks a feature flag' do let(:scope) { :project } @@ -212,4 +226,38 @@ RSpec.describe QA::Runtime::Feature do end end end + + describe '.set' do + let(:scope) { { scope: 'actor' } } + + it 'raises an error when the flag state is unknown' do + expect(described_class).not_to receive(:enable) + expect(described_class).not_to receive(:disable) + + expect { described_class.set({ foo: 'bar' }, **scope) }.to raise_error(QA::Runtime::Feature::UnknownStateError, 'Unknown feature flag state: bar') + end + + it 'enables feature flags' do + expect(described_class).to receive(:enable).with(:flag1, scope) + expect(described_class).to receive(:enable).with(:flag2, scope) + expect(described_class).not_to receive(:disable) + + described_class.set({ flag1: 'enabled', flag2: 'enable' }, **scope) + end + + it 'disables feature flags' do + expect(described_class).to receive(:disable).with(:flag1, scope) + expect(described_class).to receive(:disable).with(:flag2, scope) + expect(described_class).not_to receive(:enable) + + described_class.set({ flag1: 'disable', flag2: 'disable' }, **scope) + end + + it 'enables and disables feature flags' do + expect(described_class).to receive(:enable).with(:flag1, scope) + expect(described_class).to receive(:disable).with(:flag2, scope) + + described_class.set({ flag1: 'enabled', flag2: 'disabled' }, **scope) + end + end end diff --git a/qa/spec/scenario/test/instance/reliable_spec.rb b/qa/spec/scenario/test/instance/reliable_spec.rb new file mode 100644 index 00000000000..4001d386bf3 --- /dev/null +++ b/qa/spec/scenario/test/instance/reliable_spec.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +RSpec.describe QA::Scenario::Test::Instance::Reliable do + it_behaves_like 'a QA scenario class' do + let(:tags) { [:reliable] } + end +end diff --git a/qa/spec/spec_helper.rb b/qa/spec/spec_helper.rb index e25892a008f..640f2de0ca2 100644 --- a/qa/spec/spec_helper.rb +++ b/qa/spec/spec_helper.rb @@ -27,8 +27,12 @@ RSpec.configure do |config| config.add_formatter QA::Support::Formatters::QuarantineFormatter config.add_formatter QA::Support::Formatters::TestStatsFormatter if QA::Runtime::Env.export_metrics? - config.before do |example| + config.prepend_before do |example| QA::Runtime::Logger.debug("\nStarting test: #{example.full_description}\n") + + # Reset fabrication counters tracked in resource base + Thread.current[:api_fabrication] = 0 + Thread.current[:browser_ui_fabrication] = 0 end config.after do @@ -36,6 +40,12 @@ RSpec.configure do |config| QA::Git::Repository.new.delete_netrc end + # Add fabrication time to spec metadata + config.append_after do |example| + example.metadata[:api_fabrication] = Thread.current[:api_fabrication] + example.metadata[:browser_ui_fabrication] = Thread.current[:browser_ui_fabrication] + end + config.after(:context) do if !QA::Runtime::Browser.blank_page? && QA::Page::Main::Menu.perform(&:signed_in?) QA::Page::Main::Menu.perform(&:sign_out) diff --git a/qa/spec/specs/allure_report_spec.rb b/qa/spec/specs/allure_report_spec.rb index 03bf77039cc..06b09106140 100644 --- a/qa/spec/specs/allure_report_spec.rb +++ b/qa/spec/specs/allure_report_spec.rb @@ -3,7 +3,7 @@ describe QA::Runtime::AllureReport do include QA::Support::Helpers::StubEnv - let(:rspec_config) { double('RSpec::Core::Configuration', 'add_formatter': nil, after: nil) } + let(:rspec_config) { double('RSpec::Core::Configuration', 'add_formatter': nil, append_after: nil) } let(:png_path) { 'png_path' } let(:html_path) { 'html_path' } @@ -46,6 +46,8 @@ describe QA::Runtime::AllureReport do let(:html_file) { 'html-file' } let(:ci_job) { 'ee:relative 5' } let(:versions) { { version: '14', revision: '6ced31db947' } } + let(:session) { double('session') } + let(:browser_log) { ['log message 1', 'log message 2'] } before do stub_env('CI', 'true') @@ -58,6 +60,9 @@ describe QA::Runtime::AllureReport do allow(RestClient::Request).to receive(:execute) { double('response', code: 200, body: versions.to_json) } allow(QA::Runtime::Scenario).to receive(:method_missing).with(:gitlab_address).and_return('gitlab.com') + allow(Capybara).to receive(:current_session).and_return(session) + allow(session).to receive_message_chain('driver.browser.logs.get').and_return(browser_log) + described_class.configure! end @@ -76,7 +81,11 @@ describe QA::Runtime::AllureReport do .with(QA::Support::Formatters::AllureMetadataFormatter).ordered end - it 'configures screenshot saving' do + it 'configures attachments saving' do + expect(rspec_config).to have_received(:append_after) do |&arg| + arg.call + end + aggregate_failures do expect(Allure).to have_received(:add_attachment).with( name: 'screenshot', @@ -90,6 +99,12 @@ describe QA::Runtime::AllureReport do type: 'text/html', test_case: true ) + expect(Allure).to have_received(:add_attachment).with( + name: 'browser.log', + source: browser_log.join("\n\n"), + type: Allure::ContentType::TXT, + test_case: true + ) end end end diff --git a/qa/spec/specs/helpers/context_selector_spec.rb b/qa/spec/specs/helpers/context_selector_spec.rb index 0152fee6f5b..5a320cde71f 100644 --- a/qa/spec/specs/helpers/context_selector_spec.rb +++ b/qa/spec/specs/helpers/context_selector_spec.rb @@ -186,6 +186,24 @@ RSpec.describe QA::Specs::Helpers::ContextSelector do end end + context 'staging-ref' do + before do + QA::Runtime::Scenario.define(:gitlab_address, 'https://staging-ref.gitlab.com/') + end + + it 'runs on staging-ref' do + group = describe_successfully do + it('does not run in staging', only: { subdomain: :staging }) {} + it('runs in staging-ref', only: { subdomain: /^staging-ref./ }) {} + end + + aggregate_failures do + expect(group.examples[0].execution_result.status).to eq(:pending) + expect(group.examples[1].execution_result.status).to eq(:passed) + end + end + end + context 'production' do before do QA::Runtime::Scenario.define(:gitlab_address, 'https://gitlab.com/') diff --git a/qa/spec/support/formatters/test_stats_formatter_spec.rb b/qa/spec/support/formatters/test_stats_formatter_spec.rb index 859d45a660b..f9baf9bd9d9 100644 --- a/qa/spec/support/formatters/test_stats_formatter_spec.rb +++ b/qa/spec/support/formatters/test_stats_formatter_spec.rb @@ -20,6 +20,8 @@ describe QA::Support::Formatters::TestStatsFormatter do let(:influx_write_api) { instance_double('InfluxDB2::WriteApi', write: nil) } let(:stage) { '1_manage' } let(:file_path) { "./qa/specs/features/#{stage}/subfolder/some_spec.rb" } + let(:ui_fabrication) { 0 } + let(:api_fabrication) { 0 } let(:influx_client_args) do { @@ -48,6 +50,9 @@ describe QA::Support::Formatters::TestStatsFormatter do fields: { id: './spec/support/formatters/test_stats_formatter_spec.rb[1:1]', run_time: 0, + api_fabrication: api_fabrication * 1000, + ui_fabrication: ui_fabrication * 1000, + total_fabrication: (api_fabrication + ui_fabrication) * 1000, retry_attempts: 0, job_url: ci_job_url, pipeline_url: ci_pipeline_url, @@ -69,6 +74,11 @@ describe QA::Support::Formatters::TestStatsFormatter do RSpec::Core::Sandbox.sandboxed do |config| config.formatter = QA::Support::Formatters::TestStatsFormatter + config.append_after do |example| + example.metadata[:api_fabrication] = Thread.current[:api_fabrication] + example.metadata[:browser_ui_fabrication] = Thread.current[:browser_ui_fabrication] + end + config.before(:context) { RSpec.current_example = nil } example.run @@ -171,5 +181,21 @@ describe QA::Support::Formatters::TestStatsFormatter do expect(influx_write_api).to have_received(:write).with(data: [data]) end end + + context 'with fabrication runtimes' do + let(:ui_fabrication) { 10 } + let(:api_fabrication) { 4 } + + before do + Thread.current[:api_fabrication] = api_fabrication + Thread.current[:browser_ui_fabrication] = ui_fabrication + end + + it 'exports data to influxdb with fabrication times' do + run_spec + + expect(influx_write_api).to have_received(:write).with(data: [data]) + end + end end end diff --git a/qa/spec/support/repeater_spec.rb b/qa/spec/support/repeater_spec.rb index da8d6b18fb0..4fa3bcde5e7 100644 --- a/qa/spec/support/repeater_spec.rb +++ b/qa/spec/support/repeater_spec.rb @@ -23,7 +23,7 @@ RSpec.describe QA::Support::Repeater do context 'when retry_on_exception is not provided (default: false)' do context 'when max_duration is provided' do context 'when max duration is reached' do - it 'raises an exception' do + it 'raises an exception with default message' do expect do Timecop.freeze do subject.repeat_until(max_duration: 1) do @@ -31,7 +31,20 @@ RSpec.describe QA::Support::Repeater do false end end - end.to raise_error(QA::Support::Repeater::WaitExceededError, "Wait condition not met after 1 second") + end.to raise_error(QA::Support::Repeater::WaitExceededError, "Wait failed after 1 second") + end + + it 'raises an exception with custom message' do + message = 'Some custom action' + + expect do + Timecop.freeze do + subject.repeat_until(max_duration: 1, message: message) do + Timecop.travel(2) + false + end + end + end.to raise_error(QA::Support::Repeater::WaitExceededError, "#{message} failed after 1 second") end it 'ignores attempts' do @@ -70,14 +83,26 @@ RSpec.describe QA::Support::Repeater do context 'when max_attempts is provided' do context 'when max_attempts is reached' do - it 'raises an exception' do + it 'raises an exception with default message' do expect do Timecop.freeze do subject.repeat_until(max_attempts: 1) do false end end - end.to raise_error(QA::Support::Repeater::RetriesExceededError, "Retry condition not met after 1 attempt") + end.to raise_error(QA::Support::Repeater::RetriesExceededError, "Retry failed after 1 attempt") + end + + it 'raises an exception with custom message' do + message = 'Some custom action' + + expect do + Timecop.freeze do + subject.repeat_until(max_attempts: 1, message: message) do + false + end + end + end.to raise_error(QA::Support::Repeater::RetriesExceededError, "#{message} failed after 1 attempt") end it 'ignores duration' do @@ -126,7 +151,7 @@ RSpec.describe QA::Support::Repeater do false end end - end.to raise_error(QA::Support::Repeater::RetriesExceededError, "Retry condition not met after 1 attempt") + end.to raise_error(QA::Support::Repeater::RetriesExceededError, "Retry failed after 1 attempt") end end @@ -141,7 +166,7 @@ RSpec.describe QA::Support::Repeater do false end end - end.to raise_error(QA::Support::Repeater::WaitExceededError, "Wait condition not met after 1 second") + end.to raise_error(QA::Support::Repeater::WaitExceededError, "Wait failed after 1 second") end end end @@ -210,7 +235,7 @@ RSpec.describe QA::Support::Repeater do false end end - end.to raise_error(QA::Support::Repeater::RetriesExceededError, "Retry condition not met after 1 attempt") + end.to raise_error(QA::Support::Repeater::RetriesExceededError, "Retry failed after 1 attempt") end end @@ -225,7 +250,7 @@ RSpec.describe QA::Support::Repeater do false end end - end.to raise_error(QA::Support::Repeater::WaitExceededError, "Wait condition not met after 1 second") + end.to raise_error(QA::Support::Repeater::WaitExceededError, "Wait failed after 1 second") end end end @@ -380,34 +405,67 @@ RSpec.describe QA::Support::Repeater do end end - it 'logs attempts' do - attempted = false + context 'with logging' do + before do + allow(QA::Runtime::Logger).to receive(:debug) + end - expect do - subject.repeat_until(max_attempts: 1) do - unless attempted - attempted = true - break false - end + it 'skips logging single attempt with max_attempts' do + subject.repeat_until(max_attempts: 3) do + true + end + expect(QA::Runtime::Logger).not_to have_received(:debug) + end + + it 'skips logging single attempt with max_duration' do + subject.repeat_until(max_duration: 3) do true end - end.to output(/Attempt number/).to_stdout_from_any_process - end - it 'allows logging to be silenced' do - attempted = false + expect(QA::Runtime::Logger).not_to have_received(:debug) + end - expect do - subject.repeat_until(max_attempts: 1, log: false) do - unless attempted - attempted = true - break false - end + it 'allows logging to be silenced' do + subject.repeat_until(max_attempts: 3, log: false, raise_on_failure: false) do + false + end - true + expect(QA::Runtime::Logger).not_to have_received(:debug) + end + + it 'starts logging on subsequent attempts for max_duration' do + subject.repeat_until(max_duration: 0.3, sleep_interval: 0.1, raise_on_failure: false) do + false + end + + aggregate_failures do + expect(QA::Runtime::Logger).to have_received(:debug).with(<<~MSG.strip).ordered.once + Retrying action with: max_duration: 0.3; sleep_interval: 0.1; raise_on_failure: false; retry_on_exception: false + MSG + expect(QA::Runtime::Logger).to have_received(:debug).with('ended retry').ordered.once + expect(QA::Runtime::Logger).not_to have_received(:debug).with(/Attempt number/) + end + end + + it 'starts logging subsequent attempts for max_attempts' do + attempts = 0 + subject.repeat_until(max_attempts: 4, raise_on_failure: false) do + next true if attempts == 2 + + attempts += 1 + false end - end.not_to output.to_stdout_from_any_process + + aggregate_failures do + expect(QA::Runtime::Logger).to have_received(:debug).with(<<~MSG.strip).ordered.once + Retrying action with: max_attempts: 4; sleep_interval: 0; raise_on_failure: false; retry_on_exception: false + MSG + expect(QA::Runtime::Logger).to have_received(:debug).with('Attempt number 2').ordered.once + expect(QA::Runtime::Logger).to have_received(:debug).with('Attempt number 3').ordered.once + expect(QA::Runtime::Logger).to have_received(:debug).with('ended retry').ordered.once + end + end end end end diff --git a/qa/spec/support/retrier_spec.rb b/qa/spec/support/retrier_spec.rb index 9ad3e85fea9..1f303093a00 100644 --- a/qa/spec/support/retrier_spec.rb +++ b/qa/spec/support/retrier_spec.rb @@ -1,42 +1,7 @@ # frozen_string_literal: true RSpec.describe QA::Support::Retrier do - before do - logger = ::Logger.new $stdout - logger.level = ::Logger::DEBUG - QA::Runtime::Logger.logger = logger - end - describe '.retry_until' do - context 'when the condition is true' do - it 'logs max attempts (3 by default)' do - expect { subject.retry_until { true } } - .to output(/with retry_until: max_attempts: 3; reload_page: ; sleep_interval: 0; raise_on_failure: true; retry_on_exception: false/).to_stdout_from_any_process - end - - it 'logs max duration' do - expect { subject.retry_until(max_duration: 1) { true } } - .to output(/with retry_until: max_duration: 1; reload_page: ; sleep_interval: 0; raise_on_failure: true; retry_on_exception: false/).to_stdout_from_any_process - end - - it 'logs the end' do - expect { subject.retry_until { true } } - .to output(/ended retry_until$/).to_stdout_from_any_process - end - end - - context 'when the condition is false' do - it 'logs the start' do - expect { subject.retry_until(max_duration: 0, raise_on_failure: false) { false } } - .to output(/with retry_until: max_duration: 0; reload_page: ; sleep_interval: 0; raise_on_failure: false; retry_on_exception: false/).to_stdout_from_any_process - end - - it 'logs the end' do - expect { subject.retry_until(max_duration: 0, raise_on_failure: false) { false } } - .to output(/ended retry_until$/).to_stdout_from_any_process - end - end - context 'when max_duration and max_attempts are nil' do it 'sets max attempts to 3 by default' do expect(subject).to receive(:repeat_until).with(hash_including(max_attempts: 3)) @@ -62,35 +27,21 @@ RSpec.describe QA::Support::Retrier do subject.retry_until end - end - describe '.retry_on_exception' do - context 'when the condition is true' do - it 'logs max_attempts, reload_page, and sleep_interval parameters' do - message = /with retry_on_exception: max_attempts: 1; reload_page: true; sleep_interval: 0/ - expect { subject.retry_on_exception(max_attempts: 1, reload_page: true, sleep_interval: 0) { true } } - .to output(message).to_stdout_from_any_process - end + it 'allows logs to be silenced' do + expect(subject).to receive(:repeat_until).with(hash_including(log: false)) - it 'logs the end' do - expect { subject.retry_on_exception(max_attempts: 1, reload_page: nil, sleep_interval: 0) { true } } - .to output(/ended retry_on_exception$/).to_stdout_from_any_process - end + subject.retry_until(log: false) end - context 'when the condition is false' do - it 'logs the start' do - message = /with retry_on_exception: max_attempts: 1; reload_page: true; sleep_interval: 0/ - expect { subject.retry_on_exception(max_attempts: 1, reload_page: true, sleep_interval: 0) { false } } - .to output(message).to_stdout_from_any_process - end + it 'sets custom error message' do + expect(subject).to receive(:repeat_until).with(hash_including(message: 'Custom message')) - it 'logs the end' do - expect { subject.retry_on_exception(max_attempts: 1, reload_page: nil, sleep_interval: 0) { false } } - .to output(/ended retry_on_exception$/).to_stdout_from_any_process - end + subject.retry_until(message: 'Custom message') end + end + describe '.retry_on_exception' do it 'does not repeat if no exception is raised' do loop_counter = 0 return_value = "test passed" @@ -121,5 +72,11 @@ RSpec.describe QA::Support::Retrier do subject.retry_on_exception end + + it 'allows logs to be silenced' do + expect(subject).to receive(:repeat_until).with(hash_including(log: false)) + + subject.retry_on_exception(log: false) + end end end diff --git a/qa/spec/support/shared_contexts/packages_registry_shared_context.rb b/qa/spec/support/shared_contexts/packages_registry_shared_context.rb index 6e197015640..e686d254a44 100644 --- a/qa/spec/support/shared_contexts/packages_registry_shared_context.rb +++ b/qa/spec/support/shared_contexts/packages_registry_shared_context.rb @@ -43,8 +43,13 @@ module QA let(:project_deploy_token) do Resource::DeployToken.fabricate_via_browser_ui! do |deploy_token| - deploy_token.name = 'helm-package-deploy-token' + deploy_token.name = 'package-deploy-token' deploy_token.project = package_project + deploy_token.scopes = [ + :read_repository, + :read_package_registry, + :write_package_registry + ] end end diff --git a/qa/spec/support/waiter_spec.rb b/qa/spec/support/waiter_spec.rb index d0b216b5dc1..c575a27bc35 100644 --- a/qa/spec/support/waiter_spec.rb +++ b/qa/spec/support/waiter_spec.rb @@ -1,40 +1,11 @@ # frozen_string_literal: true RSpec.describe QA::Support::Waiter do - before do - logger = ::Logger.new $stdout - logger.level = ::Logger::DEBUG - QA::Runtime::Logger.logger = logger - end - describe '.wait_until' do - context 'when the condition is true' do - it 'logs the start' do - expect { subject.wait_until(max_duration: 0, raise_on_failure: false) { true } } - .to output(/with wait_until: max_duration: 0; reload_page: ; sleep_interval: 0.1/).to_stdout_from_any_process - end - - it 'logs the end' do - expect { subject.wait_until(max_duration: 0, raise_on_failure: false) { true } } - .to output(/ended wait_until$/).to_stdout_from_any_process - end - end - - context 'when the condition is false' do - it 'logs the start' do - expect { subject.wait_until(max_duration: 0, raise_on_failure: false) { false } } - .to output(/with wait_until: max_duration: 0; reload_page: ; sleep_interval: 0.1/).to_stdout_from_any_process - end - - it 'logs the end' do - expect { subject.wait_until(max_duration: 0, raise_on_failure: false) { false } } - .to output(/ended wait_until$/).to_stdout_from_any_process - end - end - it 'allows logs to be silenced' do - expect { subject.wait_until(max_duration: 0, raise_on_failure: false, log: false) { false } } - .not_to output.to_stdout_from_any_process + expect(subject).to receive(:repeat_until).with(hash_including(log: false)) + + subject.wait_until(log: false) end it 'sets max_duration to 60 by default' do diff --git a/qa/spec/tools/reliable_report_spec.rb b/qa/spec/tools/reliable_report_spec.rb new file mode 100644 index 00000000000..c7d4d28fb21 --- /dev/null +++ b/qa/spec/tools/reliable_report_spec.rb @@ -0,0 +1,145 @@ +# frozen_string_literal: true + +describe QA::Tools::ReliableReport do + include QA::Support::Helpers::StubEnv + + subject(:reporter) { described_class.new(run_type, range) } + + let(:slack_notifier) { instance_double("Slack::Notifier", post: nil) } + let(:influx_client) { instance_double("InfluxDB2::Client", create_query_api: query_api) } + let(:query_api) { instance_double("InfluxDB2::QueryApi") } + + let(:slack_channel) { "#quality-reports" } + let(:run_type) { "package-and-qa" } + let(:range) { 30 } + let(:results) { 10 } + + let(:runs) { { 0 => stable_spec, 1 => unstable_spec } } + + let(:stable_spec) do + spec_values = { "name" => "stable spec", "status" => "passed", "file_path" => "some/spec.rb" } + instance_double( + "InfluxDB2::FluxTable", + records: [ + instance_double("InfluxDB2::FluxRecord", values: spec_values), + instance_double("InfluxDB2::FluxRecord", values: spec_values), + instance_double("InfluxDB2::FluxRecord", values: spec_values) + ] + ) + end + + let(:unstable_spec) do + spec_values = { "name" => "unstable spec", "status" => "failed", "file_path" => "some/spec.rb" } + instance_double( + "InfluxDB2::FluxTable", + records: [ + instance_double("InfluxDB2::FluxRecord", values: { **spec_values, "status" => "passed" }), + instance_double("InfluxDB2::FluxRecord", values: spec_values), + instance_double("InfluxDB2::FluxRecord", values: spec_values) + ] + ) + end + + def flux_query(reliable) + <<~QUERY + from(bucket: "e2e-test-stats") + |> range(start: -#{range}d) + |> filter(fn: (r) => r._measurement == "test-stats" and + r.run_type == "#{run_type}" and + r.status != "pending" and + r.merge_request == "false" and + r.quarantined == "false" and + r.reliable == "#{reliable}" and + r._field == "id" + ) + |> group(columns: ["name"]) + QUERY + end + + def table(rows, title = nil) + Terminal::Table.new( + headings: ["name", "runs", "failed", "failure rate"], + style: { all_separators: true }, + title: title, + rows: rows + ) + end + + def name_column(spec_name) + name = "name: '#{spec_name}'" + file = "file: 'spec.rb'".ljust(110) + + "#{name}\n#{file}" + end + + before do + stub_env("QA_INFLUXDB_URL", "url") + stub_env("QA_INFLUXDB_TOKEN", "token") + stub_env("CI_SLACK_WEBHOOK_URL", "slack_url") + + allow(Slack::Notifier).to receive(:new).and_return(slack_notifier) + allow(InfluxDB2::Client).to receive(:new).and_return(influx_client) + allow(query_api).to receive(:query).with(query: query).and_return(runs) + end + + context "with stable spec report" do + let(:query) { flux_query(false) } + let(:fetch_message) { "Fetching data on test execution for past #{range} days in '#{run_type}' runs" } + let(:slack_send_message) { "Sending top stable spec report to #{slack_channel} slack channel" } + let(:title) { "Top #{results} stable specs for past #{range} days in '#{run_type}' runs" } + let(:rows) do + [ + [name_column("stable spec"), 3, 0, "0%"], + [name_column("unstable spec"), 3, 2, "66.67%"] + ] + end + + it "prints top stable spec report to console" do + expect { reporter.show_top_stable }.to output("#{fetch_message}\n\n#{table(rows, title)}\n").to_stdout + end + + it "sends top stable spec report to slack" do + slack_args = { icon_emoji: ":mtg_green:", username: "Stable Spec Report" } + + expect { reporter.notify_top_stable }.to output("#{fetch_message}\n\n\n#{slack_send_message}\n").to_stdout + expect(slack_notifier).to have_received(:post).with(text: "*#{title}*", **slack_args) + expect(slack_notifier).to have_received(:post).with(text: "```#{table(rows)}```", **slack_args) + end + end + + context "with unstable spec report" do + let(:query) { flux_query(true) } + let(:fetch_message) { "Fetching data on reliable test execution for past #{range} days in '#{run_type}' runs" } + let(:slack_send_message) { "Sending top unstable reliable spec report to #{slack_channel} slack channel" } + let(:title) { "Top #{results} unstable reliable specs for past #{range} days in '#{run_type}' runs" } + let(:rows) { [[name_column("unstable spec"), 3, 2, "66.67%"]] } + + it "prints top unstable spec report to console" do + expect { reporter.show_top_unstable }.to output("#{fetch_message}\n\n#{table(rows, title)}\n").to_stdout + end + + it "sends top unstable reliable spec report to slack" do + slack_args = { icon_emoji: ":sadpanda:", username: "Unstable Spec Report" } + + expect { reporter.notify_top_unstable }.to output("#{fetch_message}\n\n\n#{slack_send_message}\n").to_stdout + expect(slack_notifier).to have_received(:post).with(text: "*#{title}*", **slack_args) + expect(slack_notifier).to have_received(:post).with(text: "```#{table(rows)}```", **slack_args) + end + end + + context "without unstable reliable specs" do + let(:query) { flux_query(true) } + let(:runs) { { 0 => stable_spec } } + let(:fetch_message) { "Fetching data on reliable test execution for past #{range} days in '#{run_type}' runs" } + let(:no_result_message) { "No unstable tests present!" } + + it "prints no result message to console" do + expect { reporter.show_top_unstable }.to output("#{fetch_message}\n\n#{no_result_message}\n").to_stdout + end + + it "skips slack notification" do + expect { reporter.notify_top_unstable }.to output("#{fetch_message}\n\n#{no_result_message}\n").to_stdout + expect(slack_notifier).not_to have_received(:post) + end + end +end |