Read about our upcoming Code of Conduct on this issue

pipeline_spec.rb 36.8 KB
Newer Older
Douwe Maan's avatar
Douwe Maan committed
1
2
require 'spec_helper'

3
describe Ci::Pipeline, :mailer do
4
  let(:user) { create(:user) }
5
  set(:project) { create(:project) }
6
7
8
9

  let(:pipeline) do
    create(:ci_empty_pipeline, status: :created, project: project)
  end
Douwe Maan's avatar
Douwe Maan committed
10

11
  it { is_expected.to belong_to(:project) }
12
  it { is_expected.to belong_to(:user) }
13
  it { is_expected.to belong_to(:auto_canceled_by) }
14
  it { is_expected.to belong_to(:pipeline_schedule) }
15

16
  it { is_expected.to have_many(:statuses) }
Kamil Trzcinski's avatar
Kamil Trzcinski committed
17
  it { is_expected.to have_many(:trigger_requests) }
Shinya Maeda's avatar
init    
Shinya Maeda committed
18
  it { is_expected.to have_many(:variables) }
Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
19
  it { is_expected.to have_many(:builds) }
20
  it { is_expected.to have_many(:auto_canceled_pipelines) }
21
  it { is_expected.to have_many(:auto_canceled_jobs) }
22

23
24
  it { is_expected.to validate_presence_of(:sha) }
  it { is_expected.to validate_presence_of(:status) }
Douwe Maan's avatar
Douwe Maan committed
25

Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
26
27
28
  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 }
Douwe Maan's avatar
Douwe Maan committed
29

30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
  describe '#source' do
    context 'when creating new pipeline' do
      let(:pipeline) do
        build(:ci_empty_pipeline, status: :created, project: project, source: nil)
      end

      it "prevents from creating an object" do
        expect(pipeline).not_to be_valid
      end
    end

    context 'when updating existing pipeline' do
      before do
        pipeline.update_attribute(:source, nil)
      end

      it "object is valid" do
        expect(pipeline).to be_valid
      end
    end
  end

52
53
54
55
56
57
58
59
  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

60
  describe '#valid_commit_sha' do
Douwe Maan's avatar
Douwe Maan committed
61
62
    context 'commit.sha can not start with 00000000' do
      before do
63
64
        pipeline.sha = '0' * 40
        pipeline.valid_commit_sha
Douwe Maan's avatar
Douwe Maan committed
65
66
      end

67
      it('commit errors should not be empty') { expect(pipeline.errors).not_to be_empty }
Douwe Maan's avatar
Douwe Maan committed
68
69
70
    end
  end

71
  describe '#short_sha' do
72
    subject { pipeline.short_sha }
Douwe Maan's avatar
Douwe Maan committed
73

Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
74
75
76
    it 'has 8 items' do
      expect(subject.size).to eq(8)
    end
77
    it { expect(pipeline.sha).to start_with(subject) }
Douwe Maan's avatar
Douwe Maan committed
78
79
  end

80
  describe '#retried' do
81
    subject { pipeline.retried }
Kamil Trzcinski's avatar
Kamil Trzcinski committed
82
83

    before do
84
85
      @build1 = create(:ci_build, pipeline: pipeline, name: 'deploy', retried: true)
      @build2 = create(:ci_build, pipeline: pipeline, name: 'deploy')
Kamil Trzcinski's avatar
Kamil Trzcinski committed
86
87
88
    end

    it 'returns old builds' do
89
      is_expected.to contain_exactly(@build1)
Kamil Trzcinski's avatar
Kamil Trzcinski committed
90
91
92
    end
  end

Douwe Maan's avatar
Douwe Maan committed
93
  describe "coverage" do
94
    let(:project) { create(:project, build_coverage_regex: "/.*/") }
95
    let(:pipeline) { create(:ci_empty_pipeline, project: project) }
Douwe Maan's avatar
Douwe Maan committed
96
97

    it "calculates average when there are two builds with coverage" do
