pipeline_spec.rb 40.7 KB
Newer Older
D
Douwe Maan 已提交
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
D
Douwe Maan 已提交
10

K
Kamil Trzcinski 已提交
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) }
K
Kamil Trzcinski 已提交
17
  it { is_expected.to have_many(:trigger_requests) }
S
init  
Shinya Maeda 已提交
18
  it { is_expected.to have_many(:variables) }
D
Dmitriy Zaporozhets 已提交
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) }
D
Douwe Maan 已提交
25

D
Dmitriy Zaporozhets 已提交
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 }
29
  it { is_expected.to delegate_method(:full_path).to(:project).with_prefix }
D
Douwe Maan 已提交
30

31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52
  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

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

61
  describe '#valid_commit_sha' do
D
Douwe Maan 已提交
62 63
    context 'commit.sha can not start with 00000000' do
      before do
64 65
        pipeline.sha = '0' * 40
        pipeline.valid_commit_sha
D
Douwe Maan 已提交
66 67
      end

68
      it('commit errors should not be empty') { expect(pipeline.errors).not_to be_empty }
D
Douwe Maan 已提交
69 70 71
    end
  end

72
  describe '#short_sha' do
73
    subject { pipeline.short_sha }
D
Douwe Maan 已提交
74

D
Dmitriy Zaporozhets 已提交
75 76 77
    it 'has 8 items' do
      expect(subject.size).to eq(8)
    end
78
    it { expect(pipeline.sha).to start_with(subject) }
D
Douwe Maan 已提交
79 80
  end

81
  describe '#retried' do
82
    subject { pipeline.retried }
K
Kamil Trzcinski 已提交
83 84

    before do
85 86
      @build1 = create(:ci_build, pipeline: pipeline, name: 'deploy', retried: true)
      @build2 = create(:ci_build, pipeline: pipeline, name: 'deploy')
K
Kamil Trzcinski 已提交
87 88 89
    end

    it 'returns old builds' do
90
      is_expected.to contain_exactly(@build1)
K
Kamil Trzcinski 已提交
91 92 93
    end
  end

D
Douwe Maan 已提交
94
  describe "coverage" do
95
    let(:project) { create(:project, build_coverage_regex: "/.*/") }
96
    let(:pipeline) { create(:ci_empty_pipeline, project: project) }
D
Douwe Maan 已提交
97 98

    it "calculates average when there are two builds with coverage" do
99 100
      create(:ci_build, name: "rspec", coverage: 30, pipeline: pipeline)
      create(:ci_build, name: "rubocop", coverage: 40, pipeline: pipeline)
101
      expect(pipeline.coverage).to eq("35.00")
D
Douwe Maan 已提交
102 103 104
    end

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

    it "calculates average when there are two builds with coverage and one is retried" do
112 113 114
      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)
115
      expect(pipeline.coverage).to eq("35.00")
D
Douwe Maan 已提交
116 117 118
    end

    it "calculates average when there is one build without coverage" do
119
      FactoryGirl.create(:ci_build, pipeline: pipeline)
120
      expect(pipeline.coverage).to be_nil
D
Douwe Maan 已提交
121 122
    end
  end
K
Kamil Trzcinski 已提交
123 124

  describe '#retryable?' do
125
    subject { pipeline.retryable? }
K
Kamil Trzcinski 已提交
126 127 128

    context 'no failed builds' do
      before do
129
        create_build('rspec', 'success')
K
Kamil Trzcinski 已提交
130 131
      end

132
      it 'is not retryable' do
K
Kamil Trzcinski 已提交
133 134
        is_expected.to be_falsey
      end
135 136 137 138 139 140 141 142 143 144

      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 已提交
145 146 147 148
    end

    context 'with failed builds' do
      before do
149 150
        create_build('rspec', 'running')
        create_build('rubocop', 'failed')
K
Kamil Trzcinski 已提交
151 152
      end

153
      it 'is retryable' do
K
Kamil Trzcinski 已提交
154 155 156
        is_expected.to be_truthy
      end
    end
157 158 159 160

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

163 164 165 166 167 168 169 170 171 172 173 174
  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

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 211
  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

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

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

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

250 251 252 253 254 255 256 257
    describe '#seeds_size' do
      let(:pipeline) { build(:ci_pipeline_with_one_job) }

      it 'returns number of jobs in stage seeds' do
        expect(pipeline.seeds_size).to eq 1
      end
    end

