commit_status_spec.rb 16.3 KB
Newer Older
1 2
require 'spec_helper'

3
describe CommitStatus do
4
  set(:project) { create(:project, :repository) }
5

6
  set(:pipeline) do
7 8 9
    create(:ci_pipeline, project: project, sha: project.commit.id)
  end

10
  let(:commit_status) { create_status(stage: 'test') }
11

12 13
  def create_status(**opts)
    create(:commit_status, pipeline: pipeline, **opts)
14
  end
15

Shinya Maeda's avatar
Shinya Maeda committed
16 17
  it_behaves_like 'having unique enum values'

18
  it { is_expected.to belong_to(:pipeline) }
19
  it { is_expected.to belong_to(:user) }
Kamil Trzcinski's avatar
Kamil Trzcinski committed
20
  it { is_expected.to belong_to(:project) }
21
  it { is_expected.to belong_to(:auto_canceled_by) }
Kamil Trzcinski's avatar
Kamil Trzcinski committed
22

23 24 25
  it { is_expected.to validate_presence_of(:name) }
  it { is_expected.to validate_inclusion_of(:status).in_array(%w(pending running failed success canceled)) }

26 27
  it { is_expected.to delegate_method(:sha).to(:pipeline) }
  it { is_expected.to delegate_method(:short_sha).to(:pipeline) }
28

29 30 31 32 33
  it { is_expected.to respond_to :success? }
  it { is_expected.to respond_to :failed? }
  it { is_expected.to respond_to :running? }
  it { is_expected.to respond_to :pending? }

34
  describe '#author' do
Kamil Trzcinski's avatar
Kamil Trzcinski committed
35
    subject { commit_status.author }
36 37 38 39

    before do
      commit_status.author = User.new
    end
Kamil Trzcinski's avatar
Kamil Trzcinski committed
40 41 42 43

    it { is_expected.to eq(commit_status.user) }
  end

44 45 46 47 48 49 50 51 52 53
  describe 'status state machine' do
    let!(:commit_status) { create(:commit_status, :running, project: project) }

    it 'invalidates the cache after a transition' do
      expect(ExpireJobCacheWorker).to receive(:perform_async).with(commit_status.id)

      commit_status.success!
    end
  end

54
  describe '#started?' do
55 56 57
    subject { commit_status.started? }

    context 'without started_at' do
58 59 60
      before do
        commit_status.started_at = nil
      end
61 62 63 64

      it { is_expected.to be_falsey }
    end

Lin Jen-Shin's avatar
Lin Jen-Shin committed
65
    %w[running success failed].each do |status|
66
      context "if commit status is #{status}" do
67 68 69
        before do
          commit_status.status = status
        end
70 71 72 73 74

        it { is_expected.to be_truthy }
      end
    end

Lin Jen-Shin's avatar
Lin Jen-Shin committed
75
    %w[pending canceled].each do |status|
76
      context "if commit status is #{status}" do
77 78 79
        before do
          commit_status.status = status
        end
80 81 82 83 84 85

        it { is_expected.to be_falsey }
      end
    end
  end

86
  describe '#active?' do
87 88
    subject { commit_status.active? }

Lin Jen-Shin's avatar
Lin Jen-Shin committed
89
    %w[pending running].each do |state|
90
      context "if commit_status.status is #{state}" do
91 92 93
        before do
          commit_status.status = state
        end
94 95 96 97 98

        it { is_expected.to be_truthy }
      end
    end

Lin Jen-Shin's avatar
Lin Jen-Shin committed
99
    %w[success failed canceled].each do |state|
100
      context "if commit_status.status is #{state}" do
101 102 103
        before do
          commit_status.status = state
        end
104 105 106 107 108 109

        it { is_expected.to be_falsey }
      end
    end
  end

110
  describe '#complete?' do
111 112
    subject { commit_status.complete? }

Lin Jen-Shin's avatar
Lin Jen-Shin committed
113
    %w[success failed canceled].each do |state|
