diff options
author | Grzegorz Bizon <grzesiek.bizon@gmail.com> | 2017-01-16 14:21:44 +0100 |
---|---|---|
committer | Grzegorz Bizon <grzesiek.bizon@gmail.com> | 2017-01-16 14:21:44 +0100 |
commit | 4657c28777dfc2729261f895f8e36cbef51395bc (patch) | |
tree | 86c2483e843be06ad10c0f0b43f6a20da3a5a6b8 /spec/models/ci | |
parent | 5456859b5b28baca95ced74179a349563498a5f0 (diff) | |
parent | daaaf5ef9f182761ac07d76d24d971765134c7e1 (diff) | |
download | gitlab-ce-4657c28777dfc2729261f895f8e36cbef51395bc.tar.gz |
Merge branch 'master' into fix/keep-artifacts-button-visibilityfix/keep-artifacts-button-visibility
* master: (579 commits)
Conflicts:
spec/models/build_spec.rb
Diffstat (limited to 'spec/models/ci')
-rw-r--r-- | spec/models/ci/build_spec.rb | 1296 | ||||
-rw-r--r-- | spec/models/ci/pipeline_spec.rb | 42 |
2 files changed, 1335 insertions, 3 deletions
diff --git a/spec/models/ci/build_spec.rb b/spec/models/ci/build_spec.rb index 7e1d1126b97..3309a7fff9f 100644 --- a/spec/models/ci/build_spec.rb +++ b/spec/models/ci/build_spec.rb @@ -1,14 +1,962 @@ require 'spec_helper' -describe Ci::Build, models: true do - let(:build) { create(:ci_build) } +describe Ci::Build, :models do + let(:project) { create(:project) } + let(:build) { create(:ci_build, pipeline: pipeline) } let(:test_trace) { 'This is a test' } + let(:pipeline) do + create(:ci_pipeline, project: project, + sha: project.commit.id, + ref: project.default_branch, + status: 'success') + end + it { is_expected.to belong_to(:runner) } it { is_expected.to belong_to(:trigger_request) } it { is_expected.to belong_to(:erased_by) } - it { is_expected.to have_many(:deployments) } + it { is_expected.to validate_presence_of :ref } + it { is_expected.to respond_to :trace_html } + + describe '#any_runners_online?' do + subject { build.any_runners_online? } + + context 'when no runners' do + it { is_expected.to be_falsey } + end + + context 'when there are runners' do + let(:runner) { create(:ci_runner) } + + before do + build.project.runners << runner + runner.update_attributes(contacted_at: 1.second.ago) + end + + it { is_expected.to be_truthy } + + it 'that is inactive' do + runner.update_attributes(active: false) + is_expected.to be_falsey + end + + it 'that is not online' do + runner.update_attributes(contacted_at: nil) + is_expected.to be_falsey + end + + it 'that cannot handle build' do + expect_any_instance_of(Ci::Runner).to receive(:can_pick?).and_return(false) + is_expected.to be_falsey + end + end + end + + describe '#append_trace' do + subject { build.trace_html } + + context 'when build.trace hides runners token' do + let(:token) { 'my_secret_token' } + + before do + build.project.update(runners_token: token) + build.append_trace(token, 0) + end + + it { is_expected.not_to include(token) } + end + + context 'when build.trace hides build token' do + let(:token) { 'my_secret_token' } + + before do + build.update(token: token) + build.append_trace(token, 0) + end + + it { is_expected.not_to include(token) } + end + end + + describe '#artifacts?' do + subject { build.artifacts? } + + context 'artifacts archive does not exist' do + before do + build.update_attributes(artifacts_file: nil) + end + + it { is_expected.to be_falsy } + end + + context 'artifacts archive exists' do + let(:build) { create(:ci_build, :artifacts) } + it { is_expected.to be_truthy } + + context 'is expired' do + before { build.update(artifacts_expire_at: Time.now - 7.days) } + it { is_expected.to be_falsy } + end + + context 'is not expired' do + before { build.update(artifacts_expire_at: Time.now + 7.days) } + it { is_expected.to be_truthy } + end + end + end + + describe '#artifacts_expired?' do + subject { build.artifacts_expired? } + + context 'is expired' do + before { build.update(artifacts_expire_at: Time.now - 7.days) } + + it { is_expected.to be_truthy } + end + + context 'is not expired' do + before { build.update(artifacts_expire_at: Time.now + 7.days) } + + it { is_expected.to be_falsey } + end + end + + describe '#artifacts_metadata?' do + subject { build.artifacts_metadata? } + context 'artifacts metadata does not exist' do + it { is_expected.to be_falsy } + end + + context 'artifacts archive is a zip file and metadata exists' do + let(:build) { create(:ci_build, :artifacts) } + it { is_expected.to be_truthy } + end + end + + describe '#artifacts_expire_in' do + subject { build.artifacts_expire_in } + it { is_expected.to be_nil } + + context 'when artifacts_expire_at is specified' do + let(:expire_at) { Time.now + 7.days } + + before { build.artifacts_expire_at = expire_at } + + it { is_expected.to be_within(5).of(expire_at - Time.now) } + end + end + + describe '#artifacts_expire_in=' do + subject { build.artifacts_expire_in } + + it 'when assigning valid duration' do + build.artifacts_expire_in = '7 days' + + is_expected.to be_within(10).of(7.days.to_i) + end + + it 'when assigning invalid duration' do + expect { build.artifacts_expire_in = '7 elephants' }.to raise_error(ChronicDuration::DurationParseError) + is_expected.to be_nil + end + + it 'when resseting value' do + build.artifacts_expire_in = nil + + is_expected.to be_nil + end + end + + describe '#commit' do + it 'returns commit pipeline has been created for' do + expect(build.commit).to eq project.commit + end + end + + describe '#create_from' do + before do + build.status = 'success' + build.save + end + let(:create_from_build) { Ci::Build.create_from build } + + it 'exists a pending task' do + expect(Ci::Build.pending.count(:all)).to eq 0 + create_from_build + expect(Ci::Build.pending.count(:all)).to be > 0 + end + end + + describe '#depends_on_builds' do + let!(:build) { create(:ci_build, pipeline: pipeline, name: 'build', stage_idx: 0, stage: 'build') } + let!(:rspec_test) { create(:ci_build, pipeline: pipeline, name: 'rspec', stage_idx: 1, stage: 'test') } + let!(:rubocop_test) { create(:ci_build, pipeline: pipeline, name: 'rubocop', stage_idx: 1, stage: 'test') } + let!(:staging) { create(:ci_build, pipeline: pipeline, name: 'staging', stage_idx: 2, stage: 'deploy') } + + it 'expects to have no dependents if this is first build' do + expect(build.depends_on_builds).to be_empty + end + + it 'expects to have one dependent if this is test' do + expect(rspec_test.depends_on_builds.map(&:id)).to contain_exactly(build.id) + end + + it 'expects to have all builds from build and test stage if this is last' do + expect(staging.depends_on_builds.map(&:id)).to contain_exactly(build.id, rspec_test.id, rubocop_test.id) + end + + it 'expects to have retried builds instead the original ones' do + retried_rspec = Ci::Build.retry(rspec_test) + expect(staging.depends_on_builds.map(&:id)).to contain_exactly(build.id, retried_rspec.id, rubocop_test.id) + end + end + + describe '#detailed_status' do + let(:user) { create(:user) } + + it 'returns a detailed status' do + expect(build.detailed_status(user)) + .to be_a Gitlab::Ci::Status::Build::Cancelable + end + end + + describe 'deployment' do + describe '#last_deployment' do + subject { build.last_deployment } + + context 'when multiple deployments are created' do + let!(:deployment1) { create(:deployment, deployable: build) } + let!(:deployment2) { create(:deployment, deployable: build) } + + it 'returns the latest one' do + is_expected.to eq(deployment2) + end + end + end + + describe '#outdated_deployment?' do + subject { build.outdated_deployment? } + + context 'when build succeeded' do + let(:build) { create(:ci_build, :success) } + let!(:deployment) { create(:deployment, deployable: build) } + + context 'current deployment is latest' do + it { is_expected.to be_falsey } + end + + context 'current deployment is not latest on environment' do + let!(:deployment2) { create(:deployment, environment: deployment.environment) } + + it { is_expected.to be_truthy } + end + end + + context 'when build failed' do + let(:build) { create(:ci_build, :failed) } + + it { is_expected.to be_falsey } + end + end + end + + describe 'environment' do + describe '#has_environment?' do + subject { build.has_environment? } + + context 'when environment is defined' do + before do + build.update(environment: 'review') + end + + it { is_expected.to be_truthy } + end + + context 'when environment is not defined' do + before do + build.update(environment: nil) + end + + it { is_expected.to be_falsey } + end + end + + describe '#expanded_environment_name' do + subject { build.expanded_environment_name } + + context 'when environment uses $CI_BUILD_REF_NAME' do + let(:build) do + create(:ci_build, + ref: 'master', + environment: 'review/$CI_BUILD_REF_NAME') + end + + it { is_expected.to eq('review/master') } + end + + context 'when environment uses yaml_variables containing symbol keys' do + let(:build) do + create(:ci_build, + yaml_variables: [{ key: :APP_HOST, value: 'host' }], + environment: 'review/$APP_HOST') + end + + it { is_expected.to eq('review/host') } + end + end + + describe '#starts_environment?' do + subject { build.starts_environment? } + + context 'when environment is defined' do + before do + build.update(environment: 'review') + end + + context 'no action is defined' do + it { is_expected.to be_truthy } + end + + context 'and start action is defined' do + before do + build.update(options: { environment: { action: 'start' } } ) + end + + it { is_expected.to be_truthy } + end + end + + context 'when environment is not defined' do + before do + build.update(environment: nil) + end + + it { is_expected.to be_falsey } + end + end + + describe '#stops_environment?' do + subject { build.stops_environment? } + + context 'when environment is defined' do + before do + build.update(environment: 'review') + end + + context 'no action is defined' do + it { is_expected.to be_falsey } + end + + context 'and stop action is defined' do + before do + build.update(options: { environment: { action: 'stop' } } ) + end + + it { is_expected.to be_truthy } + end + end + + context 'when environment is not defined' do + before do + build.update(environment: nil) + end + + it { is_expected.to be_falsey } + end + end + end + + describe 'erasable build' do + shared_examples 'erasable' do + it 'removes artifact file' do + expect(build.artifacts_file.exists?).to be_falsy + end + + it 'removes artifact metadata file' do + expect(build.artifacts_metadata.exists?).to be_falsy + end + + it 'erases build trace in trace file' do + expect(build.trace).to be_empty + end + + it 'sets erased to true' do + expect(build.erased?).to be true + end + + it 'sets erase date' do + expect(build.erased_at).not_to be_falsy + end + end + + context 'build is not erasable' do + let!(:build) { create(:ci_build) } + + describe '#erase' do + subject { build.erase } + + it { is_expected.to be false } + end + + describe '#erasable?' do + subject { build.erasable? } + it { is_expected.to eq false } + end + end + + context 'build is erasable' do + let!(:build) { create(:ci_build, :trace, :success, :artifacts) } + + describe '#erase' do + before do + build.erase(erased_by: user) + end + + context 'erased by user' do + let!(:user) { create(:user, username: 'eraser') } + + include_examples 'erasable' + + it 'records user who erased a build' do + expect(build.erased_by).to eq user + end + end + + context 'erased by system' do + let(:user) { nil } + + include_examples 'erasable' + + it 'does not set user who erased a build' do + expect(build.erased_by).to be_nil + end + end + end + + describe '#erasable?' do + subject { build.erasable? } + it { is_expected.to be_truthy } + end + + describe '#erased?' do + let!(:build) { create(:ci_build, :trace, :success, :artifacts) } + subject { build.erased? } + + context 'build has not been erased' do + it { is_expected.to be_falsey } + end + + context 'build has been erased' do + before do + build.erase + end + + it { is_expected.to be_truthy } + end + end + + context 'metadata and build trace are not available' do + let!(:build) { create(:ci_build, :success, :artifacts) } + + before do + build.remove_artifacts_metadata! + end + + describe '#erase' do + it 'does not raise error' do + expect { build.erase }.not_to raise_error + end + end + end + end + end + + describe '#extract_coverage' do + context 'valid content & regex' do + subject { build.extract_coverage('Coverage 1033 / 1051 LOC (98.29%) covered', '\(\d+.\d+\%\) covered') } + + it { is_expected.to eq(98.29) } + end + + context 'valid content & bad regex' do + subject { build.extract_coverage('Coverage 1033 / 1051 LOC (98.29%) covered', 'very covered') } + + it { is_expected.to be_nil } + end + + context 'no coverage content & regex' do + subject { build.extract_coverage('No coverage for today :sad:', '\(\d+.\d+\%\) covered') } + + it { is_expected.to be_nil } + end + + context 'multiple results in content & regex' do + subject { build.extract_coverage(' (98.39%) covered. (98.29%) covered', '\(\d+.\d+\%\) covered') } + + it { is_expected.to eq(98.29) } + end + + context 'using a regex capture' do + subject { build.extract_coverage('TOTAL 9926 3489 65%', 'TOTAL\s+\d+\s+\d+\s+(\d{1,3}\%)') } + + it { is_expected.to eq(65) } + end + end + + describe '#first_pending' do + let!(:first) { create(:ci_build, pipeline: pipeline, status: 'pending', created_at: Date.yesterday) } + let!(:second) { create(:ci_build, pipeline: pipeline, status: 'pending') } + subject { Ci::Build.first_pending } + + it { is_expected.to be_a(Ci::Build) } + it('returns with the first pending build') { is_expected.to eq(first) } + end + + describe '#failed_but_allowed?' do + subject { build.failed_but_allowed? } + + context 'when build is not allowed to fail' do + before do + build.allow_failure = false + end + + context 'and build.status is success' do + before do + build.status = 'success' + end + + it { is_expected.to be_falsey } + end + + context 'and build.status is failed' do + before do + build.status = 'failed' + end + + it { is_expected.to be_falsey } + end + end + + context 'when build is allowed to fail' do + before do + build.allow_failure = true + end + + context 'and build.status is success' do + before do + build.status = 'success' + end + + it { is_expected.to be_falsey } + end + + context 'and build.status is failed' do + before do + build.status = 'failed' + end + + it { is_expected.to be_truthy } + end + end + end + + describe 'flags' do + describe '#cancelable?' do + subject { build } + + context 'when build is cancelable' do + context 'when build is pending' do + it { is_expected.to be_cancelable } + end + + context 'when build is running' do + before do + build.run! + end + + it { is_expected.to be_cancelable } + end + end + + context 'when build is not cancelable' do + context 'when build is successful' do + before do + build.success! + end + + it { is_expected.not_to be_cancelable } + end + + context 'when build is failed' do + before do + build.drop! + end + + it { is_expected.not_to be_cancelable } + end + end + end + + describe '#retryable?' do + subject { build } + + context 'when build is retryable' do + context 'when build is successful' do + before do + build.success! + end + + it { is_expected.to be_retryable } + end + + context 'when build is failed' do + before do + build.drop! + end + + it { is_expected.to be_retryable } + end + + context 'when build is canceled' do + before do + build.cancel! + end + + it { is_expected.to be_retryable } + end + end + + context 'when build is not retryable' do + context 'when build is running' do + before do + build.run! + end + + it { is_expected.not_to be_retryable } + end + + context 'when build is skipped' do + before do + build.skip! + end + + it { is_expected.not_to be_retryable } + end + end + end + + describe '#manual?' do + before do + build.update(when: value) + end + + subject { build.manual? } + + context 'when is set to manual' do + let(:value) { 'manual' } + + it { is_expected.to be_truthy } + end + + context 'when set to something else' do + let(:value) { 'something else' } + + it { is_expected.to be_falsey } + end + end + end + + describe '#has_tags?' do + context 'when build has tags' do + subject { create(:ci_build, tag_list: ['tag']) } + it { is_expected.to have_tags } + end + + context 'when build does not have tags' do + subject { create(:ci_build, tag_list: []) } + it { is_expected.not_to have_tags } + end + end + + describe '#keep_artifacts!' do + let(:build) { create(:ci_build, artifacts_expire_at: Time.now + 7.days) } + + it 'to reset expire_at' do + build.keep_artifacts! + + expect(build.artifacts_expire_at).to be_nil + end + end + + describe '#merge_request' do + def create_mr(build, pipeline, factory: :merge_request, created_at: Time.now) + create(factory, source_project_id: pipeline.gl_project_id, + target_project_id: pipeline.gl_project_id, + source_branch: build.ref, + created_at: created_at) + end + + context 'when a MR has a reference to the pipeline' do + before do + @merge_request = create_mr(build, pipeline, factory: :merge_request) + + commits = [double(id: pipeline.sha)] + allow(@merge_request).to receive(:commits).and_return(commits) + allow(MergeRequest).to receive_message_chain(:includes, :where, :reorder).and_return([@merge_request]) + end + + it 'returns the single associated MR' do + expect(build.merge_request.id).to eq(@merge_request.id) + end + end + + context 'when there is not a MR referencing the pipeline' do + it 'returns nil' do + expect(build.merge_request).to be_nil + end + end + + context 'when more than one MR have a reference to the pipeline' do + before do + @merge_request = create_mr(build, pipeline, factory: :merge_request) + @merge_request.close! + @merge_request2 = create_mr(build, pipeline, factory: :merge_request) + + commits = [double(id: pipeline.sha)] + allow(@merge_request).to receive(:commits).and_return(commits) + allow(@merge_request2).to receive(:commits).and_return(commits) + allow(MergeRequest).to receive_message_chain(:includes, :where, :reorder).and_return([@merge_request, @merge_request2]) + end + + it 'returns the first MR' do + expect(build.merge_request.id).to eq(@merge_request.id) + end + end + + context 'when a Build is created after the MR' do + before do + @merge_request = create_mr(build, pipeline, factory: :merge_request_with_diffs) + pipeline2 = create(:ci_pipeline, project: project) + @build2 = create(:ci_build, pipeline: pipeline2) + + allow(@merge_request).to receive(:commits_sha). + and_return([pipeline.sha, pipeline2.sha]) + allow(MergeRequest).to receive_message_chain(:includes, :where, :reorder).and_return([@merge_request]) + end + + it 'returns the current MR' do + expect(@build2.merge_request.id).to eq(@merge_request.id) + end + end + end + + describe '#options' do + let(:options) do + { + image: "ruby:2.1", + services: [ + "postgres" + ] + } + end + + it 'contains options' do + expect(build.options).to eq(options) + end + end + + describe '#other_actions' do + let(:build) { create(:ci_build, :manual, pipeline: pipeline) } + let!(:other_build) { create(:ci_build, :manual, pipeline: pipeline, name: 'other action') } + + subject { build.other_actions } + + it 'returns other actions' do + is_expected.to contain_exactly(other_build) + end + + context 'when build is retried' do + let!(:new_build) { Ci::Build.retry(build) } + + it 'does not return any of them' do + is_expected.not_to include(build, new_build) + end + end + + context 'when other build is retried' do + let!(:retried_build) { Ci::Build.retry(other_build) } + + it 'returns a retried build' do + is_expected.to contain_exactly(retried_build) + end + end + end + + describe '#persisted_environment' do + before do + @environment = create(:environment, project: project, name: "foo-#{project.default_branch}") + end + + subject { build.persisted_environment } + + context 'referenced literally' do + let(:build) { create(:ci_build, pipeline: pipeline, environment: "foo-#{project.default_branch}") } + + it { is_expected.to eq(@environment) } + end + + context 'referenced with a variable' do + let(:build) { create(:ci_build, pipeline: pipeline, environment: "foo-$CI_BUILD_REF_NAME") } + + it { is_expected.to eq(@environment) } + end + end + + describe '#play' do + let(:build) { create(:ci_build, :manual, pipeline: pipeline) } + + subject { build.play } + + it 'enqueues a build' do + is_expected.to be_pending + is_expected.to eq(build) + end + + context 'for successful build' do + before do + build.update(status: 'success') + end + + it 'creates a new build' do + is_expected.to be_pending + is_expected.not_to eq(build) + end + end + end + + describe 'project settings' do + describe '#timeout' do + it 'returns project timeout configuration' do + expect(build.timeout).to eq(project.build_timeout) + end + end + + describe '#allow_git_fetch' do + it 'return project allow_git_fetch configuration' do + expect(build.allow_git_fetch).to eq(project.build_allow_git_fetch) + end + end + end + + describe '#project' do + subject { build.project } + + it { is_expected.to eq(pipeline.project) } + end + + describe '#project_id' do + subject { build.project_id } + + it { is_expected.to eq(pipeline.project_id) } + end + + describe '#project_name' do + subject { build.project_name } + + it { is_expected.to eq(project.name) } + end + + describe '#raw_trace' do + subject { build.raw_trace } + + context 'when build.trace hides runners token' do + let(:token) { 'my_secret_token' } + + before do + build.project.update(runners_token: token) + build.update(trace: token) + end + + it { is_expected.not_to include(token) } + end + + context 'when build.trace hides build token' do + let(:token) { 'my_secret_token' } + + before do + build.update(token: token) + build.update(trace: token) + end + + it { is_expected.not_to include(token) } + end + end + + describe '#ref_slug' do + { + 'master' => 'master', + '1-foo' => '1-foo', + 'fix/1-foo' => 'fix-1-foo', + 'fix-1-foo' => 'fix-1-foo', + 'a' * 63 => 'a' * 63, + 'a' * 64 => 'a' * 63, + 'FOO' => 'foo', + }.each do |ref, slug| + it "transforms #{ref} to #{slug}" do + build.ref = ref + + expect(build.ref_slug).to eq(slug) + end + end + end + + describe '#repo_url' do + let(:build) { create(:ci_build) } + let(:project) { build.project } + + subject { build.repo_url } + + it { is_expected.to be_a(String) } + it { is_expected.to end_with(".git") } + it { is_expected.to start_with(project.web_url[0..6]) } + it { is_expected.to include(build.token) } + it { is_expected.to include('gitlab-ci-token') } + it { is_expected.to include(project.web_url[7..-1]) } + end + + describe '#stuck?' do + subject { build.stuck? } + + context "when commit_status.status is pending" do + before do + build.status = 'pending' + end + + it { is_expected.to be_truthy } + + context "and there are specific runner" do + let(:runner) { create(:ci_runner, contacted_at: 1.second.ago) } + + before do + build.project.runners << runner + runner.save + end + + it { is_expected.to be_falsey } + end + end + + %w[success failed canceled running].each do |state| + context "when commit_status.status is #{state}" do + before do + build.status = state + end + + it { is_expected.to be_falsey } + end + end + end describe '#trace' do it 'obfuscates project runners token' do @@ -24,6 +972,63 @@ describe Ci::Build, models: true do expect(build.trace).to eq(test_trace) end + + context 'when build does not have trace' do + it 'is is empty' do + expect(build.trace).to be_nil + end + end + + context 'when trace contains text' do + let(:text) { 'example output' } + before do + build.trace = text + end + + it { expect(build.trace).to eq(text) } + end + + context 'when trace hides runners token' do + let(:token) { 'my_secret_token' } + + before do + build.update(trace: token) + build.project.update(runners_token: token) + end + + it { expect(build.trace).not_to include(token) } + it { expect(build.raw_trace).to include(token) } + end + + context 'when build.trace hides build token' do + let(:token) { 'my_secret_token' } + + before do + build.update(trace: token) + build.update(token: token) + end + + it { expect(build.trace).not_to include(token) } + it { expect(build.raw_trace).to include(token) } + end + end + + describe '#has_expiring_artifacts?' do + context 'when artifacts have expiration date set' do + before { build.update(artifacts_expire_at: 1.day.from_now) } + + it 'has expiring artifacts' do + expect(build).to have_expiring_artifacts + end + end + + context 'when artifacts do not have expiration date set' do + before { build.update(artifacts_expire_at: nil) } + + it 'does not have expiring artifacts' do + expect(build).not_to have_expiring_artifacts + end + end end describe '#has_trace_file?' do @@ -111,4 +1116,289 @@ describe Ci::Build, models: true do build.destroy end end + + describe '#when' do + subject { build.when } + + context 'when `when` is undefined' do + before do + build.when = nil + end + + context 'use from gitlab-ci.yml' do + before do + stub_ci_pipeline_yaml_file(config) + end + + context 'when config is not found' do + let(:config) { nil } + + it { is_expected.to eq('on_success') } + end + + context 'when config does not have a questioned job' do + let(:config) do + YAML.dump({ + test_other: { + script: 'Hello World' + } + }) + end + + it { is_expected.to eq('on_success') } + end + + context 'when config has `when`' do + let(:config) do + YAML.dump({ + test: { + script: 'Hello World', + when: 'always' + } + }) + end + + it { is_expected.to eq('always') } + end + end + end + end + + describe '#variables' do + let(:container_registry_enabled) { false } + let(:predefined_variables) do + [ + { key: 'CI', value: 'true', public: true }, + { key: 'GITLAB_CI', value: 'true', public: true }, + { key: 'CI_BUILD_ID', value: build.id.to_s, public: true }, + { key: 'CI_BUILD_TOKEN', value: build.token, public: false }, + { key: 'CI_BUILD_REF', value: build.sha, public: true }, + { key: 'CI_BUILD_BEFORE_SHA', value: build.before_sha, public: true }, + { key: 'CI_BUILD_REF_NAME', value: 'master', public: true }, + { key: 'CI_BUILD_REF_SLUG', value: 'master', public: true }, + { key: 'CI_BUILD_NAME', value: 'test', public: true }, + { key: 'CI_BUILD_STAGE', value: 'test', public: true }, + { key: 'CI_SERVER_NAME', value: 'GitLab', public: true }, + { key: 'CI_SERVER_VERSION', value: Gitlab::VERSION, public: true }, + { key: 'CI_SERVER_REVISION', value: Gitlab::REVISION, public: true }, + { key: 'CI_PROJECT_ID', value: project.id.to_s, public: true }, + { key: 'CI_PROJECT_NAME', value: project.path, public: true }, + { key: 'CI_PROJECT_PATH', value: project.path_with_namespace, public: true }, + { key: 'CI_PROJECT_NAMESPACE', value: project.namespace.path, public: true }, + { key: 'CI_PROJECT_URL', value: project.web_url, public: true }, + { key: 'CI_PIPELINE_ID', value: pipeline.id.to_s, public: true } + ] + end + + before do + stub_container_registry_config(enabled: container_registry_enabled, host_port: 'registry.example.com') + end + + subject { build.variables } + + context 'returns variables' do + before do + build.yaml_variables = [] + end + + it { is_expected.to eq(predefined_variables) } + end + + context 'when build has user' do + let(:user) { create(:user, username: 'starter') } + let(:user_variables) do + [ + { key: 'GITLAB_USER_ID', value: user.id.to_s, public: true }, + { key: 'GITLAB_USER_EMAIL', value: user.email, public: true } + ] + end + + before do + build.update_attributes(user: user) + end + + it { user_variables.each { |v| is_expected.to include(v) } } + end + + context 'when build has an environment' do + before do + build.update(environment: 'production') + create(:environment, project: build.project, name: 'production', slug: 'prod-slug') + end + + let(:environment_variables) do + [ + { key: 'CI_ENVIRONMENT_NAME', value: 'production', public: true }, + { key: 'CI_ENVIRONMENT_SLUG', value: 'prod-slug', public: true } + ] + end + + it { environment_variables.each { |v| is_expected.to include(v) } } + end + + context 'when build started manually' do + before do + build.update_attributes(when: :manual) + end + + let(:manual_variable) do + { key: 'CI_BUILD_MANUAL', value: 'true', public: true } + end + + it { is_expected.to include(manual_variable) } + end + + context 'when build is for tag' do + let(:tag_variable) do + { key: 'CI_BUILD_TAG', value: 'master', public: true } + end + + before do + build.update_attributes(tag: true) + end + + it { is_expected.to include(tag_variable) } + end + + context 'when secure variable is defined' do + let(:secure_variable) do + { key: 'SECRET_KEY', value: 'secret_value', public: false } + end + + before do + build.project.variables << Ci::Variable.new(key: 'SECRET_KEY', value: 'secret_value') + end + + it { is_expected.to include(secure_variable) } + end + + context 'when build is for triggers' do + let(:trigger) { create(:ci_trigger, project: project) } + let(:trigger_request) { create(:ci_trigger_request_with_variables, pipeline: pipeline, trigger: trigger) } + let(:user_trigger_variable) do + { key: :TRIGGER_KEY_1, value: 'TRIGGER_VALUE_1', public: false } + end + let(:predefined_trigger_variable) do + { key: 'CI_BUILD_TRIGGERED', value: 'true', public: true } + end + + before do + build.trigger_request = trigger_request + end + + it { is_expected.to include(user_trigger_variable) } + it { is_expected.to include(predefined_trigger_variable) } + end + + context 'when yaml_variables are undefined' do + before do + build.yaml_variables = nil + end + + context 'use from gitlab-ci.yml' do + before do + stub_ci_pipeline_yaml_file(config) + end + + context 'when config is not found' do + let(:config) { nil } + + it { is_expected.to eq(predefined_variables) } + end + + context 'when config does not have a questioned job' do + let(:config) do + YAML.dump({ + test_other: { + script: 'Hello World' + } + }) + end + + it { is_expected.to eq(predefined_variables) } + end + + context 'when config has variables' do + let(:config) do + YAML.dump({ + test: { + script: 'Hello World', + variables: { + KEY: 'value' + } + } + }) + end + let(:variables) do + [{ key: 'KEY', value: 'value', public: true }] + end + + it { is_expected.to eq(predefined_variables + variables) } + end + end + end + + context 'when container registry is enabled' do + let(:container_registry_enabled) { true } + let(:ci_registry) do + { key: 'CI_REGISTRY', value: 'registry.example.com', public: true } + end + let(:ci_registry_image) do + { key: 'CI_REGISTRY_IMAGE', value: project.container_registry_repository_url, public: true } + end + + context 'and is disabled for project' do + before do + project.update(container_registry_enabled: false) + end + + it { is_expected.to include(ci_registry) } + it { is_expected.not_to include(ci_registry_image) } + end + + context 'and is enabled for project' do + before do + project.update(container_registry_enabled: true) + end + + it { is_expected.to include(ci_registry) } + it { is_expected.to include(ci_registry_image) } + end + end + + context 'when runner is assigned to build' do + let(:runner) { create(:ci_runner, description: 'description', tag_list: ['docker', 'linux']) } + + before do + build.update(runner: runner) + end + + it { is_expected.to include({ key: 'CI_RUNNER_ID', value: runner.id.to_s, public: true }) } + it { is_expected.to include({ key: 'CI_RUNNER_DESCRIPTION', value: 'description', public: true }) } + it { is_expected.to include({ key: 'CI_RUNNER_TAGS', value: 'docker, linux', public: true }) } + end + + context 'when build is for a deployment' do + let(:deployment_variable) { { key: 'KUBERNETES_TOKEN', value: 'TOKEN', public: false } } + + before do + build.environment = 'production' + allow(project).to receive(:deployment_variables).and_return([deployment_variable]) + end + + it { is_expected.to include(deployment_variable) } + end + + context 'returns variables in valid order' do + before do + allow(build).to receive(:predefined_variables) { ['predefined'] } + allow(project).to receive(:predefined_variables) { ['project'] } + allow(pipeline).to receive(:predefined_variables) { ['pipeline'] } + allow(build).to receive(:yaml_variables) { ['yaml'] } + allow(project).to receive(:secret_variables) { ['secret'] } + end + + it { is_expected.to eq(%w[predefined project pipeline yaml secret]) } + end + end end diff --git a/spec/models/ci/pipeline_spec.rb b/spec/models/ci/pipeline_spec.rb index cebaa157ef3..d1aee27057a 100644 --- a/spec/models/ci/pipeline_spec.rb +++ b/spec/models/ci/pipeline_spec.rb @@ -888,6 +888,48 @@ describe Ci::Pipeline, models: true do end end + describe '#stuck?' do + before do + create(:ci_build, :pending, pipeline: pipeline) + end + + context 'when pipeline is stuck' do + it 'is stuck' do + expect(pipeline).to be_stuck + end + end + + context 'when pipeline is not stuck' do + before { create(:ci_runner, :shared, :online) } + + it 'is not stuck' do + expect(pipeline).not_to be_stuck + end + end + end + + describe '#has_yaml_errors?' do + context 'when pipeline has errors' do + let(:pipeline) do + create(:ci_pipeline, config: { rspec: nil }) + end + + it 'contains yaml errors' do + expect(pipeline).to have_yaml_errors + end + end + + context 'when pipeline does not have errors' do + let(:pipeline) do + create(:ci_pipeline, config: { rspec: { script: 'rake test' } }) + end + + it 'does not containyaml errors' do + expect(pipeline).not_to have_yaml_errors + end + end + end + describe 'notifications when pipeline success or failed' do let(:project) { create(:project) } |