258 259
    describe '#legacy_stages' do
      subject { pipeline.legacy_stages }
260 261 262 263 264

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

267 268 269 270
      context 'stages with statuses' do
        let(:statuses) do
          subject.map { |stage| [stage.name, stage.status] }
        end
271

272
        it 'returns list of stages with correct statuses' do
D
Douwe Maan 已提交
273 274 275
          expect(statuses).to eq([%w(build failed),
                                  %w(test success),
                                  %w(deploy running)])
276
        end
K
Kamil Trzcinski 已提交
277

278
        context 'when commit status is retried' do
279 280 281 282 283 284
          before do
            create(:commit_status, pipeline: pipeline,
                                   stage: 'build',
                                   name: 'mac',
                                   stage_idx: 0,
                                   status: 'success')
285 286

            pipeline.process!
287 288 289
          end

          it 'ignores the previous state' do
D
Douwe Maan 已提交
290 291 292
            expect(statuses).to eq([%w(build success),
                                    %w(test success),
                                    %w(deploy running)])
293
          end
294 295
        end
      end
296 297 298 299 300 301 302 303 304 305 306 307

      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
308
          deploy_stage = pipeline.legacy_stages.third
309 310 311 312 313

          expect(deploy_stage).not_to receive(:statuses)
          expect(deploy_stage).to have_warnings
        end
      end
314
    end
315

316 317 318
    describe '#stages_count' do
      it 'returns a valid number of stages' do
        expect(pipeline.stages_count).to eq(3)
319
      end
320
    end
321

322
    describe '#stages_names' do
323
      it 'returns a valid names of stages' do
324
        expect(pipeline.stages_names).to eq(%w(build test deploy))
325
      end
K
Kamil Trzcinski 已提交
326 327 328
    end
  end

329 330
  describe '#legacy_stage' do
    subject { pipeline.legacy_stage('test') }
K
Kamil Trzcinski 已提交
331 332

    context 'with status in stage' do
K
Kamil Trzcinski 已提交
333 334
      before do
        create(:commit_status, pipeline: pipeline, stage: 'test')
K
Kamil Trzcinski 已提交
335
      end
K
Kamil Trzcinski 已提交
336

337
      it { expect(subject).to be_a Ci::LegacyStage }
338 339
      it { expect(subject.name).to eq 'test' }
      it { expect(subject.statuses).not_to be_empty }
K
Kamil Trzcinski 已提交
340 341 342
    end

    context 'without status in stage' do
K
Kamil Trzcinski 已提交
343 344 345
      before do
        create(:commit_status, pipeline: pipeline, stage: 'build')
      end
K
Kamil Trzcinski 已提交
346 347 348 349 350 351 352

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

353
  describe 'state machine' do
354
    let(:current) { Time.now.change(usec: 0) }
355 356 357
    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) }
K
Kamil Trzcinski 已提交
358

359
    describe '#duration' do
360 361 362 363 364 365 366 367
      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
L
Lin Jen-Shin 已提交
368

369 370 371 372 373 374 375
          travel_to(current + 40) do
            build_b.drop!
          end

          travel_to(current + 70) do
            build_c.success!
          end
376
        end
377

378 379 380 381
        it 'matches sum of builds duration' do
          pipeline.reload

          expect(pipeline.duration).to eq(40)
382
        end
383 384
      end

385 386
      context 'when pipeline becomes blocked' do
        let!(:build) { create_build('build:1') }
387
        let!(:action) { create_build('manual:action', :manual) }
388

389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404
        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
405
      end
K
Kamil Trzcinski 已提交
406 407
    end

408 409 410
    describe '#started_at' do
      it 'updates on transitioning to running' do
        build.run
K
Kamil Trzcinski 已提交
411

412 413 414
        expect(pipeline.reload.started_at).not_to be_nil
      end

415
      it 'does not update on transitioning to success' do
416 417 418
        build.success

        expect(pipeline.reload.started_at).to be_nil
K
Kamil Trzcinski 已提交
419 420 421
      end
    end

422 423 424
    describe '#finished_at' do
      it 'updates on transitioning to success' do
        build.success
K
Kamil Trzcinski 已提交
425

426
        expect(pipeline.reload.finished_at).not_to be_nil
K
Kamil Trzcinski 已提交
427 428
      end

429
      it 'does not update on transitioning to running' do
430 431 432
        build.run

        expect(pipeline.reload.finished_at).to be_nil