114
      context "if commit_status.status is #{state}" do
115 116 117
        before do
          commit_status.status = state
        end
118 119 120 121 122

        it { is_expected.to be_truthy }
      end
    end

Lin Jen-Shin's avatar
Lin Jen-Shin committed
123
    %w[pending running].each do |state|
124
      context "if commit_status.status is #{state}" do
125 126 127
        before do
          commit_status.status = state
        end
128 129 130 131 132 133

        it { is_expected.to be_falsey }
      end
    end
  end

134 135 136 137 138 139 140 141 142 143 144 145 146 147
  describe '#cancel' do
    subject { job.cancel }

    context 'when status is scheduled' do
      let(:job) { build(:commit_status, :scheduled) }

      it 'updates the status' do
        subject

        expect(job).to be_canceled
      end
    end
  end

148 149 150 151 152
  describe '#auto_canceled?' do
    subject { commit_status.auto_canceled? }

    context 'when it is canceled' do
      before do
Lin Jen-Shin's avatar
Lin Jen-Shin committed
153
        commit_status.update(status: 'canceled')
154 155 156 157
      end

      context 'when there is auto_canceled_by' do
        before do
Lin Jen-Shin's avatar
Lin Jen-Shin committed
158
          commit_status.update(auto_canceled_by: create(:ci_empty_pipeline))
159 160 161 162 163 164 165 166 167 168 169 170 171 172 173
        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
    end
  end

174
  describe '#duration' do
175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197
    subject { commit_status.duration }

    it { is_expected.to eq(120.0) }

    context 'if the building process has not started yet' do
      before do
        commit_status.started_at = nil
        commit_status.finished_at = nil
      end

      it { is_expected.to be_nil }
    end

    context 'if the building process has started' do
      before do
        commit_status.started_at = Time.now - 1.minute
        commit_status.finished_at = nil
      end

      it { is_expected.to be_a(Float) }
      it { is_expected.to be > 0.0 }
    end
  end
198

199
  describe '.latest' do
200
    subject { described_class.latest.order(:id) }
201

202
    let(:statuses) do
203 204 205
      [create_status(name: 'aa', ref: 'bb', status: 'running', retried: true),
       create_status(name: 'cc', ref: 'cc', status: 'pending', retried: true),
       create_status(name: 'aa', ref: 'cc', status: 'success', retried: true),
206 207
       create_status(name: 'cc', ref: 'bb', status: 'success'),
       create_status(name: 'aa', ref: 'bb', status: 'success')]
208 209
    end

210
    it 'returns unique statuses' do
211
      is_expected.to eq(statuses.values_at(3, 4))
212 213 214
    end
  end

215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230
  describe '.retried' do
    subject { described_class.retried.order(:id) }

    let(:statuses) do
      [create_status(name: 'aa', ref: 'bb', status: 'running', retried: true),
       create_status(name: 'cc', ref: 'cc', status: 'pending', retried: true),
       create_status(name: 'aa', ref: 'cc', status: 'success', retried: true),
       create_status(name: 'cc', ref: 'bb', status: 'success'),
       create_status(name: 'aa', ref: 'bb', status: 'success')]
    end

    it 'returns unique statuses' do
      is_expected.to contain_exactly(*statuses.values_at(0, 1, 2))
    end
  end

231
  describe '.running_or_pending' do
232
    subject { described_class.running_or_pending.order(:id) }
233

234 235 236 237 238 239
    let(:statuses) do
      [create_status(name: 'aa', ref: 'bb', status: 'running'),
       create_status(name: 'cc', ref: 'cc', status: 'pending'),
       create_status(name: 'aa', ref: nil, status: 'success'),
       create_status(name: 'dd', ref: nil, status: 'failed'),
       create_status(name: 'ee', ref: nil, status: 'canceled')]
240 241
    end

242
    it 'returns statuses that are running or pending' do
