summaryrefslogtreecommitdiff
path: root/spec/models/ci/build_spec.rb
diff options
context:
space:
mode:
Diffstat (limited to 'spec/models/ci/build_spec.rb')
-rw-r--r--spec/models/ci/build_spec.rb425
1 files changed, 357 insertions, 68 deletions
diff --git a/spec/models/ci/build_spec.rb b/spec/models/ci/build_spec.rb
index 6e8845cdcf4..767f0ad9e65 100644
--- a/spec/models/ci/build_spec.rb
+++ b/spec/models/ci/build_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe Ci::Build, :models do
+describe Ci::Build do
let(:user) { create(:user) }
let(:project) { create(:project, :repository) }
let(:build) { create(:ci_build, pipeline: pipeline) }
@@ -21,6 +21,18 @@ describe Ci::Build, :models do
it { is_expected.to respond_to(:has_trace?) }
it { is_expected.to respond_to(:trace) }
+ describe '.manual_actions' do
+ let!(:manual_but_created) { create(:ci_build, :manual, status: :created, pipeline: pipeline) }
+ let!(:manual_but_succeeded) { create(:ci_build, :manual, status: :success, pipeline: pipeline) }
+ let!(:manual_action) { create(:ci_build, :manual, pipeline: pipeline) }
+
+ subject { described_class.manual_actions }
+
+ it { is_expected.to include(manual_action) }
+ it { is_expected.to include(manual_but_succeeded) }
+ it { is_expected.not_to include(manual_but_created) }
+ end
+
describe '#actionize' do
context 'when build is a created' do
before do
@@ -95,12 +107,18 @@ describe Ci::Build, :models do
it { is_expected.to be_truthy }
context 'is expired' do
- before { build.update(artifacts_expire_at: Time.now - 7.days) }
+ before do
+ build.update(artifacts_expire_at: Time.now - 7.days)
+ end
+
it { is_expected.to be_falsy }
end
context 'is not expired' do
- before { build.update(artifacts_expire_at: Time.now + 7.days) }
+ before do
+ build.update(artifacts_expire_at: Time.now + 7.days)
+ end
+
it { is_expected.to be_truthy }
end
end
@@ -110,13 +128,17 @@ describe Ci::Build, :models do
subject { build.artifacts_expired? }
context 'is expired' do
- before { build.update(artifacts_expire_at: Time.now - 7.days) }
+ before do
+ build.update(artifacts_expire_at: Time.now - 7.days)
+ end
it { is_expected.to be_truthy }
end
context 'is not expired' do
- before { build.update(artifacts_expire_at: Time.now + 7.days) }
+ before do
+ build.update(artifacts_expire_at: Time.now + 7.days)
+ end
it { is_expected.to be_falsey }
end
@@ -141,7 +163,9 @@ describe Ci::Build, :models do
context 'when artifacts_expire_at is specified' do
let(:expire_at) { Time.now + 7.days }
- before { build.artifacts_expire_at = expire_at }
+ before do
+ build.artifacts_expire_at = expire_at
+ end
it { is_expected.to be_within(5).of(expire_at - Time.now) }
end
@@ -201,7 +225,7 @@ describe Ci::Build, :models do
it 'expects to have retried builds instead the original ones' do
project.add_developer(user)
- retried_rspec = Ci::Build.retry(rspec_test, user)
+ retried_rspec = described_class.retry(rspec_test, user)
expect(staging.depends_on_builds.map(&:id))
.to contain_exactly(build.id, retried_rspec.id, rubocop_test.id)
@@ -596,9 +620,9 @@ describe Ci::Build, :models do
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 }
+ subject { described_class.first_pending }
- it { is_expected.to be_a(Ci::Build) }
+ it { is_expected.to be_a(described_class) }
it('returns with the first pending build') { is_expected.to eq(first) }
end
@@ -778,6 +802,47 @@ describe Ci::Build, :models do
end
end
+ describe 'build auto retry feature' do
+ describe '#retries_count' do
+ subject { create(:ci_build, name: 'test', pipeline: pipeline) }
+
+ context 'when build has been retried several times' do
+ before do
+ create(:ci_build, :retried, name: 'test', pipeline: pipeline)
+ create(:ci_build, :retried, name: 'test', pipeline: pipeline)
+ end
+
+ it 'reports a correct retry count value' do
+ expect(subject.retries_count).to eq 2
+ end
+ end
+
+ context 'when build has not been retried' do
+ it 'returns zero' do
+ expect(subject.retries_count).to eq 0
+ end
+ end
+ end
+
+ describe '#retries_max' do
+ context 'when max retries value is defined' do
+ subject { create(:ci_build, options: { retry: 1 }) }
+
+ it 'returns a number of configured max retries' do
+ expect(subject.retries_max).to eq 1
+ end
+ end
+
+ context 'when max retries value is not defined' do
+ subject { create(:ci_build) }
+
+ it 'returns zero' do
+ expect(subject.retries_max).to eq 0
+ end
+ end
+ end
+ end
+
describe '#keep_artifacts!' do
let(:build) { create(:ci_build, artifacts_expire_at: Time.now + 7.days) }
@@ -839,8 +904,8 @@ describe Ci::Build, :models do
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(@merge_request).to receive(:commit_shas)
+ .and_return([pipeline.sha, pipeline2.sha])
allow(MergeRequest).to receive_message_chain(:includes, :where, :reorder).and_return([@merge_request])
end
@@ -880,7 +945,7 @@ describe Ci::Build, :models do
end
context 'when build is retried' do
- let!(:new_build) { Ci::Build.retry(build, user) }
+ let!(:new_build) { described_class.retry(build, user) }
it 'does not return any of them' do
is_expected.not_to include(build, new_build)
@@ -888,7 +953,11 @@ describe Ci::Build, :models do
end
context 'when other build is retried' do
- let!(:retried_build) { Ci::Build.retry(other_build, user) }
+ let!(:retried_build) { described_class.retry(other_build, user) }
+
+ before do
+ retried_build.success
+ end
it 'returns a retried build' do
is_expected.to contain_exactly(retried_build)
@@ -897,22 +966,30 @@ describe Ci::Build, :models do
end
describe '#persisted_environment' do
- before do
- @environment = create(:environment, project: project, name: "foo-#{project.default_branch}")
+ let!(:environment) do
+ 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}") }
+ context 'when referenced literally' do
+ let(:build) do
+ create(:ci_build, pipeline: pipeline, environment: "foo-#{project.default_branch}")
+ end
- it { is_expected.to eq(@environment) }
+ it { is_expected.to eq(environment) }
end
- context 'referenced with a variable' do
- let(:build) { create(:ci_build, pipeline: pipeline, environment: "foo-$CI_COMMIT_REF_NAME") }
+ context 'when referenced with a variable' do
+ let(:build) do
+ create(:ci_build, pipeline: pipeline, environment: "foo-$CI_COMMIT_REF_NAME")
+ end
+
+ it { is_expected.to eq(environment) }
+ end
- it { is_expected.to eq(@environment) }
+ context 'when there is no environment' do
+ it { is_expected.to be_nil }
end
end
@@ -923,26 +1000,8 @@ describe Ci::Build, :models do
project.add_developer(user)
end
- context 'when build is manual' do
- it 'enqueues a build' do
- new_build = build.play(user)
-
- expect(new_build).to be_pending
- expect(new_build).to eq(build)
- end
- end
-
- context 'when build is passed' do
- before do
- build.update(status: 'success')
- end
-
- it 'creates a new build' do
- new_build = build.play(user)
-
- expect(new_build).to be_pending
- expect(new_build).not_to eq(build)
- end
+ it 'enqueues the build' do
+ expect(build.play(user)).to be_pending
end
end
@@ -980,13 +1039,17 @@ describe Ci::Build, :models do
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',
+ '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',
+ '-' + 'a' * 61 + '-' => 'a' * 61,
+ '-' + 'a' * 62 + '-' => 'a' * 62,
+ '-' + 'a' * 63 + '-' => 'a' * 62,
+ 'a' * 62 + ' ' => 'a' * 62
}.each do |ref, slug|
it "transforms #{ref} to #{slug}" do
build.ref = ref
@@ -1045,7 +1108,9 @@ describe Ci::Build, :models do
describe '#has_expiring_artifacts?' do
context 'when artifacts have expiration date set' do
- before { build.update(artifacts_expire_at: 1.day.from_now) }
+ before do
+ build.update(artifacts_expire_at: 1.day.from_now)
+ end
it 'has expiring artifacts' do
expect(build).to have_expiring_artifacts
@@ -1053,7 +1118,9 @@ describe Ci::Build, :models do
end
context 'when artifacts do not have expiration date set' do
- before { build.update(artifacts_expire_at: nil) }
+ before do
+ build.update(artifacts_expire_at: nil)
+ end
it 'does not have expiring artifacts' do
expect(build).not_to have_expiring_artifacts
@@ -1153,12 +1220,14 @@ describe Ci::Build, :models do
{ 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.full_path, public: true },
+ { key: 'CI_PROJECT_PATH_SLUG', value: project.full_path_slug, public: true },
{ key: 'CI_PROJECT_NAMESPACE', value: project.namespace.full_path, public: true },
{ key: 'CI_PROJECT_URL', value: project.web_url, public: true },
{ key: 'CI_PIPELINE_ID', value: pipeline.id.to_s, public: true },
+ { key: 'CI_CONFIG_PATH', value: pipeline.ci_yaml_file_path, public: true },
{ key: 'CI_REGISTRY_USER', value: 'gitlab-ci-token', public: true },
{ key: 'CI_REGISTRY_PASSWORD', value: build.token, public: false },
- { key: 'CI_REPOSITORY_URL', value: build.repo_url, public: false },
+ { key: 'CI_REPOSITORY_URL', value: build.repo_url, public: false }
]
end
@@ -1190,11 +1259,6 @@ describe Ci::Build, :models do
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 },
@@ -1202,7 +1266,66 @@ describe Ci::Build, :models do
]
end
- it { environment_variables.each { |v| is_expected.to include(v) } }
+ let!(:environment) do
+ create(:environment,
+ project: build.project,
+ name: 'production',
+ slug: 'prod-slug',
+ external_url: '')
+ end
+
+ before do
+ build.update(environment: 'production')
+ end
+
+ shared_examples 'containing environment variables' do
+ it { environment_variables.each { |v| is_expected.to include(v) } }
+ end
+
+ context 'when no URL was set' do
+ it_behaves_like 'containing environment variables'
+
+ it 'does not have CI_ENVIRONMENT_URL' do
+ keys = subject.map { |var| var[:key] }
+
+ expect(keys).not_to include('CI_ENVIRONMENT_URL')
+ end
+ end
+
+ context 'when an URL was set' do
+ let(:url) { 'http://host/test' }
+
+ before do
+ environment_variables <<
+ { key: 'CI_ENVIRONMENT_URL', value: url, public: true }
+ end
+
+ context 'when the URL was set from the job' do
+ before do
+ build.update(options: { environment: { url: url } })
+ end
+
+ it_behaves_like 'containing environment variables'
+
+ context 'when variables are used in the URL, it does not expand' do
+ let(:url) { 'http://$CI_PROJECT_NAME-$CI_ENVIRONMENT_SLUG' }
+
+ it_behaves_like 'containing environment variables'
+
+ it 'puts $CI_ENVIRONMENT_URL in the last so all other variables are available to be used when runners are trying to expand it' do
+ expect(subject.last).to eq(environment_variables.last)
+ end
+ end
+ end
+
+ context 'when the URL was not set from the job, but environment' do
+ before do
+ environment.update(external_url: url)
+ end
+
+ it_behaves_like 'containing environment variables'
+ end
+ end
end
context 'when build started manually' do
@@ -1229,16 +1352,102 @@ describe Ci::Build, :models do
it { is_expected.to include(tag_variable) }
end
- context 'when secure variable is defined' do
- let(:secure_variable) do
+ context 'when secret variable is defined' do
+ let(:secret_variable) do
+ { key: 'SECRET_KEY', value: 'secret_value', public: false }
+ end
+
+ before do
+ create(:ci_variable,
+ secret_variable.slice(:key, :value).merge(project: project))
+ end
+
+ it { is_expected.to include(secret_variable) }
+ end
+
+ context 'when protected variable is defined' do
+ let(:protected_variable) do
+ { key: 'PROTECTED_KEY', value: 'protected_value', public: false }
+ end
+
+ before do
+ create(:ci_variable,
+ :protected,
+ protected_variable.slice(:key, :value).merge(project: project))
+ end
+
+ context 'when the branch is protected' do
+ before do
+ create(:protected_branch, project: build.project, name: build.ref)
+ end
+
+ it { is_expected.to include(protected_variable) }
+ end
+
+ context 'when the tag is protected' do
+ before do
+ create(:protected_tag, project: build.project, name: build.ref)
+ end
+
+ it { is_expected.to include(protected_variable) }
+ end
+
+ context 'when the ref is not protected' do
+ it { is_expected.not_to include(protected_variable) }
+ end
+ end
+
+ context 'when group secret variable is defined' do
+ let(:secret_variable) do
{ key: 'SECRET_KEY', value: 'secret_value', public: false }
end
+ let(:group) { create(:group, :access_requestable) }
+
+ before do
+ build.project.update(group: group)
+
+ create(:ci_group_variable,
+ secret_variable.slice(:key, :value).merge(group: group))
+ end
+
+ it { is_expected.to include(secret_variable) }
+ end
+
+ context 'when group protected variable is defined' do
+ let(:protected_variable) do
+ { key: 'PROTECTED_KEY', value: 'protected_value', public: false }
+ end
+
+ let(:group) { create(:group, :access_requestable) }
+
before do
- build.project.variables << Ci::Variable.new(key: 'SECRET_KEY', value: 'secret_value')
+ build.project.update(group: group)
+
+ create(:ci_group_variable,
+ :protected,
+ protected_variable.slice(:key, :value).merge(group: group))
+ end
+
+ context 'when the branch is protected' do
+ before do
+ create(:protected_branch, project: build.project, name: build.ref)
+ end
+
+ it { is_expected.to include(protected_variable) }
end
- it { is_expected.to include(secure_variable) }
+ context 'when the tag is protected' do
+ before do
+ create(:protected_tag, project: build.project, name: build.ref)
+ end
+
+ it { is_expected.to include(protected_variable) }
+ end
+
+ context 'when the ref is not protected' do
+ it { is_expected.not_to include(protected_variable) }
+ end
end
context 'when build is for triggers' do
@@ -1259,6 +1468,29 @@ describe Ci::Build, :models do
it { is_expected.to include(predefined_trigger_variable) }
end
+ context 'when pipeline has a variable' do
+ let!(:pipeline_variable) { create(:ci_pipeline_variable, pipeline: pipeline) }
+
+ it { is_expected.to include(pipeline_variable.to_runner_variable) }
+ end
+
+ context 'when a job was triggered by a pipeline schedule' do
+ let(:pipeline_schedule) { create(:ci_pipeline_schedule, project: project) }
+
+ let!(:pipeline_schedule_variable) do
+ create(:ci_pipeline_schedule_variable,
+ key: 'SCHEDULE_VARIABLE_KEY',
+ pipeline_schedule: pipeline_schedule)
+ end
+
+ before do
+ pipeline_schedule.pipelines << pipeline
+ pipeline_schedule.reload
+ end
+
+ it { is_expected.to include(pipeline_schedule_variable.to_runner_variable) }
+ end
+
context 'when yaml_variables are undefined' do
before do
build.yaml_variables = nil
@@ -1359,20 +1591,46 @@ describe Ci::Build, :models do
it { is_expected.to include(deployment_variable) }
end
+ context 'when project has custom CI config path' do
+ let(:ci_config_path) { { key: 'CI_CONFIG_PATH', value: 'custom', public: true } }
+
+ before do
+ project.update(ci_config_path: 'custom')
+ end
+
+ it { is_expected.to include(ci_config_path) }
+ end
+
context 'returns variables in valid order' do
+ let(:build_pre_var) { { key: 'build', value: 'value' } }
+ let(:project_pre_var) { { key: 'project', value: 'value' } }
+ let(:pipeline_pre_var) { { key: 'pipeline', value: 'value' } }
+ let(:build_yaml_var) { { key: 'yaml', value: 'value' } }
+
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'] }
+ allow(build).to receive(:predefined_variables) { [build_pre_var] }
+ allow(project).to receive(:predefined_variables) { [project_pre_var] }
+ allow(pipeline).to receive(:predefined_variables) { [pipeline_pre_var] }
+ allow(build).to receive(:yaml_variables) { [build_yaml_var] }
+
+ allow(project).to receive(:secret_variables_for)
+ .with(ref: 'master', environment: nil) do
+ [create(:ci_variable, key: 'secret', value: 'value')]
+ end
end
- it { is_expected.to eq(%w[predefined project pipeline yaml secret]) }
+ it do
+ is_expected.to eq(
+ [build_pre_var,
+ project_pre_var,
+ pipeline_pre_var,
+ build_yaml_var,
+ { key: 'secret', value: 'value', public: false }])
+ end
end
end
- describe 'State transition: any => [:pending]' do
+ describe 'state transition: any => [:pending]' do
let(:build) { create(:ci_build, :created) }
it 'queues BuildQueueWorker' do
@@ -1381,4 +1639,35 @@ describe Ci::Build, :models do
build.enqueue
end
end
+
+ describe 'state transition when build fails' do
+ context 'when build is configured to be retried' do
+ subject { create(:ci_build, :running, options: { retry: 3 }) }
+
+ it 'retries builds and assigns a same user to it' do
+ expect(described_class).to receive(:retry)
+ .with(subject, subject.user)
+
+ subject.drop!
+ end
+ end
+
+ context 'when build is not configured to be retried' do
+ subject { create(:ci_build, :running) }
+
+ it 'does not retry build' do
+ expect(described_class).not_to receive(:retry)
+
+ subject.drop!
+ end
+
+ it 'does not count retries when not necessary' do
+ expect(described_class).not_to receive(:retry)
+ expect_any_instance_of(described_class)
+ .not_to receive(:retries_count)
+
+ subject.drop!
+ end
+ end
+ end
end