K
Kamil Trzcinski 已提交
433 434
      end
    end
435

436
    describe 'merge request metrics' do
437
      let(:project) { create(:project, :repository) }
438 439 440
      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) }

441 442 443
      before do
        expect(PipelineMetricsWorker).to receive(:perform_async).with(pipeline.id)
      end
444

445 446 447
      context 'when transitioning to running' do
        it 'schedules metrics workers' do
          pipeline.run
448 449 450 451
        end
      end

      context 'when transitioning to success' do
452 453
        it 'schedules metrics workers' do
          pipeline.succeed
454 455 456
        end
      end
    end
457

458
    describe 'pipeline caching' do
459 460
      it 'performs ExpirePipelinesCacheWorker' do
        expect(ExpirePipelineCacheWorker).to receive(:perform_async).with(pipeline.id)
T
Toon Claes 已提交
461 462 463 464 465

        pipeline.cancel
      end
    end

466 467
    def create_build(name, *traits, queued_at: current, started_from: 0, **opts)
      create(:ci_build, *traits,
L
Lin Jen-Shin 已提交
468 469
             name: name,
             pipeline: pipeline,
470
             queued_at: queued_at,
471 472
             started_at: queued_at + started_from,
             **opts)
L
Lin Jen-Shin 已提交
473
    end
K
Kamil Trzcinski 已提交
474
  end
K
Kamil Trzcinski 已提交
475 476

  describe '#branch?' do
477
    subject { pipeline.branch? }
K
Kamil Trzcinski 已提交
478 479 480

    context 'is not a tag' do
      before do
481
        pipeline.tag = false
K
Kamil Trzcinski 已提交
482 483 484 485 486 487 488 489 490
      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
491
        pipeline.tag = true
K
Kamil Trzcinski 已提交
492 493 494 495 496 497 498
      end

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

500
  context 'with non-empty project' do
501
    let(:project) { create(:project, :repository) }
502 503 504 505 506 507 508

    let(:pipeline) do
      create(:ci_pipeline,
             project: project,
             ref: project.default_branch,
             sha: project.commit.sha)
    end
509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529

    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

530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546
  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') }

547 548 549 550
        before do
          manual.update(retried: true)
        end

551 552 553 554 555 556
        it 'returns latest one' do
          is_expected.to contain_exactly(manual2)
        end
      end
    end
  end
557

558
  describe '#has_kubernetes_active?' do
559
    context 'when kubernetes is active' do
560
      let(:project) { create(:kubernetes_project) }
561 562

      it 'returns true' do
563
        expect(pipeline).to have_kubernetes_active
564 565 566
      end
    end

567
    context 'when kubernetes is not active' do
568
      it 'returns false' do
569
        expect(pipeline).not_to have_kubernetes_active
570 571 572 573
      end
    end
  end

574
  describe '#has_stage_seeds?' do
575
    context 'when pipeline has stage seeds' do
576
      subject { build(:ci_pipeline_with_one_job) }
577

578
      it { is_expected.to have_stage_seeds }
579 580
    end

581
    context 'when pipeline does not have stage seeds' do
582 583
      subject { create(:ci_pipeline_without_jobs) }

584
      it { is_expected.not_to have_stage_seeds }
585 586 587
    end
  end

C
Connor Shea 已提交
588 589
  describe '#has_warnings?' do
    subject { pipeline.has_warnings? }
590 591 592

    context 'build which is allowed to fail fails' do
      before do
C
Connor Shea 已提交
593 594
        create :ci_build, :success, pipeline: pipeline, name: 'rspec'
        create :ci_build, :allowed_to_fail, :failed, pipeline: pipeline, name: 'rubocop'
595
      end
596

597 598 599 600 601 602 603
      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 已提交
604 605
        create :ci_build, :success, pipeline: pipeline, name: 'rspec'
        create :ci_build, :allowed_to_fail, :success, pipeline: pipeline, name: 'rubocop'
606
      end
607

608 609 610 611
      it 'returns false' do
        is_expected.to be_falsey
      end
    end
C
Connor Shea 已提交
612 613 614 615 616 617 618 619 620 621 622 623

    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
624
  end
625

626
  shared_context 'with some outdated pipelines' do
627
    before do
628 629 630 631
      create_pipeline(:canceled, 'ref', 'A', project)
      create_pipeline(:success, 'ref', 'A', project)
      create_pipeline(:failed, 'ref', 'B', project)
      create_pipeline(:skipped, 'feature', 'C', project)