98
99
      create(:ci_build, name: "rspec", coverage: 30, pipeline: pipeline)
      create(:ci_build, name: "rubocop", coverage: 40, pipeline: pipeline)
100
      expect(pipeline.coverage).to eq("35.00")
Douwe Maan's avatar
Douwe Maan committed
101
102
103
    end

    it "calculates average when there are two builds with coverage and one with nil" do
104
105
106
      create(:ci_build, name: "rspec", coverage: 30, pipeline: pipeline)
      create(:ci_build, name: "rubocop", coverage: 40, pipeline: pipeline)
      create(:ci_build, pipeline: pipeline)
107
      expect(pipeline.coverage).to eq("35.00")
Douwe Maan's avatar
Douwe Maan committed
108
109
110
    end

    it "calculates average when there are two builds with coverage and one is retried" do
111
112
113
      create(:ci_build, name: "rspec", coverage: 30, pipeline: pipeline)
      create(:ci_build, name: "rubocop", coverage: 30, pipeline: pipeline, retried: true)
      create(:ci_build, name: "rubocop", coverage: 40, pipeline: pipeline)
114
      expect(pipeline.coverage).to eq("35.00")
Douwe Maan's avatar
Douwe Maan committed
115
116
117
    end

    it "calculates average when there is one build without coverage" do
118
      FactoryGirl.create(:ci_build, pipeline: pipeline)
119
      expect(pipeline.coverage).to be_nil
Douwe Maan's avatar
Douwe Maan committed
120
121
    end
  end
Kamil Trzcinski's avatar
Kamil Trzcinski committed
122
123

  describe '#retryable?' do
124
    subject { pipeline.retryable? }
Kamil Trzcinski's avatar
Kamil Trzcinski committed
125
126
127

    context 'no failed builds' do
      before do
128
        create_build('rspec', 'success')
Kamil Trzcinski's avatar
Kamil Trzcinski committed
129
130
      end

131
      it 'is not retryable' do
Kamil Trzcinski's avatar
Kamil Trzcinski committed
132
133
        is_expected.to be_falsey
      end
134
135
136
137
138
139
140
141
142
143

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

        it 'is retryable' do
          is_expected.to be_truthy
        end
      end
Kamil Trzcinski's avatar
Kamil Trzcinski committed
144
145
146
147
    end

    context 'with failed builds' do
      before do
148
149
        create_build('rspec', 'running')
        create_build('rubocop', 'failed')
Kamil Trzcinski's avatar
Kamil Trzcinski committed
150
151
      end

152
      it 'is retryable' do
Kamil Trzcinski's avatar
Kamil Trzcinski committed
153
154
155
        is_expected.to be_truthy
      end
    end
156
157
158
159

    def create_build(name, status)
      create(:ci_build, name: name, status: status, pipeline: pipeline)
    end
Kamil Trzcinski's avatar
Kamil Trzcinski committed
160
161
  end

162
163
164
165
166
167
168
169
170
171
172
173
  describe '#predefined_variables' do
    subject { pipeline.predefined_variables }

    it { is_expected.to be_an(Array) }

    it 'includes the defined keys' do
      keys = subject.map { |v| v[:key] }

      expect(keys).to include('CI_PIPELINE_ID', 'CI_CONFIG_PATH', 'CI_PIPELINE_SOURCE')
    end
  end

174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
  describe '#auto_canceled?' do
    subject { pipeline.auto_canceled? }

    context 'when it is canceled' do
      before do
        pipeline.cancel
      end

      context 'when there is auto_canceled_by' do
        before do
          pipeline.update(auto_canceled_by: create(:ci_empty_pipeline))
        end

        it 'is auto canceled' do
          is_expected.to be_truthy
        end
      end

      context 'when there is no auto_canceled_by' do
        it 'is not auto canceled' do
          is_expected.to be_falsey
        end
      end

      context 'when it is retried and canceled manually' do
        before do
          pipeline.enqueue
          pipeline.cancel
        end

        it 'is not auto canceled' do
          is_expected.to be_falsey
        end
      end
    end
  end

211
  describe 'pipeline stages' do
