pipeline_spec.rb 30.5 KB
Newer Older
D
Douwe Maan 已提交
1 2
require 'spec_helper'

3
describe Ci::Pipeline, models: true do
4 5
  include EmailHelpers

6 7 8 9 10 11
  let(:user) { create(:user) }
  let(:project) { create(:empty_project) }

  let(:pipeline) do
    create(:ci_empty_pipeline, status: :created, project: project)
  end
D
Douwe Maan 已提交
12

K
Kamil Trzcinski 已提交
13
  it { is_expected.to belong_to(:project) }
14 15
  it { is_expected.to belong_to(:user) }

16
  it { is_expected.to have_many(:statuses) }
K
Kamil Trzcinski 已提交
17
  it { is_expected.to have_many(:trigger_requests) }
D
Dmitriy Zaporozhets 已提交
18
  it { is_expected.to have_many(:builds) }
19

D
Dmitriy Zaporozhets 已提交
20
  it { is_expected.to validate_presence_of :sha }
K
Kamil Trzcinski 已提交
21
  it { is_expected.to validate_presence_of :status }
D
Douwe Maan 已提交
22

D
Dmitriy Zaporozhets 已提交
23 24 25
  it { is_expected.to respond_to :git_author_name }
  it { is_expected.to respond_to :git_author_email }
  it { is_expected.to respond_to :short_sha }
D
Douwe Maan 已提交
26

27 28 29 30 31 32 33 34
  describe '#block' do
    it 'changes pipeline status to manual' do
      expect(pipeline.block).to be true
      expect(pipeline.reload).to be_manual
      expect(pipeline.reload).to be_blocked
    end
  end

35
  describe '#valid_commit_sha' do
D
Douwe Maan 已提交
36 37
    context 'commit.sha can not start with 00000000' do
      before do
38 39
        pipeline.sha = '0' * 40
        pipeline.valid_commit_sha
D
Douwe Maan 已提交
40 41
      end

42
      it('commit errors should not be empty') { expect(pipeline.errors).not_to be_empty }
D
Douwe Maan 已提交
43 44 45
    end
  end

46
  describe '#short_sha' do
47
    subject { pipeline.short_sha }
D
Douwe Maan 已提交
48

D
Dmitriy Zaporozhets 已提交
49 50 51
    it 'has 8 items' do
      expect(subject.size).to eq(8)
    end
52
    it { expect(pipeline.sha).to start_with(subject) }
D
Douwe Maan 已提交
53 54
  end

55
  describe '#retried' do
56
    subject { pipeline.retried }
K
Kamil Trzcinski 已提交
57 58

    before do
59 60
      @build1 = FactoryGirl.create :ci_build, pipeline: pipeline, name: 'deploy'
      @build2 = FactoryGirl.create :ci_build, pipeline: pipeline, name: 'deploy'
K
Kamil Trzcinski 已提交
61 62 63
    end

    it 'returns old builds' do
64
      is_expected.to contain_exactly(@build1)
K
Kamil Trzcinski 已提交
65 66 67
    end
  end

D
Douwe Maan 已提交
68
  describe "coverage" do
K
Kamil Trzcinski 已提交
69
    let(:project) { FactoryGirl.create :empty_project, build_coverage_regex: "/.*/" }
70
    let(:pipeline) { FactoryGirl.create :ci_empty_pipeline, project: project }
D
Douwe Maan 已提交
71 72

    it "calculates average when there are two builds with coverage" do
73 74 75
      FactoryGirl.create :ci_build, name: "rspec", coverage: 30, pipeline: pipeline
      FactoryGirl.create :ci_build, name: "rubocop", coverage: 40, pipeline: pipeline
      expect(pipeline.coverage).to eq("35.00")
D
Douwe Maan 已提交
76 77 78
    end

    it "calculates average when there are two builds with coverage and one with nil" do
79 80 81 82
      FactoryGirl.create :ci_build, name: "rspec", coverage: 30, pipeline: pipeline
      FactoryGirl.create :ci_build, name: "rubocop", coverage: 40, pipeline: pipeline
      FactoryGirl.create :ci_build, pipeline: pipeline
      expect(pipeline.coverage).to eq("35.00")
D
Douwe Maan 已提交
83 84 85
    end

    it "calculates average when there are two builds with coverage and one is retried" do
86 87 88 89
      FactoryGirl.create :ci_build, name: "rspec", coverage: 30, pipeline: pipeline
      FactoryGirl.create :ci_build, name: "rubocop", coverage: 30, pipeline: pipeline
      FactoryGirl.create :ci_build, name: "rubocop", coverage: 40, pipeline: pipeline
      expect(pipeline.coverage).to eq("35.00")