632 633
    end

634 635 636 637 638 639 640 641
    def create_pipeline(status, ref, sha, project)
      create(
        :ci_empty_pipeline,
        status: status,
        ref: ref,
        sha: sha,
        project: project
      )
642 643 644
    end
  end

645
  describe '.newest_first' do
646
    include_context 'with some outdated pipelines'
647

648 649 650
    it 'returns the pipelines from new to old' do
      expect(described_class.newest_first.pluck(:status))
        .to eq(%w[skipped failed success canceled])
651 652 653 654
    end
  end

  describe '.latest_status' do
655
    include_context 'with some outdated pipelines'
656 657

    context 'when no ref is specified' do
658 659
      it 'returns the status of the latest pipeline' do
        expect(described_class.latest_status).to eq('skipped')
660 661 662 663
      end
    end

    context 'when ref is specified' do
664 665
      it 'returns the status of the latest pipeline for the given ref' do
        expect(described_class.latest_status('ref')).to eq('failed')
666 667 668 669
      end
    end
  end

670 671 672 673
  describe '.latest_successful_for' do
    include_context 'with some outdated pipelines'

    let!(:latest_successful_pipeline) do
674
      create_pipeline(:success, 'ref', 'D', project)
675 676 677
    end

    it 'returns the latest successful pipeline' do
678 679
      expect(described_class.latest_successful_for('ref'))
        .to eq(latest_successful_pipeline)
680 681 682
    end
  end

683 684 685
  describe '.latest_successful_for_refs' do
    include_context 'with some outdated pipelines'

686 687 688 689 690 691 692
    let!(:latest_successful_pipeline1) do
      create_pipeline(:success, 'ref1', 'D', project)
    end

    let!(:latest_successful_pipeline2) do
      create_pipeline(:success, 'ref2', 'D', project)
    end
693 694 695 696 697 698 699 700

    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

701 702 703 704 705 706 707 708 709 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 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756
  describe '.latest_status_per_commit' do
    let(:project) { create(:project) }

    before do
      pairs = [
        %w[success ref1 123],
        %w[manual master 123],
        %w[failed ref 456]
      ]

      pairs.each do |(status, ref, sha)|
        create(
          :ci_empty_pipeline,
          status: status,
          ref: ref,
          sha: sha,
          project: project
        )
      end
    end

    context 'without a ref' do
      it 'returns a Hash containing the latest status per commit for all refs' do
        expect(described_class.latest_status_per_commit(%w[123 456]))
          .to eq({ '123' => 'manual', '456' => 'failed' })
      end

      it 'only includes the status of the given commit SHAs' do
        expect(described_class.latest_status_per_commit(%w[123]))
          .to eq({ '123' => 'manual' })
      end

      context 'when there are two pipelines for a ref and SHA' do
        it 'returns the status of the latest pipeline' do
          create(
            :ci_empty_pipeline,
            status: 'failed',
            ref: 'master',
            sha: '123',
            project: project
          )

          expect(described_class.latest_status_per_commit(%w[123]))
            .to eq({ '123' => 'failed' })
        end
      end
    end

    context 'with a ref' do
      it 'only includes the pipelines for the given ref' do
        expect(described_class.latest_status_per_commit(%w[123 456], 'master'))
          .to eq({ '123' => 'manual' })
      end
    end
  end

757 758 759 760 761 762
  describe '.internal_sources' do
    subject { described_class.internal_sources }

    it { is_expected.to be_an(Array) }
  end

763
  describe '#status' do
764 765 766
    let(:build) do
      create(:ci_build, :created, pipeline: pipeline, name: 'test')
    end
767 768 769 770

    subject { pipeline.reload.status }

    context 'on queuing' do
771 772 773
      before do
        build.enqueue
      end
774 775 776 777 778 779

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

    context 'on run' do
      before do
780
        build.enqueue
781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807
        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

808 809 810 811 812 813 814
      context 'when build is pending' do
        let(:build) do
          create(:ci_build, :pending, pipeline: pipeline)
        end

        it { is_expected.to eq('canceled') }
      end
815
    end
816 817 818

    context 'on failure and build retry' do
      before do
S
Shinya Maeda 已提交
819 820
        stub_not_protect_default_branch

821
        build.drop
822
        project.add_developer(user)
823 824

        Ci::Build.retry(build, user)
