build_spec.rb 17.1 KB
Newer Older
1 2
require 'spec_helper'

Douwe Maan's avatar
Douwe Maan committed
3
describe Ci::Build, models: true do
4
  let(:project) { create(:project) }
5 6
  let(:pipeline) { create(:ci_pipeline, project: project) }
  let(:build) { create(:ci_build, pipeline: pipeline) }
7

Kamil Trzcinski's avatar
Kamil Trzcinski committed
8
  it { is_expected.to validate_presence_of :ref }
9

Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
10
  it { is_expected.to respond_to :trace_html }
11

Kamil Trzcinski's avatar
Kamil Trzcinski committed
12
  describe '#first_pending' do
13 14
    let!(:first) { create(:ci_build, pipeline: pipeline, status: 'pending', created_at: Date.yesterday) }
    let!(:second) { create(:ci_build, pipeline: pipeline, status: 'pending') }
Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
15
    subject { Ci::Build.first_pending }
16

Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
17 18
    it { is_expected.to be_a(Ci::Build) }
    it('returns with the first pending build') { is_expected.to eq(first) }
19 20
  end

Kamil Trzcinski's avatar
Kamil Trzcinski committed
21
  describe '#create_from' do
22 23 24 25
    before do
      build.status = 'success'
      build.save
    end
Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
26
    let(:create_from_build) { Ci::Build.create_from build }
27

Valery Sizov's avatar
Valery Sizov committed
28
    it 'there should be a pending task' do
Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
29
      expect(Ci::Build.pending.count(:all)).to eq 0
30
      create_from_build
Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
31
      expect(Ci::Build.pending.count(:all)).to be > 0
32 33 34
    end
  end

Kamil Trzcinski's avatar
Kamil Trzcinski committed
35
  describe '#ignored?' do
36 37 38 39 40 41 42 43
    subject { build.ignored? }

    context 'if build is not allowed to fail' do
      before { build.allow_failure = false }

      context 'and build.status is success' do
        before { build.status = 'success' }

Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
44
        it { is_expected.to be_falsey }
45 46 47 48 49
      end

      context 'and build.status is failed' do
        before { build.status = 'failed' }

Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
50
        it { is_expected.to be_falsey }
51 52 53 54 55 56 57 58 59
      end
    end

    context 'if build is allowed to fail' do
      before { build.allow_failure = true }

      context 'and build.status is success' do
        before { build.status = 'success' }

Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
60
        it { is_expected.to be_falsey }
61 62 63 64 65
      end

      context 'and build.status is failed' do
        before { build.status = 'failed' }

Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
66
        it { is_expected.to be_truthy }
67 68 69 70
      end
    end
  end

Kamil Trzcinski's avatar
Kamil Trzcinski committed
71
  describe '#trace' do
72 73
    subject { build.trace_html }

Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
74
    it { is_expected.to be_empty }
75 76 77 78 79

    context 'if build.trace contains text' do
      let(:text) { 'example output' }
      before { build.trace = text }

Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
80
      it { is_expected.to include(text) }
81
      it { expect(subject.length).to be >= text.length }
82
    end
83 84 85 86 87

    context 'if build.trace hides token' do
      let(:token) { 'my_secret_token' }

      before do
Kamil Trzcinski's avatar
Kamil Trzcinski committed
88
        build.project.update_attributes(runners_token: token)
89 90 91
        build.update_attributes(trace: token)
      end

92
      it { is_expected.not_to include(token) }
93
    end
94 95
  end

96 97 98 99
  # TODO: build timeout
  # describe :timeout do
  #   subject { build.timeout }
  #
100
  #   it { is_expected.to eq(pipeline.project.timeout) }
101
  # end
102

Kamil Trzcinski's avatar
Kamil Trzcinski committed
103
  describe '#options' do
Valery Sizov's avatar
Valery Sizov committed
104
    let(:options) do
105
      {
Valery Sizov's avatar
Valery Sizov committed
106 107
        image: "ruby:2.1",
        services: [
108 109 110
          "postgres"
        ]
      }
Valery Sizov's avatar
Valery Sizov committed
111
    end
112 113

    subject { build.options }
Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
114
    it { is_expected.to eq(options) }
115 116
  end

117 118 119 120 121 122
  # TODO: allow_git_fetch
  # describe :allow_git_fetch do
  #   subject { build.allow_git_fetch }
  #
  #   it { is_expected.to eq(project.allow_git_fetch) }
  # end