Kamil Trzcinski's avatar
Kamil Trzcinski committed
212
    before do
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
      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

238
239
240
241
242
243
    describe '#stage_seeds' do
      let(:pipeline) do
        create(:ci_pipeline, config: { rspec: { script: 'rake' } })
      end

      it 'returns preseeded stage seeds object' do
244
245
        expect(pipeline.stage_seeds).to all(be_a Gitlab::Ci::Stage::Seed)
        expect(pipeline.stage_seeds.count).to eq 1
246
247
248
      end
    end

249
250
    describe '#legacy_stages' do
      subject { pipeline.legacy_stages }
251
252
253
254
255

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

258
259
260
261
      context 'stages with statuses' do
        let(:statuses) do
          subject.map { |stage| [stage.name, stage.status] }
        end
262

263
        it 'returns list of stages with correct statuses' do
Douwe Maan's avatar
Douwe Maan committed
264
265
266
          expect(statuses).to eq([%w(build failed),
                                  %w(test success),
                                  %w(deploy running)])
267
        end
Kamil Trzcinski's avatar
Kamil Trzcinski committed
268

269
        context 'when commit status is retried' do
270
271
272
273
274
275
          before do
            create(:commit_status, pipeline: pipeline,
                                   stage: 'build',
                                   name: 'mac',
                                   stage_idx: 0,
                                   status: 'success')
276
277

            pipeline.process!
278
279
280
          end

          it 'ignores the previous state' do
Douwe Maan's avatar
Douwe Maan committed
281
282
283
            expect(statuses).to eq([%w(build success),
                                    %w(test success),
                                    %w(deploy running)])
284
          end
285
286
        end
      end
287
288
289
290
291
292
293
294
295
296
297
298

      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
299
          deploy_stage = pipeline.legacy_stages.third
300
301
302
303
304

          expect(deploy_stage).not_to receive(:statuses)
          expect(deploy_stage).to have_warnings
        end
      end
305
    end
306

307
308
309
    describe '#stages_count' do
      it 'returns a valid number of stages' do
        expect(pipeline.stages_count).to eq(3)
310
      end
311
    end
312

313
    describe '#stages_names' do
314
      it 'returns a valid names of stages' do
315
        expect(pipeline.stages_names).to eq(%w(build test deploy))
316
      end
Kamil Trzcinski's avatar
Kamil Trzcinski committed
317
318
319
    end
  end

320
321
  describe '#legacy_stage' do
    subject { pipeline.legacy_stage('test') }
322
323

    context 'with status in stage' do
Kamil Trzcinski's avatar
Kamil Trzcinski committed
324
325
      before do
        create(:commit_status, pipeline: pipeline, stage: 'test')
326
      end
Kamil Trzcinski's avatar
Kamil Trzcinski committed
327

328
      it { expect(subject).to be_a Ci::LegacyStage }
329
330
      it { expect(subject.name).to eq 'test' }
      it { expect(subject.statuses).not_to be_empty }
331
332
333
    end

    context 'without status in stage' do
Kamil Trzcinski's avatar
Kamil Trzcinski committed
334
335
336
      before do
        create(:commit_status, pipeline: pipeline, stage: 'build')
      end
337
338
339
340
341
342
343

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

344
  describe 'state machine' do
345
    let(:current) { Time.now.change(usec: 0) }
346
347
348
    let(:build) { create_build('build1', queued_at: 0) }
    let(:build_b) { create_build('build2', queued_at: 0) }
    let(:build_c) { create_build('build3', queued_at: 0) }
Kamil Trzcinski's avatar
Kamil Trzcinski committed
349

350
    describe '#duration' do
351
352
353
354
355
356
357
358
      context 'when multiple builds are finished' do
        before do
          travel_to(current + 30) do
            build.run!
            build.success!
            build_b.run!
            build_c.run!
          end
Lin Jen-Shin's avatar
Lin Jen-Shin committed
359

360
361
362
363
364
365
366
          travel_to(current + 40) do
            build_b.drop!
          end

          travel_to(current + 70) do
            build_c.success!
          end
