BigW Consortium Gitlab

pipeline_spec.rb 18.9 KB
Newer Older
1 2
require 'spec_helper'

3
describe Ci::Pipeline, models: true do
4
  let(:project) { FactoryGirl.create :empty_project }
5
  let(:pipeline) { FactoryGirl.create :ci_pipeline, project: project }
6

7
  it { is_expected.to belong_to(:project) }
8 9
  it { is_expected.to belong_to(:user) }

10
  it { is_expected.to have_many(:statuses) }
11
  it { is_expected.to have_many(:trigger_requests) }
Dmitriy Zaporozhets committed
12
  it { is_expected.to have_many(:builds) }
13

Dmitriy Zaporozhets committed
14
  it { is_expected.to validate_presence_of :sha }
15
  it { is_expected.to validate_presence_of :status }
16

Dmitriy Zaporozhets committed
17 18 19
  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 }
20

21
  describe '#valid_commit_sha' do
22 23
    context 'commit.sha can not start with 00000000' do
      before do
24 25
        pipeline.sha = '0' * 40
        pipeline.valid_commit_sha
26 27
      end

28
      it('commit errors should not be empty') { expect(pipeline.errors).not_to be_empty }
29 30 31
    end
  end

32
  describe '#short_sha' do
33
    subject { pipeline.short_sha }
34

Dmitriy Zaporozhets committed
35 36 37
    it 'has 8 items' do
      expect(subject.size).to eq(8)
    end
38
    it { expect(pipeline.sha).to start_with(subject) }
39 40
  end

41
  describe '#create_next_builds' do
Kamil Trzcinski committed
42 43
  end

44
  describe '#retried' do
45
    subject { pipeline.retried }
46 47

    before do
48 49
      @build1 = FactoryGirl.create :ci_build, pipeline: pipeline, name: 'deploy'
      @build2 = FactoryGirl.create :ci_build, pipeline: pipeline, name: 'deploy'
50 51 52
    end

    it 'returns old builds' do
53
      is_expected.to contain_exactly(@build1)
54 55 56
    end
  end

57
  describe '#create_builds' do
58
    let!(:pipeline) { FactoryGirl.create :ci_pipeline, project: project, ref: 'master', tag: false }
Kamil Trzcinski committed
59 60

    def create_builds(trigger_request = nil)
61
      pipeline.create_builds(nil, trigger_request)
62 63
    end

64
    def create_next_builds
65
      pipeline.create_next_builds(pipeline.builds.order(:id).last)
Kamil Trzcinski committed
66 67 68 69
    end

    it 'creates builds' do
      expect(create_builds).to be_truthy
70 71
      pipeline.builds.update_all(status: "success")
      expect(pipeline.builds.count(:all)).to eq(2)
72

Kamil Trzcinski committed
73
      expect(create_next_builds).to be_truthy
74 75
      pipeline.builds.update_all(status: "success")
      expect(pipeline.builds.count(:all)).to eq(4)
76

Kamil Trzcinski committed
77
      expect(create_next_builds).to be_truthy
78 79
      pipeline.builds.update_all(status: "success")
      expect(pipeline.builds.count(:all)).to eq(5)
80

Kamil Trzcinski committed
81
      expect(create_next_builds).to be_falsey
82 83
    end

84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100
    context 'custom stage with first job allowed to fail' do
      let(:yaml) do
        {
          stages: ['clean', 'test'],
          clean_job: {
            stage: 'clean',
            allow_failure: true,
            script: 'BUILD',
          },
          test_job: {
            stage: 'test',
            script: 'TEST',
          },
        }
      end

      before do
101
        stub_ci_pipeline_yaml_file(YAML.dump(yaml))
102 103 104 105
        create_builds
      end

      it 'properly schedules builds' do
106 107 108
        expect(pipeline.builds.pluck(:status)).to contain_exactly('pending')
        pipeline.builds.running_or_pending.each(&:drop)
        expect(pipeline.builds.pluck(:status)).to contain_exactly('pending', 'failed')
109 110 111
      end
    end

112
    context 'properly creates builds when "when" is defined' do
Kamil Trzcinski committed
113
      let(:yaml) do