123

Kamil Trzcinski's avatar
Kamil Trzcinski committed
124
  describe '#project' do
125 126
    subject { build.project }

127
    it { is_expected.to eq(pipeline.project) }
128 129
  end

Kamil Trzcinski's avatar
Kamil Trzcinski committed
130
  describe '#project_id' do
131 132
    subject { build.project_id }

133
    it { is_expected.to eq(pipeline.project_id) }
134 135
  end

Kamil Trzcinski's avatar
Kamil Trzcinski committed
136
  describe '#project_name' do
137 138
    subject { build.project_name }

Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
139
    it { is_expected.to eq(project.name) }
140 141
  end

Kamil Trzcinski's avatar
Kamil Trzcinski committed
142
  describe '#extract_coverage' do
143 144 145
    context 'valid content & regex' do
      subject { build.extract_coverage('Coverage 1033 / 1051 LOC (98.29%) covered', '\(\d+.\d+\%\) covered') }

Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
146
      it { is_expected.to eq(98.29) }
147 148 149 150 151
    end

    context 'valid content & bad regex' do
      subject { build.extract_coverage('Coverage 1033 / 1051 LOC (98.29%) covered', 'very covered') }

Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
152
      it { is_expected.to be_nil }
153 154 155 156 157
    end

    context 'no coverage content & regex' do
      subject { build.extract_coverage('No coverage for today :sad:', '\(\d+.\d+\%\) covered') }

Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
158
      it { is_expected.to be_nil }
159 160 161 162 163
    end

    context 'multiple results in content & regex' do
      subject { build.extract_coverage(' (98.39%) covered. (98.29%) covered', '\(\d+.\d+\%\) covered') }

Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
164
      it { is_expected.to eq(98.29) }
165
    end
Jared Szechy's avatar
Jared Szechy committed
166 167 168 169 170 171

    context 'using a regex capture' do
      subject { build.extract_coverage('TOTAL      9926   3489    65%', 'TOTAL\s+\d+\s+\d+\s+(\d{1,3}\%)') }

      it { is_expected.to eq(65) }
    end
172 173
  end

Kamil Trzcinski's avatar
Kamil Trzcinski committed
174
  describe '#variables' do
175 176 177
    context 'returns variables' do
      subject { build.variables }

178 179 180 181 182 183 184 185
      let(:predefined_variables) do
        [
          { key: :CI_BUILD_NAME, value: 'test', public: true },
          { key: :CI_BUILD_STAGE, value: 'stage', public: true },
        ]
      end

      let(:yaml_variables) do
186
        [
Valery Sizov's avatar
Valery Sizov committed
187
          { key: :DB_NAME, value: 'postgres', public: true }
188
        ]
Valery Sizov's avatar
Valery Sizov committed
189
      end
190

191 192 193 194 195 196 197 198 199 200 201 202 203 204 205
      before { build.update_attributes(stage: 'stage') }

      it { is_expected.to eq(predefined_variables + yaml_variables) }

      context 'for tag' do
        let(:tag_variable) do
          [
            { key: :CI_BUILD_TAG, value: 'master', public: true }
          ]
        end

        before { build.update_attributes(tag: true) }

        it { is_expected.to eq(tag_variable + predefined_variables + yaml_variables) }
      end
206 207

      context 'and secure variables' do
Valery Sizov's avatar
Valery Sizov committed
208
        let(:secure_variables) do
209
          [
Valery Sizov's avatar
Valery Sizov committed
210
            { key: 'SECRET_KEY', value: 'secret_value', public: false }
211
          ]
Valery Sizov's avatar
Valery Sizov committed
212
        end
213 214

        before do
215
          build.project.variables << Ci::Variable.new(key: 'SECRET_KEY', value: 'secret_value')
216 217
        end

218
        it { is_expected.to eq(predefined_variables + yaml_variables + secure_variables) }
219 220

        context 'and trigger variables' do
221
          let(:trigger) { create(:ci_trigger, project: project) }
Kamil Trzcinski's avatar
Kamil Trzcinski committed
222
          let(:trigger_request) { create(:ci_trigger_request_with_variables, commit: pipeline, trigger: trigger) }
Valery Sizov's avatar
Valery Sizov committed
223
          let(:trigger_variables) do