367
        end
368

369
370
371
372
        it 'matches sum of builds duration' do
          pipeline.reload

          expect(pipeline.duration).to eq(40)
373
        end
374
375
      end

376
377
      context 'when pipeline becomes blocked' do
        let!(:build) { create_build('build:1') }
378
        let!(:action) { create_build('manual:action', :manual) }
379

380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
        before do
          travel_to(current + 1.minute) do
            build.run!
          end

          travel_to(current + 5.minutes) do
            build.success!
          end
        end

        it 'recalculates pipeline duration' do
          pipeline.reload

          expect(pipeline).to be_manual
          expect(pipeline.duration).to eq 4.minutes
        end
396
      end
Kamil Trzcinski's avatar
Kamil Trzcinski committed
397
398
    end

399
400
401
    describe '#started_at' do
      it 'updates on transitioning to running' do
        build.run
Kamil Trzcinski's avatar
Kamil Trzcinski committed
402

403
404
405
        expect(pipeline.reload.started_at).not_to be_nil
      end

406
      it 'does not update on transitioning to success' do
407
408
409
        build.success

        expect(pipeline.reload.started_at).to be_nil
Kamil Trzcinski's avatar
Kamil Trzcinski committed
410
411
412
      end
    end

413
414
415
    describe '#finished_at' do
      it 'updates on transitioning to success' do
        build.success
Kamil Trzcinski's avatar
Kamil Trzcinski committed
416

417
        expect(pipeline.reload.finished_at).not_to be_nil
Kamil Trzcinski's avatar
Kamil Trzcinski committed
418
419
      end

420
      it 'does not update on transitioning to running' do
421
422
423
        build.run

        expect(pipeline.reload.finished_at).to be_nil
Kamil Trzcinski's avatar
Kamil Trzcinski committed
424
425
      end
    end
426

427
    describe 'merge request metrics' do
428
      let(:project) { create(:project, :repository) }
429
430
431
      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) }

432
433
434
      before do
        expect(PipelineMetricsWorker).to receive(:perform_async).with(pipeline.id)
      end
435

436
437
438
      context 'when transitioning to running' do
        it 'schedules metrics workers' do
          pipeline.run
439
440
441
442
        end
      end

      context 'when transitioning to success' do
443
444
        it 'schedules metrics workers' do
          pipeline.succeed
445
446
447
        end
      end
    end
448

449
    describe 'pipeline caching' do
450
451
      it 'performs ExpirePipelinesCacheWorker' do
        expect(ExpirePipelineCacheWorker).to receive(:perform_async).with(pipeline.id)
452
453
454
455
456

        pipeline.cancel
      end
    end

457
458
    def create_build(name, *traits, queued_at: current, started_from: 0, **opts)
      create(:ci_build, *traits,
Lin Jen-Shin's avatar
Lin Jen-Shin committed
459
460
             name: name,
             pipeline: pipeline,
461
             queued_at: queued_at,
462
463
             started_at: queued_at + started_from,
             **opts)
Lin Jen-Shin's avatar
Lin Jen-Shin committed
464
    end
Kamil Trzcinski's avatar
Kamil Trzcinski committed
465
  end
Kamil Trzcinski's avatar
Kamil Trzcinski committed
466
467

  describe '#branch?' do
468
    subject { pipeline.branch? }
Kamil Trzcinski's avatar
Kamil Trzcinski committed
469
470
471

    context 'is not a tag' do
      before do
472
        pipeline.tag = false
Kamil Trzcinski's avatar
Kamil Trzcinski committed
473
474
475
476
477
478
479
480
481
      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
482
        pipeline.tag = true
Kamil Trzcinski's avatar
Kamil Trzcinski committed
483
484
485
486
487
488
489
      end

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

491
  context 'with non-empty project' do
492
    let(:project) { create(:project, :repository) }
493
494
495
496
497
498
499

    let(:pipeline) do
      create(:ci_pipeline,
             project: project,
             ref: project.default_branch,
             sha: project.commit.sha)
    end
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520

    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