825 826 827 828 829 830 831 832
      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
833
  end
834

835 836 837 838
  describe '#ci_yaml_file_path' do
    subject { pipeline.ci_yaml_file_path }

    it 'returns the path from project' do
839
      allow(pipeline.project).to receive(:ci_config_path) { 'custom/path' }
840 841 842 843 844

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

    it 'returns default when custom path is nil' do
845
      allow(pipeline.project).to receive(:ci_config_path) { nil }
846 847 848 849 850

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

    it 'returns default when custom path is empty' do
851
      allow(pipeline.project).to receive(:ci_config_path) { '' }
852 853 854 855 856

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

857 858 859 860 861 862
  describe '#set_config_source' do
    context 'on object initialisation' do
      context 'when pipelines does not contain needed data' do
        let(:pipeline) do
          Ci::Pipeline.new
        end
863

864 865 866
        it 'defines source to be unknown' do
          expect(pipeline).to be_unknown_source
        end
867
      end
868

869 870 871 872 873 874 875 876
      context 'when pipeline contains all needed data' do
        let(:pipeline) do
          Ci::Pipeline.new(
            project: project,
            sha: '1234',
            ref: 'master',
            source: :push)
        end
877

878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897
        context 'when the repository has a config file' do
          before do
            allow(project.repository).to receive(:gitlab_ci_yml_for)
              .and_return('config')
          end

          it 'defines source to be from repository' do
            expect(pipeline).to be_repository_source
          end

          context 'when loading an object' do
            let(:new_pipeline) { Ci::Pipeline.find(pipeline.id) }

            it 'does not redefine the source' do
              # force to overwrite the source
              pipeline.unknown_source!

              expect(new_pipeline).to be_unknown_source
            end
          end
898
        end
899

900 901
        context 'when the repository does not have a config file' do
          let(:implied_yml) { Gitlab::Template::GitlabCiYmlTemplate.find('Auto-DevOps').content }
902

903 904 905 906 907 908 909 910 911 912 913 914
          context 'auto devops enabled' do
            before do
              stub_application_setting(auto_devops_enabled: true)
              allow(project).to receive(:ci_config_path) { 'custom' }
            end

            it 'defines source to be auto devops' do
              subject

              expect(pipeline).to be_auto_devops_source
            end
          end
915
        end
916 917 918
      end
    end
  end
919

920 921
  describe '#ci_yaml_file' do
    let(:implied_yml) { Gitlab::Template::GitlabCiYmlTemplate.find('Auto-DevOps').content }
922

923
    context 'the source is unknown' do
924 925 926
      before do
        pipeline.unknown_source!
      end
927 928 929 930 931 932 933 934 935 936 937 938 939 940

      it 'returns the configuration if found' do
        allow(pipeline.project.repository).to receive(:gitlab_ci_yml_for)
          .and_return('config')

        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 'sets yaml errors if not found' do
        expect(pipeline.ci_yaml_file).to be_nil
        expect(pipeline.yaml_errors)
            .to start_with('Failed to load CI/CD config file')
941 942 943
      end
    end

944
    context 'the source is the repository' do
945 946 947
      before do
        pipeline.repository_source!
      end
948

949 950 951 952 953 954 955
      it 'returns the configuration if found' do
        allow(pipeline.project.repository).to receive(:gitlab_ci_yml_for)
          .and_return('config')

        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
956
      end
957
    end
958

959
    context 'when the source is auto_devops_source' do
960
      before do
961
        stub_application_setting(auto_devops_enabled: true)
962 963
        pipeline.auto_devops_source!
      end
964

965 966 967
      it 'finds the implied config' do
        expect(pipeline.ci_yaml_file).to eq(implied_yml)
        expect(pipeline.yaml_errors).to be_nil
968
      end
969 970 971
    end
  end

972
  describe '#detailed_status' do
973 974
    subject { pipeline.detailed_status(user) }

975 976 977 978
    context 'when pipeline is created' do
      let(:pipeline) { create(:ci_pipeline, status: :created) }

      it 'returns detailed status for created pipeline' do
979
        expect(subject.text).to eq 'created'
980 981 982 983 984 985 986
      end
    end

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

      it 'returns detailed status for pending pipeline' do
987
        expect(subject.text).to eq 'pending'
988 989 990 991 992 993 994
      end
    end

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

      it 'returns detailed status for running pipeline' do