114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138
        {
          stages: ["build", "test", "test_failure", "deploy", "cleanup"],
          build: {
            stage: "build",
            script: "BUILD",
          },
          test: {
            stage: "test",
            script: "TEST",
          },
          test_failure: {
            stage: "test_failure",
            script: "ON test failure",
            when: "on_failure",
          },
          deploy: {
            stage: "deploy",
            script: "PUBLISH",
          },
          cleanup: {
            stage: "cleanup",
            script: "TIDY UP",
            when: "always",
          }
        }
Kamil Trzcinski committed
139
      end
140 141

      before do
142
        stub_ci_pipeline_yaml_file(YAML.dump(yaml))
143 144
      end

145 146 147
      context 'when builds are successful' do
        it 'properly creates builds' do
          expect(create_builds).to be_truthy
148 149 150
          expect(pipeline.builds.pluck(:name)).to contain_exactly('build')
          expect(pipeline.builds.pluck(:status)).to contain_exactly('pending')
          pipeline.builds.running_or_pending.each(&:success)
151

152 153 154
          expect(pipeline.builds.pluck(:name)).to contain_exactly('build', 'test')
          expect(pipeline.builds.pluck(:status)).to contain_exactly('success', 'pending')
          pipeline.builds.running_or_pending.each(&:success)
155

156 157 158
          expect(pipeline.builds.pluck(:name)).to contain_exactly('build', 'test', 'deploy')
          expect(pipeline.builds.pluck(:status)).to contain_exactly('success', 'success', 'pending')
          pipeline.builds.running_or_pending.each(&:success)
159

160 161 162
          expect(pipeline.builds.pluck(:name)).to contain_exactly('build', 'test', 'deploy', 'cleanup')
          expect(pipeline.builds.pluck(:status)).to contain_exactly('success', 'success', 'success', 'pending')
          pipeline.builds.running_or_pending.each(&:success)
163

164 165 166
          expect(pipeline.builds.pluck(:status)).to contain_exactly('success', 'success', 'success', 'success')
          pipeline.reload
          expect(pipeline.status).to eq('success')
167
        end
168 169
      end

170 171 172
      context 'when test job fails' do
        it 'properly creates builds' do
          expect(create_builds).to be_truthy
173 174 175
          expect(pipeline.builds.pluck(:name)).to contain_exactly('build')
          expect(pipeline.builds.pluck(:status)).to contain_exactly('pending')
          pipeline.builds.running_or_pending.each(&:success)
176

177 178 179
          expect(pipeline.builds.pluck(:name)).to contain_exactly('build', 'test')
          expect(pipeline.builds.pluck(:status)).to contain_exactly('success', 'pending')
          pipeline.builds.running_or_pending.each(&:drop)
180

181 182 183
          expect(pipeline.builds.pluck(:name)).to contain_exactly('build', 'test', 'test_failure')
          expect(pipeline.builds.pluck(:status)).to contain_exactly('success', 'failed', 'pending')
          pipeline.builds.running_or_pending.each(&:success)
184

185 186 187
          expect(pipeline.builds.pluck(:name)).to contain_exactly('build', 'test', 'test_failure', 'cleanup')
          expect(pipeline.builds.pluck(:status)).to contain_exactly('success', 'failed', 'success', 'pending')
          pipeline.builds.running_or_pending.each(&:success)
188

189 190 191
          expect(pipeline.builds.pluck(:status)).to contain_exactly('success', 'failed', 'success', 'success')
          pipeline.reload
          expect(pipeline.status).to eq('failed')
192
        end
193 194
      end

195 196 197
      context 'when test and test_failure jobs fail' do
        it 'properly creates builds' do
          expect(create_builds).to be_truthy