D
Douwe Maan 已提交
90 91 92
    end

    it "calculates average when there is one build without coverage" do
93 94
      FactoryGirl.create :ci_build, pipeline: pipeline
      expect(pipeline.coverage).to be_nil
D
Douwe Maan 已提交
95 96
    end
  end
K
Kamil Trzcinski 已提交
97 98

  describe '#retryable?' do
99
    subject { pipeline.retryable? }
K
Kamil Trzcinski 已提交
100 101 102

    context 'no failed builds' do
      before do
103
        create_build('rspec', 'success')
K
Kamil Trzcinski 已提交
104 105
      end

106
      it 'is not retryable' do
K
Kamil Trzcinski 已提交
107 108
        is_expected.to be_falsey
      end
109 110 111 112 113 114 115 116 117 118

      context 'one canceled job' do
        before do
          create_build('rubocop', 'canceled')
        end

        it 'is retryable' do
          is_expected.to be_truthy
        end
      end
K
Kamil Trzcinski 已提交
119 120 121 122
    end

    context 'with failed builds' do
      before do
123 124
        create_build('rspec', 'running')
        create_build('rubocop', 'failed')
K
Kamil Trzcinski 已提交
125 126
      end

127
      it 'is retryable' do
K
Kamil Trzcinski 已提交
128 129 130
        is_expected.to be_truthy
      end
    end
131 132 133 134

    def create_build(name, status)
      create(:ci_build, name: name, status: status, pipeline: pipeline)
    end
K
Kamil Trzcinski 已提交
135 136
  end

137
  describe 'pipeline stages' do
K
Kamil Trzcinski 已提交
138
    before do
139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170
      create(:commit_status, pipeline: pipeline,
                             stage: 'build',
                             name: 'linux',
                             stage_idx: 0,
                             status: 'success')

      create(:commit_status, pipeline: pipeline,
                             stage: 'build',
                             name: 'mac',
                             stage_idx: 0,
                             status: 'failed')

      create(:commit_status, pipeline: pipeline,
                             stage: 'deploy',
                             name: 'staging',
                             stage_idx: 2,
                             status: 'running')

      create(:commit_status, pipeline: pipeline,
                             stage: 'test',
                             name: 'rspec',
                             stage_idx: 1,
                             status: 'success')
    end

    describe '#stages' do
      subject { pipeline.stages }

      context 'stages list' do
        it 'returns ordered list of stages' do
          expect(subject.map(&:name)).to eq(%w[build test deploy])
        end
171 172
      end

173 174 175 176
      context 'stages with statuses' do
        let(:statuses) do
          subject.map { |stage| [stage.name, stage.status] }
        end
177

178
        it 'returns list of stages with correct statuses' do
D
Douwe Maan 已提交
179 180 181
          expect(statuses).to eq([%w(build failed),
                                  %w(test success),
                                  %w(deploy running)])
182
        end
K
Kamil Trzcinski 已提交
183

184 185 186 187 188 189 190 191 192 193
        context 'when commit status  is retried' do
          before do
            create(:commit_status, pipeline: pipeline,
                                   stage: 'build',
                                   name: 'mac',
                                   stage_idx: 0,
                                   status: 'success')
          end

          it 'ignores the previous state' do
D
Douwe Maan 已提交
194 195 196
            expect(statuses).to eq([%w(build success),
                                    %w(test success),
                                    %w(deploy running)])
197
          end
198 199
        end
      end
200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217

      context 'when there is a stage with warnings' do
        before do
          create(:commit_status, pipeline: pipeline,
                                 stage: 'deploy',
                                 name: 'prod:2',
                                 stage_idx: 2,
                                 status: 'failed',
                                 allow_failure: true)
        end

        it 'populates stage with correct number of warnings' do
          deploy_stage = pipeline.stages.third

          expect(deploy_stage).not_to receive(:statuses)
          expect(deploy_stage).to have_warnings
        end
      end
218
    end
219

220 221 222
    describe '#stages_count' do
      it 'returns a valid number of stages' do
        expect(pipeline.stages_count).to eq(3)
223
      end
224
    end
225

226 227
    describe '#stages_name' do
      it 'returns a valid names of stages' do
D
Douwe Maan 已提交
228
        expect(pipeline.stages_name).to eq(%w(build test deploy))
229
      end
K
Kamil Trzcinski 已提交
230 231 232
    end
  end

K
Kamil Trzcinski 已提交
233 234 235 236
  describe '#stage' do
    subject { pipeline.stage('test') }

    context 'with status in stage' do
K
Kamil Trzcinski 已提交
237 238
      before do
        create(:commit_status, pipeline: pipeline, stage: 'test')
