diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb index d2402b55184..119c6fd7b45 100644 --- a/app/models/ci/build.rb +++ b/app/models/ci/build.rb @@ -139,6 +139,12 @@ module Ci Ci::Build.retry(build, build.user) end end + + before_transition any => [:running] do |build| + if !build.empty_dependencies? && build.dependencies.empty? + raise Gitlab::Ci::Error::MissingDependencies + end + end end def detailed_status(current_user) diff --git a/app/models/commit_status.rb b/app/models/commit_status.rb index ee21ed8e420..c0263c0b4e2 100644 --- a/app/models/commit_status.rb +++ b/app/models/commit_status.rb @@ -43,7 +43,8 @@ class CommitStatus < ActiveRecord::Base script_failure: 1, api_failure: 2, stuck_or_timeout_failure: 3, - runner_system_failure: 4 + runner_system_failure: 4, + missing_dependency_failure: 5 } ## diff --git a/app/services/ci/register_job_service.rb b/app/services/ci/register_job_service.rb index 2ef76e03031..f73902935e6 100644 --- a/app/services/ci/register_job_service.rb +++ b/app/services/ci/register_job_service.rb @@ -54,6 +54,9 @@ module Ci # we still have to return 409 in the end, # to make sure that this is properly handled by runner. valid = false + rescue Gitlab::Ci::Error::MissingDependencies + build.drop!(:missing_dependency_failure) + valid = false end end diff --git a/lib/gitlab/ci/error/missing_dependencies.rb b/lib/gitlab/ci/error/missing_dependencies.rb new file mode 100644 index 00000000000..f4b1940d84f --- /dev/null +++ b/lib/gitlab/ci/error/missing_dependencies.rb @@ -0,0 +1,7 @@ +module Gitlab + module Ci + module Error + class MissingDependencies < StandardError; end + end + end +end diff --git a/spec/lib/gitlab/ci/error/missing_dependencies_spec.rb b/spec/lib/gitlab/ci/error/missing_dependencies_spec.rb new file mode 100644 index 00000000000..039a4776dc3 --- /dev/null +++ b/spec/lib/gitlab/ci/error/missing_dependencies_spec.rb @@ -0,0 +1,5 @@ +require 'spec_helper' + +describe Gitlab::Ci::Error::MissingDependencies do + it { expect(described_class).to be < StandardError } +end diff --git a/spec/models/ci/build_spec.rb b/spec/models/ci/build_spec.rb index 26d33663dad..61ac2dd78d1 100644 --- a/spec/models/ci/build_spec.rb +++ b/spec/models/ci/build_spec.rb @@ -1868,6 +1868,36 @@ describe Ci::Build do end end + describe 'state transition: any => [:running]' do + let(:build) { create(:ci_build, :pending, pipeline: pipeline, stage_idx: 1, options: options) } + + context 'when "dependencies" keyword is not defined' do + let(:options) { {} } + + it { expect { build.run! }.not_to raise_error } + end + + context 'when "dependencies" keyword is empty' do + let(:options) { { dependencies: [] } } + + it { expect { build.run! }.not_to raise_error } + end + + context 'when "dependencies" keyword is specified' do + let(:options) { { dependencies: ['test'] } } + + context 'when a depended job exists' do + let!(:pre_build) { create(:ci_build, pipeline: pipeline, name: 'test', stage_idx: 0) } + + it { expect { build.run! }.not_to raise_error } + end + + context 'when depended jobs do not exist' do + it { expect { build.run! }.to raise_error(Gitlab::Ci::Error::MissingDependencies) } + end + end + end + describe 'state transition when build fails' do let(:service) { MergeRequests::AddTodoWhenBuildFailsService.new(project, user) } diff --git a/spec/services/ci/register_job_service_spec.rb b/spec/services/ci/register_job_service_spec.rb index decdd577226..e779d02cc52 100644 --- a/spec/services/ci/register_job_service_spec.rb +++ b/spec/services/ci/register_job_service_spec.rb @@ -276,6 +276,34 @@ module Ci end end + context 'when "dependencies" keyword is specified' do + let!(:pre_stage_job) { create(:ci_build, :success, pipeline: pipeline, name: job_name, stage_idx: 0) } + + let!(:pending_job) do + create(:ci_build, :pending, pipeline: pipeline, stage_idx: 1, options: { dependencies: ['spec'] } ) + end + + let(:picked_job) { execute(specific_runner) } + + context 'when a depended job exists' do + let(:job_name) { 'spec' } + + it "picks the build" do + expect(picked_job).to eq(pending_job) + end + end + + context 'when depended jobs do not exist' do + let(:job_name) { 'robocop' } + + it 'does not pick the build and drops the build' do + expect(picked_job).to be_nil + expect(pending_job.reload).to be_failed + expect(pending_job).to be_missing_dependency_failure + end + end + end + def execute(runner) described_class.new(runner).execute.build end