198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217
          expect(pipeline.builds.pluck(:name)).to contain_exactly('build')
          expect(pipeline.builds.pluck(:status)).to contain_exactly('pending')
          pipeline.builds.running_or_pending.each(&:success)

          expect(pipeline.builds.pluck(:name)).to contain_exactly('build', 'test')
          expect(pipeline.builds.pluck(:status)).to contain_exactly('success', 'pending')
          pipeline.builds.running_or_pending.each(&:drop)

          expect(pipeline.builds.pluck(:name)).to contain_exactly('build', 'test', 'test_failure')
          expect(pipeline.builds.pluck(:status)).to contain_exactly('success', 'failed', 'pending')
          pipeline.builds.running_or_pending.each(&:drop)

          expect(pipeline.builds.pluck(:name)).to contain_exactly('build', 'test', 'test_failure', 'cleanup')
          expect(pipeline.builds.pluck(:status)).to contain_exactly('success', 'failed', 'failed', 'pending')
          pipeline.builds.running_or_pending.each(&:success)

          expect(pipeline.builds.pluck(:name)).to contain_exactly('build', 'test', 'test_failure', 'cleanup')
          expect(pipeline.builds.pluck(:status)).to contain_exactly('success', 'failed', 'failed', 'success')
          pipeline.reload
          expect(pipeline.status).to eq('failed')
218
        end
219 220
      end

221 222 223
      context 'when deploy job fails' do
        it 'properly creates builds' do
          expect(create_builds).to be_truthy
224 225 226
          expect(pipeline.builds.pluck(:name)).to contain_exactly('build')
          expect(pipeline.builds.pluck(:status)).to contain_exactly('pending')
          pipeline.builds.running_or_pending.each(&:success)
227

228 229 230
          expect(pipeline.builds.pluck(:name)).to contain_exactly('build', 'test')
          expect(pipeline.builds.pluck(:status)).to contain_exactly('success', 'pending')
          pipeline.builds.running_or_pending.each(&:success)
231

232 233 234
          expect(pipeline.builds.pluck(:name)).to contain_exactly('build', 'test', 'deploy')
          expect(pipeline.builds.pluck(:status)).to contain_exactly('success', 'success', 'pending')
          pipeline.builds.running_or_pending.each(&:drop)
235

236 237 238
          expect(pipeline.builds.pluck(:name)).to contain_exactly('build', 'test', 'deploy', 'cleanup')
          expect(pipeline.builds.pluck(:status)).to contain_exactly('success', 'success', 'failed', 'pending')
          pipeline.builds.running_or_pending.each(&:success)
239

240 241 242
          expect(pipeline.builds.pluck(:status)).to contain_exactly('success', 'success', 'failed', 'success')
          pipeline.reload
          expect(pipeline.status).to eq('failed')
243
        end
244
      end
245 246 247 248

      context 'when build is canceled in the second stage' do
        it 'does not schedule builds after build has been canceled' do
          expect(create_builds).to be_truthy
249 250 251
          expect(pipeline.builds.pluck(:name)).to contain_exactly('build')
          expect(pipeline.builds.pluck(:status)).to contain_exactly('pending')
          pipeline.builds.running_or_pending.each(&:success)
252

253
          expect(pipeline.builds.running_or_pending).not_to be_empty
254

255 256 257
          expect(pipeline.builds.pluck(:name)).to contain_exactly('build', 'test')
          expect(pipeline.builds.pluck(:status)).to contain_exactly('success', 'pending')
          pipeline.builds.running_or_pending.each(&:cancel)
258

259 260
          expect(pipeline.builds.running_or_pending).to be_empty
          expect(pipeline.reload.status).to eq('canceled')
261 262
        end
      end
263 264 265 266

      context 'when listing manual actions' do
        let(:yaml) do
          {
267
            stages: ["build", "test", "staging", "production", "cleanup"],
268 269 270 271 272 273 274 275
            build: {
              stage: "build",
              script: "BUILD",
            },
            test: {
              stage: "test",
              script: "TEST",
            },
276 277
            staging: {
              stage: "staging",
278 279 280
              script: "PUBLISH",
            },
            production: {
281
              stage: "production",
282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308
              script: "PUBLISH",
              when: "manual",
            },
            cleanup: {
              stage: "cleanup",
              script: "TIDY UP",
              when: "always",
            },
            clear_cache: {
              stage: "cleanup",
              script: "CLEAR CACHE",
              when: "manual",
            }
          }
        end

        it 'returns only for skipped builds' do
          # currently all builds are created
          expect(create_builds).to be_truthy
          expect(manual_actions).to be_empty

          # succeed stage build
          pipeline.builds.running_or_pending.each(&:success)
          expect(manual_actions).to be_empty

          # succeed stage test
          pipeline.builds.running_or_pending.each(&:success)