521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
  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') }

538
539
540
541
        before do
          manual.update(retried: true)
        end

542
543
544
545
546
547
        it 'returns latest one' do
          is_expected.to contain_exactly(manual2)
        end
      end
    end
  end
548

549
  describe '#has_stage_seeds?' do
550
    context 'when pipeline has stage seeds' do
551
      subject { build(:ci_pipeline_with_one_job) }
552

553
      it { is_expected.to have_stage_seeds }
554
555
    end

556
    context 'when pipeline does not have stage seeds' do
557
558
      subject { create(:ci_pipeline_without_jobs) }

559
      it { is_expected.not_to have_stage_seeds }
560
561
562
    end
  end

Connor Shea's avatar
Connor Shea committed
563
564
  describe '#has_warnings?' do
    subject { pipeline.has_warnings? }
565
566
567

    context 'build which is allowed to fail fails' do
      before do
Connor Shea's avatar
Connor Shea committed
568
569
        create :ci_build, :success, pipeline: pipeline, name: 'rspec'
        create :ci_build, :allowed_to_fail, :failed, pipeline: pipeline, name: 'rubocop'
570
      end
571

572
573
574
575
576
577
578
      it 'returns true' do
        is_expected.to be_truthy
      end
    end

    context 'build which is allowed to fail succeeds' do
      before do
Connor Shea's avatar
Connor Shea committed
579
580
        create :ci_build, :success, pipeline: pipeline, name: 'rspec'
        create :ci_build, :allowed_to_fail, :success, pipeline: pipeline, name: 'rubocop'
581
      end
582

583
584
585
586
      it 'returns false' do
        is_expected.to be_falsey
      end
    end
Connor Shea's avatar
Connor Shea committed
587
588
589
590
591
592
593
594
595
596
597
598

    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
599
  end
600

601
  shared_context 'with some outdated pipelines' do
602
603
604
605
606
607
608
609
610
611
612
613
614
    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
615
    include_context 'with some outdated pipelines'
616
617

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

620
621
      it 'returns the latest pipeline for the same ref and different sha' do
        expect(pipelines.map(&:sha)).to contain_exactly('A', 'B', 'C')
622
623
        expect(pipelines.map(&:status))
          .to contain_exactly('success', 'failed', 'skipped')
624
625
626
627
      end
    end

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

630
631
      it 'returns the latest pipeline for ref and different sha' do
        expect(pipelines.map(&:sha)).to contain_exactly('A', 'B')
632
633
        expect(pipelines.map(&:status))
          .to contain_exactly('success', 'failed')
634
635
636
637
638
      end
    end
  end

  describe '.latest_status' do
639
    include_context 'with some outdated pipelines'
640
641

    context 'when no ref is specified' do
642
      let(:latest_status) { described_class.latest_status }
643
644

      it 'returns the latest status for the same ref and different sha' do
645
        expect(latest_status).to eq(described_class.latest.status)
646
        expect(latest_status).to eq('failed')
647
648
649
650
      end
    end

    context 'when ref is specified' do
651
      let(:latest_status) { described_class.latest_status('ref') }
652
653

      it 'returns the latest status for ref and different sha' do
654
        expect(latest_status).to eq(described_class.latest_status('ref'))
655
        expect(latest_status).to eq('failed')
656
657
658
659
      end
    end
  end

660
661
662
663
664
665
666
667
  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
668
669
      expect(described_class.latest_successful_for('ref'))
        .to eq(latest_successful_pipeline)
670
671
672
    end
  end

673
674
675
676
677
678
679
680
681
682
683
684
685
  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

686
687
688
689
690
691
  describe '.internal_sources' do
    subject { described_class.internal_sources }

    it { is_expected.to be_an(Array) }
  end

692
  describe '#status' do
693
694
695
    let(:build) do
      create(:ci_build, :created, pipeline: pipeline, name: 'test')
    end
696
697
698
699

    subject { pipeline.reload.status }

    context 'on queuing' do
