Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
path: root/spec
diff options
context:
space:
mode:
authorShinya Maeda <shinya@gitlab.com>2018-12-05 09:57:00 +0300
committerShinya Maeda <shinya@gitlab.com>2018-12-05 09:57:52 +0300
commite62bfc7817ec024645383a9661fe7e36c13c1e01 (patch)
tree04227bafe6859ddbf5760ff9d3e1dc5e404fcb10 /spec
parent23d921989b66881a647afaeafec357f15293790a (diff)
Merge request pipelines
Diffstat (limited to 'spec')
-rw-r--r--spec/features/merge_request/user_sees_merge_request_pipelines_spec.rb333
-rw-r--r--spec/lib/gitlab/ci/pipeline/chain/build_spec.rb29
-rw-r--r--spec/lib/gitlab/ci/pipeline/chain/validate/config_spec.rb30
-rw-r--r--spec/lib/gitlab/import_export/all_models.yml2
-rw-r--r--spec/lib/gitlab/import_export/safe_model_attributes.yml1
-rw-r--r--spec/models/ci/pipeline_spec.rb238
-rw-r--r--spec/models/merge_request_spec.rb113
-rw-r--r--spec/services/ci/create_pipeline_service_spec.rb223
-rw-r--r--spec/services/merge_requests/create_service_spec.rb72
-rw-r--r--spec/services/merge_requests/refresh_service_spec.rb88
-rw-r--r--spec/workers/update_head_pipeline_for_merge_request_worker_spec.rb28
11 files changed, 1145 insertions, 12 deletions
diff --git a/spec/features/merge_request/user_sees_merge_request_pipelines_spec.rb b/spec/features/merge_request/user_sees_merge_request_pipelines_spec.rb
new file mode 100644
index 00000000000..ae1313cf117
--- /dev/null
+++ b/spec/features/merge_request/user_sees_merge_request_pipelines_spec.rb
@@ -0,0 +1,333 @@
+# frozen_string_literal: true
+
+require 'rails_helper'
+
+describe 'Merge request > User sees merge request pipelines', :js do
+ include ProjectForksHelper
+ include TestReportsHelper
+
+ let(:project) { create(:project, :public, :repository) }
+ let(:user) { project.creator }
+
+ let(:config) do
+ {
+ build: {
+ script: 'build'
+ },
+ test: {
+ script: 'test',
+ only: ['merge_requests']
+ },
+ deploy: {
+ script: 'deploy',
+ except: ['merge_requests']
+ }
+ }
+ end
+
+ before do
+ stub_application_setting(auto_devops_enabled: false)
+ stub_feature_flags(ci_merge_request_pipeline: true)
+ stub_ci_pipeline_yaml_file(YAML.dump(config))
+ project.add_maintainer(user)
+ sign_in(user)
+ end
+
+ context 'when a user created a merge request in the parent project' do
+ let(:merge_request) do
+ create(:merge_request,
+ source_project: project,
+ target_project: project,
+ source_branch: 'feature',
+ target_branch: 'master')
+ end
+
+ let!(:push_pipeline) do
+ Ci::CreatePipelineService.new(project, user, ref: 'feature')
+ .execute(:push)
+ end
+
+ let!(:merge_request_pipeline) do
+ Ci::CreatePipelineService.new(project, user, ref: 'feature')
+ .execute(:merge_request, merge_request: merge_request)
+ end
+
+ before do
+ visit project_merge_request_path(project, merge_request)
+
+ page.within('.merge-request-tabs') do
+ click_link('Pipelines')
+ end
+ end
+
+ it 'sees branch pipelines and merge request pipelines in correct order' do
+ page.within('.ci-table') do
+ expect(page).to have_selector('.ci-pending', count: 2)
+ expect(first('.js-pipeline-url-link')).to have_content("##{merge_request_pipeline.id}")
+ end
+ end
+
+ it 'sees the latest merge request pipeline as the head pipeline' do
+ page.within('.ci-widget-content') do
+ expect(page).to have_content("##{merge_request_pipeline.id}")
+ end
+ end
+
+ context 'when a user updated a merge request in the parent project' do
+ let!(:push_pipeline_2) do
+ Ci::CreatePipelineService.new(project, user, ref: 'feature')
+ .execute(:push)
+ end
+
+ let!(:merge_request_pipeline_2) do
+ Ci::CreatePipelineService.new(project, user, ref: 'feature')
+ .execute(:merge_request, merge_request: merge_request)
+ end
+
+ before do
+ visit project_merge_request_path(project, merge_request)
+
+ page.within('.merge-request-tabs') do
+ click_link('Pipelines')
+ end
+ end
+
+ it 'sees branch pipelines and merge request pipelines in correct order' do
+ page.within('.ci-table') do
+ expect(page).to have_selector('.ci-pending', count: 4)
+
+ expect(all('.js-pipeline-url-link')[0])
+ .to have_content("##{merge_request_pipeline_2.id}")
+
+ expect(all('.js-pipeline-url-link')[1])
+ .to have_content("##{merge_request_pipeline.id}")
+
+ expect(all('.js-pipeline-url-link')[2])
+ .to have_content("##{push_pipeline_2.id}")
+
+ expect(all('.js-pipeline-url-link')[3])
+ .to have_content("##{push_pipeline.id}")
+ end
+ end
+
+ it 'sees the latest merge request pipeline as the head pipeline' do
+ page.within('.ci-widget-content') do
+ expect(page).to have_content("##{merge_request_pipeline_2.id}")
+ end
+ end
+ end
+
+ context 'when a user merges a merge request in the parent project' do
+ before do
+ click_button 'Merge when pipeline succeeds'
+
+ wait_for_requests
+ end
+
+ context 'when merge request pipeline is pending' do
+ it 'waits the head pipeline' do
+ expect(page).to have_content('to be merged automatically when the pipeline succeeds')
+ expect(page).to have_link('Cancel automatic merge')
+ end
+ end
+
+ context 'when merge request pipeline succeeds' do
+ before do
+ merge_request_pipeline.succeed!
+
+ wait_for_requests
+ end
+
+ it 'merges the merge request' do
+ expect(page).to have_content('Merged by')
+ expect(page).to have_link('Revert')
+ end
+ end
+
+ context 'when branch pipeline succeeds' do
+ before do
+ push_pipeline.succeed!
+
+ wait_for_requests
+ end
+
+ it 'waits the head pipeline' do
+ expect(page).to have_content('to be merged automatically when the pipeline succeeds')
+ expect(page).to have_link('Cancel automatic merge')
+ end
+ end
+ end
+
+ context 'when there are no `merge_requests` keyword in .gitlab-ci.yml' do
+ let(:config) do
+ {
+ build: {
+ script: 'build'
+ },
+ test: {
+ script: 'test'
+ },
+ deploy: {
+ script: 'deploy'
+ }
+ }
+ end
+
+ it 'sees a branch pipeline in pipeline tab' do
+ page.within('.ci-table') do
+ expect(page).to have_selector('.ci-pending', count: 1)
+ expect(first('.js-pipeline-url-link')).to have_content("##{push_pipeline.id}")
+ end
+ end
+
+ it 'sees the latest branch pipeline as the head pipeline' do
+ page.within('.ci-widget-content') do
+ expect(page).to have_content("##{push_pipeline.id}")
+ end
+ end
+ end
+ end
+
+ context 'when a user created a merge request from a forked project to the parent project' do
+ let(:merge_request) do
+ create(:merge_request,
+ source_project: forked_project,
+ target_project: project,
+ source_branch: 'feature',
+ target_branch: 'master')
+ end
+
+ let!(:push_pipeline) do
+ Ci::CreatePipelineService.new(forked_project, user2, ref: 'feature')
+ .execute(:push)
+ end
+
+ let!(:merge_request_pipeline) do
+ Ci::CreatePipelineService.new(forked_project, user2, ref: 'feature')
+ .execute(:merge_request, merge_request: merge_request)
+ end
+
+ let(:forked_project) { fork_project(project, user2, repository: true) }
+ let(:user2) { create(:user) }
+
+ before do
+ forked_project.add_maintainer(user2)
+
+ visit project_merge_request_path(project, merge_request)
+
+ page.within('.merge-request-tabs') do
+ click_link('Pipelines')
+ end
+ end
+
+ it 'sees branch pipelines and merge request pipelines in correct order' do
+ page.within('.ci-table') do
+ expect(page).to have_selector('.ci-pending', count: 2)
+ expect(first('.js-pipeline-url-link')).to have_content("##{merge_request_pipeline.id}")
+ end
+ end
+
+ it 'sees the latest merge request pipeline as the head pipeline' do
+ page.within('.ci-widget-content') do
+ expect(page).to have_content("##{merge_request_pipeline.id}")
+ end
+ end
+
+ it 'sees pipeline list in forked project' do
+ visit project_pipelines_path(forked_project)
+
+ expect(page).to have_selector('.ci-pending', count: 2)
+ end
+
+ context 'when a user updated a merge request from a forked project to the parent project' do
+ let!(:push_pipeline_2) do
+ Ci::CreatePipelineService.new(forked_project, user2, ref: 'feature')
+ .execute(:push)
+ end
+
+ let!(:merge_request_pipeline_2) do
+ Ci::CreatePipelineService.new(forked_project, user2, ref: 'feature')
+ .execute(:merge_request, merge_request: merge_request)
+ end
+
+ before do
+ visit project_merge_request_path(project, merge_request)
+
+ page.within('.merge-request-tabs') do
+ click_link('Pipelines')
+ end
+ end
+
+ it 'sees branch pipelines and merge request pipelines in correct order' do
+ page.within('.ci-table') do
+ expect(page).to have_selector('.ci-pending', count: 4)
+
+ expect(all('.js-pipeline-url-link')[0])
+ .to have_content("##{merge_request_pipeline_2.id}")
+
+ expect(all('.js-pipeline-url-link')[1])
+ .to have_content("##{merge_request_pipeline.id}")
+
+ expect(all('.js-pipeline-url-link')[2])
+ .to have_content("##{push_pipeline_2.id}")
+
+ expect(all('.js-pipeline-url-link')[3])
+ .to have_content("##{push_pipeline.id}")
+ end
+ end
+
+ it 'sees the latest merge request pipeline as the head pipeline' do
+ page.within('.ci-widget-content') do
+ expect(page).to have_content("##{merge_request_pipeline_2.id}")
+ end
+ end
+
+ it 'sees pipeline list in forked project' do
+ visit project_pipelines_path(forked_project)
+
+ expect(page).to have_selector('.ci-pending', count: 4)
+ end
+ end
+
+ context 'when a user merges a merge request from a forked project to the parent project' do
+ before do
+ click_button 'Merge when pipeline succeeds'
+
+ wait_for_requests
+ end
+
+ context 'when merge request pipeline is pending' do
+ it 'waits the head pipeline' do
+ expect(page).to have_content('to be merged automatically when the pipeline succeeds')
+ expect(page).to have_link('Cancel automatic merge')
+ end
+ end
+
+ context 'when merge request pipeline succeeds' do
+ before do
+ merge_request_pipeline.succeed!
+
+ wait_for_requests
+ end
+
+ it 'merges the merge request' do
+ expect(page).to have_content('Merged by')
+ expect(page).to have_link('Revert')
+ end
+ end
+
+ context 'when branch pipeline succeeds' do
+ before do
+ push_pipeline.succeed!
+
+ wait_for_requests
+ end
+
+ it 'waits the head pipeline' do
+ expect(page).to have_content('to be merged automatically when the pipeline succeeds')
+ expect(page).to have_link('Cancel automatic merge')
+ end
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/ci/pipeline/chain/build_spec.rb b/spec/lib/gitlab/ci/pipeline/chain/build_spec.rb
index 85d73e5c382..fab071405df 100644
--- a/spec/lib/gitlab/ci/pipeline/chain/build_spec.rb
+++ b/spec/lib/gitlab/ci/pipeline/chain/build_spec.rb
@@ -18,6 +18,7 @@ describe Gitlab::Ci::Pipeline::Chain::Build do
before_sha: nil,
trigger_request: nil,
schedule: nil,
+ merge_request: nil,
project: project,
current_user: user,
variables_attributes: variables_attributes)
@@ -76,6 +77,7 @@ describe Gitlab::Ci::Pipeline::Chain::Build do
before_sha: nil,
trigger_request: nil,
schedule: nil,
+ merge_request: nil,
project: project,
current_user: user)
end
@@ -90,4 +92,31 @@ describe Gitlab::Ci::Pipeline::Chain::Build do
expect(pipeline).to be_tag
end
end
+
+ context 'when pipeline is running for a merge request' do
+ let(:command) do
+ Gitlab::Ci::Pipeline::Chain::Command.new(
+ source: :merge_request,
+ origin_ref: 'feature',
+ checkout_sha: project.commit.id,
+ after_sha: nil,
+ before_sha: nil,
+ trigger_request: nil,
+ schedule: nil,
+ merge_request: merge_request,
+ project: project,
+ current_user: user)
+ end
+
+ let(:merge_request) { build(:merge_request, target_project: project) }
+
+ before do
+ step.perform!
+ end
+
+ it 'correctly indicated that this is a merge request pipeline' do
+ expect(pipeline).to be_merge_request
+ expect(pipeline.merge_request).to eq(merge_request)
+ end
+ end
end
diff --git a/spec/lib/gitlab/ci/pipeline/chain/validate/config_spec.rb b/spec/lib/gitlab/ci/pipeline/chain/validate/config_spec.rb
index a8dc5356413..053bc421649 100644
--- a/spec/lib/gitlab/ci/pipeline/chain/validate/config_spec.rb
+++ b/spec/lib/gitlab/ci/pipeline/chain/validate/config_spec.rb
@@ -106,4 +106,34 @@ describe Gitlab::Ci::Pipeline::Chain::Validate::Config do
expect(step.break?).to be false
end
end
+
+ context 'when pipeline source is merge request' do
+ before do
+ stub_ci_pipeline_yaml_file(YAML.dump(config))
+ end
+
+ let(:pipeline) { build_stubbed(:ci_pipeline, project: project) }
+
+ let(:merge_request_pipeline) do
+ build(:ci_pipeline, source: :merge_request, project: project)
+ end
+
+ let(:chain) { described_class.new(merge_request_pipeline, command).tap(&:perform!) }
+
+ context "when config contains 'merge_requests' keyword" do
+ let(:config) { { rspec: { script: 'echo', only: ['merge_requests'] } } }
+
+ it 'does not break the chain' do
+ expect(chain).not_to be_break
+ end
+ end
+
+ context "when config contains 'merge_request' keyword" do
+ let(:config) { { rspec: { script: 'echo', only: ['merge_request'] } } }
+
+ it 'does not break the chain' do
+ expect(chain).not_to be_break
+ end
+ end
+ end
end
diff --git a/spec/lib/gitlab/import_export/all_models.yml b/spec/lib/gitlab/import_export/all_models.yml
index 31ab11bbf8d..fa01eb7901b 100644
--- a/spec/lib/gitlab/import_export/all_models.yml
+++ b/spec/lib/gitlab/import_export/all_models.yml
@@ -94,6 +94,7 @@ merge_requests:
- timelogs
- head_pipeline
- latest_merge_request_diff
+- merge_request_pipelines
merge_request_diff:
- merge_request
- merge_request_diff_commits
@@ -121,6 +122,7 @@ pipelines:
- artifacts
- pipeline_schedule
- merge_requests
+- merge_request
- deployments
- environments
pipeline_variables:
diff --git a/spec/lib/gitlab/import_export/safe_model_attributes.yml b/spec/lib/gitlab/import_export/safe_model_attributes.yml
index f7935149b23..d3bfde181bc 100644
--- a/spec/lib/gitlab/import_export/safe_model_attributes.yml
+++ b/spec/lib/gitlab/import_export/safe_model_attributes.yml
@@ -243,6 +243,7 @@ Ci::Pipeline:
- failure_reason
- protected
- iid
+- merge_request_id
Ci::Stage:
- id
- name
diff --git a/spec/models/ci/pipeline_spec.rb b/spec/models/ci/pipeline_spec.rb
index 3076a882445..dcaea9d3f83 100644
--- a/spec/models/ci/pipeline_spec.rb
+++ b/spec/models/ci/pipeline_spec.rb
@@ -14,6 +14,7 @@ describe Ci::Pipeline, :mailer do
it { is_expected.to belong_to(:user) }
it { is_expected.to belong_to(:auto_canceled_by) }
it { is_expected.to belong_to(:pipeline_schedule) }
+ it { is_expected.to belong_to(:merge_request) }
it { is_expected.to have_many(:statuses) }
it { is_expected.to have_many(:trigger_requests) }
@@ -37,6 +38,128 @@ describe Ci::Pipeline, :mailer do
end
end
+ describe '.sort_by_merge_request_pipelines' do
+ subject { described_class.sort_by_merge_request_pipelines }
+
+ context 'when branch pipelines exist' do
+ let!(:branch_pipeline_1) { create(:ci_pipeline, source: :push) }
+ let!(:branch_pipeline_2) { create(:ci_pipeline, source: :push) }
+
+ it 'returns pipelines order by id' do
+ expect(subject).to eq([branch_pipeline_2,
+ branch_pipeline_1])
+ end
+ end
+
+ context 'when merge request pipelines exist' do
+ let!(:merge_request_pipeline_1) do
+ create(:ci_pipeline, source: :merge_request, merge_request: merge_request)
+ end
+
+ let!(:merge_request_pipeline_2) do
+ create(:ci_pipeline, source: :merge_request, merge_request: merge_request)
+ end
+
+ let(:merge_request) do
+ create(:merge_request,
+ source_project: project,
+ source_branch: 'feature',
+ target_project: project,
+ target_branch: 'master')
+ end
+
+ it 'returns pipelines order by id' do
+ expect(subject).to eq([merge_request_pipeline_2,
+ merge_request_pipeline_1])
+ end
+ end
+
+ context 'when both branch pipeline and merge request pipeline exist' do
+ let!(:branch_pipeline_1) { create(:ci_pipeline, source: :push) }
+ let!(:branch_pipeline_2) { create(:ci_pipeline, source: :push) }
+
+ let!(:merge_request_pipeline_1) do
+ create(:ci_pipeline, source: :merge_request, merge_request: merge_request)
+ end
+
+ let!(:merge_request_pipeline_2) do
+ create(:ci_pipeline, source: :merge_request, merge_request: merge_request)
+ end
+
+ let(:merge_request) do
+ create(:merge_request,
+ source_project: project,
+ source_branch: 'feature',
+ target_project: project,
+ target_branch: 'master')
+ end
+
+ it 'returns merge request pipeline first' do
+ expect(subject).to eq([merge_request_pipeline_2,
+ merge_request_pipeline_1,
+ branch_pipeline_2,
+ branch_pipeline_1])
+ end
+ end
+ end
+
+ describe '.merge_request' do
+ subject { described_class.merge_request }
+
+ context 'when there is a merge request pipeline' do
+ let!(:pipeline) { create(:ci_pipeline, source: :merge_request, merge_request: merge_request) }
+ let(:merge_request) { create(:merge_request) }
+
+ it 'returns merge request pipeline first' do
+ expect(subject).to eq([pipeline])
+ end
+ end
+
+ context 'when there are no merge request pipelines' do
+ let!(:pipeline) { create(:ci_pipeline, source: :push) }
+
+ it 'returns empty array' do
+ expect(subject).to be_empty
+ end
+ end
+ end
+
+ describe 'Validations for merge request pipelines' do
+ let(:pipeline) { build(:ci_pipeline, source: source, merge_request: merge_request) }
+
+ context 'when source is merge request' do
+ let(:source) { :merge_request }
+
+ context 'when merge request is specified' do
+ let(:merge_request) { create(:merge_request, source_project: project, source_branch: 'feature', target_project: project, target_branch: 'master') }
+
+ it { expect(pipeline).to be_valid }
+ end
+
+ context 'when merge request is empty' do
+ let(:merge_request) { nil }
+
+ it { expect(pipeline).not_to be_valid }
+ end
+ end
+
+ context 'when source is web' do
+ let(:source) { :web }
+
+ context 'when merge request is specified' do
+ let(:merge_request) { create(:merge_request, source_project: project, source_branch: 'feature', target_project: project, target_branch: 'master') }
+
+ it { expect(pipeline).not_to be_valid }
+ end
+
+ context 'when merge request is empty' do
+ let(:merge_request) { nil }
+
+ it { expect(pipeline).to be_valid }
+ end
+ end
+ end
+
describe 'modules' do
it_behaves_like 'AtomicInternalId', validate_presence: false do
let(:internal_id_attribute) { :iid }
@@ -760,27 +883,85 @@ describe Ci::Pipeline, :mailer do
describe '#branch?' do
subject { pipeline.branch? }
- context 'is not a tag' do
+ context 'when ref is not a tag' do
before do
pipeline.tag = false
end
- it 'return true when tag is set to false' do
+ it 'return true' do
is_expected.to be_truthy
end
+
+ context 'when source is merge request' do
+ let(:pipeline) do
+ create(:ci_pipeline, source: :merge_request, merge_request: merge_request)
+ end
+
+ let(:merge_request) do
+ create(:merge_request,
+ source_project: project,
+ source_branch: 'feature',
+ target_project: project,
+ target_branch: 'master')
+ end
+
+ it 'returns false' do
+ is_expected.to be_falsey
+ end
+ end
end
- context 'is not a tag' do
+ context 'when ref is a tag' do
before do
pipeline.tag = true
end
- it 'return false when tag is set to true' do
+ it 'return false' do
is_expected.to be_falsey
end
end
end
+ describe '#git_ref' do
+ subject { pipeline.send(:git_ref) }
+
+ context 'when ref is branch' do
+ let(:pipeline) { create(:ci_pipeline, tag: false) }
+
+ it 'returns branch ref' do
+ is_expected.to eq(Gitlab::Git::BRANCH_REF_PREFIX + pipeline.ref.to_s)
+ end
+ end
+
+ context 'when ref is tag' do
+ let(:pipeline) { create(:ci_pipeline, tag: true) }
+
+ it 'returns branch ref' do
+ is_expected.to eq(Gitlab::Git::TAG_REF_PREFIX + pipeline.ref.to_s)
+ end
+ end
+
+ context 'when ref is merge request' do
+ let(:pipeline) do
+ create(:ci_pipeline,
+ source: :merge_request,
+ merge_request: merge_request)
+ end
+
+ let(:merge_request) do
+ create(:merge_request,
+ source_project: project,
+ source_branch: 'feature',
+ target_project: project,
+ target_branch: 'master')
+ end
+
+ it 'returns branch ref' do
+ is_expected.to eq(Gitlab::Git::BRANCH_REF_PREFIX + pipeline.ref.to_s)
+ end
+ end
+ end
+
describe 'ref_exists?' do
context 'when repository exists' do
using RSpec::Parameterized::TableSyntax
@@ -1855,6 +2036,55 @@ describe Ci::Pipeline, :mailer do
expect(pipeline.all_merge_requests).to be_empty
end
+
+ context 'when there is a merge request pipeline' do
+ let(:source_branch) { 'feature' }
+ let(:target_branch) { 'master' }
+
+ let!(:pipeline) do
+ create(:ci_pipeline,
+ source: :merge_request,
+ project: project,
+ ref: source_branch,
+ merge_request: merge_request)
+ end
+
+ let(:merge_request) do
+ create(:merge_request,
+ source_project: project,
+ source_branch: source_branch,
+ target_project: project,
+ target_branch: target_branch)
+ end
+
+ it 'returns an associated merge request' do
+ expect(pipeline.all_merge_requests).to eq([merge_request])
+ end
+
+ context 'when there is another merge request pipeline that targets a different branch' do
+ let(:target_branch_2) { 'merge-test' }
+
+ let!(:pipeline_2) do
+ create(:ci_pipeline,
+ source: :merge_request,
+ project: project,
+ ref: source_branch,
+ merge_request: merge_request_2)
+ end
+
+ let(:merge_request_2) do
+ create(:merge_request,
+ source_project: project,
+ source_branch: source_branch,
+ target_project: project,
+ target_branch: target_branch_2)
+ end
+
+ it 'does not return an associated merge request' do
+ expect(pipeline.all_merge_requests).not_to include(merge_request_2)
+ end
+ end
+ end
end
describe '#stuck?' do
diff --git a/spec/models/merge_request_spec.rb b/spec/models/merge_request_spec.rb
index ad55c280399..9b60054e14a 100644
--- a/spec/models/merge_request_spec.rb
+++ b/spec/models/merge_request_spec.rb
@@ -1206,6 +1206,119 @@ describe MergeRequest do
expect(subject.all_pipelines).to contain_exactly(pipeline)
end
end
+
+ context 'when pipelines exist for the branch and merge request' do
+ let(:source_ref) { 'feature' }
+ let(:target_ref) { 'master' }
+
+ let!(:branch_pipeline) do
+ create(:ci_pipeline,
+ source: :push,
+ project: project,
+ ref: source_ref,
+ sha: shas.second)
+ end
+
+ let!(:merge_request_pipeline) do
+ create(:ci_pipeline,
+ source: :merge_request,
+ project: project,
+ ref: source_ref,
+ sha: shas.second,
+ merge_request: merge_request)
+ end
+
+ let(:merge_request) do
+ create(:merge_request,
+ source_project: project,
+ source_branch: source_ref,
+ target_project: project,
+ target_branch: target_ref)
+ end
+
+ let(:project) { create(:project, :repository) }
+ let(:shas) { project.repository.commits(source_ref, limit: 2).map(&:id) }
+
+ before do
+ allow(merge_request).to receive(:all_commit_shas) { shas }
+ end
+
+ it 'returns merge request pipeline first' do
+ expect(merge_request.all_pipelines)
+ .to eq([merge_request_pipeline,
+ branch_pipeline])
+ end
+
+ context 'when there are a branch pipeline and a merge request pipeline' do
+ let!(:branch_pipeline_2) do
+ create(:ci_pipeline,
+ source: :push,
+ project: project,
+ ref: source_ref,
+ sha: shas.first)
+ end
+
+ let!(:merge_request_pipeline_2) do
+ create(:ci_pipeline,
+ source: :merge_request,
+ project: project,
+ ref: source_ref,
+ sha: shas.first,
+ merge_request: merge_request)
+ end
+
+ it 'returns merge request pipelines first' do
+ expect(merge_request.all_pipelines)
+ .to eq([merge_request_pipeline_2,
+ merge_request_pipeline,
+ branch_pipeline_2,
+ branch_pipeline])
+ end
+ end
+
+ context 'when there are multiple merge request pipelines from the same branch' do
+ let!(:branch_pipeline_2) do
+ create(:ci_pipeline,
+ source: :push,
+ project: project,
+ ref: source_ref,
+ sha: shas.first)
+ end
+
+ let!(:merge_request_pipeline_2) do
+ create(:ci_pipeline,
+ source: :merge_request,
+ project: project,
+ ref: source_ref,
+ sha: shas.first,
+ merge_request: merge_request_2)
+ end
+
+ let(:merge_request_2) do
+ create(:merge_request,
+ source_project: project,
+ source_branch: source_ref,
+ target_project: project,
+ target_branch: 'stable')
+ end
+
+ before do
+ allow(merge_request_2).to receive(:all_commit_shas) { shas }
+ end
+
+ it 'returns only related merge request pipelines' do
+ expect(merge_request.all_pipelines)
+ .to eq([merge_request_pipeline,
+ branch_pipeline_2,
+ branch_pipeline])
+
+ expect(merge_request_2.all_pipelines)
+ .to eq([merge_request_pipeline_2,
+ branch_pipeline_2,
+ branch_pipeline])
+ end
+ end
+ end
end
describe '#has_test_reports?' do
diff --git a/spec/services/ci/create_pipeline_service_spec.rb b/spec/services/ci/create_pipeline_service_spec.rb
index a4582d1bc64..7bc990f05c2 100644
--- a/spec/services/ci/create_pipeline_service_spec.rb
+++ b/spec/services/ci/create_pipeline_service_spec.rb
@@ -18,7 +18,8 @@ describe Ci::CreatePipelineService do
message: 'Message',
ref: ref_name,
trigger_request: nil,
- variables_attributes: nil)
+ variables_attributes: nil,
+ merge_request: nil)
params = { ref: ref,
before: '00000000',
after: after,
@@ -26,7 +27,7 @@ describe Ci::CreatePipelineService do
variables_attributes: variables_attributes }
described_class.new(project, user, params).execute(
- source, trigger_request: trigger_request)
+ source, trigger_request: trigger_request, merge_request: merge_request)
end
context 'valid params' do
@@ -60,10 +61,10 @@ describe Ci::CreatePipelineService do
context 'when merge requests already exist for this source branch' do
let(:merge_request_1) do
- create(:merge_request, source_branch: 'master', target_branch: "branch_1", source_project: project)
+ create(:merge_request, source_branch: 'feature', target_branch: "master", source_project: project)
end
let(:merge_request_2) do
- create(:merge_request, source_branch: 'master', target_branch: "branch_2", source_project: project)
+ create(:merge_request, source_branch: 'feature', target_branch: "v1.1.0", source_project: project)
end
context 'when related merge request is already merged' do
@@ -83,7 +84,7 @@ describe Ci::CreatePipelineService do
merge_request_1
merge_request_2
- head_pipeline = execute_service
+ head_pipeline = execute_service(ref: 'feature', after: nil)
expect(merge_request_1.reload.head_pipeline).to eq(head_pipeline)
expect(merge_request_2.reload.head_pipeline).to eq(head_pipeline)
@@ -123,12 +124,12 @@ describe Ci::CreatePipelineService do
let!(:target_project) { create(:project, :repository) }
it 'updates head pipeline for merge request' do
- merge_request = create(:merge_request, source_branch: 'master',
- target_branch: "branch_1",
+ merge_request = create(:merge_request, source_branch: 'feature',
+ target_branch: "master",
source_project: project,
target_project: target_project)
- head_pipeline = execute_service
+ head_pipeline = execute_service(ref: 'feature', after: nil)
expect(merge_request.reload.head_pipeline).to eq(head_pipeline)
end
@@ -656,6 +657,212 @@ describe Ci::CreatePipelineService do
end
end
end
+
+ describe 'Merge request pipelines' do
+ let(:pipeline) do
+ execute_service(source: source, merge_request: merge_request, ref: ref_name)
+ end
+
+ before do
+ stub_ci_pipeline_yaml_file(YAML.dump(config))
+ end
+
+ let(:ref_name) { 'feature' }
+
+ context 'when source is merge request' do
+ let(:source) { :merge_request }
+
+ context "when config has merge_requests keywords" do
+ let(:config) do
+ {
+ build: {
+ stage: 'build',
+ script: 'echo'
+ },
+ test: {
+ stage: 'test',
+ script: 'echo',
+ only: ['merge_requests']
+ },
+ pages: {
+ stage: 'deploy',
+ script: 'echo',
+ except: ['merge_requests']
+ }
+ }
+ end
+
+ context 'when merge request is specified' do
+ let(:merge_request) do
+ create(:merge_request,
+ source_project: project,
+ source_branch: ref_name,
+ target_project: project,
+ target_branch: 'master')
+ end
+
+ it 'creates a merge request pipeline' do
+ expect(pipeline).to be_persisted
+ expect(pipeline).to be_merge_request
+ expect(pipeline.merge_request).to eq(merge_request)
+ expect(pipeline.builds.order(:stage_id).map(&:name)).to eq(%w[test])
+ end
+
+ context 'when ref is tag' do
+ let(:ref_name) { 'v1.1.0' }
+
+ it 'does not create a merge request pipeline' do
+ expect(pipeline).not_to be_persisted
+ expect(pipeline.errors[:tag]).to eq(["is not included in the list"])
+ end
+ end
+
+ context 'when merge request is created from a forked project' do
+ let(:merge_request) do
+ create(:merge_request,
+ source_project: project,
+ source_branch: ref_name,
+ target_project: target_project,
+ target_branch: 'master')
+ end
+
+ let!(:project) { fork_project(target_project, nil, repository: true) }
+ let!(:target_project) { create(:project, :repository) }
+
+ it 'creates a merge request pipeline in the forked project' do
+ expect(pipeline).to be_persisted
+ expect(project.pipelines).to eq([pipeline])
+ expect(target_project.pipelines).to be_empty
+ end
+ end
+
+ context "when there are no matched jobs" do
+ let(:config) do
+ {
+ test: {
+ stage: 'test',
+ script: 'echo',
+ except: ['merge_requests']
+ }
+ }
+ end
+
+ it 'does not create a merge request pipeline' do
+ expect(pipeline).not_to be_persisted
+ expect(pipeline.errors[:base]).to eq(["No stages / jobs for this pipeline."])
+ end
+ end
+ end
+
+ context 'when merge request is not specified' do
+ let(:merge_request) { nil }
+
+ it 'does not create a merge request pipeline' do
+ expect(pipeline).not_to be_persisted
+ expect(pipeline.errors[:merge_request]).to eq(["can't be blank"])
+ end
+ end
+ end
+
+ context "when config does not have merge_requests keywords" do
+ let(:config) do
+ {
+ build: {
+ stage: 'build',
+ script: 'echo'
+ },
+ test: {
+ stage: 'test',
+ script: 'echo'
+ },
+ pages: {
+ stage: 'deploy',
+ script: 'echo'
+ }
+ }
+ end
+
+ context 'when merge request is specified' do
+ let(:merge_request) do
+ create(:merge_request,
+ source_project: project,
+ source_branch: ref_name,
+ target_project: project,
+ target_branch: 'master')
+ end
+
+ it 'does not create a merge request pipeline' do
+ expect(pipeline).not_to be_persisted
+
+ expect(pipeline.errors[:base])
+ .to eq(['No stages / jobs for this pipeline.'])
+ end
+ end
+
+ context 'when merge request is not specified' do
+ let(:merge_request) { nil }
+
+ it 'does not create a merge request pipeline' do
+ expect(pipeline).not_to be_persisted
+
+ expect(pipeline.errors[:base])
+ .to eq(['No stages / jobs for this pipeline.'])
+ end
+ end
+ end
+ end
+
+ context 'when source is web' do
+ let(:source) { :web }
+
+ context "when config has merge_requests keywords" do
+ let(:config) do
+ {
+ build: {
+ stage: 'build',
+ script: 'echo'
+ },
+ test: {
+ stage: 'test',
+ script: 'echo',
+ only: ['merge_requests']
+ },
+ pages: {
+ stage: 'deploy',
+ script: 'echo',
+ except: ['merge_requests']
+ }
+ }
+ end
+
+ context 'when merge request is specified' do
+ let(:merge_request) do
+ create(:merge_request,
+ source_project: project,
+ source_branch: ref_name,
+ target_project: project,
+ target_branch: 'master')
+ end
+
+ it 'does not create a merge request pipeline' do
+ expect(pipeline).not_to be_persisted
+ expect(pipeline.errors[:merge_request]).to eq(["must be blank"])
+ end
+ end
+
+ context 'when merge request is not specified' do
+ let(:merge_request) { nil }
+
+ it 'creates a branch pipeline' do
+ expect(pipeline).to be_persisted
+ expect(pipeline).to be_web
+ expect(pipeline.merge_request).to be_nil
+ expect(pipeline.builds.order(:stage_id).map(&:name)).to eq(%w[build pages])
+ end
+ end
+ end
+ end
+ end
end
describe '#execute!' do
diff --git a/spec/services/merge_requests/create_service_spec.rb b/spec/services/merge_requests/create_service_spec.rb
index 74bcc15f912..5a3ecb1019b 100644
--- a/spec/services/merge_requests/create_service_spec.rb
+++ b/spec/services/merge_requests/create_service_spec.rb
@@ -159,6 +159,78 @@ describe MergeRequests::CreateService do
end
end
end
+
+ describe 'Merge request pipelines' do
+ before do
+ stub_ci_pipeline_yaml_file(YAML.dump(config))
+ end
+
+ context "when .gitlab-ci.yml has merge_requests keywords" do
+ let(:config) do
+ {
+ test: {
+ stage: 'test',
+ script: 'echo',
+ only: ['merge_requests']
+ }
+ }
+ end
+
+ it 'creates a merge request pipeline and sets it as a head pipeline' do
+ expect(merge_request).to be_persisted
+
+ merge_request.reload
+ expect(merge_request.merge_request_pipelines.count).to eq(1)
+ expect(merge_request.actual_head_pipeline).to be_merge_request
+ end
+
+ context "when branch pipeline was created before a merge request pipline has been created" do
+ before do
+ create(:ci_pipeline, project: merge_request.source_project,
+ sha: merge_request.diff_head_sha,
+ ref: merge_request.source_branch,
+ tag: false)
+
+ merge_request
+ end
+
+ it 'sets the latest merge request pipeline as the head pipeline' do
+ expect(merge_request.actual_head_pipeline).to be_merge_request
+ end
+ end
+
+ context "when the 'ci_merge_request_pipeline' feature flag is disabled" do
+ before do
+ stub_feature_flags(ci_merge_request_pipeline: false)
+ end
+
+ it 'does not create a merge request pipeline' do
+ expect(merge_request).to be_persisted
+
+ merge_request.reload
+ expect(merge_request.merge_request_pipelines.count).to eq(0)
+ end
+ end
+ end
+
+ context "when .gitlab-ci.yml does not have merge_requests keywords" do
+ let(:config) do
+ {
+ test: {
+ stage: 'test',
+ script: 'echo'
+ }
+ }
+ end
+
+ it 'does not create a merge request pipeline' do
+ expect(merge_request).to be_persisted
+
+ merge_request.reload
+ expect(merge_request.merge_request_pipelines.count).to eq(0)
+ end
+ end
+ end
end
it_behaves_like 'new issuable record that supports quick actions' do
diff --git a/spec/services/merge_requests/refresh_service_spec.rb b/spec/services/merge_requests/refresh_service_spec.rb
index 61c6ba7d550..d29a1091d95 100644
--- a/spec/services/merge_requests/refresh_service_spec.rb
+++ b/spec/services/merge_requests/refresh_service_spec.rb
@@ -132,6 +132,94 @@ describe MergeRequests::RefreshService do
end
end
+ describe 'Merge request pipelines' do
+ before do
+ stub_ci_pipeline_yaml_file(YAML.dump(config))
+ end
+
+ subject { service.new(@project, @user).execute(@oldrev, @newrev, 'refs/heads/master') }
+
+ context "when .gitlab-ci.yml has merge_requests keywords" do
+ let(:config) do
+ {
+ test: {
+ stage: 'test',
+ script: 'echo',
+ only: ['merge_requests']
+ }
+ }
+ end
+
+ it 'create merge request pipeline' do
+ expect { subject }
+ .to change { @merge_request.merge_request_pipelines.count }.by(1)
+ .and change { @fork_merge_request.merge_request_pipelines.count }.by(1)
+ .and change { @another_merge_request.merge_request_pipelines.count }.by(1)
+ end
+
+ context "when branch pipeline was created before a merge request pipline has been created" do
+ before do
+ create(:ci_pipeline, project: @merge_request.source_project,
+ sha: @merge_request.diff_head_sha,
+ ref: @merge_request.source_branch,
+ tag: false)
+
+ subject
+ end
+
+ it 'sets the latest merge request pipeline as a head pipeline' do
+ @merge_request.reload
+ expect(@merge_request.actual_head_pipeline).to be_merge_request
+ end
+
+ it 'returns pipelines in correct order' do
+ @merge_request.reload
+ expect(@merge_request.all_pipelines.first).to be_merge_request
+ expect(@merge_request.all_pipelines.second).to be_push
+ end
+ end
+
+ context "when MergeRequestUpdateWorker is retried by an exception" do
+ it 'does not re-create a duplicate merge request pipeline' do
+ expect do
+ service.new(@project, @user).execute(@oldrev, @newrev, 'refs/heads/master')
+ end.to change { @merge_request.merge_request_pipelines.count }.by(1)
+
+ expect do
+ service.new(@project, @user).execute(@oldrev, @newrev, 'refs/heads/master')
+ end.not_to change { @merge_request.merge_request_pipelines.count }
+ end
+ end
+
+ context "when the 'ci_merge_request_pipeline' feature flag is disabled" do
+ before do
+ stub_feature_flags(ci_merge_request_pipeline: false)
+ end
+
+ it 'does not create a merge request pipeline' do
+ expect { subject }
+ .not_to change { @merge_request.merge_request_pipelines.count }
+ end
+ end
+ end
+
+ context "when .gitlab-ci.yml does not have merge_requests keywords" do
+ let(:config) do
+ {
+ test: {
+ stage: 'test',
+ script: 'echo'
+ }
+ }
+ end
+
+ it 'does not create a merge request pipeline' do
+ expect { subject }
+ .not_to change { @merge_request.merge_request_pipelines.count }
+ end
+ end
+ end
+
context 'push to origin repo source branch when an MR was reopened' do
let(:refresh_service) { service.new(@project, @user) }
let(:notification_service) { spy('notification_service') }
diff --git a/spec/workers/update_head_pipeline_for_merge_request_worker_spec.rb b/spec/workers/update_head_pipeline_for_merge_request_worker_spec.rb
index 9adde5fc21a..a2bc264b0f6 100644
--- a/spec/workers/update_head_pipeline_for_merge_request_worker_spec.rb
+++ b/spec/workers/update_head_pipeline_for_merge_request_worker_spec.rb
@@ -34,5 +34,33 @@ describe UpdateHeadPipelineForMergeRequestWorker do
expect { subject.perform(merge_request.id) }.not_to change { merge_request.reload.head_pipeline_id }
end
end
+
+ context 'when a merge request pipeline exists' do
+ let!(:merge_request_pipeline) do
+ create(:ci_pipeline,
+ project: project,
+ source: :merge_request,
+ sha: latest_sha,
+ merge_request: merge_request)
+ end
+
+ it 'sets the merge request pipeline as the head pipeline' do
+ expect { subject.perform(merge_request.id) }
+ .to change { merge_request.reload.head_pipeline_id }
+ .from(nil).to(merge_request_pipeline.id)
+ end
+
+ context 'when branch pipeline exists' do
+ let!(:branch_pipeline) do
+ create(:ci_pipeline, project: project, source: :push, sha: latest_sha)
+ end
+
+ it 'prioritizes the merge request pipeline as the head pipeline' do
+ expect { subject.perform(merge_request.id) }
+ .to change { merge_request.reload.head_pipeline_id }
+ .from(nil).to(merge_request_pipeline.id)
+ end
+ end
+ end
end
end