995
        expect(subject.text).to eq 'running'
996 997 998 999 1000 1001 1002
      end
    end

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

      it 'returns detailed status for successful pipeline' do
1003
        expect(subject.text).to eq 'passed'
1004 1005 1006 1007 1008 1009 1010
      end
    end

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

      it 'returns detailed status for failed pipeline' do
1011
        expect(subject.text).to eq 'failed'
1012 1013 1014 1015 1016 1017 1018
      end
    end

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

      it 'returns detailed status for canceled pipeline' do
1019
        expect(subject.text).to eq 'canceled'
1020 1021 1022 1023 1024 1025 1026
      end
    end

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

      it 'returns detailed status for skipped pipeline' do
1027
        expect(subject.text).to eq 'skipped'
1028 1029 1030
      end
    end

1031 1032 1033 1034
    context 'when pipeline is blocked' do
      let(:pipeline) { create(:ci_pipeline, status: :manual) }

      it 'returns detailed status for blocked pipeline' do
1035
        expect(subject.text).to eq 'blocked'
1036 1037 1038
      end
    end

1039 1040 1041 1042 1043 1044 1045 1046
    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
1047
        expect(subject.label).to eq 'passed with warnings'
1048 1049 1050 1051
      end
    end
  end

1052
  describe '#cancelable?' do
1053 1054
    %i[created running pending].each do |status0|
      context "when there is a build #{status0}" do
1055
        before do
1056
          create(:ci_build, status0, pipeline: pipeline)
1057 1058
        end

1059 1060 1061
        it 'is cancelable' do
          expect(pipeline.cancelable?).to be_truthy
        end
1062 1063
      end

1064
      context "when there is an external job #{status0}" do
1065
        before do
1066
          create(:generic_commit_status, status0, pipeline: pipeline)
1067 1068
        end

1069 1070 1071
        it 'is cancelable' do
          expect(pipeline.cancelable?).to be_truthy
        end
1072
      end
1073

1074
      %i[success failed canceled].each do |status1|
1075 1076 1077 1078 1079 1080 1081 1082 1083 1084 1085 1086 1087 1088 1089 1090 1091 1092 1093 1094 1095 1096 1097 1098 1099 1100 1101 1102 1103 1104
        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
1105
          end
1106 1107
        end
      end
1108 1109 1110 1111 1112 1113 1114 1115
    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

1116 1117 1118
        it 'is not cancelable' do
          expect(pipeline.cancelable?).to be_falsey
        end
1119 1120 1121 1122 1123 1124 1125
      end

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

1126 1127 1128
        it 'is not cancelable' do
          expect(pipeline.cancelable?).to be_falsey
        end
1129 1130
      end
    end
1131 1132 1133 1134 1135 1136 1137 1138 1139 1140

    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
1141 1142
  end

1143
  describe '#cancel_running' do
1144 1145
    let(:latest_status) { pipeline.statuses.pluck(:status) }

1146
    context 'when there is a running external job and a regular job' do
1147
      before do
1148
        create(:ci_build, :running, pipeline: pipeline)
1149 1150 1151 1152 1153 1154
        create(:generic_commit_status, :running, pipeline: pipeline)

        pipeline.cancel_running
      end

      it 'cancels both jobs' do
1155 1156 1157 1158
        expect(latest_status).to contain_exactly('canceled', 'canceled')
      end
    end

1159
    context 'when jobs are in different stages' do
1160 1161 1162 1163 1164 1165 1166 1167 1168 1169 1170
      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
1171 1172 1173 1174 1175 1176 1177 1178 1179

    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

1180
      it 'cancels created builds' do
D
Douwe Maan 已提交
1181
        expect(latest_status).to eq %w(canceled canceled)
1182 1183
      end
    end
1184 1185 1186 1187 1188
  end

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

1189
    before do
S
Shinya Maeda 已提交
1190 1191
      stub_not_protect_default_branch

1192
      project.add_developer(user)
1193 1194
    end

1195 1196 1197 1198 1199
    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)

1200
        pipeline.retry_failed(user)
1201 1202 1203 1204 1205 1206 1207 1208 1209 1210 1211 1212
      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)

1213
        pipeline.retry_failed(user)
1214 1215 1216
      end

      it 'retries both builds' do
1217
        expect(latest_status).to contain_exactly('pending', 'created')
1218 1219 1220 1221 1222 1223 1224 1225
      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)

1226
        pipeline.retry_failed(user)
