BigW Consortium Gitlab

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

Douwe Maan committed
3
describe Ci::Build, models: true do
Douwe Maan committed
4
  let(:project) { FactoryGirl.create :project }
5
  let(:commit) { FactoryGirl.create :ci_commit, project: project }
6
  let(:build) { FactoryGirl.create :ci_build, commit: commit }
7

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

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

Kamil Trzcinski committed
12
  describe '#first_pending' do
13 14
    let(:first) { FactoryGirl.create :ci_build, commit: commit, status: 'pending', created_at: Date.yesterday }
    let(:second) { FactoryGirl.create :ci_build, commit: commit, status: 'pending' }
15
    before { first; second }
16
    subject { Ci::Build.first_pending }
17

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

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

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

Kamil Trzcinski committed
36
  describe '#ignored?' do
37 38 39 40 41 42 43 44
    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 committed
45
        it { is_expected.to be_falsey }
46 47 48 49 50
      end

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

Dmitriy Zaporozhets committed
51
        it { is_expected.to be_falsey }
52 53 54 55 56 57 58 59 60
      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 committed
61
        it { is_expected.to be_falsey }
62 63 64 65 66
      end

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

Dmitriy Zaporozhets committed
67
        it { is_expected.to be_truthy }
68 69 70 71
      end
    end
  end

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

Dmitriy Zaporozhets committed
75
    it { is_expected.to be_empty }
76 77 78 79 80

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

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

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

      before do
89
        build.project.update_attributes(runners_token: token)
90 91 92 93 94
        build.update_attributes(trace: token)
      end

      it { is_expected.to_not include(token) }
    end
95 96
  end

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

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

    subject { build.options }
Dmitriy Zaporozhets committed
115
    it { is_expected.to eq(options) }
116 117
  end

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

Kamil Trzcinski committed
125
  describe '#project' do
126 127
    subject { build.project }

Dmitriy Zaporozhets committed
128
    it { is_expected.to eq(commit.project) }
129 130
  end

Kamil Trzcinski committed
131
  describe '#project_id' do
132 133
    subject { build.project_id }

Dmitriy Zaporozhets committed
134
    it { is_expected.to eq(commit.project_id) }
135 136
  end

Kamil Trzcinski committed
137
  describe '#project_name' do
138 139
    subject { build.project_name }

Dmitriy Zaporozhets committed
140
    it { is_expected.to eq(project.name) }
141 142
  end

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

Dmitriy Zaporozhets committed
147
      it { is_expected.to eq(98.29) }
148 149 150 151 152
    end

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

Dmitriy Zaporozhets committed
153
      it { is_expected.to be_nil }
154 155 156 157 158
    end

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

Dmitriy Zaporozhets committed
159
      it { is_expected.to be_nil }
160 161 162 163 164
    end

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

Dmitriy Zaporozhets committed
165
      it { is_expected.to eq(98.29) }
166
    end
167 168 169 170 171 172

    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
173 174
  end

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

179 180 181 182 183 184 185 186
      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
187
        [
Valery Sizov committed
188
          { key: :DB_NAME, value: 'postgres', public: true }
189
        ]
Valery Sizov committed
190
      end
191

192 193 194 195 196 197 198 199 200 201 202 203 204 205 206
      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
207 208

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

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

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

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

          before do
            build.trigger_request = trigger_request
          end

239
          it { is_expected.to eq(predefined_variables + predefined_trigger_variable + yaml_variables + secure_variables + trigger_variables) }
240 241 242 243
        end
      end
    end
  end
Kamil Trzcinski committed
244

Kamil Trzcinski committed
245
  describe '#can_be_served?' do
246
    let(:runner) { FactoryGirl.create :ci_runner }
247

248
    before { build.project.runners << runner }
249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279

    context 'runner without tags' do
      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

    context 'runner with tags' do
      before { runner.tag_list = ['bb', 'cc'] }

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

      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 with not matching tags' do
        build.tag_list = ['aa']
        expect(build.can_be_served?(runner)).to be_falsey
      end
    end
  end