243
      is_expected.to contain_exactly(*statuses.values_at(0, 1))
244 245 246
    end
  end

247
  describe '.after_stage' do
248 249 250 251 252 253 254 255 256 257 258 259 260 261 262
    subject { described_class.after_stage(0) }

    let(:statuses) do
      [create_status(name: 'aa', stage_idx: 0),
       create_status(name: 'cc', stage_idx: 1),
       create_status(name: 'aa', stage_idx: 2)]
    end

    it 'returns statuses from second and third stage' do
      is_expected.to eq(statuses.values_at(1, 2))
    end
  end

  describe '.exclude_ignored' do
    subject { described_class.exclude_ignored.order(:id) }
263 264 265 266 267 268 269 270 271 272 273

    let(:statuses) do
      [create_status(when: 'manual', status: 'skipped'),
       create_status(when: 'manual', status: 'success'),
       create_status(when: 'manual', status: 'failed'),
       create_status(when: 'on_failure', status: 'skipped'),
       create_status(when: 'on_failure', status: 'success'),
       create_status(when: 'on_failure', status: 'failed'),
       create_status(allow_failure: true, status: 'success'),
       create_status(allow_failure: true, status: 'failed'),
       create_status(allow_failure: false, status: 'success'),
274 275 276
       create_status(allow_failure: false, status: 'failed'),
       create_status(allow_failure: true, status: 'manual'),
       create_status(allow_failure: false, status: 'manual')]
277 278 279
    end

    it 'returns statuses without what we want to ignore' do
280
      is_expected.to eq(statuses.values_at(0, 1, 2, 3, 4, 5, 6, 8, 9, 11))
281 282
    end
  end
283

284 285 286 287
  describe '.failed_but_allowed' do
    subject { described_class.failed_but_allowed.order(:id) }

    let(:statuses) do
288 289 290 291 292 293 294 295
      [create_status(allow_failure: true, status: 'success'),
       create_status(allow_failure: true, status: 'failed'),
       create_status(allow_failure: false, status: 'success'),
       create_status(allow_failure: false, status: 'failed'),
       create_status(allow_failure: true, status: 'canceled'),
       create_status(allow_failure: false, status: 'canceled'),
       create_status(allow_failure: true, status: 'manual'),
       create_status(allow_failure: false, status: 'manual')]
296 297 298 299 300 301 302
    end

    it 'returns statuses without what we want to ignore' do
      is_expected.to eq(statuses.values_at(1, 4))
    end
  end

303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324
  describe '.status' do
    context 'when there are multiple statuses present' do
      before do
        create_status(status: 'running')
        create_status(status: 'success')
        create_status(allow_failure: true, status: 'failed')
      end

      it 'returns a correct compound status' do
        expect(described_class.all.status).to eq 'running'
      end
    end

    context 'when there are only allowed to fail commit statuses present' do
      before do
        create_status(allow_failure: true, status: 'failed')
      end

      it 'returns status that indicates success' do
        expect(described_class.all.status).to eq 'success'
      end
    end
325 326 327

    context 'when using a scope to select latest statuses' do
      before do
328
        create_status(name: 'test', retried: true, status: 'failed')
329 330 331 332 333 334 335
        create_status(allow_failure: true, name: 'test', status: 'failed')
      end

      it 'returns status according to the scope' do
        expect(described_class.latest.status).to eq 'success'
      end
    end
336 337
  end

338 339 340
  describe '#before_sha' do
    subject { commit_status.before_sha }

341
    context 'when no before_sha is set for pipeline' do
342 343 344
      before do
        pipeline.before_sha = nil
      end
345

346
      it 'returns blank sha' do
347 348 349 350
        is_expected.to eq(Gitlab::Git::BLANK_SHA)
      end
    end

351
    context 'for before_sha set for pipeline' do
352
      let(:value) { '1234' }
353 354 355 356

      before do
        pipeline.before_sha = value
      end
