diff --git a/qa/.gitignore b/qa/.gitignore index 537798a0bb6ad3e2847e869b1065e5ebddef3657..97fb08534795ae600b66cff90d17349c1e110cb9 100644 --- a/qa/.gitignore +++ b/qa/.gitignore @@ -1,5 +1,4 @@ reports/ -no_of_examples/ .ruby-version .tool-versions .ruby-gemset diff --git a/qa/.rspec_parallel b/qa/.rspec_parallel deleted file mode 100644 index e5927927eaa76d0203c8983f16a37be743d53fd0..0000000000000000000000000000000000000000 --- a/qa/.rspec_parallel +++ /dev/null @@ -1,5 +0,0 @@ ---color ---format documentation ---format ParallelTests::RSpec::SummaryLogger --out tmp/spec_summary.log ---format ParallelTests::RSpec::RuntimeLogger --out tmp/parallel_runtime_rspec.log ---require spec_helper diff --git a/qa/qa/runtime/env.rb b/qa/qa/runtime/env.rb index bba5cfcdac593565fc4a7433eca179638e365cce..84f5f56a5830bd3fde9fef76f1bd8267eb365654 100644 --- a/qa/qa/runtime/env.rb +++ b/qa/qa/runtime/env.rb @@ -2,6 +2,7 @@ require 'active_support/deprecation' require 'uri' +require 'etc' module QA module Runtime @@ -734,6 +735,16 @@ def rspec_retried? enabled?(ENV['QA_RSPEC_RETRIED'], default: false) end + def parallel_processes + ENV.fetch('QA_PARALLEL_PROCESSES') do + [Etc.nprocessors / 2, 1].max + end.to_i + end + + def parallel_run? + enabled?(ENV["QA_PARALLEL_RUN"], default: false) + end + private def remote_grid_credentials diff --git a/qa/qa/runtime/logger.rb b/qa/qa/runtime/logger.rb index 7e78ba470d8287903faf751d685118f9a4fe95b3..8728dd2010ab035a3db6a171520ae6411e84eec5 100644 --- a/qa/qa/runtime/logger.rb +++ b/qa/qa/runtime/logger.rb @@ -10,12 +10,26 @@ class << self def logger @logger ||= Gitlab::QA::TestLogger.logger( level: Gitlab::QA::Runtime::Env.log_level, - source: 'QA Tests', - path: File.expand_path('../../tmp', __dir__) + source: logger_source, + path: log_path ) end delegate :debug, :info, :warn, :error, :fatal, :unknown, to: :logger + + private + + def logger_source + if ENV['TEST_ENV_NUMBER'] + "QA Tests ENV-#{ENV['TEST_ENV_NUMBER']}" + else + "QA Tests" + end + end + + def log_path + File.expand_path('../../tmp', __dir__) + end end end end diff --git a/qa/qa/specs/knapsack_runner.rb b/qa/qa/specs/knapsack_runner.rb index 0c4f938ee28b16df68e774463b2888103aeeeaad..b8914ca3846ae3137bb49656e3bc1a8f5c64ec5f 100644 --- a/qa/qa/specs/knapsack_runner.rb +++ b/qa/qa/specs/knapsack_runner.rb @@ -3,7 +3,7 @@ module QA module Specs class KnapsackRunner - def self.run(args) + def self.run(args, parallel: false) QA::Support::KnapsackReport.configure! allocator = Knapsack::AllocatorBuilder.new(Knapsack::Adapters::RSpecAdapter).allocator @@ -14,6 +14,12 @@ def self.run(args) Knapsack.logger.info 'Leftover specs:' Knapsack.logger.info allocator.leftover_node_tests + if parallel + rspec_args = args.reject { |arg| arg == "--" || arg.start_with?("qa/specs/features") } + run_args = [*rspec_args, '--', *allocator.node_tests] + return ParallelRunner.run(run_args) + end + status = RSpec::Core::Runner.run([*args, '--', *allocator.node_tests]) yield status if block_given? status diff --git a/qa/qa/specs/parallel_runner.rb b/qa/qa/specs/parallel_runner.rb index b92fdb610b6951762034e05c72001d552ef1b376..b3349b985b599096a7964c30db78554299331ebc 100644 --- a/qa/qa/specs/parallel_runner.rb +++ b/qa/qa/specs/parallel_runner.rb @@ -1,31 +1,52 @@ # frozen_string_literal: true -require 'open3' +require "parallel_tests" +require "etc" module QA module Specs - module ParallelRunner - module_function + class ParallelRunner + class << self + def run(rspec_args) + used_processes = Runtime::Env.parallel_processes - def run(args) - unless args.include?('--') - index = args.index { |opt| opt.include?('features') } + args = [ + "--type", "rspec", + "-n", used_processes.to_s, + "--serialize-stdout", + '--first-is-1', + "--combine-stderr" + ] - args.insert(index, '--') if index + unless rspec_args.include?('--') + index = rspec_args.index { |opt| opt.include?("qa/specs/features") } + + rspec_args.insert(index, '--') if index + end + + args.push("--", *rspec_args) unless rspec_args.empty? + + set_environment! + perform_global_setup! + + ParallelTests::CLI.new.run(args) end - env = {} - Runtime::Env::ENV_VARIABLES.each_key do |key| - env[key] = ENV[key] if ENV[key] + private + + def perform_global_setup! + Runtime::Browser.configure! + Runtime::Release.perform_before_hooks end - env['QA_RUNTIME_SCENARIO_ATTRIBUTES'] = Runtime::Scenario.attributes.to_json - env['GITLAB_QA_ACCESS_TOKEN'] = Runtime::API::Client.new(:gitlab).personal_access_token unless env['GITLAB_QA_ACCESS_TOKEN'] - cmd = "bundle exec parallel_test -t rspec --combine-stderr --serialize-stdout -- #{args.flatten.join(' ')}" - ::Open3.popen2e(env, cmd) do |_, out, wait| - out.each { |line| puts line } + def set_environment! + ENV.store("NO_KNAPSACK", "true") + ENV.store("QA_PARALLEL_RUN", "true") + + return if ENV["QA_GITLAB_URL"].present? - exit wait.value.exitstatus + Support::GitlabAddress.define_gitlab_address_attribute! + ENV.store("QA_GITLAB_URL", Support::GitlabAddress.address_with_port(with_default_port: false)) end end end diff --git a/qa/qa/specs/runner.rb b/qa/qa/specs/runner.rb index 5650587f573b3b09a377aa105605dd5676cb02cf..431a7e3fc10a8639a299e90ad70f57c7ae1e8833 100644 --- a/qa/qa/specs/runner.rb +++ b/qa/qa/specs/runner.rb @@ -2,6 +2,7 @@ require 'rspec/core' require 'rspec/expectations' +require 'tempfile' module QA module Specs @@ -47,51 +48,57 @@ def rspec_tags end def perform - args = [] - args.push('--tty') if tty - args.push(rspec_tags) - args.push(options) + args = build_initial_args + # use options from default .rspec file for metadata only runs + configure_default_formatters!(args) unless metadata_run? + args.push(DEFAULT_TEST_PATH_ARGS) unless custom_test_paths? - unless Runtime::Env.knapsack? || options.any? { |opt| opt.include?('features') } - args.push(DEFAULT_TEST_PATH_ARGS) + run_rspec(args) + end + + private + + delegate :rspec_retried?, :parallel_run?, to: Runtime::Env + + def build_initial_args + [].tap do |args| + args.push('--tty') if tty + args.push(rspec_tags) + args.push(options) end + end - if Runtime::Env.knapsack? - KnapsackRunner.run(args.flatten) { |status| abort if status.nonzero? } - elsif Runtime::Scenario.attributes[:parallel] - ParallelRunner.run(args.flatten) - elsif Runtime::Scenario.attributes[:loop] - LoopRunner.run(args.flatten) - elsif Runtime::Scenario.attributes[:count_examples_only] + def run_rspec(args) + if Runtime::Scenario.attributes[:count_examples_only] count_examples_only(args) elsif Runtime::Scenario.attributes[:test_metadata_only] test_metadata_only(args) + elsif Runtime::Env.knapsack? + KnapsackRunner.run(args.flatten, parallel: parallel_run?) { |status| abort if status.nonzero? } + elsif !rspec_retried? && parallel_run? + ParallelRunner.run(args.flatten) + elsif Runtime::Scenario.attributes[:loop] + LoopRunner.run(args.flatten) else RSpec::Core::Runner.run(args.flatten, *DEFAULT_STD_ARGS).tap { |status| abort if status.nonzero? } end end - private - def count_examples_only(args) args.unshift('--dry-run') out = StringIO.new + err = StringIO.new - RSpec::Core::Runner.run(args.flatten, $stderr, out).tap do |status| - abort if status.nonzero? - end + total_examples = Tempfile.open('test-metadata.json') do |file| + RSpec.configure { |config| config.add_formatter(QA::Support::JsonFormatter, file.path) } + RSpec::Core::Runner.run(args.flatten, err, out).tap { |status| abort if status.nonzero? } - begin - total_examples = out.string.match(/(\d+) examples?,/)[1] - rescue StandardError - raise RegexMismatchError, 'Rspec output did not match regex' + JSON.load_file(file, symbolize_names: true).dig(:summary, :example_count) end - filename = build_filename - - File.open(filename, 'w') { |f| f.write(total_examples) } if total_examples.to_i > 0 - - $stdout.puts total_examples + puts total_examples + rescue StandardError => e + raise e, "Failed to detect example count, error: '#{e}'.\nOut: '#{out.string}'\nErr: #{err.string}" end def test_metadata_only(args) @@ -111,23 +118,28 @@ def test_metadata_only(args) $stdout.puts "Saved to file: #{output_file}" end - def build_filename - filename = Runtime::Scenario.klass.split('::').last(3).join('_').downcase + def configure_default_formatters!(args) + default_formatter_file_name = "tmp/rspec-#{ENV['CI_JOB_ID'] || 'local'}-retried-#{rspec_retried?}" + filename = if parallel_run? + rspec_retried? ? default_formatter_file_name : "#{default_formatter_file_name}-$TEST_ENV_NUMBER" + else + default_formatter_file_name + end - tags = [] - tag_opts = %w[--tag -t] - options.reduce do |before, after| - tags << after if tag_opts.include?(before) - after - end - tags = tags.compact.join('_') + args.push("--format", "documentation") unless args.flatten.include?("documentation") + { "QA::Support::JsonFormatter" => "json", "RspecJunitFormatter" => "xml" }.each do |formatter, extension| + next if args.flatten.include?(formatter) - filename.concat("_#{tags}") unless tags.empty? + args.push("--format", formatter, "--out", "#{filename}.#{extension}") + end + end - filename.concat('.txt') + def custom_test_paths? + Runtime::Env.knapsack? || options.any? { |opt| opt.include?('features') } + end - FileUtils.mkdir_p('no_of_examples') - File.join('no_of_examples', filename) + def metadata_run? + Runtime::Scenario.attributes[:count_examples_only] || Runtime::Scenario.attributes[:test_metadata_only] end end end diff --git a/qa/qa/specs/spec_helper.rb b/qa/qa/specs/spec_helper.rb index 915d7acff19950189ebbdd259e278d38ace4f30e..1eb3eaaa48d1196c7489a4b85fa5ddd3ec9348ae 100644 --- a/qa/qa/specs/spec_helper.rb +++ b/qa/qa/specs/spec_helper.rb @@ -42,7 +42,12 @@ config.example_status_persistence_file_path = ENV.fetch('RSPEC_LAST_RUN_RESULTS_FILE', 'tmp/examples.txt') config.prepend_before do |example| - QA::Runtime::Logger.info("Starting test: #{Rainbow(example.full_description).bright}") + if QA::Runtime::Env.parallel_run? + QA::Runtime::Logger.info("Starting test - PID #{Process.pid}: #{Rainbow(example.full_description).bright}") + else + QA::Runtime::Logger.info("Starting test: #{Rainbow(example.full_description).bright}") + end + QA::Runtime::Example.current = example visit(QA::Runtime::Scenario.gitlab_address) if QA::Runtime::Env.mobile_layout? @@ -53,8 +58,8 @@ end config.prepend_before(:suite) do - # Perform before hooks at the very start of the test run - QA::Runtime::Release.perform_before_hooks unless QA::Runtime::Env.dry_run + # Perform before hooks at the very start of the test run, perform once for parallel runs + QA::Runtime::Release.perform_before_hooks unless QA::Runtime::Env.dry_run || QA::Runtime::Env.parallel_run? end config.before(:suite) do diff --git a/qa/qa/support/formatters/test_metrics_formatter.rb b/qa/qa/support/formatters/test_metrics_formatter.rb index 4bb0a4f4343c5a199d27a11925847aeb1b0e0c55..5418d48a0a54252f90e13e670eb8d38444d2dcc4 100644 --- a/qa/qa/support/formatters/test_metrics_formatter.rb +++ b/qa/qa/support/formatters/test_metrics_formatter.rb @@ -97,9 +97,12 @@ def push_test_metrics_to_influxdb def push_test_metrics_to_gcs init_gcs_client! # init client and exit early if mandatory configuration is missing retry_on_exception(sleep_interval: 30, message: 'Failed to push test metrics to GCS') do - gcs_client.put_object(gcs_bucket, metrics_file_name(prefix: 'test', - postfix: "-#{env('CI_PIPELINE_ID') || 'local'}"), execution_data.to_json, - force: true, content_type: 'application/json') + gcs_client.put_object( + gcs_bucket, + metrics_file_name(prefix: 'test', postfix: metrics_filename_postfix), + execution_data.to_json, + force: true, content_type: 'application/json' + ) log(:info, "Pushed #{execution_data.length} test execution entries to GCS") end @@ -140,10 +143,12 @@ def push_code_runtime_metrics def push_fabrication_metrics_gcs(data) init_gcs_client! # init client and exit early if mandatory configuration is missing retry_on_exception(sleep_interval: 30, message: 'Failed to push resource fabrication metrics to GCS') do - gcs_client.put_object(gcs_bucket, - metrics_file_name(prefix: 'fabrication', - postfix: "-#{env('CI_PIPELINE_ID') || 'local'}"), - data.to_json, force: true, content_type: 'application/json') + gcs_client.put_object( + gcs_bucket, + metrics_file_name(prefix: 'fabrication', postfix: metrics_filename_postfix), + data.to_json, force: true, + content_type: 'application/json' + ) log(:info, "Pushed #{data.length} resource fabrication entries to GCS") end @@ -186,6 +191,17 @@ def metrics_file_name(prefix:, postfix: '') "#{retry_failed_specs? ? "-retry-#{rspec_retried?}" : ''}#{postfix}.json" end + # Postfix for metrics filenames + # + # @return [String] + def metrics_filename_postfix + @metrics_filename_postfix ||= if QA::Runtime::Env.parallel_run? + "-#{Process.pid}-#{env('CI_PIPELINE_ID') || 'local'}" + else + "-#{env('CI_PIPELINE_ID') || 'local'}" + end + end + # Transform example to influxdb compatible metrics data # https://github.com/influxdata/influxdb-client-ruby#data-format # diff --git a/qa/spec/specs/parallel_runner_spec.rb b/qa/spec/specs/parallel_runner_spec.rb index d77b50fbe094a2ece1e41a2ff661df37ac3f4e6b..1622d0b210a1336ac34c0f4c1506c20165b1452c 100644 --- a/qa/spec/specs/parallel_runner_spec.rb +++ b/qa/spec/specs/parallel_runner_spec.rb @@ -1,58 +1,93 @@ # frozen_string_literal: true +require 'etc' + RSpec.describe QA::Specs::ParallelRunner do include QA::Support::Helpers::StubEnv - before do - allow(QA::Runtime::Scenario).to receive(:attributes).and_return(parallel: true) - stub_env('GITLAB_QA_ACCESS_TOKEN', 'skip_token_creation') - end + subject(:runner) { described_class } - it 'passes args to parallel_tests' do - expect_cli_arguments(['--tag', '~orchestrated', *QA::Specs::Runner::DEFAULT_TEST_PATH_ARGS]) + let(:parallel_tests) { instance_double(ParallelTests::CLI, run: nil) } - subject.run(['--tag', '~orchestrated', *QA::Specs::Runner::DEFAULT_TEST_PATH_ARGS]) - end + before do + allow(ParallelTests::CLI).to receive(:new).and_return(parallel_tests) + allow(Etc).to receive(:nprocessors).and_return(8) + allow(ENV).to receive(:store) - it 'passes a given test path to parallel_tests and adds a separator' do - expect_cli_arguments(%w[-- qa/specs/features/foo]) + allow(QA::Runtime::Browser).to receive(:configure!) + allow(QA::Runtime::Release).to receive(:perform_before_hooks) - subject.run(%w[qa/specs/features/foo]) + stub_env("QA_GITLAB_URL", "http://127.0.0.1:3000") + stub_env("QA_PARALLEL_PROCESSES", "8") end - it 'passes tags and test paths to parallel_tests and adds a separator' do - expect_cli_arguments(%w[--tag smoke -- qa/specs/features/foo qa/specs/features/bar]) + it "runs cli without additional rspec args" do + runner.run([]) - subject.run(%w[--tag smoke qa/specs/features/foo qa/specs/features/bar]) + expect(parallel_tests).to have_received(:run).with([ + "--type", "rspec", + "-n", "8", + "--serialize-stdout", + "--first-is-1", + "--combine-stderr" + ]) end - it 'passes tags and test paths with separators to parallel_tests' do - expect_cli_arguments(%w[-- --tag smoke -- qa/specs/features/foo qa/specs/features/bar]) - - subject.run(%w[-- --tag smoke -- qa/specs/features/foo qa/specs/features/bar]) + it "runs cli with additional rspec args" do + runner.run(["--force-color", "qa/specs/features/api"]) + + expect(parallel_tests).to have_received(:run).with([ + "--type", "rspec", + "-n", "8", + "--serialize-stdout", + "--first-is-1", + "--combine-stderr", + "--", "--force-color", + "--", "qa/specs/features/api" + ]) end - it 'passes supported environment variables' do - # Test only env vars starting with GITLAB because some of the others - # affect how the runner behaves, and we're not concerned with those - # behaviors in this test - gitlab_env_vars = QA::Runtime::Env::ENV_VARIABLES.reject { |v| !v.start_with?('GITLAB') } + context "with QA_GITLAB_URL not set" do + before do + stub_env("QA_GITLAB_URL", nil) - gitlab_env_vars.each do |k, v| - stub_env(k, v) + QA::Support::GitlabAddress.instance_variable_set(:@initialized, nil) end - gitlab_env_vars['QA_RUNTIME_SCENARIO_ATTRIBUTES'] = '{"parallel":true}' + after do + QA::Support::GitlabAddress.instance_variable_set(:@initialized, nil) + end - expect_cli_arguments([], gitlab_env_vars) + it "sets QA_GITLAB_URL variable for subprocess" do + runner.run([]) - subject.run([]) + expect(ENV).to have_received(:store).with("QA_GITLAB_URL", "http://127.0.0.1:3000") + end end - def expect_cli_arguments(arguments, env = { 'QA_RUNTIME_SCENARIO_ATTRIBUTES' => '{"parallel":true}' }) - cmd = "bundle exec parallel_test -t rspec --combine-stderr --serialize-stdout -- #{arguments.join(' ')}" - expect(Open3).to receive(:popen2e) - .with(hash_including(env), cmd) - .and_return(0) + context "with QA_PARALLEL_PROCESSES not set" do + before do + stub_env("QA_PARALLEL_PROCESSES", nil) + allow(Etc).to receive(:nprocessors).and_return(8) + end + + it "sets number of processes to half of available processors" do + allow(QA::Runtime::Env).to receive(:parallel_processes).and_call_original + + runner.run([]) + + expect(QA::Runtime::Env).to have_received(:parallel_processes) + actual_processes = QA::Runtime::Env.parallel_processes + + expect(parallel_tests).to have_received(:run) do |args| + expect(args).to eq([ + "--type", "rspec", + "-n", actual_processes.to_s, + "--serialize-stdout", + "--first-is-1", + "--combine-stderr" + ]) + end + end end end diff --git a/qa/spec/specs/runner_spec.rb b/qa/spec/specs/runner_spec.rb index c5a6f455f2dca201d3fc9b434fba0dd49a4e97f8..51cb80d728aa9b7ece3f6480dfbf6581e86752e2 100644 --- a/qa/spec/specs/runner_spec.rb +++ b/qa/spec/specs/runner_spec.rb @@ -3,7 +3,10 @@ RSpec.describe QA::Specs::Runner do shared_examples 'excludes default skipped, and geo' do it 'excludes the default skipped and geo tags, and includes default args' do - expect_rspec_runner_arguments(DEFAULT_SKIPPED_TAGS + ['--tag', '~geo', *described_class::DEFAULT_TEST_PATH_ARGS]) + expect_rspec_runner_arguments(DEFAULT_SKIPPED_TAGS + ['--tag', '~geo', + '--format', 'documentation', '--format', 'QA::Support::JsonFormatter', + '--out', "tmp/rspec-#{ENV['CI_JOB_ID'] || 'local'}-retried-false.json", + '--format', 'RspecJunitFormatter', '--out', "tmp/rspec-#{ENV['CI_JOB_ID'] || 'local'}-retried-false.xml", *described_class::DEFAULT_TEST_PATH_ARGS]) subject.perform end @@ -28,7 +31,10 @@ it 'sets the `--tty` flag' do expect_rspec_runner_arguments( - ['--tty'] + DEFAULT_SKIPPED_TAGS + ['--tag', '~geo', *described_class::DEFAULT_TEST_PATH_ARGS] + ['--tty'] + DEFAULT_SKIPPED_TAGS + ['--tag', '~geo', + '--format', 'documentation', '--format', 'QA::Support::JsonFormatter', + '--out', "tmp/rspec-#{ENV['CI_JOB_ID'] || 'local'}-retried-false.json", + '--format', 'RspecJunitFormatter', '--out', "tmp/rspec-#{ENV['CI_JOB_ID'] || 'local'}-retried-false.xml", *described_class::DEFAULT_TEST_PATH_ARGS] ) subject.perform @@ -37,63 +43,37 @@ context 'when count_examples_only is set as an option' do let(:out) { StringIO.new } + let(:err) { StringIO.new } before do QA::Runtime::Scenario.define(:count_examples_only, true) - out.string = '22 examples,' - allow(StringIO).to receive(:new).and_return(out) + allow(StringIO).to receive(:new).and_return(out, err) + allow(RSpec::Core::Runner).to receive(:run).and_return(0) end after do QA::Runtime::Scenario.attributes.delete(:count_examples_only) end - it 'sets the `--dry-run` flag' do + it 'sets the `--dry-run` flag and minimal arguments' do expect_rspec_runner_arguments( - ['--dry-run'] + DEFAULT_SKIPPED_TAGS + ['--tag', '~geo', *described_class::DEFAULT_TEST_PATH_ARGS], - [$stderr, anything] + ['--dry-run', '--tag', '~orchestrated', '--tag', '~transient', '--tag', '~geo', *described_class::DEFAULT_TEST_PATH_ARGS], + [err, out] ) - subject.perform - end - - it 'writes to file when examples are more than zero' do - allow(RSpec::Core::Runner).to receive(:run).and_return(0) - - expect(File).to receive(:open).with('no_of_examples/test_instance_all.txt', 'w') { '22' } - - subject.perform - end - - it 'does not write to file when zero examples' do - out.string = '0 examples,' - allow(RSpec::Core::Runner).to receive(:run).and_return(0) - - expect(File).not_to receive(:open) - - subject.perform - end - - it 'raises error when Rspec output does not match regex' do - out.string = '0' - allow(RSpec::Core::Runner).to receive(:run).and_return(0) - - expect { subject.perform } - .to raise_error(QA::Specs::Runner::RegexMismatchError, 'Rspec output did not match regex') + expect { subject.perform }.to raise_error(JSON::ParserError, /Failed to detect example count/) end context 'when --tag is specified as an option' do subject { described_class.new.tap { |runner| runner.options = %w[--tag actioncable] } } - it 'includes the option value in the file name' do + it 'includes the tag in the RSpec arguments' do expect_rspec_runner_arguments( ['--dry-run', '--tag', '~geo', '--tag', 'actioncable', *described_class::DEFAULT_TEST_PATH_ARGS], - [$stderr, anything] + [err, out] ) - expect(File).to receive(:open).with('no_of_examples/test_instance_all_actioncable.txt', 'w') { '22' } - - subject.perform + expect { subject.perform }.to raise_error(JSON::ParserError, /Failed to detect example count/) end end end @@ -116,24 +96,11 @@ it 'sets the `--dry-run` flag' do expect_rspec_runner_arguments( ['--dry-run', *described_class::DEFAULT_TEST_PATH_ARGS], - [$stderr, anything] + [$stderr, $stdout] ) subject.perform end - - it 'configures json formatted output to file' do - allow(QA::Runtime::Path).to receive(:qa_root).and_return('/root') - - expect(rspec_config).to receive(:add_formatter) - .with(QA::Support::JsonFormatter, output_file) - expect(rspec_config).to receive(:fail_if_no_examples=) - .with(true) - - allow(RSpec::Core::Runner).to receive(:run).and_return(0) - - subject.perform - end end context 'when tags are set' do @@ -141,7 +108,10 @@ it 'focuses on the given tags' do expect_rspec_runner_arguments( - ['--tag', 'orchestrated', '--tag', 'github', '--tag', '~geo', *described_class::DEFAULT_TEST_PATH_ARGS] + ['--tag', 'orchestrated', '--tag', 'github', '--tag', '~geo', + '--format', 'documentation', '--format', 'QA::Support::JsonFormatter', + '--out', "tmp/rspec-#{ENV['CI_JOB_ID'] || 'local'}-retried-false.json", + '--format', 'RspecJunitFormatter', '--out', "tmp/rspec-#{ENV['CI_JOB_ID'] || 'local'}-retried-false.xml", *described_class::DEFAULT_TEST_PATH_ARGS] ) subject.perform @@ -152,7 +122,10 @@ subject { described_class.new.tap { |runner| runner.options = %w[--tag smoke] } } it 'focuses on the given tag without excluded tags' do - expect_rspec_runner_arguments(['--tag', '~geo', '--tag', 'smoke', *described_class::DEFAULT_TEST_PATH_ARGS]) + expect_rspec_runner_arguments(['--tag', '~geo', '--tag', 'smoke', + '--format', 'documentation', '--format', 'QA::Support::JsonFormatter', + '--out', "tmp/rspec-#{ENV['CI_JOB_ID'] || 'local'}-retried-false.json", + '--format', 'RspecJunitFormatter', '--out', "tmp/rspec-#{ENV['CI_JOB_ID'] || 'local'}-retried-false.xml", *described_class::DEFAULT_TEST_PATH_ARGS]) subject.perform end @@ -162,7 +135,13 @@ subject { described_class.new.tap { |runner| runner.options = %w[qa/specs/features/foo] } } it 'passes the given tests path and excludes the default skipped, and geo tags' do - expect_rspec_runner_arguments(DEFAULT_SKIPPED_TAGS + ['--tag', '~geo', 'qa/specs/features/foo']) + expect_rspec_runner_arguments( + ['--tag', '~orchestrated', '--tag', '~transient', '--tag', '~geo', + 'qa/specs/features/foo', + '--format', 'documentation', '--format', 'QA::Support::JsonFormatter', + '--out', "tmp/rspec-#{ENV['CI_JOB_ID'] || 'local'}-retried-false.json", + '--format', 'RspecJunitFormatter', '--out', "tmp/rspec-#{ENV['CI_JOB_ID'] || 'local'}-retried-false.xml"] + ) subject.perform end @@ -172,7 +151,13 @@ subject { described_class.new.tap { |runner| runner.options = %w[--tag smoke qa/specs/features/foo] } } it 'focuses on the given tag and includes the path without excluding the orchestrated or transient tags' do - expect_rspec_runner_arguments(['--tag', '~geo', '--tag', 'smoke', 'qa/specs/features/foo']) + expect_rspec_runner_arguments( + ['--tag', '~geo', '--tag', 'smoke', + 'qa/specs/features/foo', + '--format', 'documentation', '--format', 'QA::Support::JsonFormatter', + '--out', "tmp/rspec-#{ENV['CI_JOB_ID'] || 'local'}-retried-false.json", + '--format', 'RspecJunitFormatter', '--out', "tmp/rspec-#{ENV['CI_JOB_ID'] || 'local'}-retried-false.xml"] + ) subject.perform end @@ -184,7 +169,10 @@ end it 'includes default args and excludes the skip_signup_disabled tag' do - expect_rspec_runner_arguments(DEFAULT_SKIPPED_TAGS + ['--tag', '~geo', '--tag', '~skip_signup_disabled', *described_class::DEFAULT_TEST_PATH_ARGS]) + expect_rspec_runner_arguments(DEFAULT_SKIPPED_TAGS + ['--tag', '~geo', '--tag', '~skip_signup_disabled', + '--format', 'documentation', '--format', 'QA::Support::JsonFormatter', + '--out', "tmp/rspec-#{ENV['CI_JOB_ID'] || 'local'}-retried-false.json", + '--format', 'RspecJunitFormatter', '--out', "tmp/rspec-#{ENV['CI_JOB_ID'] || 'local'}-retried-false.xml", *described_class::DEFAULT_TEST_PATH_ARGS]) subject.perform end @@ -196,7 +184,10 @@ end it 'includes default args and excludes the skip_live_env tag' do - expect_rspec_runner_arguments(DEFAULT_SKIPPED_TAGS + ['--tag', '~geo', '--tag', '~skip_live_env', *described_class::DEFAULT_TEST_PATH_ARGS]) + expect_rspec_runner_arguments(DEFAULT_SKIPPED_TAGS + ['--tag', '~geo', '--tag', '~skip_live_env', + '--format', 'documentation', '--format', 'QA::Support::JsonFormatter', + '--out', "tmp/rspec-#{ENV['CI_JOB_ID'] || 'local'}-retried-false.json", + '--format', 'RspecJunitFormatter', '--out', "tmp/rspec-#{ENV['CI_JOB_ID'] || 'local'}-retried-false.xml", *described_class::DEFAULT_TEST_PATH_ARGS]) subject.perform end end @@ -213,7 +204,10 @@ subject { described_class.new.tap { |runner| runner.tags = %i[geo] } } it 'includes the geo tag' do - expect_rspec_runner_arguments(['--tag', 'geo', *described_class::DEFAULT_TEST_PATH_ARGS]) + expect_rspec_runner_arguments(['--tag', 'geo', + '--format', 'documentation', '--format', 'QA::Support::JsonFormatter', + '--out', "tmp/rspec-#{ENV['CI_JOB_ID'] || 'local'}-retried-false.json", + '--format', 'RspecJunitFormatter', '--out', "tmp/rspec-#{ENV['CI_JOB_ID'] || 'local'}-retried-false.xml", *described_class::DEFAULT_TEST_PATH_ARGS]) subject.perform end end @@ -231,7 +225,9 @@ it 'includes default args and excludes all unsupported tags' do expect_rspec_runner_arguments( DEFAULT_SKIPPED_TAGS + ['--tag', '~geo', *excluded_feature_tags_except(feature), - *described_class::DEFAULT_TEST_PATH_ARGS] + '--format', 'documentation', '--format', 'QA::Support::JsonFormatter', + '--out', "tmp/rspec-#{ENV['CI_JOB_ID'] || 'local'}-retried-false.json", + '--format', 'RspecJunitFormatter', '--out', "tmp/rspec-#{ENV['CI_JOB_ID'] || 'local'}-retried-false.xml", *described_class::DEFAULT_TEST_PATH_ARGS] ) subject.perform