224
            [
Valery Sizov's avatar
Valery Sizov committed
225
              { key: :TRIGGER_KEY, value: 'TRIGGER_VALUE', public: false }
226
            ]
Valery Sizov's avatar
Valery Sizov committed
227
          end
228 229 230 231 232
          let(:predefined_trigger_variable) do
            [
              { key: :CI_BUILD_TRIGGERED, value: 'true', public: true }
            ]
          end
233 234 235 236 237

          before do
            build.trigger_request = trigger_request
          end

238
          it { is_expected.to eq(predefined_variables + predefined_trigger_variable + yaml_variables + secure_variables + trigger_variables) }
239
        end
240 241

        context 'when job variables are defined' do
242 243 244
          ##
          # Job-level variables are defined in gitlab_ci.yml fixture
          #
245
          context 'when job variables are unique' do
246
            let(:build) { create(:ci_build, name: 'staging') }
247 248

            it 'includes job variables' do
249 250 251 252
              expect(subject).to include(
                { key: :KEY1, value: 'value1', public: true },
                { key: :KEY2, value: 'value2', public: true }
              )
253 254
            end
          end
255
        end
256 257 258
      end
    end
  end
Kamil Trzcinski's avatar
Kamil Trzcinski committed
259

Kamil Trzcinski's avatar
Kamil Trzcinski committed
260
  describe '#can_be_served?' do
261
    let(:runner) { create(:ci_runner) }
262

263
    before { build.project.runners << runner }
264

265
    context 'when runner does not have tags' do
266 267 268 269 270 271 272 273 274 275
      it 'can handle builds without tags' do
        expect(build.can_be_served?(runner)).to be_truthy
      end

      it 'cannot handle build with tags' do
        build.tag_list = ['aa']
        expect(build.can_be_served?(runner)).to be_falsey
      end
    end

276
    context 'when runner has tags' do
277 278
      before { runner.tag_list = ['bb', 'cc'] }

279 280 281 282 283 284 285 286 287 288
      shared_examples 'tagged build picker' do
        it 'can handle build with matching tags' do
          build.tag_list = ['bb']
          expect(build.can_be_served?(runner)).to be_truthy
        end

        it 'cannot handle build without matching tags' do
          build.tag_list = ['aa']
          expect(build.can_be_served?(runner)).to be_falsey
        end
289 290
      end

291 292 293 294 295 296
      context 'when runner can pick untagged jobs' do
        it 'can handle builds without tags' do
          expect(build.can_be_served?(runner)).to be_truthy
        end

        it_behaves_like 'tagged build picker'
297 298
      end

299 300 301 302 303 304 305 306
      context 'when runner can not pick untagged jobs' do
        before { runner.run_untagged = false }

        it 'can not handle builds without tags' do
          expect(build.can_be_served?(runner)).to be_falsey
        end

        it_behaves_like 'tagged build picker'
307 308 309 310
      end
    end
  end

311 312 313 314 315 316 317 318
  describe '#has_tags?' do
    context 'when build has tags' do
      subject { create(:ci_build, tag_list: ['tag']) }
      it { is_expected.to have_tags }
    end

    context 'when build does not have tags' do
      subject { create(:ci_build, tag_list: []) }
319
      it { is_expected.not_to have_tags }
320 321 322
    end
  end

Kamil Trzcinski's avatar
Kamil Trzcinski committed
323
  describe '#any_runners_online?' do
324 325 326 327 328 329 330
    subject { build.any_runners_online? }

    context 'when no runners' do
      it { is_expected.to be_falsey }
    end

    context 'if there are runner' do
331
      let(:runner) { create(:ci_runner) }
332 333

      before do
334
        build.project.runners << runner
335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357
        runner.update_attributes(contacted_at: 1.second.ago)
      end

      it { is_expected.to be_truthy }

      it 'that is inactive' do
        runner.update_attributes(active: false)
        is_expected.to be_falsey
      end

      it 'that is not online' do
        runner.update_attributes(contacted_at: nil)
        is_expected.to be_falsey
      end

      it 'that cannot handle build' do
        expect_any_instance_of(Ci::Build).to receive(:can_be_served?).and_return(false)
        is_expected.to be_falsey
      end

    end
  end

Kamil Trzcinski's avatar
Kamil Trzcinski committed
358
  describe '#stuck?' do
359
    subject { build.stuck? }