K
Kamil Trzcinski 已提交
239
      end
K
Kamil Trzcinski 已提交
240

241 242 243
      it { expect(subject).to be_a Ci::Stage }
      it { expect(subject.name).to eq 'test' }
      it { expect(subject.statuses).not_to be_empty }
K
Kamil Trzcinski 已提交
244 245 246
    end

    context 'without status in stage' do
K
Kamil Trzcinski 已提交
247 248 249
      before do
        create(:commit_status, pipeline: pipeline, stage: 'build')
      end
K
Kamil Trzcinski 已提交
250 251 252 253 254 255 256

      it 'return stage object' do
        is_expected.to be_nil
      end
    end
  end

257
  describe 'state machine' do
258
    let(:current) { Time.now.change(usec: 0) }
K
Kamil Trzcinski 已提交
259 260 261
    let(:build) { create_build('build1', 0) }
    let(:build_b) { create_build('build2', 0) }
    let(:build_c) { create_build('build3', 0) }
K
Kamil Trzcinski 已提交
262

263 264
    describe '#duration' do
      before do
265
        travel_to(current + 30) do
K
Kamil Trzcinski 已提交
266 267 268 269
          build.run!
          build.success!
          build_b.run!
          build_c.run!
L
Lin Jen-Shin 已提交
270 271
        end

272
        travel_to(current + 40) do
K
Kamil Trzcinski 已提交
273
          build_b.drop!
274
        end
275

276
        travel_to(current + 70) do
K
Kamil Trzcinski 已提交
277
          build_c.success!
278
        end
279 280 281
      end

      it 'matches sum of builds duration' do
282 283
        pipeline.reload

284
        expect(pipeline.duration).to eq(40)
285
      end
K
Kamil Trzcinski 已提交
286 287
    end

288 289 290
    describe '#started_at' do
      it 'updates on transitioning to running' do
        build.run
K
Kamil Trzcinski 已提交
291

292 293 294
        expect(pipeline.reload.started_at).not_to be_nil
      end

295
      it 'does not update on transitioning to success' do
296 297 298
        build.success

        expect(pipeline.reload.started_at).to be_nil
K
Kamil Trzcinski 已提交
299 300 301
      end
    end

302 303 304
    describe '#finished_at' do
      it 'updates on transitioning to success' do
        build.success
K
Kamil Trzcinski 已提交
305

306
        expect(pipeline.reload.finished_at).not_to be_nil
K
Kamil Trzcinski 已提交
307 308
      end

309
      it 'does not update on transitioning to running' do
310 311 312
        build.run

        expect(pipeline.reload.finished_at).to be_nil
K
Kamil Trzcinski 已提交
313 314
      end
    end
315

316
    describe 'merge request metrics' do
317
      let(:project) { create(:project, :repository) }
318 319 320
      let(:pipeline) { FactoryGirl.create(:ci_empty_pipeline, status: 'created', project: project, ref: 'master', sha: project.repository.commit('master').id) }
      let!(:merge_request) { create(:merge_request, source_project: project, source_branch: pipeline.ref) }

321 322 323
      before do
        expect(PipelineMetricsWorker).to receive(:perform_async).with(pipeline.id)
      end
324

325 326 327
      context 'when transitioning to running' do
        it 'schedules metrics workers' do
          pipeline.run
328 329 330 331
        end
      end

      context 'when transitioning to success' do
332 333
        it 'schedules metrics workers' do
          pipeline.succeed
334 335 336
        end
      end
    end
337

338
    def create_build(name, queued_at = current, started_from = 0)
L
Lin Jen-Shin 已提交
339 340 341
      create(:ci_build,
             name: name,
             pipeline: pipeline,
342 343
             queued_at: queued_at,
             started_at: queued_at + started_from)
L
Lin Jen-Shin 已提交
344
    end
K
Kamil Trzcinski 已提交
345
  end
K
Kamil Trzcinski 已提交
346 347

  describe '#branch?' do
348
    subject { pipeline.branch? }
K
Kamil Trzcinski 已提交
349 350 351

    context 'is not a tag' do
      before do
352
        pipeline.tag = false
K
Kamil Trzcinski 已提交
353 354 355 356 357 358 359 360 361
      end

      it 'return true when tag is set to false' do
        is_expected.to be_truthy
      end
    end

    context 'is not a tag' do
      before do
362
        pipeline.tag = true
K
Kamil Trzcinski 已提交
363 364 365 366 367 368 369
      end

      it 'return false when tag is set to true' do
        is_expected.to be_falsey
      end
    end
  end
370

371
  context 'with non-empty project' do