700
701
702
      before do
        build.enqueue
      end
703
704
705
706
707
708

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

    context 'on run' do
      before do
709
        build.enqueue
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
        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

737
738
739
740
741
742
743
      context 'when build is pending' do
        let(:build) do
          create(:ci_build, :pending, pipeline: pipeline)
        end

        it { is_expected.to eq('canceled') }
      end
744
    end
745
746
747

    context 'on failure and build retry' do
      before do
Shinya Maeda's avatar
Shinya Maeda committed
748
749
        stub_not_protect_default_branch

750
        build.drop
751
        project.add_developer(user)
752
753

        Ci::Build.retry(build, user)
754
755
756
757
758
759
760
761
      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
762
  end
763

764
765
766
767
  describe '#ci_yaml_file_path' do
    subject { pipeline.ci_yaml_file_path }

    it 'returns the path from project' do
768
      allow(pipeline.project).to receive(:ci_config_path) { 'custom/path' }
769
770
771
772
773

      is_expected.to eq('custom/path')
    end

    it 'returns default when custom path is nil' do
774
      allow(pipeline.project).to receive(:ci_config_path) { nil }
775
776
777
778
779

      is_expected.to eq('.gitlab-ci.yml')
    end

    it 'returns default when custom path is empty' do
780
      allow(pipeline.project).to receive(:ci_config_path) { '' }
781
782
783
784
785

      is_expected.to eq('.gitlab-ci.yml')
    end
  end

786
  describe '#ci_yaml_file' do
787
    let(:implied_yml) { Gitlab::Template::GitlabCiYmlTemplate.find('Auto-DevOps').content }
788

789
790
791
792
    context 'when AutoDevops is enabled' do
      it 'returns the configuration if found' do
        allow(pipeline.project.repository).to receive(:gitlab_ci_yml_for)
          .and_return('config')
793

794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
        expect(pipeline.ci_yaml_file).to be_a(String)
        expect(pipeline.ci_yaml_file).not_to eq(implied_yml)
        expect(pipeline.yaml_errors).to be_nil
      end

      it 'returns the implied configuration when its not found' do
        allow_any_instance_of(ApplicationSetting)
          .to receive(:auto_devops_enabled?) { true }
        allow(pipeline.project).to receive(:ci_config_path) { 'custom' }

        expect(pipeline.ci_yaml_file).to eq(implied_yml)
      end
    end

    context 'when AudoDevOps is disabled' do
      context 'when an invalid path is given' do
        it 'sets the yaml errors' do
          allow(pipeline.project).to receive(:ci_config_path) { 'custom' }

          expect(pipeline.ci_yaml_file).to be_nil
          expect(pipeline.yaml_errors)
            .to start_with('Failed to load CI/CD config file')
        end
      end

      context 'when the config file can be found' do
        it 'has no yaml_errors' do
          allow(pipeline.project.repository).to receive(:gitlab_ci_yml_for)
            .and_return('config')

          expect(pipeline.ci_yaml_file).to eq('config')
          expect(pipeline.yaml_errors).to be_nil
        end
      end
828
829
830
    end
  end

831
  describe '#detailed_status' do
832
833
    subject { pipeline.detailed_status(user) }

834
835
836
837
    context 'when pipeline is created' do
      let(:pipeline) { create(:ci_pipeline, status: :created) }

      it 'returns detailed status for created pipeline' do
838
        expect(subject.text).to eq 'created'
839
840
841
842
843
844
845
      end
    end

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

      it 'returns detailed status for pending pipeline' do
846
        expect(subject.text).to eq 'pending'
847
848
849
850
851
852
853
      end
    end

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

      it 'returns detailed status for running pipeline' do
854
        expect(subject.text).to eq 'running'
855
856
857
858
859
860
861
      end
    end

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

      it 'returns detailed status for successful pipeline' do
862
        expect(subject.text).to eq 'passed'
863
864
865
866
867
868
869
      end
    end

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

      it 'returns detailed status for failed pipeline' do
870
        expect(subject.text).to eq 'failed'