360 361 362 363 364 365 366 367

    %w(pending).each do |state|
      context "if commit_status.status is #{state}" do
        before { build.status = state }

        it { is_expected.to be_truthy }

        context "and there are specific runner" do
368
          let(:runner) { create(:ci_runner, contacted_at: 1.second.ago) }
369 370

          before do
371
            build.project.runners << runner
372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387
            runner.save
          end

          it { is_expected.to be_falsey }
        end
      end
    end

    %w(success failed canceled running).each do |state|
      context "if commit_status.status is #{state}" do
        before { build.status = state }

        it { is_expected.to be_falsey }
      end
    end
  end
388

Kamil Trzcinski's avatar
Kamil Trzcinski committed
389
  describe '#artifacts?' do
390 391 392 393 394 395 396 397
    subject { build.artifacts? }

    context 'artifacts archive does not exist' do
      before { build.update_attributes(artifacts_file: nil) }
      it { is_expected.to be_falsy }
    end

    context 'artifacts archive exists' do
398
      let(:build) { create(:ci_build, :artifacts) }
399 400 401 402
      it { is_expected.to be_truthy }
    end
  end

403

Kamil Trzcinski's avatar
Kamil Trzcinski committed
404
  describe '#artifacts_metadata?' do
405
    subject { build.artifacts_metadata? }
406
    context 'artifacts metadata does not exist' do
407 408 409
      it { is_expected.to be_falsy }
    end

410
    context 'artifacts archive is a zip file and metadata exists' do
411
      let(:build) { create(:ci_build, :artifacts) }
412 413 414 415
      it { is_expected.to be_truthy }
    end
  end

Kamil Trzcinski's avatar
Kamil Trzcinski committed
416
  describe '#repo_url' do
417
    let(:build) { create(:ci_build) }
418 419 420 421 422 423 424
    let(:project) { build.project }

    subject { build.repo_url }

    it { is_expected.to be_a(String) }
    it { is_expected.to end_with(".git") }
    it { is_expected.to start_with(project.web_url[0..6]) }
Kamil Trzcinski's avatar
Kamil Trzcinski committed
425
    it { is_expected.to include(build.token) }
426 427 428
    it { is_expected.to include('gitlab-ci-token') }
    it { is_expected.to include(project.web_url[7..-1]) }
  end
429

Kamil Trzcinski's avatar
Kamil Trzcinski committed
430
  describe '#depends_on_builds' do
431 432 433 434
    let!(:build) { create(:ci_build, pipeline: pipeline, name: 'build', stage_idx: 0, stage: 'build') }
    let!(:rspec_test) { create(:ci_build, pipeline: pipeline, name: 'rspec', stage_idx: 1, stage: 'test') }
    let!(:rubocop_test) { create(:ci_build, pipeline: pipeline, name: 'rubocop', stage_idx: 1, stage: 'test') }
    let!(:staging) { create(:ci_build, pipeline: pipeline, name: 'staging', stage_idx: 2, stage: 'deploy') }
435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453

    it 'to have no dependents if this is first build' do
      expect(build.depends_on_builds).to be_empty
    end

    it 'to have one dependent if this is test' do
      expect(rspec_test.depends_on_builds.map(&:id)).to contain_exactly(build.id)
    end

    it 'to have all builds from build and test stage if this is last' do
      expect(staging.depends_on_builds.map(&:id)).to contain_exactly(build.id, rspec_test.id, rubocop_test.id)
    end

    it 'to have retried builds instead the original ones' do
      retried_rspec = Ci::Build.retry(rspec_test)
      expect(staging.depends_on_builds.map(&:id)).to contain_exactly(build.id, retried_rspec.id, rubocop_test.id)
    end
  end

454 455 456
  def create_mr(build, pipeline, factory: :merge_request, created_at: Time.now)
    create(factory, source_project_id: pipeline.gl_project_id,
                    target_project_id: pipeline.gl_project_id,
457 458
                    source_branch: build.ref,
                    created_at: created_at)
459 460
  end

Kamil Trzcinski's avatar
Kamil Trzcinski committed
461
  describe '#merge_request' do
462
    context 'when a MR has a reference to the pipeline' do
463
      before do
464
        @merge_request = create_mr(build, pipeline, factory: :merge_request)
465

466
        commits = [double(id: pipeline.sha)]