357

358
      it 'returns the set value' do
359 360 361 362 363
        is_expected.to eq(value)
      end
    end
  end

364 365 366 367 368
  describe '#commit' do
    it 'returns commit pipeline has been created for' do
      expect(commit_status.commit).to eq project.commit
    end
  end
Kamil Trzcinski's avatar
Kamil Trzcinski committed
369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385

  describe '#group_name' do
    subject { commit_status.group_name }

    tests = {
      'rspec:windows' => 'rspec:windows',
      'rspec:windows 0' => 'rspec:windows 0',
      'rspec:windows 0 test' => 'rspec:windows 0 test',
      'rspec:windows 0 1' => 'rspec:windows',
      'rspec:windows 0 1 name' => 'rspec:windows name',
      'rspec:windows 0/1' => 'rspec:windows',
      'rspec:windows 0/1 name' => 'rspec:windows name',
      'rspec:windows 0:1' => 'rspec:windows',
      'rspec:windows 0:1 name' => 'rspec:windows name',
      'rspec:windows 10000 20000' => 'rspec:windows',
      'rspec:windows 0 : / 1' => 'rspec:windows',
      'rspec:windows 0 : / 1 name' => 'rspec:windows name',
Kamil Trzcinski's avatar
Kamil Trzcinski committed
386
      '0 1 name ruby' => 'name ruby',
387
      '0 :/ 1 name ruby' => 'name ruby'
Kamil Trzcinski's avatar
Kamil Trzcinski committed
388 389 390 391 392 393 394 395 396 397
    }

    tests.each do |name, group_name|
      it "'#{name}' puts in '#{group_name}'" do
        commit_status.name = name

        is_expected.to eq(group_name)
      end
    end
  end
398 399 400 401 402 403 404 405 406

  describe '#detailed_status' do
    let(:user) { create(:user) }

    it 'returns a detailed status' do
      expect(commit_status.detailed_status(user))
        .to be_a Gitlab::Ci::Status::Success
    end
  end
407 408 409 410 411 412 413 414 415 416 417 418 419 420 421

  describe '#sortable_name' do
    tests = {
      'karma' => ['karma'],
      'karma 0 20' => ['karma ', 0, ' ', 20],
      'karma 10 20' => ['karma ', 10, ' ', 20],
      'karma 50:100' => ['karma ', 50, ':', 100],
      'karma 1.10' => ['karma ', 1, '.', 10],
      'karma 1.5.1' => ['karma ', 1, '.', 5, '.', 1],
      'karma 1 a' => ['karma ', 1, ' a']
    }

    tests.each do |name, sortable_name|
      it "'#{name}' sorts as '#{sortable_name}'" do
        commit_status.name = name
422
        expect(commit_status.sortable_name).to eq(sortable_name)
423 424 425
      end
    end
  end
426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443

  describe '#locking_enabled?' do
    before do
      commit_status.lock_version = 100
    end

    subject { commit_status.locking_enabled? }

    context "when changing status" do
      before do
        commit_status.status = "running"
      end

      it "lock" do
        is_expected.to be true
      end

      it "raise exception when trying to update" do
444
        expect { commit_status.save }.to raise_error(ActiveRecord::StaleObjectError)
445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461
      end
    end

    context "when changing description" do
      before do
        commit_status.description = "test"
      end

      it "do not lock" do
        is_expected.to be false
      end

      it "save correctly" do
        expect(commit_status.save).to be true
      end
    end
  end
Shinya Maeda's avatar
Shinya Maeda committed
462 463

  describe 'set failure_reason when drop' do
464
    let(:commit_status) { create(:commit_status, :created) }
Shinya Maeda's avatar
Shinya Maeda committed
465

Shinya Maeda's avatar
Shinya Maeda committed
466 467 468 469
    subject do
      commit_status.drop!(reason)
      commit_status
    end