309
          expect(manual_actions).to be_empty
310

311
          # succeed stage staging and skip stage production
312 313
          pipeline.builds.running_or_pending.each(&:success)
          expect(manual_actions).to be_many # production and clear cache
314 315 316 317 318 319 320

          # succeed stage cleanup
          pipeline.builds.running_or_pending.each(&:success)

          # after processing a pipeline we should have 6 builds, 5 succeeded
          expect(pipeline.builds.count).to eq(6)
          expect(pipeline.builds.success.count).to eq(4)
321 322 323 324 325 326
        end

        def manual_actions
          pipeline.manual_actions
        end
      end
327
    end
328 329

    context 'when no builds created' do
330 331
      let(:pipeline) { build(:ci_pipeline) }

332 333 334 335 336 337
      before do
        stub_ci_pipeline_yaml_file(YAML.dump(before_script: ['ls']))
      end

      it 'returns false' do
        expect(pipeline.create_builds(nil)).to be_falsey
338
        expect(pipeline).not_to be_persisted
339 340
      end
    end
341 342 343
  end

  describe "#finished_at" do
344
    let(:pipeline) { FactoryGirl.create :ci_pipeline }
345 346

    it "returns finished_at of latest build" do
347 348
      build = FactoryGirl.create :ci_build, pipeline: pipeline, finished_at: Time.now - 60
      FactoryGirl.create :ci_build, pipeline: pipeline, finished_at: Time.now - 120
349

350
      expect(pipeline.finished_at.to_i).to eq(build.finished_at.to_i)
351 352 353
    end

    it "returns nil if there is no finished build" do
354
      FactoryGirl.create :ci_not_started_build, pipeline: pipeline
355

356
      expect(pipeline.finished_at).to be_nil
357 358 359 360
    end
  end

  describe "coverage" do
361
    let(:project) { FactoryGirl.create :empty_project, build_coverage_regex: "/.*/" }
362
    let(:pipeline) { FactoryGirl.create :ci_pipeline, project: project }
363 364

    it "calculates average when there are two builds with coverage" do
365 366 367
      FactoryGirl.create :ci_build, name: "rspec", coverage: 30, pipeline: pipeline
      FactoryGirl.create :ci_build, name: "rubocop", coverage: 40, pipeline: pipeline
      expect(pipeline.coverage).to eq("35.00")
368 369 370
    end

    it "calculates average when there are two builds with coverage and one with nil" do
371 372 373 374
      FactoryGirl.create :ci_build, name: "rspec", coverage: 30, pipeline: pipeline
      FactoryGirl.create :ci_build, name: "rubocop", coverage: 40, pipeline: pipeline
      FactoryGirl.create :ci_build, pipeline: pipeline
      expect(pipeline.coverage).to eq("35.00")
375 376 377
    end

    it "calculates average when there are two builds with coverage and one is retried" do
378 379 380 381
      FactoryGirl.create :ci_build, name: "rspec", coverage: 30, pipeline: pipeline
      FactoryGirl.create :ci_build, name: "rubocop", coverage: 30, pipeline: pipeline
      FactoryGirl.create :ci_build, name: "rubocop", coverage: 40, pipeline: pipeline
      expect(pipeline.coverage).to eq("35.00")
382 383 384
    end

    it "calculates average when there is one build without coverage" do
385 386
      FactoryGirl.create :ci_build, pipeline: pipeline
      expect(pipeline.coverage).to be_nil
387 388
    end
  end
389 390

  describe '#retryable?' do
391
    subject { pipeline.retryable? }
392 393 394

    context 'no failed builds' do
      before do
395
        FactoryGirl.create :ci_build, name: "rspec", pipeline: pipeline, status: 'success'
396 397 398 399 400 401 402 403 404
      end

      it 'be not retryable' do
        is_expected.to be_falsey
      end
    end

    context 'with failed builds' do
      before do