372
    let(:project) { create(:project, :repository) }
373 374 375 376 377 378 379

    let(:pipeline) do
      create(:ci_pipeline,
             project: project,
             ref: project.default_branch,
             sha: project.commit.sha)
    end
380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400

    describe '#latest?' do
      context 'with latest sha' do
        it 'returns true' do
          expect(pipeline).to be_latest
        end
      end

      context 'with not latest sha' do
        before do
          pipeline.update(
            sha: project.commit("#{project.default_branch}~1").sha)
        end

        it 'returns false' do
          expect(pipeline).not_to be_latest
        end
      end
    end
  end

401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423
  describe '#manual_actions' do
    subject { pipeline.manual_actions }

    it 'when none defined' do
      is_expected.to be_empty
    end

    context 'when action defined' do
      let!(:manual) { create(:ci_build, :manual, pipeline: pipeline, name: 'deploy') }

      it 'returns one action' do
        is_expected.to contain_exactly(manual)
      end

      context 'there are multiple of the same name' do
        let!(:manual2) { create(:ci_build, :manual, pipeline: pipeline, name: 'deploy') }

        it 'returns latest one' do
          is_expected.to contain_exactly(manual2)
        end
      end
    end
  end
424

C
Connor Shea 已提交
425 426
  describe '#has_warnings?' do
    subject { pipeline.has_warnings? }
427 428 429

    context 'build which is allowed to fail fails' do
      before do
C
Connor Shea 已提交
430 431
        create :ci_build, :success, pipeline: pipeline, name: 'rspec'
        create :ci_build, :allowed_to_fail, :failed, pipeline: pipeline, name: 'rubocop'
432
      end
433

434 435 436 437 438 439 440
      it 'returns true' do
        is_expected.to be_truthy
      end
    end

    context 'build which is allowed to fail succeeds' do
      before do
C
Connor Shea 已提交
441 442
        create :ci_build, :success, pipeline: pipeline, name: 'rspec'
        create :ci_build, :allowed_to_fail, :success, pipeline: pipeline, name: 'rubocop'
443
      end
444

445 446 447 448
      it 'returns false' do
        is_expected.to be_falsey
      end
    end
C
Connor Shea 已提交
449 450 451 452 453 454 455 456 457 458 459 460

    context 'build is retried and succeeds' do
      before do
        create :ci_build, :success, pipeline: pipeline, name: 'rubocop'
        create :ci_build, :failed, pipeline: pipeline, name: 'rspec'
        create :ci_build, :success, pipeline: pipeline, name: 'rspec'
      end

      it 'returns false' do
        is_expected.to be_falsey
      end
    end
461
  end
462

463
  shared_context 'with some outdated pipelines' do
464 465 466 467 468 469 470 471 472 473 474 475 476
    before do
      create_pipeline(:canceled, 'ref', 'A')
      create_pipeline(:success, 'ref', 'A')
      create_pipeline(:failed, 'ref', 'B')
      create_pipeline(:skipped, 'feature', 'C')
    end

    def create_pipeline(status, ref, sha)
      create(:ci_empty_pipeline, status: status, ref: ref, sha: sha)
    end
  end

  describe '.latest' do
477
    include_context 'with some outdated pipelines'
478 479

    context 'when no ref is specified' do
480
      let(:pipelines) { described_class.latest.all }
481

482 483
      it 'returns the latest pipeline for the same ref and different sha' do
        expect(pipelines.map(&:sha)).to contain_exactly('A', 'B', 'C')
484 485
        expect(pipelines.map(&:status)).
          to contain_exactly('success', 'failed', 'skipped')
486 487 488 489
      end
    end

    context 'when ref is specified' do
490
      let(:pipelines) { described_class.latest('ref').all }
491

492 493
      it 'returns the latest pipeline for ref and different sha' do
        expect(pipelines.map(&:sha)).to contain_exactly('A', 'B')
494 495
        expect(pipelines.map(&:status)).
          to contain_exactly('success', 'failed')
496 497 498 499 500
      end
    end
  end

  describe '.latest_status' do
501
    include_context 'with some outdated pipelines'
502 503

    context 'when no ref is specified' do
504
      let(:latest_status) { described_class.latest_status }
505 506

      it 'returns the latest status for the same ref and different sha' do
507
        expect(latest_status).to eq(described_class.latest.status)
L
Lin Jen-Shin 已提交
508
        expect(latest_status).to eq('failed')
509 510 511 512
      end
    end

    context 'when ref is specified' do
513
      let(:latest_status) { described_class.latest_status('ref') }
514 515

      it 'returns the latest status for ref and different sha' do
516
        expect(latest_status).to eq(described_class.latest_status('ref'))