871
872
873
874
875
876
877
      end
    end

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

      it 'returns detailed status for canceled pipeline' do
878
        expect(subject.text).to eq 'canceled'
879
880
881
882
883
884
885
      end
    end

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

      it 'returns detailed status for skipped pipeline' do
886
        expect(subject.text).to eq 'skipped'
887
888
889
      end
    end

890
891
892
893
    context 'when pipeline is blocked' do
      let(:pipeline) { create(:ci_pipeline, status: :manual) }

      it 'returns detailed status for blocked pipeline' do
894
        expect(subject.text).to eq 'blocked'
895
896
897
      end
    end

898
899
900
901
902
903
904
905
    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
906
        expect(subject.label).to eq 'passed with warnings'
907
908
909
910
      end
    end
  end

911
  describe '#cancelable?' do
912
913
    %i[created running pending].each do |status0|
      context "when there is a build #{status0}" do
914
        before do
915
          create(:ci_build, status0, pipeline: pipeline)
916
917
        end

918
919
920
        it 'is cancelable' do
          expect(pipeline.cancelable?).to be_truthy
        end
921
922
      end

923
      context "when there is an external job #{status0}" do
924
        before do
925
          create(:generic_commit_status, status0, pipeline: pipeline)
926
927
        end

928
929
930
        it 'is cancelable' do
          expect(pipeline.cancelable?).to be_truthy
        end
931
      end
932

933
      %i[success failed canceled].each do |status1|
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
        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
964
          end
965
966
        end
      end
967
968
969
970
971
972
973
974
    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

975
976
977
        it 'is not cancelable' do
          expect(pipeline.cancelable?).to be_falsey
        end
978
979
980
981
982
983
984
      end

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

985
986
987
        it 'is not cancelable' do
          expect(pipeline.cancelable?).to be_falsey
        end
988
989
      end
    end
990
991
992
993
994
995
996
997
998
999

    context 'when there is a manual action present in the pipeline' do
      before do
        create(:ci_build, :manual, pipeline: pipeline)
      end

      it 'is not cancelable' do
        expect(pipeline).not_to be_cancelable
      end
    end
1000
1001
  end

1002
  describe '#cancel_running' do
1003
1004
    let(:latest_status) { pipeline.statuses.pluck(:status) }

1005
    context 'when there is a running external job and a regular job' do
1006
      before do
1007
        create(:ci_build, :running, pipeline: pipeline)
1008
1009
1010
1011
1012
1013
        create(:generic_commit_status, :running, pipeline: pipeline)

        pipeline.cancel_running
      end

      it 'cancels both jobs' do
1014
1015
1016
1017
        expect(latest_status).to contain_exactly('canceled', 'canceled')
      end
    end

1018
    context 'when jobs are in different stages' do
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
      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
1030
1031
1032
1033
1034
1035
1036
1037
1038

    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

1039
      it 'cancels created builds' do
Douwe Maan's avatar
Douwe Maan committed
1040
        expect(latest_status).to eq %w(canceled canceled)
1041
1042
      end
    end
1043
1044
1045
1046
1047
  end

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

1048
    before do
Shinya Maeda's avatar
Shinya Maeda committed
1049
1050
      stub_not_protect_default_branch

1051
      project.add_developer(user)
1052
1053
    end

1054
1055
1056
1057
1058
    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)

1059
        pipeline.retry_failed(user)
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
      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)

1072
        pipeline.retry_failed(user)
1073
1074
1075
      end

      it 'retries both builds' do
1076
        expect(latest_status).to contain_exactly('pending', 'created')
1077
1078
1079
1080
1081
1082
1083
1084
      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)

1085
        pipeline.retry_failed(user)
1086
1087
1088
      end

      it 'retries both builds' do
1089
        expect(latest_status).to contain_exactly('pending', 'created')
1090
1091
1092
1093
      end
    end
  end

1094
  describe '#execute_hooks' do