1227 1228 1229
      end

      it 'retries both builds' do
1230
        expect(latest_status).to contain_exactly('pending', 'created')
1231 1232 1233 1234
      end
    end
  end

1235
  describe '#execute_hooks' do
L
Lin Jen-Shin 已提交
1236 1237
    let!(:build_a) { create_build('a', 0) }
    let!(:build_b) { create_build('b', 1) }
K
Kamil Trzcinski 已提交
1238

1239 1240 1241 1242 1243
    let!(:hook) do
      create(:project_hook, project: project, pipeline_events: enabled)
    end

    before do
A
Alexander Randa 已提交
1244
      WebHookWorker.drain
1245 1246 1247 1248 1249
    end

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

K
Kamil Trzcinski 已提交
1250 1251 1252 1253
      before do
        WebMock.stub_request(:post, hook.url)
      end

1254
      context 'with multiple builds' do
K
Kamil Trzcinski 已提交
1255 1256
        context 'when build is queued' do
          before do
1257 1258
            build_a.enqueue
            build_b.enqueue
K
Kamil Trzcinski 已提交
1259
          end
1260

L
Lin Jen-Shin 已提交
1261
          it 'receives a pending event once' do
1262
            expect(WebMock).to have_requested_pipeline_hook('pending').once
K
Kamil Trzcinski 已提交
1263 1264
          end
        end
1265

K
Kamil Trzcinski 已提交
1266 1267
        context 'when build is run' do
          before do
1268
            build_a.enqueue
K
Kamil Trzcinski 已提交
1269
            build_a.run
1270
            build_b.enqueue
K
Kamil Trzcinski 已提交
1271 1272
            build_b.run
          end
1273

L
Lin Jen-Shin 已提交
1274
          it 'receives a running event once' do
1275
            expect(WebMock).to have_requested_pipeline_hook('running').once
K
Kamil Trzcinski 已提交
1276
          end
1277 1278
        end

K
Kamil Trzcinski 已提交
1279 1280 1281
        context 'when all builds succeed' do
          before do
            build_a.success
K
Kamil Trzcinski 已提交
1282 1283 1284

            # 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 已提交
1285 1286
          end

L
Lin Jen-Shin 已提交
1287
          it 'receives a success event once' do
1288
            expect(WebMock).to have_requested_pipeline_hook('success').once
K
Kamil Trzcinski 已提交
1289
          end
1290 1291
        end

L
Lin Jen-Shin 已提交
1292 1293 1294 1295 1296
        context 'when stage one failed' do
          before do
            build_a.drop
          end

L
Lin Jen-Shin 已提交
1297
          it 'receives a failed event once' do
L
Lin Jen-Shin 已提交
1298 1299 1300 1301
            expect(WebMock).to have_requested_pipeline_hook('failed').once
          end
        end

1302
        def have_requested_pipeline_hook(status)
K
Kamil Trzcinski 已提交
1303
          have_requested(:post, hook.url).with do |req|
1304 1305 1306 1307
            json_body = JSON.parse(req.body)
            json_body['object_attributes']['status'] == status &&
              json_body['builds'].length == 2
          end
1308
        end
1309
      end
1310 1311 1312 1313 1314
    end

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

K
Kamil Trzcinski 已提交
1315
      before do
1316 1317
        build_a.enqueue
        build_b.enqueue
K
Kamil Trzcinski 已提交
1318 1319
      end

1320 1321 1322 1323
      it 'did not execute pipeline_hook after touched' do
        expect(WebMock).not_to have_requested(:post, hook.url)
      end
    end
K
Kamil Trzcinski 已提交
1324

L
Lin Jen-Shin 已提交
1325 1326 1327 1328 1329 1330
    def create_build(name, stage_idx)
      create(:ci_build,
             :created,
             pipeline: pipeline,
             name: name,
             stage_idx: stage_idx)
K
Kamil Trzcinski 已提交
1331
    end
1332
  end
1333 1334

  describe "#merge_requests" do
1335
    let(:project) { create(:project) }
1336
    let(:pipeline) { create(:ci_empty_pipeline, status: 'created', project: project, ref: 'master', sha: 'a288a022a53a5a944fae87bcec6efc87b7061808') }
1337 1338

    it "returns merge requests whose `diff_head_sha` matches the pipeline's SHA" do
