spec/gitlab/qa/component/specs_spec.rb (367 lines of code) (raw):

# frozen_string_literal: true describe Gitlab::QA::Component::Specs do let(:docker_command) { spy('docker command') } let(:suite) { spy('suite') } let(:stubbed_env) { {} } before do stub_const('Gitlab::QA::Docker::Command', docker_command) allow(Gitlab::QA::Runtime::Scenario.attributes).to receive(:include?).and_return(false) allow(Gitlab::QA::Runtime::Env).to receive(:variables_to_mask).and_return(nil) end around do |example| # In CI, QA_IMAGE could be set and modify the tests' behavior. Ensure we reset it to nil. ClimateControl.modify(QA_IMAGE: nil, **stubbed_env) { example.run } end describe '#perform' do it 'bind-mounts a docker socket' do described_class.perform do |specs| specs.suite = suite specs.release = spy('release', login_params: nil) end expect(docker_command).to have_received(:volume) .with('/var/run/docker.sock', '/var/run/docker.sock') end it 'bind-mounds volumes' do allow(SecureRandom).to receive(:hex).and_return('def456') allow(Gitlab::QA::Runtime::Env) .to receive_messages(host_artifacts_dir: '/tmp/gitlab-qa/gitlab-qa-run-2018-07-11-10-00-00-abc123', qa_rspec_report_path: '/qa/rspec') release = instance_double( Gitlab::QA::Release, edition: :ce, project_name: 'gitlab-ce', qa_image: 'gitlab-ce-qa', qa_tag: 'latest' ) allow(release) .to receive(:login_params) .and_return(nil) described_class.perform do |specs| specs.suite = suite specs.release = release end expect(docker_command).to have_received(:volume) .with('/var/run/docker.sock', '/var/run/docker.sock') expect(docker_command).to have_received(:volume) .with('/tmp/gitlab-qa/gitlab-qa-run-2018-07-11-10-00-00-abc123/gitlab-ce-qa-def456', File.join( Gitlab::QA::Docker::Volumes::QA_CONTAINER_WORKDIR, 'tmp')) expect(docker_command).to have_received(:volume) .with('/qa/rspec', File.join(Gitlab::QA::Docker::Volumes::QA_CONTAINER_WORKDIR, 'rspec')) end context 'with test suite class environment variable set' do let(:stubbed_env) { { "QA_SUITE_CLASS_NAME" => "Test::Instance::Create" } } let(:docker_engine) { spy('docker engine') } before do stub_const('Gitlab::QA::Docker::Engine', docker_engine) end it 'uses scenario class from environment variable' do described_class.perform do |specs| specs.suite = suite specs.release = spy('release', login_params: nil) end expect(docker_engine).to have_received(:run) .with(a_hash_including(args: ["Test::Instance::Create"])) end end context 'when preparing QA image' do let(:docker_engine) { spy('docker engine') } let(:qa_image) { 'gitlab/gitlab-ce-qa' } let(:qa_tag) { 'latest' } let(:release) do double('release', project_name: 'gitlab-ce', qa_image: qa_image, qa_tag: qa_tag, login_params: nil) end before do stub_const('Gitlab::QA::Docker::Engine', docker_engine) end context 'when not skipping Docker pulls' do before do allow(Gitlab::QA::Runtime::Env).to receive(:skip_pull?).and_return(false) end it 'pulls the QA image' do described_class.perform do |specs| specs.suite = suite specs.release = release end expect(docker_engine).to have_received(:pull) .with(image: "#{qa_image}:#{qa_tag}") end end context 'when skipping Docker pulls' do before do allow(Gitlab::QA::Runtime::Env).to receive(:skip_pull?).and_return(true) end it 'does not pull QA image' do described_class.perform do |specs| specs.suite = suite specs.release = release end expect(docker_engine).not_to have_received(:pull) end end context 'when Runtime::Scenario.qa_image is set' do let(:custom_qa_image) { 'custom-qa-image:v1' } before do stub_const('Gitlab::QA::Runtime::Scenario', spy) allow(Gitlab::QA::Runtime::Scenario.attributes).to receive(:include?).and_return(true) allow(Gitlab::QA::Runtime::Scenario).to receive(:qa_image).and_return(custom_qa_image) allow(Gitlab::QA::Runtime::Env).to receive(:skip_pull?).and_return(false) end it 'pulls the custom QA image' do described_class.perform do |specs| specs.suite = suite specs.release = release end expect(docker_engine).to have_received(:pull) .with(image: custom_qa_image) end end end describe 'Docker::Engine#run arguments' do let(:docker_engine) { spy('docker engine') } before do stub_const('Gitlab::QA::Docker::Engine', docker_engine) end it 'accepts a GitLab image' do described_class.perform do |specs| specs.suite = suite specs.release = Gitlab::QA::Release.new('gitlab/gitlab-ce:foobar') end expect(docker_engine).to have_received(:run) .with(a_hash_including(image: 'gitlab/gitlab-ce-qa:foobar', args: [suite])) end it 'accepts a GitLab QA image' do described_class.perform do |specs| specs.suite = suite specs.release = Gitlab::QA::Release.new('gitlab/gitlab-ce-qa:foobar') end expect(docker_engine).to have_received(:run) .with(a_hash_including(image: 'gitlab/gitlab-ce-qa:foobar', args: [suite])) end context 'when Runtime::Scenario.qa_image is set' do let(:custom_qa_image) { 'custom-qa-image:v1' } before do stub_const('Gitlab::QA::Runtime::Scenario', spy) allow(Gitlab::QA::Runtime::Scenario.attributes).to receive(:include?).and_return(true) allow(Gitlab::QA::Runtime::Scenario).to receive(:qa_image).and_return(custom_qa_image) end it 'runs the custom QA image' do described_class.perform do |specs| specs.suite = suite specs.release = Gitlab::QA::Release.new('ce') end expect(docker_engine).to have_received(:run) .with(a_hash_including(image: custom_qa_image, args: [suite])) end end context 'when release.login_params are present' do it 'log into the container registry' do release = double('release', project_name: 'gitlab-ce', qa_image: 'gitlab/gitlab-ce-qa', qa_tag: 'latest', login_params: { foo: :bar }) described_class.perform do |specs| specs.suite = suite specs.release = release end expect(docker_engine).to have_received(:login).with(release.login_params) expect(docker_engine).to have_received(:run) .with(a_hash_including(image: 'gitlab/gitlab-ce-qa:latest', args: [suite])) end end context 'when --no-tests is passed' do let(:scenario_spy) { spy('scenario') } before do stub_const('Gitlab::QA::Runtime::Scenario', scenario_spy) allow(scenario_spy).to receive(:run_tests).and_return(false) end it 'skips tests' do described_class.perform do |specs| specs.suite = suite specs.release = spy('release') end expect(docker_engine).not_to have_received(:run) end end context 'when --enable-feature is passed in args' do let(:args) { %w[http://abc.test --enable-feature a] } it 'mentions the feature flag and runs exactly once' do allow(Gitlab::QA::Runtime::Logger.logger).to receive(:info) described_class.perform do |specs| specs.suite = suite specs.args = args specs.release = Gitlab::QA::Release.new('EE') end expect(Gitlab::QA::Runtime::Logger.logger).to have_received(:info).with(/Running with feature flag/) expect(docker_engine).to have_received(:run).once end end context 'when --enable-feature, --disable-feature, and --set-feature-flags are passed in args' do let(:args) { %w[http://abc.test --disable-feature a --enable-feature b --set-feature-flags c=enable,d=disable] } let(:image) { 'gitlab/gitlab-ee-qa:nightly' } it 'runs once for each option' do described_class.perform do |specs| specs.suite = suite specs.args = args specs.release = Gitlab::QA::Release.new('EE') end expect(docker_engine).to have_received(:run) .with(a_hash_including(image: image, args: [suite, 'http://abc.test', '--disable-feature', 'a'])) expect(docker_engine).to have_received(:run) .with(a_hash_including(image: image, args: [suite, 'http://abc.test', '--enable-feature', 'b'])) expect(docker_engine).to have_received(:run) .with(a_hash_including(image: image, args: [suite, 'http://abc.test', '--set-feature-flags', 'c=enable,d=disable'])) end end context 'when rspec args are specified and no feature flags passed' do let(:args) { %w[http://abc.test -- file/path --tag focus] } it 'passes the args' do described_class.perform do |specs| specs.suite = suite specs.args = args specs.release = Gitlab::QA::Release.new('EE') end expect(docker_engine).to have_received(:run) .with(a_hash_including( image: 'gitlab/gitlab-ee-qa:nightly', args: [suite, 'http://abc.test', '--', 'file/path', '--tag', 'focus'] )) end it 'does not mention feature flags' do allow(Gitlab::QA::Runtime::Logger.logger).to receive(:info) described_class.perform do |specs| specs.suite = suite specs.release = Gitlab::QA::Release.new('EE') specs.args = args end expect(Gitlab::QA::Runtime::Logger.logger).not_to have_received(:info).with(/Running with feature flag/) end end context 'when hostname is toggled' do let(:hostname) { 'test.test' } before do allow(docker_engine).to receive(:run).and_yield(docker_command) end it 'passes to the container when set' do described_class.perform do |specs| specs.suite = suite specs.hostname = hostname specs.release = Gitlab::QA::Release.new('EE') end expect(docker_command).to have_received(:<<).with("--hostname #{hostname}") expect(docker_command).to have_received(:env).with('QA_HOSTNAME', hostname) end it 'does not pass to the container when unset' do described_class.perform do |specs| specs.suite = suite specs.release = Gitlab::QA::Release.new('EE') end expect(docker_command).not_to have_received(:<<).with("--hostname #{hostname}") expect(docker_command).not_to have_received(:env).with('QA_HOSTNAME', hostname) end end context 'when docker add hosts' do before do allow(docker_engine).to receive(:run).and_yield(docker_command) end it 'does not pass --add-hosts when Runtime::Env.docker_add_hosts is not set' do described_class.perform do |specs| specs.suite = suite specs.release = Gitlab::QA::Release.new('EE') end expect(docker_command).not_to have_received(:<<).with(/add-host/) end it 'passes --add-hosts when Runtime::Env.docker_add_hosts is set' do allow(Gitlab::QA::Runtime::Env) .to receive(:docker_add_hosts) .and_return(%w[docker:93.184.216.34 gravatar.com:10.0.0.1]) described_class.perform do |specs| specs.suite = suite specs.release = Gitlab::QA::Release.new('EE') end expect(docker_command).to have_received(:<<) .at_most(1) .with("--add-host docker:93.184.216.34 --add-host gravatar.com:10.0.0.1 ") end end context 'when file variables are set' do let(:secrets) { %w[secret private_stuff] } before do allow(docker_engine).to receive(:run).and_yield(docker_command) allow(Gitlab::QA::Runtime::Env).to receive(:variables_to_mask).and_return(secrets) end it 'masks values specified' do described_class.perform do |specs| specs.suite = suite specs.release = Gitlab::QA::Release.new('EE') end expect(docker_engine).to have_received(:run).with(a_hash_including(mask_secrets: secrets)) end end end describe 'with retry enabled' do let(:docker_engine) { instance_double(Gitlab::QA::Docker::Engine, pull: nil, run: nil) } let(:artifacts_dir) { '/tmp/gitlab-qa/qa-run' } let(:args) { ['--', 'file/path', '--tag', 'some_tag'] } let(:spec_status) { 'failed' } let(:release) do instance_double( Gitlab::QA::Release, login_params: nil, edition: :ce, project_name: 'gitlab-ce', qa_image: 'gitlab-ce-qa', qa_tag: 'latest' ) end def run_tests described_class.perform do |specs| specs.suite = suite specs.release = release specs.args = args specs.retry_failed_specs = true end end before do allow(File).to receive(:exist?).and_call_original allow(File).to receive(:exist?).with(/examples.txt$/).and_return(true) allow(File).to receive(:read).with(/examples.txt$/).and_return(<<~EXAMPLES) example_id | status | run_time | --------------------- | --------------- | ------------ | ./some_spec.rb[1:1:1] | #{spec_status} | 22.7 seconds | EXAMPLES allow(Gitlab::QA::Runtime::Env).to receive(:host_artifacts_dir).and_return(artifacts_dir) allow(Gitlab::QA::Docker::Engine).to receive(:new).and_return(docker_engine) allow(docker_engine).to receive(:run) .with(image: "#{release.qa_image}:#{release.qa_tag}", args: [suite, *args], mask_secrets: nil) .and_raise(Gitlab::QA::Support::ShellCommand::StatusError) allow(docker_engine).to receive(:run) .with( image: "#{release.qa_image}:#{release.qa_tag}", args: [suite, '--', '--only-failures'], mask_secrets: nil ) .and_yield(docker_command) end context 'with failures in last run file' do it 'retries failed specs', :aggregate_failures do run_tests expect(docker_command).to have_received(:env).with('QA_RSPEC_RETRIED', 'true') expect(docker_command).to have_received(:env).with('NO_KNAPSACK', 'true') expect(docker_command).to have_received(:env).with( 'RSPEC_LAST_RUN_RESULTS_FILE', '/home/gitlab/qa/tmp/examples.txt' ) expect(docker_command).to have_received(:volume).with( %r{#{artifacts_dir}/\S+-retry}, '/home/gitlab/qa/tmp' ) expect(docker_command).to have_received(:volume).with( %r{#{artifacts_dir}/\S+/examples.txt}, '/home/gitlab/qa/tmp/examples.txt' ) expect(docker_engine).to have_received(:run).with( image: "#{release.qa_image}:#{release.qa_tag}", args: [suite, '--', '--only-failures'], mask_secrets: nil ) end end context 'without failures in last run file' do let(:spec_status) { 'unknown' } it 'does not retry failed specs' do expect { run_tests }.to raise_error(Gitlab::QA::Support::ShellCommand::StatusError) end end end end end