L
Lin Jen-Shin 已提交
517
        expect(latest_status).to eq('failed')
518 519 520 521
      end
    end
  end

522 523 524 525 526 527 528 529
  describe '.latest_successful_for' do
    include_context 'with some outdated pipelines'

    let!(:latest_successful_pipeline) do
      create_pipeline(:success, 'ref', 'D')
    end

    it 'returns the latest successful pipeline' do
530 531
      expect(described_class.latest_successful_for('ref')).
        to eq(latest_successful_pipeline)
532 533 534
    end
  end

535 536 537 538 539 540 541 542 543 544 545 546 547
  describe '.latest_successful_for_refs' do
    include_context 'with some outdated pipelines'

    let!(:latest_successful_pipeline1) { create_pipeline(:success, 'ref1', 'D') }
    let!(:latest_successful_pipeline2) { create_pipeline(:success, 'ref2', 'D') }

    it 'returns the latest successful pipeline for both refs' do
      refs = %w(ref1 ref2 ref3)

      expect(described_class.latest_successful_for_refs(refs)).to eq({ 'ref1' => latest_successful_pipeline1, 'ref2' => latest_successful_pipeline2 })
    end
  end

548
  describe '#status' do
549 550 551
    let(:build) do
      create(:ci_build, :created, pipeline: pipeline, name: 'test')
    end
552 553 554 555

    subject { pipeline.reload.status }

    context 'on queuing' do
556 557 558
      before do
        build.enqueue
      end
559 560 561 562 563 564

      it { is_expected.to eq('pending') }
    end

    context 'on run' do
      before do
565
        build.enqueue
566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592
        build.run
      end

      it { is_expected.to eq('running') }
    end

    context 'on drop' do
      before do
        build.drop
      end

      it { is_expected.to eq('failed') }
    end

    context 'on success' do
      before do
        build.success
      end

      it { is_expected.to eq('success') }
    end

    context 'on cancel' do
      before do
        build.cancel
      end

593 594 595 596 597 598 599
      context 'when build is pending' do
        let(:build) do
          create(:ci_build, :pending, pipeline: pipeline)
        end

        it { is_expected.to eq('canceled') }
      end
600
    end
601 602 603 604

    context 'on failure and build retry' do
      before do
        build.drop
605
        project.add_developer(user)
606 607

        Ci::Build.retry(build, user)
608 609 610 611 612 613 614 615
      end

      # We are changing a state: created > failed > running
      # Instead of: created > failed > pending
      # Since the pipeline already run, so it should not be pending anymore

      it { is_expected.to eq('running') }
    end
616
  end
617

618
  describe '#detailed_status' do
619 620
    subject { pipeline.detailed_status(user) }

621 622 623 624
    context 'when pipeline is created' do
      let(:pipeline) { create(:ci_pipeline, status: :created) }

      it 'returns detailed status for created pipeline' do
625
        expect(subject.text).to eq 'created'
626 627 628 629 630 631 632
      end
    end

    context 'when pipeline is pending' do
      let(:pipeline) { create(:ci_pipeline, status: :pending) }

      it 'returns detailed status for pending pipeline' do
633
        expect(subject.text).to eq 'pending'
634 635 636 637 638 639 640
      end
    end

    context 'when pipeline is running' do
      let(:pipeline) { create(:ci_pipeline, status: :running) }

      it 'returns detailed status for running pipeline' do
641
        expect(subject.text).to eq 'running'
642 643 644 645 646 647 648
      end
    end

    context 'when pipeline is successful' do
      let(:pipeline) { create(:ci_pipeline, status: :success) }

      it 'returns detailed status for successful pipeline' do
649
        expect(subject.text).to eq 'passed'
650 651 652 653 654 655 656
      end
    end

    context 'when pipeline is failed' do
      let(:pipeline) { create(:ci_pipeline, status: :failed) }

      it 'returns detailed status for failed pipeline' do
657
        expect(subject.text).to eq 'failed'
658 659 660 661 662 663 664
      end
    end

    context 'when pipeline is canceled' do
      let(:pipeline) { create(:ci_pipeline, status: :canceled) }

      it 'returns detailed status for canceled pipeline' do
665
        expect(subject.text).to eq 'canceled'
666 667 668 669 670 671 672
      end
    end

    context 'when pipeline is skipped' do
      let(:pipeline) { create(:ci_pipeline, status: :skipped) }

      it 'returns detailed status for skipped pipeline' do
673
        expect(subject.text).to eq 'skipped'
674 675 676
      end
    end

677 678 679 680
    context 'when pipeline is blocked' do
      let(:pipeline) { create(:ci_pipeline, status: :manual) }

      it 'returns detailed status for blocked pipeline' do