467 468 469 470 471 472 473 474 475
        allow(@merge_request).to receive(:commits).and_return(commits)
        allow(MergeRequest).to receive_message_chain(:includes, :where, :reorder).and_return([@merge_request])
      end

      it 'returns the single associated MR' do
        expect(build.merge_request.id).to eq(@merge_request.id)
      end
    end

476
    context 'when there is not a MR referencing the pipeline' do
477 478 479 480 481
      it 'returns nil' do
        expect(build.merge_request).to be_nil
      end
    end

482
    context 'when more than one MR have a reference to the pipeline' do
483
      before do
484
        @merge_request = create_mr(build, pipeline, factory: :merge_request)
485
        @merge_request.close!
486
        @merge_request2 = create_mr(build, pipeline, factory: :merge_request)
487

488
        commits = [double(id: pipeline.sha)]
489 490 491 492 493 494 495 496 497 498 499 500
        allow(@merge_request).to receive(:commits).and_return(commits)
        allow(@merge_request2).to receive(:commits).and_return(commits)
        allow(MergeRequest).to receive_message_chain(:includes, :where, :reorder).and_return([@merge_request, @merge_request2])
      end

      it 'returns the first MR' do
        expect(build.merge_request.id).to eq(@merge_request.id)
      end
    end

    context 'when a Build is created after the MR' do
      before do
501 502 503
        @merge_request = create_mr(build, pipeline, factory: :merge_request_with_diffs)
        pipeline2 = create(:ci_pipeline, project: project)
        @build2 = create(:ci_build, pipeline: pipeline2)
504

505
        commits = [double(id: pipeline.sha), double(id: pipeline2.sha)]
506 507 508 509 510 511 512 513 514
        allow(@merge_request).to receive(:commits).and_return(commits)
        allow(MergeRequest).to receive_message_chain(:includes, :where, :reorder).and_return([@merge_request])
      end

      it 'returns the current MR' do
        expect(@build2.merge_request.id).to eq(@merge_request.id)
      end
    end
  end
515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534

  describe 'build erasable' do
    shared_examples 'erasable' do
      it 'should remove artifact file' do
        expect(build.artifacts_file.exists?).to be_falsy
      end

      it 'should remove artifact metadata file' do
        expect(build.artifacts_metadata.exists?).to be_falsy
      end

      it 'should erase build trace in trace file' do
        expect(build.trace).to be_empty
      end

      it 'should set erased to true' do
        expect(build.erased?).to be true
      end

      it 'should set erase date' do
535
        expect(build.erased_at).not_to be_falsy
536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554
      end
    end

    context 'build is not erasable' do
      let!(:build) { create(:ci_build) }

      describe '#erase' do
        subject { build.erase }

        it { is_expected.to be false }
      end

      describe '#erasable?' do
        subject { build.erasable? }
        it { is_expected.to eq false }
      end
    end

    context 'build is erasable' do
Kamil Trzcinski's avatar
Kamil Trzcinski committed
555
      let!(:build) { create(:ci_build, :trace, :success, :artifacts) }
556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586

      describe '#erase' do
        before { build.erase(erased_by: user) }

        context 'erased by user' do
          let!(:user) { create(:user, username: 'eraser') }

          include_examples 'erasable'

          it 'should record user who erased a build' do
            expect(build.erased_by).to eq user
          end
        end

        context 'erased by system' do
          let(:user) { nil }

          include_examples 'erasable'

          it 'should not set user who erased a build' do
            expect(build.erased_by).to be_nil
          end
        end
      end

      describe '#erasable?' do
        subject { build.erasable? }
        it { is_expected.to eq true }
      end

      describe '#erased?' do
Kamil Trzcinski's avatar
Kamil Trzcinski committed
587
        let!(:build) { create(:ci_build, :trace, :success, :artifacts) }
588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606
        subject { build.erased? }

        context 'build has not been erased' do
          it { is_expected.to be false }
        end

        context 'build has been erased' do
          before { build.erase }

          it { is_expected.to be true }
        end
      end

      context 'metadata and build trace are not available' do
        let!(:build) { create(:ci_build, :success, :artifacts) }
        before { build.remove_artifacts_metadata! }

        describe '#erase' do
          it 'should not raise error' do
607
            expect { build.erase }.not_to raise_error
608 609 610 611 612
          end
        end
      end
    end
  end
613
end