405 406
        FactoryGirl.create :ci_build, name: "rspec", pipeline: pipeline, status: 'running'
        FactoryGirl.create :ci_build, name: "rubocop", pipeline: pipeline, status: 'failed'
407 408 409 410 411 412 413 414 415
      end

      it 'be retryable' do
        is_expected.to be_truthy
      end
    end
  end

  describe '#stages' do
416 417
    let(:pipeline2) { FactoryGirl.create :ci_pipeline, project: project }
    subject { CommitStatus.where(pipeline: [pipeline, pipeline2]).stages }
418 419

    before do
420 421
      FactoryGirl.create :ci_build, pipeline: pipeline2, stage: 'test', stage_idx: 1
      FactoryGirl.create :ci_build, pipeline: pipeline, stage: 'build', stage_idx: 0
422 423 424 425 426 427 428 429 430
    end

    it 'return all stages' do
      is_expected.to eq(%w(build test))
    end
  end

  describe '#update_state' do
    it 'execute update_state after touching object' do
431 432
      expect(pipeline).to receive(:update_state).and_return(true)
      pipeline.touch
433 434 435
    end

    context 'dependent objects' do
436
      let(:commit_status) { build :commit_status, pipeline: pipeline }
437 438

      it 'execute update_state after saving dependent object' do
439
        expect(pipeline).to receive(:update_state).and_return(true)
440 441 442 443 444
        commit_status.save
      end
    end

    context 'update state' do
Kamil Trzcinski committed
445
      let(:current) { Time.now.change(usec: 0) }
446
      let(:build) { FactoryGirl.create :ci_build, :success, pipeline: pipeline, started_at: current - 120, finished_at: current - 60 }
447 448 449 450 451 452 453

      before do
        build
      end

      [:status, :started_at, :finished_at, :duration].each do |param|
        it "update #{param}" do
454
          expect(pipeline.send(param)).to eq(build.send(param))
455 456 457 458
        end
      end
    end
  end
Kamil Trzcinski committed
459 460

  describe '#branch?' do
461
    subject { pipeline.branch? }
Kamil Trzcinski committed
462 463 464

    context 'is not a tag' do
      before do
465
        pipeline.tag = false
Kamil Trzcinski committed
466 467 468 469 470 471 472 473 474
      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
475
        pipeline.tag = true
Kamil Trzcinski committed
476 477 478 479 480 481 482
      end

      it 'return false when tag is set to true' do
        is_expected.to be_falsey
      end
    end
  end
483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506

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

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

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

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

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

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

Connor Shea committed
508 509
  describe '#has_warnings?' do
    subject { pipeline.has_warnings? }
510 511 512

    context 'build which is allowed to fail fails' do
      before do
Connor Shea committed
513 514
        create :ci_build, :success, pipeline: pipeline, name: 'rspec'
        create :ci_build, :allowed_to_fail, :failed, pipeline: pipeline, name: 'rubocop'
515
      end
516

517 518 519 520 521 522 523
      it 'returns true' do
        is_expected.to be_truthy
      end
    end

    context 'build which is allowed to fail succeeds' do
      before do
Connor Shea committed
524 525
        create :ci_build, :success, pipeline: pipeline, name: 'rspec'
        create :ci_build, :allowed_to_fail, :success, pipeline: pipeline, name: 'rubocop'
526
      end
527

528 529 530 531
      it 'returns false' do
        is_expected.to be_falsey
      end
    end
Connor Shea committed
532 533 534 535 536 537 538 539 540 541 542 543

    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
544
  end
545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573

  describe '#execute_hooks' do
    let!(:hook) do
      create(:project_hook, project: project, pipeline_events: enabled)
    end
    let(:enabled) { raise NotImplementedError }

    before do
      WebMock.stub_request(:post, hook.url)
      pipeline.touch
      ProjectWebHookWorker.drain
    end

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

      it 'executes pipeline_hook after touched' do
        expect(WebMock).to have_requested(:post, hook.url).once
      end
    end

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

      it 'did not execute pipeline_hook after touched' do
        expect(WebMock).not_to have_requested(:post, hook.url)
      end
    end
  end
574
end