681
        expect(subject.text).to eq 'blocked'
682 683 684
      end
    end

685 686 687 688 689 690 691 692
    context 'when pipeline is successful but with warnings' do
      let(:pipeline) { create(:ci_pipeline, status: :success) }

      before do
        create(:ci_build, :allowed_to_fail, :failed, pipeline: pipeline)
      end

      it 'retruns detailed status for successful pipeline with warnings' do
693
        expect(subject.label).to eq 'passed with warnings'
694 695 696 697
      end
    end
  end

698
  describe '#cancelable?' do
699 700
    %i[created running pending].each do |status0|
      context "when there is a build #{status0}" do
701
        before do
702
          create(:ci_build, status0, pipeline: pipeline)
703 704
        end

705 706 707
        it 'is cancelable' do
          expect(pipeline.cancelable?).to be_truthy
        end
708 709
      end

710
      context "when there is an external job #{status0}" do
711
        before do
712
          create(:generic_commit_status, status0, pipeline: pipeline)
713 714
        end

715 716 717
        it 'is cancelable' do
          expect(pipeline.cancelable?).to be_truthy
        end
718
      end
719

720
      %i[success failed canceled].each do |status1|
721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750
        context "when there are generic_commit_status jobs for #{status0} and #{status1}" do
          before do
            create(:generic_commit_status, status0, pipeline: pipeline)
            create(:generic_commit_status, status1, pipeline: pipeline)
          end

          it 'is cancelable' do
            expect(pipeline.cancelable?).to be_truthy
          end
        end

        context "when there are generic_commit_status and ci_build jobs for #{status0} and #{status1}" do
          before do
            create(:generic_commit_status, status0, pipeline: pipeline)
            create(:ci_build, status1, pipeline: pipeline)
          end

          it 'is cancelable' do
            expect(pipeline.cancelable?).to be_truthy
          end
        end

        context "when there are ci_build jobs for #{status0} and #{status1}" do
          before do
            create(:ci_build, status0, pipeline: pipeline)
            create(:ci_build, status1, pipeline: pipeline)
          end

          it 'is cancelable' do
            expect(pipeline.cancelable?).to be_truthy
751
          end
752 753
        end
      end
754 755 756 757 758 759 760 761
    end

    %i[success failed canceled].each do |status|
      context "when there is a build #{status}" do
        before do
          create(:ci_build, status, pipeline: pipeline)
        end

762 763 764
        it 'is not cancelable' do
          expect(pipeline.cancelable?).to be_falsey
        end
765 766 767 768 769 770 771
      end

      context "when there is an external job #{status}" do
        before do
          create(:generic_commit_status, status, pipeline: pipeline)
        end

772 773 774
        it 'is not cancelable' do
          expect(pipeline.cancelable?).to be_falsey
        end
775 776 777 778
      end
    end
  end

779
  describe '#cancel_running' do
780 781
    let(:latest_status) { pipeline.statuses.pluck(:status) }

782
    context 'when there is a running external job and a regular job' do
783
      before do
784
        create(:ci_build, :running, pipeline: pipeline)
785 786 787 788 789 790
        create(:generic_commit_status, :running, pipeline: pipeline)

        pipeline.cancel_running
      end

      it 'cancels both jobs' do
791 792 793 794
        expect(latest_status).to contain_exactly('canceled', 'canceled')
      end
    end

795
    context 'when jobs are in different stages' do
796 797 798 799 800 801 802 803 804 805 806
      before do
        create(:ci_build, :running, stage_idx: 0, pipeline: pipeline)
        create(:ci_build, :running, stage_idx: 1, pipeline: pipeline)

        pipeline.cancel_running
      end

      it 'cancels both jobs' do
        expect(latest_status).to contain_exactly('canceled', 'canceled')
      end
    end
807 808 809 810 811 812 813 814 815

    context 'when there are created builds present in the pipeline' do
      before do
        create(:ci_build, :running, stage_idx: 0, pipeline: pipeline)
        create(:ci_build, :created, stage_idx: 1, pipeline: pipeline)

        pipeline.cancel_running
      end

816
      it 'cancels created builds' do
D
Douwe Maan 已提交
817
        expect(latest_status).to eq %w(canceled canceled)
818 819
      end
    end
820 821 822 823 824
  end

  describe '#retry_failed' do
    let(:latest_status) { pipeline.statuses.latest.pluck(:status) }

825
    before do
826
      project.add_developer(user)
827 828
    end