Kamil Trzcinski committed
280
  describe '#any_runners_online?' do
281 282 283 284 285 286 287
    subject { build.any_runners_online? }

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

    context 'if there are runner' do
288
      let(:runner) { FactoryGirl.create :ci_runner }
289 290

      before do
291
        build.project.runners << runner
292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314
        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 committed
315
  describe '#stuck?' do
316
    subject { build.stuck? }
317 318 319 320 321 322 323 324

    %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
325
          let(:runner) { FactoryGirl.create :ci_runner, contacted_at: 1.second.ago }
326 327

          before do
328
            build.project.runners << runner
329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344
            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
345

Kamil Trzcinski committed
346
  describe '#artifacts?' do
347 348 349 350 351 352 353 354
    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
355
      let(:build) { create(:ci_build, :artifacts) }
356 357 358 359
      it { is_expected.to be_truthy }
    end
  end

360

Kamil Trzcinski committed
361
  describe '#artifacts_metadata?' do
362
    subject { build.artifacts_metadata? }
363
    context 'artifacts metadata does not exist' do
364 365 366
      it { is_expected.to be_falsy }
    end

367
    context 'artifacts archive is a zip file and metadata exists' do
368
      let(:build) { create(:ci_build, :artifacts) }
369 370 371 372
      it { is_expected.to be_truthy }
    end
  end

Kamil Trzcinski committed
373
  describe '#repo_url' do
374 375 376 377 378 379 380 381
    let(:build) { FactoryGirl.create :ci_build }
    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 committed
382
    it { is_expected.to include(build.token) }
383 384 385
    it { is_expected.to include('gitlab-ci-token') }
    it { is_expected.to include(project.web_url[7..-1]) }
  end
386

Kamil Trzcinski committed
387
  describe '#depends_on_builds' do
388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410
    let!(:build) { FactoryGirl.create :ci_build, commit: commit, name: 'build', stage_idx: 0, stage: 'build' }
    let!(:rspec_test) { FactoryGirl.create :ci_build, commit: commit, name: 'rspec', stage_idx: 1, stage: 'test' }
    let!(:rubocop_test) { FactoryGirl.create :ci_build, commit: commit, name: 'rubocop', stage_idx: 1, stage: 'test' }
    let!(:staging) { FactoryGirl.create :ci_build, commit: commit, name: 'staging', stage_idx: 2, stage: 'deploy' }

    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

411 412 413 414 415 416 417 418
  def create_mr(build, commit, factory: :merge_request, created_at: Time.now)
    FactoryGirl.create(factory,
                       source_project_id: commit.gl_project_id,
                       target_project_id: commit.gl_project_id,
                       source_branch: build.ref,
                       created_at: created_at)
  end

Kamil Trzcinski committed
419
  describe '#merge_request' do
420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459
    context 'when a MR has a reference to the commit' do
      before do
        @merge_request = create_mr(build, commit, factory: :merge_request)

        commits = [double(id: commit.sha)]
        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

    context 'when there is not a MR referencing the commit' do
      it 'returns nil' do
        expect(build.merge_request).to be_nil
      end
    end

    context 'when more than one MR have a reference to the commit' do
      before do
        @merge_request = create_mr(build, commit, factory: :merge_request)
        @merge_request.close!
        @merge_request2 = create_mr(build, commit, factory: :merge_request)

        commits = [double(id: commit.sha)]
        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
        @merge_request = create_mr(build, commit, factory: :merge_request_with_diffs)
460
        commit2 = FactoryGirl.create :ci_commit, project: project
461 462 463 464 465 466 467 468 469 470 471 472
        @build2 = FactoryGirl.create :ci_build, commit: commit2

        commits = [double(id: commit.sha), double(id: commit2.sha)]
        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
473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512

  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
        expect(build.erased_at).to_not be_falsy
      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
513
      let!(:build) { create(:ci_build, :trace, :success, :artifacts) }
514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544

      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
545
        let!(:build) { create(:ci_build, :trace, :success, :artifacts) }
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
        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
            expect { build.erase }.to_not raise_error
          end
        end
      end
    end
  end
571
end