1339
      allow_any_instance_of(MergeRequest).to receive(:diff_head_sha) { 'a288a022a53a5a944fae87bcec6efc87b7061808' }
F
Felipe Artur 已提交
1340
      merge_request = create(:merge_request, source_project: project, head_pipeline: pipeline, source_branch: pipeline.ref)
1341 1342 1343 1344 1345 1346 1347 1348 1349 1350 1351 1352 1353 1354 1355 1356 1357

      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
1358

1359
  describe "#all_merge_requests" do
1360
    let(:project) { create(:project) }
1361
    let(:pipeline) { create(:ci_empty_pipeline, status: 'created', project: project, ref: 'master') }
1362

1363
    it "returns all merge requests having the same source branch" do
1364 1365 1366 1367 1368
      merge_request = create(:merge_request, source_project: project, source_branch: pipeline.ref)

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

1369
    it "doesn't return merge requests having a different source branch" do
1370 1371 1372 1373 1374 1375
      create(:merge_request, source_project: project, source_branch: 'feature', target_branch: 'master')

      expect(pipeline.all_merge_requests).to be_empty
    end
  end

1376 1377 1378 1379 1380 1381 1382 1383 1384 1385 1386 1387
  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
1388 1389 1390
      before do
        create(:ci_runner, :shared, :online)
      end
1391 1392 1393 1394 1395 1396 1397 1398 1399 1400 1401 1402 1403 1404 1405 1406 1407 1408 1409 1410 1411 1412 1413 1414 1415 1416 1417 1418 1419

      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

1420
  describe 'notifications when pipeline success or failed' do
1421
    let(:project) { create(:project, :repository) }
L
Lin Jen-Shin 已提交
1422 1423 1424 1425 1426 1427 1428 1429

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

1430
    before do
1431 1432
      project.team << [pipeline.user, Gitlab::Access::DEVELOPER]

1433 1434
      pipeline.user.global_notification_setting
        .update(level: 'custom', failed_pipeline: true, success_pipeline: true)
S
Sean McGivern 已提交
1435

1436 1437 1438 1439
      perform_enqueued_jobs do
        pipeline.enqueue
        pipeline.run
      end
1440 1441 1442 1443
    end

    shared_examples 'sending a notification' do
      it 'sends an email' do
1444
        should_only_email(pipeline.user, kind: :bcc)
1445 1446 1447 1448 1449
      end
    end

    shared_examples 'not sending any notification' do
      it 'does not send any email' do
1450
        should_not_email_anyone
1451 1452 1453 1454 1455 1456
      end
    end

    context 'with success pipeline' do
      before do
        perform_enqueued_jobs do
L
Lin Jen-Shin 已提交
1457
          pipeline.succeed
1458 1459
        end
      end
L
Lin Jen-Shin 已提交
1460 1461

      it_behaves_like 'sending a notification'
1462 1463 1464 1465 1466
    end

    context 'with failed pipeline' do
      before do
        perform_enqueued_jobs do
1467 1468
          create(:ci_build, :failed, pipeline: pipeline)
          create(:generic_commit_status, :failed, pipeline: pipeline)
1469

1470
          pipeline.drop
1471 1472
        end
      end
L
Lin Jen-Shin 已提交
1473 1474

      it_behaves_like 'sending a notification'
1475 1476 1477 1478 1479 1480 1481 1482
    end

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

      it_behaves_like 'not sending any notification'
1485 1486 1487 1488 1489 1490 1491 1492
    end

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

      it_behaves_like 'not sending any notification'
1495 1496
    end
  end
1497 1498 1499 1500 1501 1502 1503 1504

  describe '#latest_builds_with_artifacts' do
    let!(:pipeline) { create(:ci_pipeline, :success) }

    let!(:build) do
      create(:ci_build, :success, :artifacts, pipeline: pipeline)
    end

1505 1506 1507 1508
    it 'returns an Array' do
      expect(pipeline.latest_builds_with_artifacts).to be_an_instance_of(Array)
    end

1509 1510 1511 1512 1513 1514 1515 1516 1517 1518 1519 1520
    it 'returns the latest builds' do
      expect(pipeline.latest_builds_with_artifacts).to eq([build])
    end

    it 'memoizes the returned relation' do
      query_count = ActiveRecord::QueryRecorder
        .new { 2.times { pipeline.latest_builds_with_artifacts.to_a } }
        .count

      expect(query_count).to eq(1)
    end
  end
D
Douwe Maan 已提交
1521
end