829 830 831 832 833
    context 'when there is a failed build and failed external status' do
      before do
        create(:ci_build, :failed, name: 'build', pipeline: pipeline)
        create(:generic_commit_status, :failed, name: 'jenkins', pipeline: pipeline)

834
        pipeline.retry_failed(user)
835 836 837 838 839 840 841 842 843 844 845 846
      end

      it 'retries only build' do
        expect(latest_status).to contain_exactly('pending', 'failed')
      end
    end

    context 'when builds are in different stages' do
      before do
        create(:ci_build, :failed, name: 'build', stage_idx: 0, pipeline: pipeline)
        create(:ci_build, :failed, name: 'jenkins', stage_idx: 1, pipeline: pipeline)

847
        pipeline.retry_failed(user)
848 849 850
      end

      it 'retries both builds' do
851
        expect(latest_status).to contain_exactly('pending', 'created')
852 853 854 855 856 857 858 859
      end
    end

    context 'when there are canceled and failed' do
      before do
        create(:ci_build, :failed, name: 'build', stage_idx: 0, pipeline: pipeline)
        create(:ci_build, :canceled, name: 'jenkins', stage_idx: 1, pipeline: pipeline)

860
        pipeline.retry_failed(user)
861 862 863
      end

      it 'retries both builds' do
864
        expect(latest_status).to contain_exactly('pending', 'created')
865 866 867 868
      end
    end
  end

869
  describe '#execute_hooks' do
L
Lin Jen-Shin 已提交
870 871
    let!(:build_a) { create_build('a', 0) }
    let!(:build_b) { create_build('b', 1) }
K
Kamil Trzcinski 已提交
872

873 874 875 876 877 878 879 880 881 882 883
    let!(:hook) do
      create(:project_hook, project: project, pipeline_events: enabled)
    end

    before do
      ProjectWebHookWorker.drain
    end

    context 'with pipeline hooks enabled' do
      let(:enabled) { true }

K
Kamil Trzcinski 已提交
884 885 886 887
      before do
        WebMock.stub_request(:post, hook.url)
      end

888
      context 'with multiple builds' do
K
Kamil Trzcinski 已提交
889 890
        context 'when build is queued' do
          before do
891 892
            build_a.enqueue
            build_b.enqueue
K
Kamil Trzcinski 已提交
893
          end
894

L
Lin Jen-Shin 已提交
895
          it 'receives a pending event once' do
896
            expect(WebMock).to have_requested_pipeline_hook('pending').once
K
Kamil Trzcinski 已提交
897 898
          end
        end
899

K
Kamil Trzcinski 已提交
900 901
        context 'when build is run' do
          before do
902
            build_a.enqueue
K
Kamil Trzcinski 已提交
903
            build_a.run
904
            build_b.enqueue
K
Kamil Trzcinski 已提交
905 906
            build_b.run
          end
907

L
Lin Jen-Shin 已提交
908
          it 'receives a running event once' do
909
            expect(WebMock).to have_requested_pipeline_hook('running').once
K
Kamil Trzcinski 已提交
910
          end
911 912
        end

K
Kamil Trzcinski 已提交
913 914 915
        context 'when all builds succeed' do
          before do
            build_a.success
K
Kamil Trzcinski 已提交
916 917 918

            # We have to reload build_b as this is in next stage and it gets triggered by PipelineProcessWorker
            build_b.reload.success
K
Kamil Trzcinski 已提交
919 920
          end

L
Lin Jen-Shin 已提交
921
          it 'receives a success event once' do
922
            expect(WebMock).to have_requested_pipeline_hook('success').once
K
Kamil Trzcinski 已提交
923
          end
924 925
        end

L
Lin Jen-Shin 已提交
926 927 928 929 930
        context 'when stage one failed' do
          before do
            build_a.drop
          end

L
Lin Jen-Shin 已提交
931
          it 'receives a failed event once' do
L
Lin Jen-Shin 已提交
932 933 934 935
            expect(WebMock).to have_requested_pipeline_hook('failed').once
          end
        end

936
        def have_requested_pipeline_hook(status)
K
Kamil Trzcinski 已提交
937
          have_requested(:post, hook.url).with do |req|
938 939 940 941
            json_body = JSON.parse(req.body)
            json_body['object_attributes']['status'] == status &&
              json_body['builds'].length == 2
          end
942
        end
943
      end
944 945 946 947 948
    end

    context 'with pipeline hooks disabled' do
      let(:enabled) { false }

K
Kamil Trzcinski 已提交
949
      before do
950 951
        build_a.enqueue
        build_b.enqueue
K
Kamil Trzcinski 已提交
952 953
      end

954 955 956 957
      it 'did not execute pipeline_hook after touched' do
        expect(WebMock).not_to have_requested(:post, hook.url)
      end
    end