Lin Jen-Shin's avatar
Lin Jen-Shin committed
1095
1096
    let!(:build_a) { create_build('a', 0) }
    let!(:build_b) { create_build('b', 1) }
1097

1098
1099
1100
1101
1102
    let!(:hook) do
      create(:project_hook, project: project, pipeline_events: enabled)
    end

    before do
Alexander Randa's avatar
Alexander Randa committed
1103
      WebHookWorker.drain
1104
1105
1106
1107
1108
    end

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

1109
1110
1111
1112
      before do
        WebMock.stub_request(:post, hook.url)
      end

1113
      context 'with multiple builds' do
1114
1115
        context 'when build is queued' do
          before do
1116
1117
            build_a.enqueue
            build_b.enqueue
1118
          end
1119

Lin Jen-Shin's avatar
Lin Jen-Shin committed
1120
          it 'receives a pending event once' do
1121
            expect(WebMock).to have_requested_pipeline_hook('pending').once
1122
1123
          end
        end
1124

1125
1126
        context 'when build is run' do
          before do
1127
            build_a.enqueue
1128
            build_a.run
1129
            build_b.enqueue
1130
1131
            build_b.run
          end
1132

Lin Jen-Shin's avatar
Lin Jen-Shin committed
1133
          it 'receives a running event once' do
1134
            expect(WebMock).to have_requested_pipeline_hook('running').once
1135
          end
1136
1137
        end

1138
1139
1140
        context 'when all builds succeed' do
          before do
            build_a.success
Kamil Trzcinski's avatar
Kamil Trzcinski committed
1141
1142
1143

            # We have to reload build_b as this is in next stage and it gets triggered by PipelineProcessWorker
            build_b.reload.success
1144
1145
          end

Lin Jen-Shin's avatar
Lin Jen-Shin committed
1146
          it 'receives a success event once' do
1147
            expect(WebMock).to have_requested_pipeline_hook('success').once
1148
          end
1149
1150
        end

Lin Jen-Shin's avatar
Lin Jen-Shin committed
1151
1152
1153
1154
1155
        context 'when stage one failed' do
          before do
            build_a.drop
          end

Lin Jen-Shin's avatar
Lin Jen-Shin committed
1156
          it 'receives a failed event once' do
Lin Jen-Shin's avatar
Lin Jen-Shin committed
1157
1158
1159
1160
            expect(WebMock).to have_requested_pipeline_hook('failed').once
          end
        end

1161
        def have_requested_pipeline_hook(status)
1162
          have_requested(:post, hook.url).with do |req|
1163
1164
1165
1166
            json_body = JSON.parse(req.body)
            json_body['object_attributes']['status'] == status &&
              json_body['builds'].length == 2
          end
1167
        end
1168
      end
1169
1170
1171
1172
1173
    end

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

1174
      before do
1175
1176
        build_a.enqueue
        build_b.enqueue
1177
1178
      end

1179
1180
1181
1182
      it 'did not execute pipeline_hook after touched' do
        expect(WebMock).not_to have_requested(:post, hook.url)
      end
    end
1183

Lin Jen-Shin's avatar
Lin Jen-Shin committed
1184
1185
1186
1187
1188
1189
    def create_build(name, stage_idx)
      create(:ci_build,
             :created,
             pipeline: pipeline,
             name: name,
             stage_idx: stage_idx)
1190
    end
1191
  end
1192
1193

  describe "#merge_requests" do
1194
    let(:project) { create(:project) }
1195
    let(:pipeline) { create(:ci_empty_pipeline, status: 'created', project: project, ref: 'master', sha: 'a288a022a53a5a944fae87bcec6efc87b7061808') }
1196
1197

    it "returns merge requests whose `diff_head_sha` matches the pipeline's SHA" do
1198
      allow_any_instance_of(MergeRequest).to receive(:diff_head_sha) { 'a288a022a53a5a944fae87bcec6efc87b7061808' }
Felipe Artur's avatar
Felipe Artur committed
1199
      merge_request = create(:merge_request, source_project: project, head_pipeline: pipeline, source_branch: pipeline.ref)
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216

      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,