Shinya Maeda's avatar
Shinya Maeda committed
470 471 472 473

    context 'when failure_reason is nil' do
      let(:reason) { }

474
      it { is_expected.to be_unknown_failure }
Shinya Maeda's avatar
Shinya Maeda committed
475 476
    end

477 478
    context 'when failure_reason is script_failure' do
      let(:reason) { :script_failure }
Shinya Maeda's avatar
Shinya Maeda committed
479

480
      it { is_expected.to be_script_failure }
Shinya Maeda's avatar
Shinya Maeda committed
481 482
    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 507 508 509 510 511 512 513 514 515 516 517

  describe 'ensure stage assignment' do
    context 'when commit status has a stage_id assigned' do
      let!(:stage) do
        create(:ci_stage_entity, project: project, pipeline: pipeline)
      end

      let(:commit_status) do
        create(:commit_status, stage_id: stage.id, name: 'rspec', stage: 'test')
      end

      it 'does not create a new stage' do
        expect { commit_status }.not_to change { Ci::Stage.count }
        expect(commit_status.stage_id).to eq stage.id
      end
    end

    context 'when commit status does not have a stage_id assigned' do
      let(:commit_status) do
        create(:commit_status, name: 'rspec', stage: 'test', status: :success)
      end

      let(:stage) { Ci::Stage.first }

      it 'creates a new stage' do
        expect { commit_status }.to change { Ci::Stage.count }.by(1)

        expect(stage.name).to eq 'test'
        expect(stage.project).to eq commit_status.project
        expect(stage.pipeline).to eq commit_status.pipeline
        expect(stage.status).to eq commit_status.status
        expect(commit_status.stage_id).to eq stage.id
      end
    end

518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540
    context 'when commit status does not have stage but it exists' do
      let!(:stage) do
        create(:ci_stage_entity, project: project,
                                 pipeline: pipeline,
                                 name: 'test')
      end

      let(:commit_status) do
        create(:commit_status, project: project,
                               pipeline: pipeline,
                               name: 'rspec',
                               stage: 'test',
                               status: :success)
      end

      it 'uses existing stage' do
        expect { commit_status }.not_to change { Ci::Stage.count }

        expect(commit_status.stage_id).to eq stage.id
        expect(stage.reload.status).to eq commit_status.status
      end
    end

541 542 543 544 545 546 547 548 549 550 551
    context 'when commit status is being imported' do
      let(:commit_status) do
        create(:commit_status, name: 'rspec', stage: 'test', importing: true)
      end

      it 'does not create a new stage' do
        expect { commit_status }.not_to change { Ci::Stage.count }
        expect(commit_status.stage_id).not_to be_present
      end
    end
  end
552 553 554 555 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

  describe '#enqueue' do
    let!(:current_time) { Time.new(2018, 4, 5, 14, 0, 0) }

    before do
      allow(Time).to receive(:now).and_return(current_time)
    end

    shared_examples 'commit status enqueued' do
      it 'sets queued_at value when enqueued' do
        expect { commit_status.enqueue }.to change { commit_status.reload.queued_at }.from(nil).to(current_time)
      end
    end

    context 'when initial state is :created' do
      let(:commit_status) { create(:commit_status, :created) }

      it_behaves_like 'commit status enqueued'
    end

    context 'when initial state is :skipped' do
      let(:commit_status) { create(:commit_status, :skipped) }

      it_behaves_like 'commit status enqueued'
    end

    context 'when initial state is :manual' do
      let(:commit_status) { create(:commit_status, :manual) }

      it_behaves_like 'commit status enqueued'
    end
583 584 585 586 587 588

    context 'when initial state is :scheduled' do
      let(:commit_status) { create(:commit_status, :scheduled) }

      it_behaves_like 'commit status enqueued'
    end
589
  end
590 591

  describe '#present' do
592
    subject { commit_status.present }
593

594
    it { is_expected.to be_a(CommitStatusPresenter) }
595
  end
596
end