K
Kamil Trzcinski 已提交
958

L
Lin Jen-Shin 已提交
959 960 961 962 963 964
    def create_build(name, stage_idx)
      create(:ci_build,
             :created,
             pipeline: pipeline,
             name: name,
             stage_idx: stage_idx)
K
Kamil Trzcinski 已提交
965
    end
966
  end
967 968

  describe "#merge_requests" do
969
    let(:project) { create(:project, :repository) }
970 971 972 973 974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 989 990
    let(:pipeline) { FactoryGirl.create(:ci_empty_pipeline, status: 'created', project: project, ref: 'master', sha: project.repository.commit('master').id) }

    it "returns merge requests whose `diff_head_sha` matches the pipeline's SHA" do
      merge_request = create(:merge_request, source_project: project, source_branch: pipeline.ref)

      expect(pipeline.merge_requests).to eq([merge_request])
    end

    it "doesn't return merge requests whose source branch doesn't match the pipeline's ref" do
      create(:merge_request, source_project: project, source_branch: 'feature', target_branch: 'master')

      expect(pipeline.merge_requests).to be_empty
    end

    it "doesn't return merge requests whose `diff_head_sha` doesn't match the pipeline's SHA" do
      create(:merge_request, source_project: project, source_branch: pipeline.ref)
      allow_any_instance_of(MergeRequest).to receive(:diff_head_sha) { '97de212e80737a608d939f648d959671fb0a0142b' }

      expect(pipeline.merge_requests).to be_empty
    end
  end
991

992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033
  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

1034 1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 1045 1046
  describe '#update_status' do
    let(:pipeline) { create(:ci_pipeline, sha: '123456') }

    it 'updates the cached status' do
      fake_status = double
      # after updating the status, the status is set to `skipped` for this pipeline's builds
      expect(Ci::PipelineStatus).to receive(:new).with(pipeline.project, sha: '123456', status: 'skipped').and_return(fake_status)
      expect(fake_status).to receive(:store_in_cache_if_needed)

      pipeline.update_status
    end
  end

1047
  describe 'notifications when pipeline success or failed' do
1048
    let(:project) { create(:project, :repository) }
L
Lin Jen-Shin 已提交
1049 1050 1051 1052 1053 1054 1055 1056

    let(:pipeline) do
      create(:ci_pipeline,
             project: project,
             sha: project.commit('master').sha,
             user: create(:user))
    end

1057
    before do
1058 1059
      project.team << [pipeline.user, Gitlab::Access::DEVELOPER]

S
Sean McGivern 已提交
1060 1061 1062 1063 1064
      pipeline.user.global_notification_setting.
        update(level: 'custom', failed_pipeline: true, success_pipeline: true)

      reset_delivered_emails!

1065 1066 1067 1068
      perform_enqueued_jobs do
        pipeline.enqueue
        pipeline.run
      end
1069 1070 1071 1072
    end

    shared_examples 'sending a notification' do
      it 'sends an email' do
1073
        should_only_email(pipeline.user, kind: :bcc)
1074 1075 1076 1077 1078
      end
    end

    shared_examples 'not sending any notification' do
      it 'does not send any email' do
1079
        should_not_email_anyone
1080 1081 1082 1083 1084 1085
      end
    end

    context 'with success pipeline' do
      before do
        perform_enqueued_jobs do
L
Lin Jen-Shin 已提交
1086
          pipeline.succeed
1087 1088
        end
      end
L
Lin Jen-Shin 已提交
1089 1090

      it_behaves_like 'sending a notification'
1091 1092 1093 1094 1095
    end

    context 'with failed pipeline' do
      before do
        perform_enqueued_jobs do
1096 1097
          create(:ci_build, :failed, pipeline: pipeline)
          create(:generic_commit_status, :failed, pipeline: pipeline)
1098

1099
          pipeline.drop
1100 1101
        end
      end
L
Lin Jen-Shin 已提交
1102 1103

      it_behaves_like 'sending a notification'
1104 1105 1106 1107 1108 1109 1110 1111
    end

    context 'with skipped pipeline' do
      before do
        perform_enqueued_jobs do
          pipeline.skip
        end
      end
L
Lin Jen-Shin 已提交
1112 1113

      it_behaves_like 'not sending any notification'
1114 1115 1116 1117 1118 1119 1120 1121
    end

    context 'with cancelled pipeline' do
      before do
        perform_enqueued_jobs do
          pipeline.cancel
        end
      end
L
Lin Jen-Shin 已提交
1122 1123

      it_behaves_like 'not sending any notification'
1124 1125
    end
  end
D
Douwe Maan 已提交
1126
end