1# frozen_string_literal: true 2 3require 'spec_helper' 4 5RSpec.describe Ci::Build do 6 let_it_be(:user) { create(:user) } 7 let_it_be(:group, reload: true) { create(:group) } 8 let_it_be(:project, reload: true) { create(:project, :repository, group: group) } 9 10 let_it_be(:pipeline, reload: true) do 11 create(:ci_pipeline, project: project, 12 sha: project.commit.id, 13 ref: project.default_branch, 14 status: 'success') 15 end 16 17 let_it_be(:build, refind: true) { create(:ci_build, pipeline: pipeline) } 18 19 it { is_expected.to belong_to(:runner) } 20 it { is_expected.to belong_to(:trigger_request) } 21 it { is_expected.to belong_to(:erased_by) } 22 23 it { is_expected.to have_many(:needs) } 24 it { is_expected.to have_many(:sourced_pipelines) } 25 it { is_expected.to have_many(:job_variables) } 26 it { is_expected.to have_many(:report_results) } 27 it { is_expected.to have_many(:pages_deployments) } 28 29 it { is_expected.to have_one(:deployment) } 30 it { is_expected.to have_one(:runner_session) } 31 it { is_expected.to have_one(:trace_metadata) } 32 it { is_expected.to have_many(:terraform_state_versions).inverse_of(:build) } 33 34 it { is_expected.to validate_presence_of(:ref) } 35 36 it { is_expected.to respond_to(:has_trace?) } 37 it { is_expected.to respond_to(:trace) } 38 it { is_expected.to respond_to(:set_cancel_gracefully) } 39 it { is_expected.to respond_to(:cancel_gracefully?) } 40 41 it { is_expected.to delegate_method(:merge_request?).to(:pipeline) } 42 it { is_expected.to delegate_method(:merge_request_ref?).to(:pipeline) } 43 it { is_expected.to delegate_method(:legacy_detached_merge_request_pipeline?).to(:pipeline) } 44 45 shared_examples 'calling proper BuildFinishedWorker' do 46 context 'when ci_build_finished_worker_namespace_changed feature flag enabled' do 47 before do 48 stub_feature_flags(ci_build_finished_worker_namespace_changed: build.project) 49 end 50 51 it 'calls Ci::BuildFinishedWorker' do 52 expect(Ci::BuildFinishedWorker).to receive(:perform_async) 53 expect(::BuildFinishedWorker).not_to receive(:perform_async) 54 55 subject 56 end 57 end 58 59 context 'when ci_build_finished_worker_namespace_changed feature flag disabled' do 60 before do 61 stub_feature_flags(ci_build_finished_worker_namespace_changed: false) 62 end 63 64 it 'calls ::BuildFinishedWorker' do 65 expect(::BuildFinishedWorker).to receive(:perform_async) 66 expect(Ci::BuildFinishedWorker).not_to receive(:perform_async) 67 68 subject 69 end 70 end 71 end 72 73 describe 'associations' do 74 it 'has a bidirectional relationship with projects' do 75 expect(described_class.reflect_on_association(:project).has_inverse?).to eq(:builds) 76 expect(Project.reflect_on_association(:builds).has_inverse?).to eq(:project) 77 end 78 end 79 80 describe 'callbacks' do 81 context 'when running after_create callback' do 82 it 'triggers asynchronous build hooks worker' do 83 expect(BuildHooksWorker).to receive(:perform_async) 84 85 create(:ci_build) 86 end 87 end 88 end 89 90 describe 'status' do 91 context 'when transitioning to any state from running' do 92 it 'removes runner_session' do 93 %w(success drop cancel).each do |event| 94 build = FactoryBot.create(:ci_build, :running, :with_runner_session, pipeline: pipeline) 95 96 build.fire_events!(event) 97 98 expect(build.reload.runner_session).to be_nil 99 end 100 end 101 end 102 end 103 104 describe '.manual_actions' do 105 let!(:manual_but_created) { create(:ci_build, :manual, status: :created, pipeline: pipeline) } 106 let!(:manual_but_succeeded) { create(:ci_build, :manual, status: :success, pipeline: pipeline) } 107 let!(:manual_action) { create(:ci_build, :manual, pipeline: pipeline) } 108 109 subject { described_class.manual_actions } 110 111 it { is_expected.to include(manual_action) } 112 it { is_expected.to include(manual_but_succeeded) } 113 it { is_expected.not_to include(manual_but_created) } 114 end 115 116 describe '.ref_protected' do 117 subject { described_class.ref_protected } 118 119 context 'when protected is true' do 120 let!(:job) { create(:ci_build, :protected) } 121 122 it { is_expected.to include(job) } 123 end 124 125 context 'when protected is false' do 126 let!(:job) { create(:ci_build) } 127 128 it { is_expected.not_to include(job) } 129 end 130 131 context 'when protected is nil' do 132 let!(:job) { create(:ci_build) } 133 134 before do 135 job.update_attribute(:protected, nil) 136 end 137 138 it { is_expected.not_to include(job) } 139 end 140 end 141 142 describe '.with_downloadable_artifacts' do 143 subject { described_class.with_downloadable_artifacts } 144 145 context 'when job does not have a downloadable artifact' do 146 let!(:job) { create(:ci_build) } 147 148 it 'does not return the job' do 149 is_expected.not_to include(job) 150 end 151 end 152 153 ::Ci::JobArtifact::DOWNLOADABLE_TYPES.each do |type| 154 context "when job has a #{type} artifact" do 155 it 'returns the job' do 156 job = create(:ci_build) 157 create( 158 :ci_job_artifact, 159 file_format: ::Ci::JobArtifact::TYPE_AND_FORMAT_PAIRS[type.to_sym], 160 file_type: type, 161 job: job 162 ) 163 164 is_expected.to include(job) 165 end 166 end 167 end 168 169 context 'when job has a non-downloadable artifact' do 170 let!(:job) { create(:ci_build, :trace_artifact) } 171 172 it 'does not return the job' do 173 is_expected.not_to include(job) 174 end 175 end 176 end 177 178 describe '.with_live_trace' do 179 subject { described_class.with_live_trace } 180 181 context 'when build has live trace' do 182 let!(:build) { create(:ci_build, :success, :trace_live) } 183 184 it 'selects the build' do 185 is_expected.to eq([build]) 186 end 187 end 188 189 context 'when build does not have live trace' do 190 let!(:build) { create(:ci_build, :success, :trace_artifact) } 191 192 it 'does not select the build' do 193 is_expected.to be_empty 194 end 195 end 196 end 197 198 describe '.with_stale_live_trace' do 199 subject { described_class.with_stale_live_trace } 200 201 context 'when build has a stale live trace' do 202 let!(:build) { create(:ci_build, :success, :trace_live, finished_at: 1.day.ago) } 203 204 it 'selects the build' do 205 is_expected.to eq([build]) 206 end 207 end 208 209 context 'when build does not have a stale live trace' do 210 let!(:build) { create(:ci_build, :success, :trace_live, finished_at: 1.hour.ago) } 211 212 it 'does not select the build' do 213 is_expected.to be_empty 214 end 215 end 216 end 217 218 describe '.license_management_jobs' do 219 subject { described_class.license_management_jobs } 220 221 let!(:management_build) { create(:ci_build, :success, name: :license_management) } 222 let!(:scanning_build) { create(:ci_build, :success, name: :license_scanning) } 223 let!(:another_build) { create(:ci_build, :success, name: :another_type) } 224 225 it 'returns license_scanning jobs' do 226 is_expected.to include(scanning_build) 227 end 228 229 it 'returns license_management jobs' do 230 is_expected.to include(management_build) 231 end 232 233 it 'doesnt return filtered out jobs' do 234 is_expected.not_to include(another_build) 235 end 236 end 237 238 describe '.finished_before' do 239 subject { described_class.finished_before(date) } 240 241 let(:date) { 1.hour.ago } 242 243 context 'when build has finished one day ago' do 244 let!(:build) { create(:ci_build, :success, finished_at: 1.day.ago) } 245 246 it 'selects the build' do 247 is_expected.to eq([build]) 248 end 249 end 250 251 context 'when build has finished 30 minutes ago' do 252 let!(:build) { create(:ci_build, :success, finished_at: 30.minutes.ago) } 253 254 it 'returns an empty array' do 255 is_expected.to be_empty 256 end 257 end 258 259 context 'when build is still running' do 260 let!(:build) { create(:ci_build, :running) } 261 262 it 'returns an empty array' do 263 is_expected.to be_empty 264 end 265 end 266 end 267 268 describe '.with_exposed_artifacts' do 269 subject { described_class.with_exposed_artifacts } 270 271 let!(:job1) { create(:ci_build) } 272 let!(:job2) { create(:ci_build, options: options) } 273 let!(:job3) { create(:ci_build) } 274 275 context 'when some jobs have exposed artifacs and some not' do 276 let(:options) { { artifacts: { expose_as: 'test', paths: ['test'] } } } 277 278 before do 279 job1.ensure_metadata.update!(has_exposed_artifacts: nil) 280 job3.ensure_metadata.update!(has_exposed_artifacts: false) 281 end 282 283 it 'selects only the jobs with exposed artifacts' do 284 is_expected.to eq([job2]) 285 end 286 end 287 288 context 'when job does not expose artifacts' do 289 let(:options) { nil } 290 291 it 'returns an empty array' do 292 is_expected.to be_empty 293 end 294 end 295 end 296 297 describe '.with_reports' do 298 subject { described_class.with_reports(Ci::JobArtifact.test_reports) } 299 300 context 'when build has a test report' do 301 let!(:build) { create(:ci_build, :success, :test_reports) } 302 303 it 'selects the build' do 304 is_expected.to eq([build]) 305 end 306 end 307 308 context 'when build does not have test reports' do 309 let!(:build) { create(:ci_build, :success, :trace_artifact) } 310 311 it 'does not select the build' do 312 is_expected.to be_empty 313 end 314 end 315 316 context 'when there are multiple builds with test reports' do 317 let!(:builds) { create_list(:ci_build, 5, :success, :test_reports) } 318 319 it 'does not execute a query for selecting job artifact one by one' do 320 recorded = ActiveRecord::QueryRecorder.new do 321 subject.each do |build| 322 build.job_artifacts.map { |a| a.file.exists? } 323 end 324 end 325 326 expect(recorded.count).to eq(2) 327 end 328 end 329 end 330 331 describe '.with_needs' do 332 let!(:build) { create(:ci_build) } 333 let!(:build_b) { create(:ci_build) } 334 let!(:build_need_a) { create(:ci_build_need, build: build) } 335 let!(:build_need_b) { create(:ci_build_need, build: build_b) } 336 337 context 'when passing build name' do 338 subject { described_class.with_needs(build_need_a.name) } 339 340 it { is_expected.to contain_exactly(build) } 341 end 342 343 context 'when not passing any build name' do 344 subject { described_class.with_needs } 345 346 it { is_expected.to contain_exactly(build, build_b) } 347 end 348 349 context 'when not matching build name' do 350 subject { described_class.with_needs('undefined') } 351 352 it { is_expected.to be_empty } 353 end 354 end 355 356 describe '.without_needs' do 357 subject { described_class.without_needs } 358 359 context 'when no build_need is created' do 360 it { is_expected.to contain_exactly(build) } 361 end 362 363 context 'when a build_need is created' do 364 let!(:need_a) { create(:ci_build_need, build: build) } 365 366 it { is_expected.to be_empty } 367 end 368 end 369 370 describe '#stick_build_if_status_changed' do 371 it 'sticks the build if the status changed' do 372 job = create(:ci_build, :pending) 373 374 expect(described_class.sticking).to receive(:stick) 375 .with(:build, job.id) 376 377 job.update!(status: :running) 378 end 379 end 380 381 describe '#enqueue' do 382 let(:build) { create(:ci_build, :created) } 383 384 before do 385 allow(build).to receive(:any_unmet_prerequisites?).and_return(has_prerequisites) 386 allow(Ci::PrepareBuildService).to receive(:perform_async) 387 end 388 389 context 'build has unmet prerequisites' do 390 let(:has_prerequisites) { true } 391 392 it 'transitions to preparing' do 393 build.enqueue 394 395 expect(build).to be_preparing 396 end 397 398 it 'does not push build to the queue' do 399 build.enqueue 400 401 expect(build.queuing_entry).not_to be_present 402 end 403 end 404 405 context 'build has no prerequisites' do 406 let(:has_prerequisites) { false } 407 408 it 'transitions to pending' do 409 build.enqueue 410 411 expect(build).to be_pending 412 end 413 414 it 'pushes build to a queue' do 415 build.enqueue 416 417 expect(build.queuing_entry).to be_present 418 end 419 420 context 'when build status transition fails' do 421 before do 422 ::Ci::Build.find(build.id).update_column(:lock_version, 100) 423 end 424 425 it 'does not push build to a queue' do 426 expect { build.enqueue! } 427 .to raise_error(ActiveRecord::StaleObjectError) 428 429 expect(build.queuing_entry).not_to be_present 430 end 431 end 432 433 context 'when there is a queuing entry already present' do 434 before do 435 create(:ci_pending_build, build: build, project: build.project) 436 end 437 438 it 'does not raise an error' do 439 expect { build.enqueue! }.not_to raise_error 440 expect(build.reload.queuing_entry).to be_present 441 end 442 end 443 444 context 'when both failure scenario happen at the same time' do 445 before do 446 ::Ci::Build.find(build.id).update_column(:lock_version, 100) 447 create(:ci_pending_build, build: build, project: build.project) 448 end 449 450 it 'raises stale object error exception' do 451 expect { build.enqueue! } 452 .to raise_error(ActiveRecord::StaleObjectError) 453 end 454 end 455 end 456 end 457 458 describe '#enqueue_preparing' do 459 let(:build) { create(:ci_build, :preparing) } 460 461 before do 462 allow(build).to receive(:any_unmet_prerequisites?).and_return(has_unmet_prerequisites) 463 end 464 465 context 'build completed prerequisites' do 466 let(:has_unmet_prerequisites) { false } 467 468 it 'transitions to pending' do 469 build.enqueue_preparing 470 471 expect(build).to be_pending 472 expect(build.queuing_entry).to be_present 473 end 474 end 475 476 context 'build did not complete prerequisites' do 477 let(:has_unmet_prerequisites) { true } 478 479 it 'remains in preparing' do 480 build.enqueue_preparing 481 482 expect(build).to be_preparing 483 expect(build.queuing_entry).not_to be_present 484 end 485 end 486 end 487 488 describe '#actionize' do 489 context 'when build is a created' do 490 before do 491 build.update_column(:status, :created) 492 end 493 494 it 'makes build a manual action' do 495 expect(build.actionize).to be true 496 expect(build.reload).to be_manual 497 end 498 end 499 500 context 'when build is not created' do 501 before do 502 build.update_column(:status, :pending) 503 end 504 505 it 'does not change build status' do 506 expect(build.actionize).to be false 507 expect(build.reload).to be_pending 508 end 509 end 510 end 511 512 describe '#run' do 513 context 'when build has been just created' do 514 let(:build) { create(:ci_build, :created) } 515 516 it 'creates queuing entry and then removes it' do 517 build.enqueue! 518 expect(build.queuing_entry).to be_present 519 520 build.run! 521 expect(build.reload.queuing_entry).not_to be_present 522 end 523 end 524 525 context 'when build status transition fails' do 526 let(:build) { create(:ci_build, :pending) } 527 528 before do 529 create(:ci_pending_build, build: build, project: build.project) 530 ::Ci::Build.find(build.id).update_column(:lock_version, 100) 531 end 532 533 it 'does not remove build from a queue' do 534 expect { build.run! } 535 .to raise_error(ActiveRecord::StaleObjectError) 536 537 expect(build.queuing_entry).to be_present 538 end 539 end 540 541 context 'when build has been picked by a shared runner' do 542 let(:build) { create(:ci_build, :pending) } 543 544 it 'creates runtime metadata entry' do 545 build.runner = create(:ci_runner, :instance_type) 546 547 build.run! 548 549 expect(build.reload.runtime_metadata).to be_present 550 end 551 end 552 end 553 554 describe '#drop' do 555 context 'when has a runtime tracking entry' do 556 let(:build) { create(:ci_build, :pending) } 557 558 it 'removes runtime tracking entry' do 559 build.runner = create(:ci_runner, :instance_type) 560 561 build.run! 562 expect(build.reload.runtime_metadata).to be_present 563 564 build.drop! 565 expect(build.reload.runtime_metadata).not_to be_present 566 end 567 end 568 end 569 570 describe '#schedulable?' do 571 subject { build.schedulable? } 572 573 context 'when build is schedulable' do 574 let(:build) { create(:ci_build, :created, :schedulable, project: project) } 575 576 it { expect(subject).to be_truthy } 577 end 578 579 context 'when build is not schedulable' do 580 let(:build) { create(:ci_build, :created, project: project) } 581 582 it { expect(subject).to be_falsy } 583 end 584 end 585 586 describe '#schedule' do 587 subject { build.schedule } 588 589 before do 590 project.add_developer(user) 591 end 592 593 let(:build) { create(:ci_build, :created, :schedulable, user: user, project: project) } 594 595 it 'transits to scheduled' do 596 allow(Ci::BuildScheduleWorker).to receive(:perform_at) 597 598 subject 599 600 expect(build).to be_scheduled 601 end 602 603 it 'updates scheduled_at column' do 604 allow(Ci::BuildScheduleWorker).to receive(:perform_at) 605 606 subject 607 608 expect(build.scheduled_at).not_to be_nil 609 end 610 611 it 'schedules BuildScheduleWorker at the right time' do 612 freeze_time do 613 expect(Ci::BuildScheduleWorker) 614 .to receive(:perform_at).with(be_like_time(1.minute.since), build.id) 615 616 subject 617 end 618 end 619 end 620 621 describe '#unschedule' do 622 subject { build.unschedule } 623 624 context 'when build is scheduled' do 625 let(:build) { create(:ci_build, :scheduled, pipeline: pipeline) } 626 627 it 'cleans scheduled_at column' do 628 subject 629 630 expect(build.scheduled_at).to be_nil 631 end 632 633 it 'transits to manual' do 634 subject 635 636 expect(build).to be_manual 637 end 638 end 639 640 context 'when build is not scheduled' do 641 let(:build) { create(:ci_build, :created, pipeline: pipeline) } 642 643 it 'does not transit status' do 644 subject 645 646 expect(build).to be_created 647 end 648 end 649 end 650 651 describe '#options_scheduled_at' do 652 subject { build.options_scheduled_at } 653 654 let(:build) { build_stubbed(:ci_build, options: option) } 655 656 context 'when start_in is 1 day' do 657 let(:option) { { start_in: '1 day' } } 658 659 it 'returns date after 1 day' do 660 freeze_time do 661 is_expected.to eq(1.day.since) 662 end 663 end 664 end 665 666 context 'when start_in is 1 week' do 667 let(:option) { { start_in: '1 week' } } 668 669 it 'returns date after 1 week' do 670 freeze_time do 671 is_expected.to eq(1.week.since) 672 end 673 end 674 end 675 end 676 677 describe '#enqueue_scheduled' do 678 subject { build.enqueue_scheduled } 679 680 context 'when build is scheduled and the right time has not come yet' do 681 let(:build) { create(:ci_build, :scheduled, pipeline: pipeline) } 682 683 it 'does not transits the status' do 684 subject 685 686 expect(build).to be_scheduled 687 end 688 end 689 690 context 'when build is scheduled and the right time has already come' do 691 let(:build) { create(:ci_build, :expired_scheduled, pipeline: pipeline) } 692 693 it 'cleans scheduled_at column' do 694 subject 695 696 expect(build.scheduled_at).to be_nil 697 end 698 699 it 'transits to pending' do 700 subject 701 702 expect(build).to be_pending 703 end 704 705 context 'build has unmet prerequisites' do 706 before do 707 allow(build).to receive(:prerequisites).and_return([double]) 708 end 709 710 it 'transits to preparing' do 711 subject 712 713 expect(build).to be_preparing 714 end 715 end 716 end 717 end 718 719 describe '#any_runners_online?' do 720 subject { build.any_runners_online? } 721 722 context 'when no runners' do 723 it { is_expected.to be_falsey } 724 end 725 726 context 'when there are runners' do 727 let(:runner) { create(:ci_runner, :project, projects: [build.project]) } 728 729 before do 730 runner.update!(contacted_at: 1.second.ago) 731 end 732 733 it { is_expected.to be_truthy } 734 735 it 'that is inactive' do 736 runner.update!(active: false) 737 is_expected.to be_falsey 738 end 739 740 it 'that is not online' do 741 runner.update!(contacted_at: nil) 742 is_expected.to be_falsey 743 end 744 745 it 'that cannot handle build' do 746 expect_any_instance_of(Ci::Runner).to receive(:matches_build?).with(build).and_return(false) 747 is_expected.to be_falsey 748 end 749 end 750 751 it 'caches the result in Redis' do 752 expect(Rails.cache).to receive(:fetch).with(['has-online-runners', build.id], expires_in: 1.minute) 753 754 build.any_runners_online? 755 end 756 end 757 758 describe '#any_runners_available?' do 759 subject { build.any_runners_available? } 760 761 context 'when no runners' do 762 it { is_expected.to be_falsey } 763 end 764 765 context 'when there are runners' do 766 let!(:runner) { create(:ci_runner, :project, projects: [build.project]) } 767 768 it { is_expected.to be_truthy } 769 end 770 771 it 'caches the result in Redis' do 772 expect(Rails.cache).to receive(:fetch).with(['has-available-runners', build.project.id], expires_in: 1.minute) 773 774 build.any_runners_available? 775 end 776 end 777 778 describe '#artifacts?' do 779 subject { build.artifacts? } 780 781 context 'when new artifacts are used' do 782 context 'artifacts archive does not exist' do 783 let(:build) { create(:ci_build) } 784 785 it { is_expected.to be_falsy } 786 end 787 788 context 'artifacts archive exists' do 789 let(:build) { create(:ci_build, :artifacts) } 790 791 it { is_expected.to be_truthy } 792 793 context 'is expired' do 794 let(:build) { create(:ci_build, :artifacts, :expired) } 795 796 it { is_expected.to be_falsy } 797 end 798 end 799 end 800 end 801 802 describe '#locked_artifacts?' do 803 subject(:locked_artifacts) { build.locked_artifacts? } 804 805 context 'when pipeline is artifacts_locked' do 806 before do 807 build.pipeline.artifacts_locked! 808 end 809 810 context 'artifacts archive does not exist' do 811 let(:build) { create(:ci_build) } 812 813 it { is_expected.to be_falsy } 814 end 815 816 context 'artifacts archive exists' do 817 let(:build) { create(:ci_build, :artifacts) } 818 819 it { is_expected.to be_truthy } 820 end 821 end 822 823 context 'when pipeline is unlocked' do 824 before do 825 build.pipeline.unlocked! 826 end 827 828 context 'artifacts archive does not exist' do 829 let(:build) { create(:ci_build) } 830 831 it { is_expected.to be_falsy } 832 end 833 834 context 'artifacts archive exists' do 835 let(:build) { create(:ci_build, :artifacts) } 836 837 it { is_expected.to be_falsy } 838 end 839 end 840 end 841 842 describe '#available_artifacts?' do 843 let(:build) { create(:ci_build) } 844 845 subject { build.available_artifacts? } 846 847 context 'when artifacts are not expired' do 848 before do 849 build.artifacts_expire_at = Date.tomorrow 850 end 851 852 context 'when artifacts exist' do 853 before do 854 create(:ci_job_artifact, :archive, job: build) 855 end 856 857 it { is_expected.to be_truthy } 858 end 859 860 context 'when artifacts do not exist' do 861 it { is_expected.to be_falsey } 862 end 863 end 864 865 context 'when artifacts are expired' do 866 before do 867 build.artifacts_expire_at = Date.yesterday 868 end 869 870 context 'when artifacts are not locked' do 871 before do 872 build.pipeline.locked = :unlocked 873 end 874 875 it { is_expected.to be_falsey } 876 end 877 878 context 'when artifacts are locked' do 879 before do 880 build.pipeline.locked = :artifacts_locked 881 end 882 883 context 'when artifacts exist' do 884 before do 885 create(:ci_job_artifact, :archive, job: build) 886 end 887 888 it { is_expected.to be_truthy } 889 end 890 891 context 'when artifacts do not exist' do 892 it { is_expected.to be_falsey } 893 end 894 end 895 end 896 end 897 898 describe '#browsable_artifacts?' do 899 subject { build.browsable_artifacts? } 900 901 context 'artifacts metadata does exists' do 902 let(:build) { create(:ci_build, :artifacts) } 903 904 it { is_expected.to be_truthy } 905 end 906 end 907 908 describe '#artifacts_public?' do 909 subject { build.artifacts_public? } 910 911 context 'artifacts with defaults' do 912 let(:build) { create(:ci_build, :artifacts) } 913 914 it { is_expected.to be_truthy } 915 end 916 917 context 'non public artifacts' do 918 let(:build) { create(:ci_build, :artifacts, :non_public_artifacts) } 919 920 it { is_expected.to be_falsey } 921 end 922 end 923 924 describe '#artifacts_expired?' do 925 subject { build.artifacts_expired? } 926 927 context 'is expired' do 928 before do 929 build.update!(artifacts_expire_at: Time.current - 7.days) 930 end 931 932 it { is_expected.to be_truthy } 933 end 934 935 context 'is not expired' do 936 before do 937 build.update!(artifacts_expire_at: Time.current + 7.days) 938 end 939 940 it { is_expected.to be_falsey } 941 end 942 end 943 944 describe '#artifacts_metadata?' do 945 subject { build.artifacts_metadata? } 946 947 context 'artifacts metadata does not exist' do 948 it { is_expected.to be_falsy } 949 end 950 951 context 'artifacts archive is a zip file and metadata exists' do 952 let(:build) { create(:ci_build, :artifacts) } 953 954 it { is_expected.to be_truthy } 955 end 956 end 957 958 describe '#artifacts_expire_in' do 959 subject { build.artifacts_expire_in } 960 961 it { is_expected.to be_nil } 962 963 context 'when artifacts_expire_at is specified' do 964 let(:expire_at) { Time.current + 7.days } 965 966 before do 967 build.artifacts_expire_at = expire_at 968 end 969 970 it { is_expected.to be_within(5).of(expire_at - Time.current) } 971 end 972 end 973 974 describe '#artifacts_expire_in=' do 975 subject { build.artifacts_expire_in } 976 977 it 'when assigning valid duration' do 978 build.artifacts_expire_in = '7 days' 979 980 is_expected.to be_within(10).of(7.days.to_i) 981 end 982 983 it 'when assigning invalid duration' do 984 expect { build.artifacts_expire_in = '7 elephants' }.to raise_error(ChronicDuration::DurationParseError) 985 is_expected.to be_nil 986 end 987 988 it 'when resetting value' do 989 build.artifacts_expire_in = nil 990 991 is_expected.to be_nil 992 end 993 994 it 'when setting to 0' do 995 build.artifacts_expire_in = '0' 996 997 is_expected.to be_nil 998 end 999 end 1000 1001 describe '#commit' do 1002 it 'returns commit pipeline has been created for' do 1003 expect(build.commit).to eq project.commit 1004 end 1005 end 1006 1007 describe '#cache' do 1008 let(:options) do 1009 { cache: [{ key: "key", paths: ["public"], policy: "pull-push" }] } 1010 end 1011 1012 subject { build.cache } 1013 1014 context 'when build has cache' do 1015 before do 1016 allow(build).to receive(:options).and_return(options) 1017 end 1018 1019 context 'when build has multiple caches' do 1020 let(:options) do 1021 { cache: [ 1022 { key: "key", paths: ["public"], policy: "pull-push" }, 1023 { key: "key2", paths: ["public"], policy: "pull-push" } 1024 ] } 1025 end 1026 1027 before do 1028 allow_any_instance_of(Project).to receive(:jobs_cache_index).and_return(1) 1029 end 1030 1031 it { is_expected.to match([a_hash_including(key: "key-1"), a_hash_including(key: "key2-1")]) } 1032 end 1033 1034 context 'when project has jobs_cache_index' do 1035 before do 1036 allow_any_instance_of(Project).to receive(:jobs_cache_index).and_return(1) 1037 end 1038 1039 it { is_expected.to be_an(Array).and all(include(key: "key-1")) } 1040 end 1041 1042 context 'when project does not have jobs_cache_index' do 1043 before do 1044 allow_any_instance_of(Project).to receive(:jobs_cache_index).and_return(nil) 1045 end 1046 1047 it { is_expected.to eq(options[:cache]) } 1048 end 1049 end 1050 1051 context 'when build does not have cache' do 1052 before do 1053 allow(build).to receive(:options).and_return({}) 1054 end 1055 1056 it { is_expected.to be_empty } 1057 end 1058 end 1059 1060 describe '#triggered_by?' do 1061 subject { build.triggered_by?(user) } 1062 1063 context 'when user is owner' do 1064 let(:build) { create(:ci_build, pipeline: pipeline, user: user) } 1065 1066 it { is_expected.to be_truthy } 1067 end 1068 1069 context 'when user is not owner' do 1070 let(:another_user) { create(:user) } 1071 let(:build) { create(:ci_build, pipeline: pipeline, user: another_user) } 1072 1073 it { is_expected.to be_falsy } 1074 end 1075 end 1076 1077 describe '#detailed_status' do 1078 it 'returns a detailed status' do 1079 expect(build.detailed_status(user)) 1080 .to be_a Gitlab::Ci::Status::Build::Cancelable 1081 end 1082 end 1083 1084 describe '#coverage_regex' do 1085 subject { build.coverage_regex } 1086 1087 context 'when project has build_coverage_regex set' do 1088 let(:project_regex) { '\(\d+\.\d+\) covered' } 1089 1090 before do 1091 project.update_column(:build_coverage_regex, project_regex) 1092 end 1093 1094 context 'and coverage_regex attribute is not set' do 1095 it { is_expected.to eq(project_regex) } 1096 end 1097 1098 context 'but coverage_regex attribute is also set' do 1099 let(:build_regex) { 'Code coverage: \d+\.\d+' } 1100 1101 before do 1102 build.coverage_regex = build_regex 1103 end 1104 1105 it { is_expected.to eq(build_regex) } 1106 end 1107 end 1108 1109 context 'when neither project nor build has coverage regex set' do 1110 it { is_expected.to be_nil } 1111 end 1112 end 1113 1114 describe '#update_coverage' do 1115 context "regarding coverage_regex's value," do 1116 before do 1117 build.coverage_regex = '\(\d+.\d+\%\) covered' 1118 build.trace.set('Coverage 1033 / 1051 LOC (98.29%) covered') 1119 end 1120 1121 it "saves the correct extracted coverage value" do 1122 expect(build.update_coverage).to be(true) 1123 expect(build.coverage).to eq(98.29) 1124 end 1125 end 1126 end 1127 1128 describe '#trace' do 1129 subject { build.trace } 1130 1131 it { is_expected.to be_a(Gitlab::Ci::Trace) } 1132 end 1133 1134 describe '#has_trace?' do 1135 subject { build.has_trace? } 1136 1137 it "expect to call exist? method" do 1138 expect_any_instance_of(Gitlab::Ci::Trace).to receive(:exist?) 1139 .and_return(true) 1140 1141 is_expected.to be(true) 1142 end 1143 end 1144 1145 describe '#has_live_trace?' do 1146 subject { build.has_live_trace? } 1147 1148 let(:build) { create(:ci_build, :trace_live) } 1149 1150 it { is_expected.to be_truthy } 1151 1152 context 'when build does not have live trace' do 1153 let(:build) { create(:ci_build) } 1154 1155 it { is_expected.to be_falsy } 1156 end 1157 end 1158 1159 describe '#has_archived_trace?' do 1160 subject { build.has_archived_trace? } 1161 1162 let(:build) { create(:ci_build, :trace_artifact) } 1163 1164 it { is_expected.to be_truthy } 1165 1166 context 'when build does not have archived trace' do 1167 let(:build) { create(:ci_build) } 1168 1169 it { is_expected.to be_falsy } 1170 end 1171 end 1172 1173 describe '#has_job_artifacts?' do 1174 subject { build.has_job_artifacts? } 1175 1176 context 'when build has a job artifact' do 1177 let(:build) { create(:ci_build, :artifacts) } 1178 1179 it { is_expected.to be_truthy } 1180 end 1181 end 1182 1183 describe '#has_test_reports?' do 1184 subject { build.has_test_reports? } 1185 1186 context 'when build has a test report' do 1187 let(:build) { create(:ci_build, :test_reports) } 1188 1189 it { is_expected.to be_truthy } 1190 end 1191 1192 context 'when build does not have a test report' do 1193 let(:build) { create(:ci_build) } 1194 1195 it { is_expected.to be_falsey } 1196 end 1197 end 1198 1199 describe '#has_old_trace?' do 1200 subject { build.has_old_trace? } 1201 1202 context 'when old trace exists' do 1203 before do 1204 build.update_column(:trace, 'old trace') 1205 end 1206 1207 it { is_expected.to be_truthy } 1208 end 1209 1210 context 'when old trace does not exist' do 1211 it { is_expected.to be_falsy } 1212 end 1213 end 1214 1215 describe '#trace=' do 1216 it "expect to fail trace=" do 1217 expect { build.trace = "new" }.to raise_error(NotImplementedError) 1218 end 1219 end 1220 1221 describe '#old_trace' do 1222 subject { build.old_trace } 1223 1224 before do 1225 build.update_column(:trace, 'old trace') 1226 end 1227 1228 it "expect to receive data from database" do 1229 is_expected.to eq('old trace') 1230 end 1231 end 1232 1233 describe '#erase_old_trace!' do 1234 subject { build.erase_old_trace! } 1235 1236 context 'when old trace exists' do 1237 before do 1238 build.update_column(:trace, 'old trace') 1239 end 1240 1241 it "erases old trace" do 1242 subject 1243 1244 expect(build.old_trace).to be_nil 1245 end 1246 1247 it "executes UPDATE query" do 1248 recorded = ActiveRecord::QueryRecorder.new { subject } 1249 1250 expect(recorded.log.count { |l| l.match?(/UPDATE.*ci_builds/) }).to eq(1) 1251 end 1252 end 1253 1254 context 'when old trace does not exist' do 1255 it 'does not execute UPDATE query' do 1256 recorded = ActiveRecord::QueryRecorder.new { subject } 1257 1258 expect(recorded.log.count { |l| l.match?(/UPDATE.*ci_builds/) }).to eq(0) 1259 end 1260 end 1261 end 1262 1263 describe '#hide_secrets' do 1264 let(:metrics) { spy('metrics') } 1265 let(:subject) { build.hide_secrets(data) } 1266 1267 context 'hide runners token' do 1268 let(:data) { "new #{project.runners_token} data"} 1269 1270 it { is_expected.to match(/^new x+ data$/) } 1271 1272 it 'increments trace mutation metric' do 1273 build.hide_secrets(data, metrics) 1274 1275 expect(metrics) 1276 .to have_received(:increment_trace_operation) 1277 .with(operation: :mutated) 1278 end 1279 end 1280 1281 context 'hide build token' do 1282 let(:data) { "new #{build.token} data"} 1283 1284 it { is_expected.to match(/^new x+ data$/) } 1285 1286 it 'increments trace mutation metric' do 1287 build.hide_secrets(data, metrics) 1288 1289 expect(metrics) 1290 .to have_received(:increment_trace_operation) 1291 .with(operation: :mutated) 1292 end 1293 end 1294 1295 context 'when build does not include secrets' do 1296 let(:data) { 'my build log' } 1297 1298 it 'does not mutate trace' do 1299 trace = build.hide_secrets(data) 1300 1301 expect(trace).to eq data 1302 end 1303 1304 it 'does not increment trace mutation metric' do 1305 build.hide_secrets(data, metrics) 1306 1307 expect(metrics) 1308 .not_to have_received(:increment_trace_operation) 1309 .with(operation: :mutated) 1310 end 1311 end 1312 end 1313 1314 describe 'state transition as a deployable' do 1315 subject { build.send(event) } 1316 1317 let!(:build) { create(:ci_build, :with_deployment, :start_review_app, project: project, pipeline: pipeline) } 1318 let(:deployment) { build.deployment } 1319 let(:environment) { deployment.environment } 1320 1321 before do 1322 allow(Deployments::LinkMergeRequestWorker).to receive(:perform_async) 1323 allow(Deployments::HooksWorker).to receive(:perform_async) 1324 end 1325 1326 it 'has deployments record with created status' do 1327 expect(deployment).to be_created 1328 expect(environment.name).to eq('review/master') 1329 end 1330 1331 shared_examples_for 'avoid deadlock' do 1332 it 'executes UPDATE in the right order' do 1333 recorded = with_cross_database_modification_prevented do 1334 ActiveRecord::QueryRecorder.new { subject } 1335 end 1336 1337 index_for_build = recorded.log.index { |l| l.include?("UPDATE \"ci_builds\"") } 1338 index_for_deployment = recorded.log.index { |l| l.include?("UPDATE \"deployments\"") } 1339 1340 expect(index_for_build).to be < index_for_deployment 1341 end 1342 end 1343 1344 context 'when transits to running' do 1345 let(:event) { :run! } 1346 1347 it_behaves_like 'avoid deadlock' 1348 1349 it 'transits deployment status to running' do 1350 with_cross_database_modification_prevented do 1351 subject 1352 end 1353 1354 expect(deployment).to be_running 1355 end 1356 1357 context 'when deployment is already running state' do 1358 before do 1359 build.deployment.success! 1360 end 1361 1362 it 'does not change deployment status and tracks an error' do 1363 expect(Gitlab::ErrorTracking) 1364 .to receive(:track_exception).with( 1365 instance_of(Deployment::StatusSyncError), deployment_id: deployment.id, build_id: build.id) 1366 1367 with_cross_database_modification_prevented do 1368 expect { subject }.not_to change { deployment.reload.status } 1369 end 1370 end 1371 end 1372 end 1373 1374 context 'when transits to success' do 1375 let(:event) { :success! } 1376 1377 before do 1378 allow(Deployments::UpdateEnvironmentWorker).to receive(:perform_async) 1379 allow(Deployments::HooksWorker).to receive(:perform_async) 1380 end 1381 1382 it_behaves_like 'avoid deadlock' 1383 it_behaves_like 'calling proper BuildFinishedWorker' 1384 1385 it 'transits deployment status to success' do 1386 with_cross_database_modification_prevented do 1387 subject 1388 end 1389 1390 expect(deployment).to be_success 1391 end 1392 end 1393 1394 context 'when transits to failed' do 1395 let(:event) { :drop! } 1396 1397 it_behaves_like 'avoid deadlock' 1398 it_behaves_like 'calling proper BuildFinishedWorker' 1399 1400 it 'transits deployment status to failed' do 1401 with_cross_database_modification_prevented do 1402 subject 1403 end 1404 1405 expect(deployment).to be_failed 1406 end 1407 end 1408 1409 context 'when transits to skipped' do 1410 let(:event) { :skip! } 1411 1412 it_behaves_like 'avoid deadlock' 1413 1414 it 'transits deployment status to skipped' do 1415 with_cross_database_modification_prevented do 1416 subject 1417 end 1418 1419 expect(deployment).to be_skipped 1420 end 1421 end 1422 1423 context 'when transits to canceled' do 1424 let(:event) { :cancel! } 1425 1426 it_behaves_like 'avoid deadlock' 1427 it_behaves_like 'calling proper BuildFinishedWorker' 1428 1429 it 'transits deployment status to canceled' do 1430 with_cross_database_modification_prevented do 1431 subject 1432 end 1433 1434 expect(deployment).to be_canceled 1435 end 1436 end 1437 end 1438 1439 describe '#on_stop' do 1440 subject { build.on_stop } 1441 1442 context 'when a job has a specification that it can be stopped from the other job' do 1443 let(:build) { create(:ci_build, :start_review_app) } 1444 1445 it 'returns the other job name' do 1446 is_expected.to eq('stop_review_app') 1447 end 1448 end 1449 1450 context 'when a job does not have environment information' do 1451 let(:build) { create(:ci_build) } 1452 1453 it 'returns nil' do 1454 is_expected.to be_nil 1455 end 1456 end 1457 end 1458 1459 describe '#environment_deployment_tier' do 1460 subject { build.environment_deployment_tier } 1461 1462 let(:build) { described_class.new(options: options) } 1463 let(:options) { { environment: { deployment_tier: 'production' } } } 1464 1465 it { is_expected.to eq('production') } 1466 1467 context 'when options does not include deployment_tier' do 1468 let(:options) { { environment: { name: 'production' } } } 1469 1470 it { is_expected.to be_nil } 1471 end 1472 end 1473 1474 describe 'deployment' do 1475 describe '#outdated_deployment?' do 1476 subject { build.outdated_deployment? } 1477 1478 context 'when build succeeded' do 1479 let(:build) { create(:ci_build, :success) } 1480 let!(:deployment) { create(:deployment, :success, deployable: build) } 1481 1482 context 'current deployment is latest' do 1483 it { is_expected.to be_falsey } 1484 end 1485 1486 context 'current deployment is not latest on environment' do 1487 let!(:deployment2) { create(:deployment, :success, environment: deployment.environment) } 1488 1489 it { is_expected.to be_truthy } 1490 end 1491 end 1492 1493 context 'when build failed' do 1494 let(:build) { create(:ci_build, :failed) } 1495 1496 it { is_expected.to be_falsey } 1497 end 1498 end 1499 end 1500 1501 describe 'environment' do 1502 describe '#has_environment?' do 1503 subject { build.has_environment? } 1504 1505 context 'when environment is defined' do 1506 before do 1507 build.update!(environment: 'review') 1508 end 1509 1510 it { is_expected.to be_truthy } 1511 end 1512 1513 context 'when environment is not defined' do 1514 before do 1515 build.update!(environment: nil) 1516 end 1517 1518 it { is_expected.to be_falsey } 1519 end 1520 end 1521 1522 describe '#expanded_environment_name' do 1523 subject { build.expanded_environment_name } 1524 1525 context 'when environment uses $CI_COMMIT_REF_NAME' do 1526 let(:build) do 1527 create(:ci_build, 1528 ref: 'master', 1529 environment: 'review/$CI_COMMIT_REF_NAME') 1530 end 1531 1532 it { is_expected.to eq('review/master') } 1533 end 1534 1535 context 'when environment uses yaml_variables containing symbol keys' do 1536 let(:build) do 1537 create(:ci_build, 1538 yaml_variables: [{ key: :APP_HOST, value: 'host' }], 1539 environment: 'review/$APP_HOST') 1540 end 1541 1542 it 'returns an expanded environment name with a list of variables' do 1543 expect(build).to receive(:simple_variables).once.and_call_original 1544 1545 is_expected.to eq('review/host') 1546 end 1547 1548 context 'when build metadata has already persisted the expanded environment name' do 1549 before do 1550 build.metadata.expanded_environment_name = 'review/host' 1551 end 1552 1553 it 'returns a persisted expanded environment name without a list of variables' do 1554 expect(build).not_to receive(:simple_variables) 1555 1556 is_expected.to eq('review/host') 1557 end 1558 end 1559 end 1560 1561 context 'when using persisted variables' do 1562 let(:build) do 1563 create(:ci_build, environment: 'review/x$CI_BUILD_ID') 1564 end 1565 1566 it { is_expected.to eq('review/x') } 1567 end 1568 end 1569 1570 describe '#expanded_kubernetes_namespace' do 1571 let(:build) { create(:ci_build, environment: environment, options: options) } 1572 1573 subject { build.expanded_kubernetes_namespace } 1574 1575 context 'environment and namespace are not set' do 1576 let(:environment) { nil } 1577 let(:options) { nil } 1578 1579 it { is_expected.to be_nil } 1580 end 1581 1582 context 'environment is specified' do 1583 let(:environment) { 'production' } 1584 1585 context 'namespace is not set' do 1586 let(:options) { nil } 1587 1588 it { is_expected.to be_nil } 1589 end 1590 1591 context 'namespace is provided' do 1592 let(:options) do 1593 { 1594 environment: { 1595 name: environment, 1596 kubernetes: { 1597 namespace: namespace 1598 } 1599 } 1600 } 1601 end 1602 1603 context 'with a static value' do 1604 let(:namespace) { 'production' } 1605 1606 it { is_expected.to eq namespace } 1607 end 1608 1609 context 'with a dynamic value' do 1610 let(:namespace) { 'deploy-$CI_COMMIT_REF_NAME'} 1611 1612 it { is_expected.to eq 'deploy-master' } 1613 end 1614 end 1615 end 1616 end 1617 1618 describe '#starts_environment?' do 1619 subject { build.starts_environment? } 1620 1621 context 'when environment is defined' do 1622 before do 1623 build.update!(environment: 'review') 1624 end 1625 1626 context 'no action is defined' do 1627 it { is_expected.to be_truthy } 1628 end 1629 1630 context 'and start action is defined' do 1631 before do 1632 build.update!(options: { environment: { action: 'start' } } ) 1633 end 1634 1635 it { is_expected.to be_truthy } 1636 end 1637 end 1638 1639 context 'when environment is not defined' do 1640 before do 1641 build.update!(environment: nil) 1642 end 1643 1644 it { is_expected.to be_falsey } 1645 end 1646 end 1647 1648 describe '#stops_environment?' do 1649 subject { build.stops_environment? } 1650 1651 context 'when environment is defined' do 1652 before do 1653 build.update!(environment: 'review') 1654 end 1655 1656 context 'no action is defined' do 1657 it { is_expected.to be_falsey } 1658 end 1659 1660 context 'and stop action is defined' do 1661 before do 1662 build.update!(options: { environment: { action: 'stop' } } ) 1663 end 1664 1665 it { is_expected.to be_truthy } 1666 end 1667 end 1668 1669 context 'when environment is not defined' do 1670 before do 1671 build.update!(environment: nil) 1672 end 1673 1674 it { is_expected.to be_falsey } 1675 end 1676 end 1677 end 1678 1679 describe 'erasable build' do 1680 shared_examples 'erasable' do 1681 it 'removes artifact file' do 1682 expect(build.artifacts_file.present?).to be_falsy 1683 end 1684 1685 it 'removes artifact metadata file' do 1686 expect(build.artifacts_metadata.present?).to be_falsy 1687 end 1688 1689 it 'removes all job_artifacts' do 1690 expect(build.job_artifacts.count).to eq(0) 1691 end 1692 1693 it 'erases build trace in trace file' do 1694 expect(build).not_to have_trace 1695 end 1696 1697 it 'sets erased to true' do 1698 expect(build.erased?).to be true 1699 end 1700 1701 it 'sets erase date' do 1702 expect(build.erased_at).not_to be_falsy 1703 end 1704 end 1705 1706 context 'build is not erasable' do 1707 let!(:build) { create(:ci_build) } 1708 1709 describe '#erase' do 1710 subject { build.erase } 1711 1712 it { is_expected.to be false } 1713 end 1714 1715 describe '#erasable?' do 1716 subject { build.erasable? } 1717 1718 it { is_expected.to eq false } 1719 end 1720 end 1721 1722 context 'build is erasable' do 1723 context 'new artifacts' do 1724 let!(:build) { create(:ci_build, :test_reports, :trace_artifact, :success, :artifacts) } 1725 1726 describe '#erase' do 1727 before do 1728 build.erase(erased_by: erased_by) 1729 end 1730 1731 context 'erased by user' do 1732 let!(:erased_by) { create(:user, username: 'eraser') } 1733 1734 include_examples 'erasable' 1735 1736 it 'records user who erased a build' do 1737 expect(build.erased_by).to eq erased_by 1738 end 1739 end 1740 1741 context 'erased by system' do 1742 let(:erased_by) { nil } 1743 1744 include_examples 'erasable' 1745 1746 it 'does not set user who erased a build' do 1747 expect(build.erased_by).to be_nil 1748 end 1749 end 1750 end 1751 1752 describe '#erasable?' do 1753 subject { build.erasable? } 1754 1755 it { is_expected.to be_truthy } 1756 end 1757 1758 describe '#erased?' do 1759 let!(:build) { create(:ci_build, :trace_artifact, :success, :artifacts) } 1760 1761 subject { build.erased? } 1762 1763 context 'job has not been erased' do 1764 it { is_expected.to be_falsey } 1765 end 1766 1767 context 'job has been erased' do 1768 before do 1769 build.erase 1770 end 1771 1772 it { is_expected.to be_truthy } 1773 end 1774 end 1775 1776 context 'metadata and build trace are not available' do 1777 let!(:build) { create(:ci_build, :success, :artifacts) } 1778 1779 before do 1780 build.erase_erasable_artifacts! 1781 end 1782 1783 describe '#erase' do 1784 it 'does not raise error' do 1785 expect { build.erase }.not_to raise_error 1786 end 1787 end 1788 end 1789 end 1790 end 1791 end 1792 1793 describe '#erase_erasable_artifacts!' do 1794 let!(:build) { create(:ci_build, :success) } 1795 1796 subject { build.erase_erasable_artifacts! } 1797 1798 before do 1799 Ci::JobArtifact.file_types.keys.each do |file_type| 1800 create(:ci_job_artifact, job: build, file_type: file_type, file_format: Ci::JobArtifact::TYPE_AND_FORMAT_PAIRS[file_type.to_sym]) 1801 end 1802 end 1803 1804 it "erases erasable artifacts" do 1805 subject 1806 1807 expect(build.job_artifacts.erasable).to be_empty 1808 end 1809 1810 it "keeps non erasable artifacts" do 1811 subject 1812 1813 Ci::JobArtifact::NON_ERASABLE_FILE_TYPES.each do |file_type| 1814 expect(build.send("job_artifacts_#{file_type}")).not_to be_nil 1815 end 1816 end 1817 end 1818 1819 describe '#first_pending' do 1820 let!(:first) { create(:ci_build, pipeline: pipeline, status: 'pending', created_at: Date.yesterday) } 1821 let!(:second) { create(:ci_build, pipeline: pipeline, status: 'pending') } 1822 1823 subject { described_class.first_pending } 1824 1825 it { is_expected.to be_a(described_class) } 1826 it('returns with the first pending build') { is_expected.to eq(first) } 1827 end 1828 1829 describe '#failed_but_allowed?' do 1830 subject { build.failed_but_allowed? } 1831 1832 context 'when build is not allowed to fail' do 1833 before do 1834 build.allow_failure = false 1835 end 1836 1837 context 'and build.status is success' do 1838 before do 1839 build.status = 'success' 1840 end 1841 1842 it { is_expected.to be_falsey } 1843 end 1844 1845 context 'and build.status is failed' do 1846 before do 1847 build.status = 'failed' 1848 end 1849 1850 it { is_expected.to be_falsey } 1851 end 1852 end 1853 1854 context 'when build is allowed to fail' do 1855 before do 1856 build.allow_failure = true 1857 end 1858 1859 context 'and build.status is success' do 1860 before do 1861 build.status = 'success' 1862 end 1863 1864 it { is_expected.to be_falsey } 1865 end 1866 1867 context 'and build status is failed' do 1868 before do 1869 build.status = 'failed' 1870 end 1871 1872 it { is_expected.to be_truthy } 1873 end 1874 1875 context 'when build is a manual action' do 1876 before do 1877 build.status = 'manual' 1878 end 1879 1880 it { is_expected.to be_falsey } 1881 end 1882 end 1883 end 1884 1885 describe 'flags' do 1886 describe '#cancelable?' do 1887 subject { build } 1888 1889 context 'when build is cancelable' do 1890 context 'when build is pending' do 1891 it { is_expected.to be_cancelable } 1892 end 1893 1894 context 'when build is running' do 1895 before do 1896 build.run! 1897 end 1898 1899 it { is_expected.to be_cancelable } 1900 end 1901 1902 context 'when build is created' do 1903 let(:build) { create(:ci_build, :created) } 1904 1905 it { is_expected.to be_cancelable } 1906 end 1907 1908 context 'when build is waiting for resource' do 1909 let(:build) { create(:ci_build, :waiting_for_resource) } 1910 1911 it { is_expected.to be_cancelable } 1912 end 1913 end 1914 1915 context 'when build is not cancelable' do 1916 context 'when build is successful' do 1917 before do 1918 build.success! 1919 end 1920 1921 it { is_expected.not_to be_cancelable } 1922 end 1923 1924 context 'when build is failed' do 1925 before do 1926 build.drop! 1927 end 1928 1929 it { is_expected.not_to be_cancelable } 1930 end 1931 end 1932 end 1933 1934 describe '#retryable?' do 1935 subject { build } 1936 1937 context 'when build is retryable' do 1938 context 'when build is successful' do 1939 before do 1940 build.success! 1941 end 1942 1943 it { is_expected.to be_retryable } 1944 end 1945 1946 context 'when build is failed' do 1947 before do 1948 build.drop! 1949 end 1950 1951 it { is_expected.to be_retryable } 1952 end 1953 1954 context 'when build is canceled' do 1955 before do 1956 build.cancel! 1957 end 1958 1959 it { is_expected.to be_retryable } 1960 end 1961 end 1962 1963 context 'when build is not retryable' do 1964 context 'when build is running' do 1965 before do 1966 build.run! 1967 end 1968 1969 it { is_expected.not_to be_retryable } 1970 end 1971 1972 context 'when build is skipped' do 1973 before do 1974 build.skip! 1975 end 1976 1977 it { is_expected.not_to be_retryable } 1978 end 1979 1980 context 'when build is degenerated' do 1981 before do 1982 build.degenerate! 1983 end 1984 1985 it { is_expected.not_to be_retryable } 1986 end 1987 1988 context 'when a canceled build has been retried already' do 1989 before do 1990 project.add_developer(user) 1991 build.cancel! 1992 described_class.retry(build, user) 1993 end 1994 1995 it { is_expected.not_to be_retryable } 1996 end 1997 1998 context 'when deployment is rejected' do 1999 before do 2000 build.drop!(:deployment_rejected) 2001 end 2002 2003 it { is_expected.not_to be_retryable } 2004 end 2005 end 2006 end 2007 2008 describe '#action?' do 2009 before do 2010 build.update!(when: value) 2011 end 2012 2013 subject { build.action? } 2014 2015 context 'when is set to manual' do 2016 let(:value) { 'manual' } 2017 2018 it { is_expected.to be_truthy } 2019 end 2020 2021 context 'when is set to delayed' do 2022 let(:value) { 'delayed' } 2023 2024 it { is_expected.to be_truthy } 2025 end 2026 2027 context 'when set to something else' do 2028 let(:value) { 'something else' } 2029 2030 it { is_expected.to be_falsey } 2031 end 2032 end 2033 end 2034 2035 describe '#tag_list' do 2036 let_it_be(:build) { create(:ci_build, tag_list: ['tag']) } 2037 2038 context 'when tags are preloaded' do 2039 it 'does not trigger queries' do 2040 build_with_tags = described_class.eager_load_tags.id_in([build]).to_a.first 2041 2042 expect { build_with_tags.tag_list }.not_to exceed_all_query_limit(0) 2043 expect(build_with_tags.tag_list).to eq(['tag']) 2044 end 2045 end 2046 2047 context 'when tags are not preloaded' do 2048 it { expect(described_class.find(build.id).tag_list).to eq(['tag']) } 2049 end 2050 end 2051 2052 describe '#has_tags?' do 2053 context 'when build has tags' do 2054 subject { create(:ci_build, tag_list: ['tag']) } 2055 2056 it { is_expected.to have_tags } 2057 end 2058 2059 context 'when build does not have tags' do 2060 subject { create(:ci_build, tag_list: []) } 2061 2062 it { is_expected.not_to have_tags } 2063 end 2064 end 2065 2066 describe 'build auto retry feature' do 2067 describe '#retries_count' do 2068 subject { create(:ci_build, name: 'test', pipeline: pipeline) } 2069 2070 context 'when build has been retried several times' do 2071 before do 2072 create(:ci_build, :retried, name: 'test', pipeline: pipeline) 2073 create(:ci_build, :retried, name: 'test', pipeline: pipeline) 2074 end 2075 2076 it 'reports a correct retry count value' do 2077 expect(subject.retries_count).to eq 2 2078 end 2079 end 2080 2081 context 'when build has not been retried' do 2082 it 'returns zero' do 2083 expect(subject.retries_count).to eq 0 2084 end 2085 end 2086 end 2087 end 2088 2089 describe '.keep_artifacts!' do 2090 let!(:build) { create(:ci_build, artifacts_expire_at: Time.current + 7.days) } 2091 let!(:builds_for_update) do 2092 Ci::Build.where(id: create_list(:ci_build, 3, artifacts_expire_at: Time.current + 7.days).map(&:id)) 2093 end 2094 2095 it 'resets expire_at' do 2096 builds_for_update.keep_artifacts! 2097 2098 builds_for_update.each do |build| 2099 expect(build.reload.artifacts_expire_at).to be_nil 2100 end 2101 end 2102 2103 it 'does not reset expire_at for other builds' do 2104 builds_for_update.keep_artifacts! 2105 2106 expect(build.reload.artifacts_expire_at).to be_present 2107 end 2108 2109 context 'when having artifacts files' do 2110 let!(:artifact) { create(:ci_job_artifact, job: build, expire_in: '7 days') } 2111 let!(:artifacts_for_update) do 2112 builds_for_update.map do |build| 2113 create(:ci_job_artifact, job: build, expire_in: '7 days') 2114 end 2115 end 2116 2117 it 'resets dependent objects' do 2118 builds_for_update.keep_artifacts! 2119 2120 artifacts_for_update.each do |artifact| 2121 expect(artifact.reload.expire_at).to be_nil 2122 end 2123 end 2124 2125 it 'does not reset dependent object for other builds' do 2126 builds_for_update.keep_artifacts! 2127 2128 expect(artifact.reload.expire_at).to be_present 2129 end 2130 end 2131 end 2132 2133 describe '#keep_artifacts!' do 2134 let(:build) { create(:ci_build, artifacts_expire_at: Time.current + 7.days) } 2135 2136 subject { build.keep_artifacts! } 2137 2138 it 'to reset expire_at' do 2139 subject 2140 2141 expect(build.artifacts_expire_at).to be_nil 2142 end 2143 2144 context 'when having artifacts files' do 2145 let!(:artifact) { create(:ci_job_artifact, job: build, expire_in: '7 days') } 2146 2147 it 'to reset dependent objects' do 2148 subject 2149 2150 expect(artifact.reload.expire_at).to be_nil 2151 end 2152 end 2153 end 2154 2155 describe '#artifacts_file_for_type' do 2156 let(:build) { create(:ci_build, :artifacts) } 2157 let(:file_type) { :archive } 2158 2159 subject { build.artifacts_file_for_type(file_type) } 2160 2161 it 'queries artifacts for type' do 2162 expect(build).to receive_message_chain(:job_artifacts, :find_by).with(file_type: [Ci::JobArtifact.file_types[file_type]]) 2163 2164 subject 2165 end 2166 end 2167 2168 describe '#merge_request' do 2169 subject { pipeline.builds.take.merge_request } 2170 2171 context 'on a branch pipeline' do 2172 let!(:pipeline) { create(:ci_pipeline, :with_job, project: project, ref: 'fix') } 2173 2174 context 'with no merge request' do 2175 it { is_expected.to be_nil } 2176 end 2177 2178 context 'with an open merge request from the same ref name' do 2179 let!(:merge_request) { create(:merge_request, source_project: project, source_branch: 'fix') } 2180 2181 # If no diff exists, the pipeline commit was not part of the merge 2182 # request and may have simply incidentally used the same ref name. 2183 context 'without a merge request diff containing the pipeline commit' do 2184 it { is_expected.to be_nil } 2185 end 2186 2187 # If the merge request was truly opened from the branch that the 2188 # pipeline ran on, that head sha will be present in a diff. 2189 context 'with a merge request diff containing the pipeline commit' do 2190 let!(:mr_diff) { create(:merge_request_diff, merge_request: merge_request) } 2191 let!(:mr_diff_commit) { create(:merge_request_diff_commit, sha: build.sha, merge_request_diff: mr_diff) } 2192 2193 it { is_expected.to eq(merge_request) } 2194 end 2195 end 2196 2197 context 'with multiple open merge requests' do 2198 let!(:merge_request) { create(:merge_request, source_project: project, source_branch: 'fix') } 2199 let!(:mr_diff) { create(:merge_request_diff, merge_request: merge_request) } 2200 let!(:mr_diff_commit) { create(:merge_request_diff_commit, sha: build.sha, merge_request_diff: mr_diff) } 2201 2202 let!(:new_merge_request) { create(:merge_request, source_project: project, source_branch: 'fix', target_branch: 'staging') } 2203 let!(:new_mr_diff) { create(:merge_request_diff, merge_request: new_merge_request) } 2204 let!(:new_mr_diff_commit) { create(:merge_request_diff_commit, sha: build.sha, merge_request_diff: new_mr_diff) } 2205 2206 it 'returns the first merge request' do 2207 expect(subject).to eq(merge_request) 2208 end 2209 end 2210 end 2211 2212 context 'on a detached merged request pipeline' do 2213 let(:pipeline) { create(:ci_pipeline, :detached_merge_request_pipeline, :with_job) } 2214 2215 it { is_expected.to eq(pipeline.merge_request) } 2216 end 2217 2218 context 'on a legacy detached merged request pipeline' do 2219 let(:pipeline) { create(:ci_pipeline, :legacy_detached_merge_request_pipeline, :with_job) } 2220 2221 it { is_expected.to eq(pipeline.merge_request) } 2222 end 2223 2224 context 'on a pipeline for merged results' do 2225 let(:pipeline) { create(:ci_pipeline, :merged_result_pipeline, :with_job) } 2226 2227 it { is_expected.to eq(pipeline.merge_request) } 2228 end 2229 end 2230 2231 describe '#options' do 2232 let(:options) do 2233 { 2234 image: "ruby:2.7", 2235 services: ["postgres"], 2236 script: ["ls -a"] 2237 } 2238 end 2239 2240 it 'contains options' do 2241 expect(build.options).to eq(options.symbolize_keys) 2242 end 2243 2244 it 'allows to access with symbolized keys' do 2245 expect(build.options[:image]).to eq('ruby:2.7') 2246 end 2247 2248 it 'rejects access with string keys' do 2249 expect(build.options['image']).to be_nil 2250 end 2251 2252 it 'persist data in build metadata' do 2253 expect(build.metadata.read_attribute(:config_options)).to eq(options.symbolize_keys) 2254 end 2255 2256 it 'does not persist data in build' do 2257 expect(build.read_attribute(:options)).to be_nil 2258 end 2259 2260 context 'when options include artifacts:expose_as' do 2261 let(:build) { create(:ci_build, options: { artifacts: { expose_as: 'test' } }) } 2262 2263 it 'saves the presence of expose_as into build metadata' do 2264 expect(build.metadata).to have_exposed_artifacts 2265 end 2266 end 2267 end 2268 2269 describe '#other_manual_actions' do 2270 let(:build) { create(:ci_build, :manual, pipeline: pipeline) } 2271 let!(:other_build) { create(:ci_build, :manual, pipeline: pipeline, name: 'other action') } 2272 2273 subject { build.other_manual_actions } 2274 2275 before do 2276 project.add_developer(user) 2277 end 2278 2279 it 'returns other actions' do 2280 is_expected.to contain_exactly(other_build) 2281 end 2282 2283 context 'when build is retried' do 2284 let!(:new_build) { described_class.retry(build, user) } 2285 2286 it 'does not return any of them' do 2287 is_expected.not_to include(build, new_build) 2288 end 2289 end 2290 2291 context 'when other build is retried' do 2292 let!(:retried_build) { described_class.retry(other_build, user) } 2293 2294 before do 2295 retried_build.success 2296 end 2297 2298 it 'returns a retried build' do 2299 is_expected.to contain_exactly(retried_build) 2300 end 2301 end 2302 end 2303 2304 describe '#other_scheduled_actions' do 2305 let(:build) { create(:ci_build, :scheduled, pipeline: pipeline) } 2306 2307 subject { build.other_scheduled_actions } 2308 2309 before do 2310 project.add_developer(user) 2311 end 2312 2313 context "when other build's status is success" do 2314 let!(:other_build) { create(:ci_build, :schedulable, :success, pipeline: pipeline, name: 'other action') } 2315 2316 it 'returns other actions' do 2317 is_expected.to contain_exactly(other_build) 2318 end 2319 end 2320 2321 context "when other build's status is failed" do 2322 let!(:other_build) { create(:ci_build, :schedulable, :failed, pipeline: pipeline, name: 'other action') } 2323 2324 it 'returns other actions' do 2325 is_expected.to contain_exactly(other_build) 2326 end 2327 end 2328 2329 context "when other build's status is running" do 2330 let!(:other_build) { create(:ci_build, :schedulable, :running, pipeline: pipeline, name: 'other action') } 2331 2332 it 'does not return other actions' do 2333 is_expected.to be_empty 2334 end 2335 end 2336 2337 context "when other build's status is scheduled" do 2338 let!(:other_build) { create(:ci_build, :scheduled, pipeline: pipeline, name: 'other action') } 2339 2340 it 'does not return other actions' do 2341 is_expected.to contain_exactly(other_build) 2342 end 2343 end 2344 end 2345 2346 describe '#persisted_environment' do 2347 let!(:environment) do 2348 create(:environment, project: project, name: "foo-#{project.default_branch}") 2349 end 2350 2351 subject { build.persisted_environment } 2352 2353 context 'when referenced literally' do 2354 let(:build) do 2355 create(:ci_build, pipeline: pipeline, environment: "foo-#{project.default_branch}") 2356 end 2357 2358 it { is_expected.to eq(environment) } 2359 end 2360 2361 context 'when referenced with a variable' do 2362 let(:build) do 2363 create(:ci_build, pipeline: pipeline, environment: "foo-$CI_COMMIT_REF_NAME") 2364 end 2365 2366 it { is_expected.to eq(environment) } 2367 end 2368 2369 context 'when there is no environment' do 2370 it { is_expected.to be_nil } 2371 end 2372 2373 context 'when build has a stop environment' do 2374 let(:build) { create(:ci_build, :stop_review_app, pipeline: pipeline, environment: "foo-#{project.default_branch}") } 2375 2376 it 'expands environment name' do 2377 expect(build).to receive(:expanded_environment_name).and_call_original 2378 2379 is_expected.to eq(environment) 2380 end 2381 end 2382 end 2383 2384 describe '#play' do 2385 let(:build) { create(:ci_build, :manual, pipeline: pipeline) } 2386 2387 before do 2388 project.add_developer(user) 2389 end 2390 2391 it 'enqueues the build' do 2392 expect(build.play(user)).to be_pending 2393 end 2394 end 2395 2396 describe '#playable?' do 2397 context 'when build is a manual action' do 2398 context 'when build has been skipped' do 2399 subject { build_stubbed(:ci_build, :manual, status: :skipped) } 2400 2401 it { is_expected.not_to be_playable } 2402 end 2403 2404 context 'when build has been canceled' do 2405 subject { build_stubbed(:ci_build, :manual, status: :canceled) } 2406 2407 it { is_expected.to be_playable } 2408 end 2409 2410 context 'when build is successful' do 2411 subject { build_stubbed(:ci_build, :manual, status: :success) } 2412 2413 it { is_expected.to be_playable } 2414 end 2415 2416 context 'when build has failed' do 2417 subject { build_stubbed(:ci_build, :manual, status: :failed) } 2418 2419 it { is_expected.to be_playable } 2420 end 2421 2422 context 'when build is a manual untriggered action' do 2423 subject { build_stubbed(:ci_build, :manual, status: :manual) } 2424 2425 it { is_expected.to be_playable } 2426 end 2427 2428 context 'when build is a manual and degenerated' do 2429 subject { build_stubbed(:ci_build, :manual, :degenerated, status: :manual) } 2430 2431 it { is_expected.not_to be_playable } 2432 end 2433 end 2434 2435 context 'when build is scheduled' do 2436 subject { build_stubbed(:ci_build, :scheduled) } 2437 2438 it { is_expected.to be_playable } 2439 end 2440 2441 context 'when build is not a manual action' do 2442 subject { build_stubbed(:ci_build, :success) } 2443 2444 it { is_expected.not_to be_playable } 2445 end 2446 end 2447 2448 describe 'project settings' do 2449 describe '#allow_git_fetch' do 2450 it 'return project allow_git_fetch configuration' do 2451 expect(build.allow_git_fetch).to eq(project.build_allow_git_fetch) 2452 end 2453 end 2454 end 2455 2456 describe '#project' do 2457 subject { build.project } 2458 2459 it { is_expected.to eq(pipeline.project) } 2460 end 2461 2462 describe '#project_id' do 2463 subject { build.project_id } 2464 2465 it { is_expected.to eq(pipeline.project_id) } 2466 end 2467 2468 describe '#project_name' do 2469 subject { build.project_name } 2470 2471 it { is_expected.to eq(project.name) } 2472 end 2473 2474 describe '#ref_slug' do 2475 { 2476 'master' => 'master', 2477 '1-foo' => '1-foo', 2478 'fix/1-foo' => 'fix-1-foo', 2479 'fix-1-foo' => 'fix-1-foo', 2480 'a' * 63 => 'a' * 63, 2481 'a' * 64 => 'a' * 63, 2482 'FOO' => 'foo', 2483 '-' + 'a' * 61 + '-' => 'a' * 61, 2484 '-' + 'a' * 62 + '-' => 'a' * 62, 2485 '-' + 'a' * 63 + '-' => 'a' * 62, 2486 'a' * 62 + ' ' => 'a' * 62 2487 }.each do |ref, slug| 2488 it "transforms #{ref} to #{slug}" do 2489 build.ref = ref 2490 2491 expect(build.ref_slug).to eq(slug) 2492 end 2493 end 2494 end 2495 2496 describe '#repo_url' do 2497 subject { build.repo_url } 2498 2499 context 'when token is set' do 2500 before do 2501 build.ensure_token 2502 end 2503 2504 it { is_expected.to be_a(String) } 2505 it { is_expected.to end_with(".git") } 2506 it { is_expected.to start_with(project.web_url[0..6]) } 2507 it { is_expected.to include(build.token) } 2508 it { is_expected.to include('gitlab-ci-token') } 2509 it { is_expected.to include(project.web_url[7..]) } 2510 end 2511 2512 context 'when token is empty' do 2513 before do 2514 build.update_columns(token: nil, token_encrypted: nil) 2515 end 2516 2517 it { is_expected.to be_nil} 2518 end 2519 end 2520 2521 describe '#stuck?' do 2522 subject { build.stuck? } 2523 2524 context "when commit_status.status is pending" do 2525 before do 2526 build.status = 'pending' 2527 end 2528 2529 it { is_expected.to be_truthy } 2530 2531 context "and there are specific runner" do 2532 let!(:runner) { create(:ci_runner, :project, projects: [build.project], contacted_at: 1.second.ago) } 2533 2534 it { is_expected.to be_falsey } 2535 end 2536 end 2537 2538 %w[success failed canceled running].each do |state| 2539 context "when commit_status.status is #{state}" do 2540 before do 2541 build.status = state 2542 end 2543 2544 it { is_expected.to be_falsey } 2545 end 2546 end 2547 end 2548 2549 describe '#has_expired_locked_archive_artifacts?' do 2550 subject { build.has_expired_locked_archive_artifacts? } 2551 2552 context 'when build does not have artifacts' do 2553 it { is_expected.to eq(nil) } 2554 end 2555 2556 context 'when build has artifacts' do 2557 before do 2558 create(:ci_job_artifact, :archive, job: build) 2559 end 2560 2561 context 'when artifacts are unlocked' do 2562 before do 2563 build.pipeline.unlocked! 2564 end 2565 2566 it { is_expected.to eq(false) } 2567 end 2568 2569 context 'when artifacts are locked' do 2570 before do 2571 build.pipeline.artifacts_locked! 2572 end 2573 2574 context 'when artifacts do not expire' do 2575 it { is_expected.to eq(false) } 2576 end 2577 2578 context 'when artifacts expire in the future' do 2579 before do 2580 build.update!(artifacts_expire_at: 1.day.from_now) 2581 end 2582 2583 it { is_expected.to eq(false) } 2584 end 2585 2586 context 'when artifacts expired in the past' do 2587 before do 2588 build.update!(artifacts_expire_at: 1.day.ago) 2589 end 2590 2591 it { is_expected.to eq(true) } 2592 end 2593 end 2594 end 2595 end 2596 2597 describe '#has_expiring_archive_artifacts?' do 2598 context 'when artifacts have expiration date set' do 2599 before do 2600 build.update!(artifacts_expire_at: 1.day.from_now) 2601 end 2602 2603 context 'and job artifacts archive record exists' do 2604 let!(:archive) { create(:ci_job_artifact, :archive, job: build) } 2605 2606 it 'has expiring artifacts' do 2607 expect(build).to have_expiring_archive_artifacts 2608 end 2609 end 2610 2611 context 'and job artifacts archive record does not exist' do 2612 it 'does not have expiring artifacts' do 2613 expect(build).not_to have_expiring_archive_artifacts 2614 end 2615 end 2616 end 2617 2618 context 'when artifacts do not have expiration date set' do 2619 before do 2620 build.update!(artifacts_expire_at: nil) 2621 end 2622 2623 it 'does not have expiring artifacts' do 2624 expect(build).not_to have_expiring_archive_artifacts 2625 end 2626 end 2627 end 2628 2629 describe '#variables' do 2630 let(:container_registry_enabled) { false } 2631 2632 before do 2633 stub_container_registry_config(enabled: container_registry_enabled, host_port: 'registry.example.com') 2634 stub_config(dependency_proxy: { enabled: true }) 2635 end 2636 2637 subject { build.variables } 2638 2639 context 'returns variables' do 2640 let(:predefined_variables) do 2641 [ 2642 { key: 'CI_PIPELINE_ID', value: pipeline.id.to_s, public: true, masked: false }, 2643 { key: 'CI_PIPELINE_URL', value: project.web_url + "/-/pipelines/#{pipeline.id}", public: true, masked: false }, 2644 { key: 'CI_JOB_ID', value: build.id.to_s, public: true, masked: false }, 2645 { key: 'CI_JOB_URL', value: project.web_url + "/-/jobs/#{build.id}", public: true, masked: false }, 2646 { key: 'CI_JOB_TOKEN', value: 'my-token', public: false, masked: true }, 2647 { key: 'CI_JOB_STARTED_AT', value: build.started_at&.iso8601, public: true, masked: false }, 2648 { key: 'CI_BUILD_ID', value: build.id.to_s, public: true, masked: false }, 2649 { key: 'CI_BUILD_TOKEN', value: 'my-token', public: false, masked: true }, 2650 { key: 'CI_REGISTRY_USER', value: 'gitlab-ci-token', public: true, masked: false }, 2651 { key: 'CI_REGISTRY_PASSWORD', value: 'my-token', public: false, masked: true }, 2652 { key: 'CI_REPOSITORY_URL', value: build.repo_url, public: false, masked: false }, 2653 { key: 'CI_DEPENDENCY_PROXY_USER', value: 'gitlab-ci-token', public: true, masked: false }, 2654 { key: 'CI_DEPENDENCY_PROXY_PASSWORD', value: 'my-token', public: false, masked: true }, 2655 { key: 'CI_JOB_JWT', value: 'ci.job.jwt', public: false, masked: true }, 2656 { key: 'CI_JOB_NAME', value: 'test', public: true, masked: false }, 2657 { key: 'CI_JOB_STAGE', value: 'test', public: true, masked: false }, 2658 { key: 'CI_NODE_TOTAL', value: '1', public: true, masked: false }, 2659 { key: 'CI_BUILD_NAME', value: 'test', public: true, masked: false }, 2660 { key: 'CI_BUILD_STAGE', value: 'test', public: true, masked: false }, 2661 { key: 'CI', value: 'true', public: true, masked: false }, 2662 { key: 'GITLAB_CI', value: 'true', public: true, masked: false }, 2663 { key: 'CI_SERVER_URL', value: Gitlab.config.gitlab.url, public: true, masked: false }, 2664 { key: 'CI_SERVER_HOST', value: Gitlab.config.gitlab.host, public: true, masked: false }, 2665 { key: 'CI_SERVER_PORT', value: Gitlab.config.gitlab.port.to_s, public: true, masked: false }, 2666 { key: 'CI_SERVER_PROTOCOL', value: Gitlab.config.gitlab.protocol, public: true, masked: false }, 2667 { key: 'CI_SERVER_NAME', value: 'GitLab', public: true, masked: false }, 2668 { key: 'CI_SERVER_VERSION', value: Gitlab::VERSION, public: true, masked: false }, 2669 { key: 'CI_SERVER_VERSION_MAJOR', value: Gitlab.version_info.major.to_s, public: true, masked: false }, 2670 { key: 'CI_SERVER_VERSION_MINOR', value: Gitlab.version_info.minor.to_s, public: true, masked: false }, 2671 { key: 'CI_SERVER_VERSION_PATCH', value: Gitlab.version_info.patch.to_s, public: true, masked: false }, 2672 { key: 'CI_SERVER_REVISION', value: Gitlab.revision, public: true, masked: false }, 2673 { key: 'GITLAB_FEATURES', value: project.licensed_features.join(','), public: true, masked: false }, 2674 { key: 'CI_PROJECT_ID', value: project.id.to_s, public: true, masked: false }, 2675 { key: 'CI_PROJECT_NAME', value: project.path, public: true, masked: false }, 2676 { key: 'CI_PROJECT_TITLE', value: project.title, public: true, masked: false }, 2677 { key: 'CI_PROJECT_PATH', value: project.full_path, public: true, masked: false }, 2678 { key: 'CI_PROJECT_PATH_SLUG', value: project.full_path_slug, public: true, masked: false }, 2679 { key: 'CI_PROJECT_NAMESPACE', value: project.namespace.full_path, public: true, masked: false }, 2680 { key: 'CI_PROJECT_ROOT_NAMESPACE', value: project.namespace.root_ancestor.path, public: true, masked: false }, 2681 { key: 'CI_PROJECT_URL', value: project.web_url, public: true, masked: false }, 2682 { key: 'CI_PROJECT_VISIBILITY', value: 'private', public: true, masked: false }, 2683 { key: 'CI_PROJECT_REPOSITORY_LANGUAGES', value: project.repository_languages.map(&:name).join(',').downcase, public: true, masked: false }, 2684 { key: 'CI_PROJECT_CLASSIFICATION_LABEL', value: project.external_authorization_classification_label, public: true, masked: false }, 2685 { key: 'CI_DEFAULT_BRANCH', value: project.default_branch, public: true, masked: false }, 2686 { key: 'CI_CONFIG_PATH', value: project.ci_config_path_or_default, public: true, masked: false }, 2687 { key: 'CI_PAGES_DOMAIN', value: Gitlab.config.pages.host, public: true, masked: false }, 2688 { key: 'CI_PAGES_URL', value: project.pages_url, public: true, masked: false }, 2689 { key: 'CI_DEPENDENCY_PROXY_SERVER', value: Gitlab.host_with_port, public: true, masked: false }, 2690 { key: 'CI_DEPENDENCY_PROXY_GROUP_IMAGE_PREFIX', 2691 value: "#{Gitlab.host_with_port}/#{project.namespace.root_ancestor.path.downcase}#{DependencyProxy::URL_SUFFIX}", 2692 public: true, 2693 masked: false }, 2694 { key: 'CI_DEPENDENCY_PROXY_DIRECT_GROUP_IMAGE_PREFIX', 2695 value: "#{Gitlab.host_with_port}/#{project.namespace.full_path.downcase}#{DependencyProxy::URL_SUFFIX}", 2696 public: true, 2697 masked: false }, 2698 { key: 'CI_API_V4_URL', value: 'http://localhost/api/v4', public: true, masked: false }, 2699 { key: 'CI_PIPELINE_IID', value: pipeline.iid.to_s, public: true, masked: false }, 2700 { key: 'CI_PIPELINE_SOURCE', value: pipeline.source, public: true, masked: false }, 2701 { key: 'CI_PIPELINE_CREATED_AT', value: pipeline.created_at.iso8601, public: true, masked: false }, 2702 { key: 'CI_COMMIT_SHA', value: build.sha, public: true, masked: false }, 2703 { key: 'CI_COMMIT_SHORT_SHA', value: build.short_sha, public: true, masked: false }, 2704 { key: 'CI_COMMIT_BEFORE_SHA', value: build.before_sha, public: true, masked: false }, 2705 { key: 'CI_COMMIT_REF_NAME', value: build.ref, public: true, masked: false }, 2706 { key: 'CI_COMMIT_REF_SLUG', value: build.ref_slug, public: true, masked: false }, 2707 { key: 'CI_COMMIT_BRANCH', value: build.ref, public: true, masked: false }, 2708 { key: 'CI_COMMIT_MESSAGE', value: pipeline.git_commit_message, public: true, masked: false }, 2709 { key: 'CI_COMMIT_TITLE', value: pipeline.git_commit_title, public: true, masked: false }, 2710 { key: 'CI_COMMIT_DESCRIPTION', value: pipeline.git_commit_description, public: true, masked: false }, 2711 { key: 'CI_COMMIT_REF_PROTECTED', value: (!!pipeline.protected_ref?).to_s, public: true, masked: false }, 2712 { key: 'CI_COMMIT_TIMESTAMP', value: pipeline.git_commit_timestamp, public: true, masked: false }, 2713 { key: 'CI_COMMIT_AUTHOR', value: pipeline.git_author_full_text, public: true, masked: false }, 2714 { key: 'CI_BUILD_REF', value: build.sha, public: true, masked: false }, 2715 { key: 'CI_BUILD_BEFORE_SHA', value: build.before_sha, public: true, masked: false }, 2716 { key: 'CI_BUILD_REF_NAME', value: build.ref, public: true, masked: false }, 2717 { key: 'CI_BUILD_REF_SLUG', value: build.ref_slug, public: true, masked: false } 2718 ] 2719 end 2720 2721 before do 2722 allow(Gitlab::Ci::Jwt).to receive(:for_build).and_return('ci.job.jwt') 2723 build.set_token('my-token') 2724 build.yaml_variables = [] 2725 end 2726 2727 it { is_expected.to be_instance_of(Gitlab::Ci::Variables::Collection) } 2728 it { expect(subject.to_runner_variables).to eq(predefined_variables) } 2729 2730 it 'excludes variables that require an environment or user' do 2731 environment_based_variables_collection = subject.filter do |variable| 2732 %w[ 2733 YAML_VARIABLE CI_ENVIRONMENT_NAME CI_ENVIRONMENT_SLUG 2734 CI_ENVIRONMENT_ACTION CI_ENVIRONMENT_URL 2735 ].include?(variable[:key]) 2736 end 2737 2738 expect(environment_based_variables_collection).to be_empty 2739 end 2740 2741 context 'when ci_job_jwt feature flag is disabled' do 2742 before do 2743 stub_feature_flags(ci_job_jwt: false) 2744 end 2745 2746 it 'CI_JOB_JWT is not included' do 2747 expect(subject.pluck(:key)).not_to include('CI_JOB_JWT') 2748 end 2749 end 2750 2751 context 'when CI_JOB_JWT generation fails' do 2752 [ 2753 OpenSSL::PKey::RSAError, 2754 Gitlab::Ci::Jwt::NoSigningKeyError 2755 ].each do |reason_to_fail| 2756 it 'CI_JOB_JWT is not included' do 2757 expect(Gitlab::Ci::Jwt).to receive(:for_build).and_raise(reason_to_fail) 2758 expect(Gitlab::ErrorTracking).to receive(:track_exception) 2759 2760 expect { subject }.not_to raise_error 2761 expect(subject.pluck(:key)).not_to include('CI_JOB_JWT') 2762 end 2763 end 2764 end 2765 2766 describe 'variables ordering' do 2767 context 'when variables hierarchy is stubbed' do 2768 let(:build_pre_var) { { key: 'build', value: 'value', public: true, masked: false } } 2769 let(:project_pre_var) { { key: 'project', value: 'value', public: true, masked: false } } 2770 let(:pipeline_pre_var) { { key: 'pipeline', value: 'value', public: true, masked: false } } 2771 let(:build_yaml_var) { { key: 'yaml', value: 'value', public: true, masked: false } } 2772 let(:dependency_proxy_var) { { key: 'dependency_proxy', value: 'value', public: true, masked: false } } 2773 let(:job_jwt_var) { { key: 'CI_JOB_JWT', value: 'ci.job.jwt', public: false, masked: true } } 2774 let(:job_dependency_var) { { key: 'job_dependency', value: 'value', public: true, masked: false } } 2775 2776 before do 2777 allow_next_instance_of(Gitlab::Ci::Variables::Builder) do |builder| 2778 allow(builder).to receive(:predefined_variables) { [build_pre_var] } 2779 end 2780 2781 allow(build).to receive(:yaml_variables) { [build_yaml_var] } 2782 allow(build).to receive(:persisted_variables) { [] } 2783 allow(build).to receive(:job_jwt_variables) { [job_jwt_var] } 2784 allow(build).to receive(:dependency_variables) { [job_dependency_var] } 2785 allow(build).to receive(:dependency_proxy_variables) { [dependency_proxy_var] } 2786 2787 allow(build.project) 2788 .to receive(:predefined_variables) { [project_pre_var] } 2789 2790 project.variables.create!(key: 'secret', value: 'value') 2791 2792 allow(build.pipeline) 2793 .to receive(:predefined_variables).and_return([pipeline_pre_var]) 2794 end 2795 2796 it 'returns variables in order depending on resource hierarchy' do 2797 expect(subject.to_runner_variables).to eq( 2798 [dependency_proxy_var, 2799 job_jwt_var, 2800 build_pre_var, 2801 project_pre_var, 2802 pipeline_pre_var, 2803 build_yaml_var, 2804 job_dependency_var, 2805 { key: 'secret', value: 'value', public: false, masked: false }]) 2806 end 2807 end 2808 2809 context 'when build has environment and user-provided variables' do 2810 let(:expected_variables) do 2811 predefined_variables.map { |variable| variable.fetch(:key) } + 2812 %w[YAML_VARIABLE CI_ENVIRONMENT_NAME CI_ENVIRONMENT_SLUG 2813 CI_ENVIRONMENT_TIER CI_ENVIRONMENT_ACTION CI_ENVIRONMENT_URL] 2814 end 2815 2816 before do 2817 create(:environment, project: build.project, 2818 name: 'staging') 2819 2820 build.yaml_variables = [{ key: 'YAML_VARIABLE', 2821 value: 'var', 2822 public: true }] 2823 build.environment = 'staging' 2824 end 2825 2826 it 'matches explicit variables ordering' do 2827 received_variables = subject.map { |variable| variable[:key] } 2828 2829 expect(received_variables).to eq expected_variables 2830 end 2831 2832 describe 'CI_ENVIRONMENT_ACTION' do 2833 let(:enviroment_action_variable) { subject.find { |variable| variable[:key] == 'CI_ENVIRONMENT_ACTION' } } 2834 2835 shared_examples 'defaults value' do 2836 it 'value matches start' do 2837 expect(enviroment_action_variable[:value]).to eq('start') 2838 end 2839 end 2840 2841 it_behaves_like 'defaults value' 2842 2843 context 'when options is set' do 2844 before do 2845 build.update!(options: options) 2846 end 2847 2848 context 'when options is empty' do 2849 let(:options) { {} } 2850 2851 it_behaves_like 'defaults value' 2852 end 2853 2854 context 'when options is nil' do 2855 let(:options) { nil } 2856 2857 it_behaves_like 'defaults value' 2858 end 2859 2860 context 'when options environment is specified' do 2861 let(:options) { { environment: {} } } 2862 2863 it_behaves_like 'defaults value' 2864 end 2865 2866 context 'when options environment action specified' do 2867 let(:options) { { environment: { action: 'stop' } } } 2868 2869 it 'matches the specified action' do 2870 expect(enviroment_action_variable[:value]).to eq('stop') 2871 end 2872 end 2873 end 2874 end 2875 end 2876 end 2877 end 2878 2879 context 'when build has user' do 2880 let(:user_variables) do 2881 [ 2882 { key: 'GITLAB_USER_ID', value: user.id.to_s, public: true, masked: false }, 2883 { key: 'GITLAB_USER_EMAIL', value: user.email, public: true, masked: false }, 2884 { key: 'GITLAB_USER_LOGIN', value: user.username, public: true, masked: false }, 2885 { key: 'GITLAB_USER_NAME', value: user.name, public: true, masked: false } 2886 ] 2887 end 2888 2889 before do 2890 build.update!(user: user) 2891 end 2892 2893 it { user_variables.each { |v| is_expected.to include(v) } } 2894 end 2895 2896 context 'when build belongs to a pipeline for merge request' do 2897 let(:merge_request) { create(:merge_request, :with_detached_merge_request_pipeline, source_branch: 'improve/awesome') } 2898 let(:pipeline) { merge_request.all_pipelines.first } 2899 let(:build) { create(:ci_build, ref: pipeline.ref, pipeline: pipeline) } 2900 2901 it 'returns values based on source ref' do 2902 is_expected.to include( 2903 { key: 'CI_COMMIT_REF_NAME', value: 'improve/awesome', public: true, masked: false }, 2904 { key: 'CI_COMMIT_REF_SLUG', value: 'improve-awesome', public: true, masked: false } 2905 ) 2906 end 2907 end 2908 2909 context 'when build has an environment' do 2910 let(:environment_variables) do 2911 [ 2912 { key: 'CI_ENVIRONMENT_NAME', value: 'production', public: true, masked: false }, 2913 { key: 'CI_ENVIRONMENT_SLUG', value: 'prod-slug', public: true, masked: false }, 2914 { key: 'CI_ENVIRONMENT_TIER', value: 'production', public: true, masked: false } 2915 ] 2916 end 2917 2918 let!(:environment) do 2919 create(:environment, 2920 project: build.project, 2921 name: 'production', 2922 slug: 'prod-slug', 2923 tier: 'production', 2924 external_url: '') 2925 end 2926 2927 before do 2928 build.update!(environment: 'production') 2929 end 2930 2931 shared_examples 'containing environment variables' do 2932 it { is_expected.to include(*environment_variables) } 2933 end 2934 2935 context 'when no URL was set' do 2936 it_behaves_like 'containing environment variables' 2937 2938 it 'does not have CI_ENVIRONMENT_URL' do 2939 keys = subject.pluck(:key) 2940 2941 expect(keys).not_to include('CI_ENVIRONMENT_URL') 2942 end 2943 end 2944 2945 context 'when an URL was set' do 2946 let(:url) { 'http://host/test' } 2947 2948 before do 2949 environment_variables << 2950 { key: 'CI_ENVIRONMENT_URL', value: url, public: true, masked: false } 2951 end 2952 2953 context 'when the URL was set from the job' do 2954 before do 2955 build.update!(options: { environment: { url: url } }) 2956 end 2957 2958 it_behaves_like 'containing environment variables' 2959 2960 context 'when variables are used in the URL, it does not expand' do 2961 let(:url) { 'http://$CI_PROJECT_NAME-$CI_ENVIRONMENT_SLUG' } 2962 2963 it_behaves_like 'containing environment variables' 2964 2965 it 'puts $CI_ENVIRONMENT_URL in the last so all other variables are available to be used when runners are trying to expand it' do 2966 expect(subject.to_runner_variables.last).to eq(environment_variables.last) 2967 end 2968 end 2969 end 2970 2971 context 'when the URL was not set from the job, but environment' do 2972 before do 2973 environment.update!(external_url: url) 2974 end 2975 2976 it_behaves_like 'containing environment variables' 2977 end 2978 end 2979 2980 context 'when project has an environment specific variable' do 2981 let(:environment_specific_variable) do 2982 { key: 'MY_STAGING_ONLY_VARIABLE', value: 'environment_specific_variable', public: false, masked: false } 2983 end 2984 2985 before do 2986 create(:ci_variable, environment_specific_variable.slice(:key, :value) 2987 .merge(project: project, environment_scope: 'stag*')) 2988 end 2989 2990 it_behaves_like 'containing environment variables' 2991 2992 context 'when environment scope does not match build environment' do 2993 it { is_expected.not_to include(environment_specific_variable) } 2994 end 2995 2996 context 'when environment scope matches build environment' do 2997 before do 2998 create(:environment, name: 'staging', project: project) 2999 build.update!(environment: 'staging') 3000 end 3001 3002 it { is_expected.to include(environment_specific_variable) } 3003 end 3004 end 3005 end 3006 3007 context 'when build started manually' do 3008 before do 3009 build.update!(when: :manual) 3010 end 3011 3012 let(:manual_variable) do 3013 { key: 'CI_JOB_MANUAL', value: 'true', public: true, masked: false } 3014 end 3015 3016 it { is_expected.to include(manual_variable) } 3017 end 3018 3019 context 'when job variable is defined' do 3020 let(:job_variable) { { key: 'first', value: 'first', public: false, masked: false } } 3021 3022 before do 3023 create(:ci_job_variable, job_variable.slice(:key, :value).merge(job: build)) 3024 end 3025 3026 it { is_expected.to include(job_variable) } 3027 end 3028 3029 context 'when build is for branch' do 3030 let(:branch_variable) do 3031 { key: 'CI_COMMIT_BRANCH', value: 'master', public: true, masked: false } 3032 end 3033 3034 before do 3035 build.update!(tag: false) 3036 pipeline.update!(tag: false) 3037 end 3038 3039 it { is_expected.to include(branch_variable) } 3040 end 3041 3042 context 'when build is for tag' do 3043 let(:tag_variable) do 3044 { key: 'CI_COMMIT_TAG', value: 'master', public: true, masked: false } 3045 end 3046 3047 before do 3048 build.update!(tag: true) 3049 pipeline.update!(tag: true) 3050 end 3051 3052 it do 3053 build.reload 3054 3055 expect(subject).to include(tag_variable) 3056 end 3057 end 3058 3059 context 'when CI variable is defined' do 3060 let(:ci_variable) do 3061 { key: 'SECRET_KEY', value: 'secret_value', public: false, masked: false } 3062 end 3063 3064 before do 3065 create(:ci_variable, 3066 ci_variable.slice(:key, :value).merge(project: project)) 3067 end 3068 3069 it { is_expected.to include(ci_variable) } 3070 end 3071 3072 context 'when protected variable is defined' do 3073 let(:ref) { Gitlab::Git::BRANCH_REF_PREFIX + build.ref } 3074 3075 let(:protected_variable) do 3076 { key: 'PROTECTED_KEY', value: 'protected_value', public: false, masked: false } 3077 end 3078 3079 before do 3080 create(:ci_variable, 3081 :protected, 3082 protected_variable.slice(:key, :value).merge(project: project)) 3083 end 3084 3085 context 'when the branch is protected' do 3086 before do 3087 allow(build.project).to receive(:protected_for?).with(ref).and_return(true) 3088 end 3089 3090 it { is_expected.to include(protected_variable) } 3091 end 3092 3093 context 'when the tag is protected' do 3094 before do 3095 allow(build.project).to receive(:protected_for?).with(ref).and_return(true) 3096 end 3097 3098 it { is_expected.to include(protected_variable) } 3099 end 3100 3101 context 'when the ref is not protected' do 3102 it { is_expected.not_to include(protected_variable) } 3103 end 3104 end 3105 3106 context 'when group CI variable is defined' do 3107 let(:ci_variable) do 3108 { key: 'SECRET_KEY', value: 'secret_value', public: false, masked: false } 3109 end 3110 3111 before do 3112 create(:ci_group_variable, 3113 ci_variable.slice(:key, :value).merge(group: group)) 3114 end 3115 3116 it { is_expected.to include(ci_variable) } 3117 end 3118 3119 context 'when group protected variable is defined' do 3120 let(:ref) { Gitlab::Git::BRANCH_REF_PREFIX + build.ref } 3121 3122 let(:protected_variable) do 3123 { key: 'PROTECTED_KEY', value: 'protected_value', public: false, masked: false } 3124 end 3125 3126 before do 3127 create(:ci_group_variable, 3128 :protected, 3129 protected_variable.slice(:key, :value).merge(group: group)) 3130 end 3131 3132 context 'when the branch is protected' do 3133 before do 3134 allow(build.project).to receive(:protected_for?).with(ref).and_return(true) 3135 end 3136 3137 it { is_expected.to include(protected_variable) } 3138 end 3139 3140 context 'when the tag is protected' do 3141 before do 3142 allow(build.project).to receive(:protected_for?).with(ref).and_return(true) 3143 end 3144 3145 it { is_expected.to include(protected_variable) } 3146 end 3147 3148 context 'when the ref is not protected' do 3149 before do 3150 build.update_column(:ref, 'some/feature') 3151 end 3152 3153 it { is_expected.not_to include(protected_variable) } 3154 end 3155 end 3156 3157 context 'when build is for triggers' do 3158 let(:trigger) { create(:ci_trigger, project: project) } 3159 let(:trigger_request) { create(:ci_trigger_request, pipeline: pipeline, trigger: trigger) } 3160 3161 let(:user_trigger_variable) do 3162 { key: 'TRIGGER_KEY_1', value: 'TRIGGER_VALUE_1', public: false, masked: false } 3163 end 3164 3165 let(:predefined_trigger_variable) do 3166 { key: 'CI_PIPELINE_TRIGGERED', value: 'true', public: true, masked: false } 3167 end 3168 3169 before do 3170 build.trigger_request = trigger_request 3171 end 3172 3173 shared_examples 'returns variables for triggers' do 3174 it { is_expected.to include(user_trigger_variable) } 3175 it { is_expected.to include(predefined_trigger_variable) } 3176 end 3177 3178 context 'when variables are stored in trigger_request' do 3179 before do 3180 trigger_request.update_attribute(:variables, { 'TRIGGER_KEY_1' => 'TRIGGER_VALUE_1' } ) 3181 end 3182 3183 it_behaves_like 'returns variables for triggers' 3184 end 3185 3186 context 'when variables are stored in pipeline_variables' do 3187 before do 3188 create(:ci_pipeline_variable, pipeline: pipeline, key: 'TRIGGER_KEY_1', value: 'TRIGGER_VALUE_1') 3189 end 3190 3191 it_behaves_like 'returns variables for triggers' 3192 end 3193 end 3194 3195 context 'when pipeline has a variable' do 3196 let!(:pipeline_variable) { create(:ci_pipeline_variable, pipeline: pipeline) } 3197 3198 it { is_expected.to include(key: pipeline_variable.key, value: pipeline_variable.value, public: false, masked: false) } 3199 end 3200 3201 context 'when a job was triggered by a pipeline schedule' do 3202 let(:pipeline_schedule) { create(:ci_pipeline_schedule, project: project) } 3203 3204 let!(:pipeline_schedule_variable) do 3205 create(:ci_pipeline_schedule_variable, 3206 key: 'SCHEDULE_VARIABLE_KEY', 3207 pipeline_schedule: pipeline_schedule) 3208 end 3209 3210 before do 3211 pipeline_schedule.pipelines << pipeline.reload 3212 pipeline_schedule.reload 3213 end 3214 3215 it { is_expected.to include(key: pipeline_schedule_variable.key, value: pipeline_schedule_variable.value, public: false, masked: false) } 3216 end 3217 3218 context 'when container registry is enabled' do 3219 let_it_be_with_reload(:project) { create(:project, :public, :repository, group: group) } 3220 3221 let_it_be_with_reload(:pipeline) do 3222 create(:ci_pipeline, project: project, 3223 sha: project.commit.id, 3224 ref: project.default_branch, 3225 status: 'success') 3226 end 3227 3228 let_it_be_with_refind(:build) { create(:ci_build, pipeline: pipeline) } 3229 3230 let(:container_registry_enabled) { true } 3231 let(:ci_registry) do 3232 { key: 'CI_REGISTRY', value: 'registry.example.com', public: true, masked: false } 3233 end 3234 3235 let(:ci_registry_image) do 3236 { key: 'CI_REGISTRY_IMAGE', value: project.container_registry_url, public: true, masked: false } 3237 end 3238 3239 context 'and is disabled for project' do 3240 before do 3241 project.project_feature.update_column(:container_registry_access_level, ProjectFeature::DISABLED) 3242 end 3243 3244 it { is_expected.to include(ci_registry) } 3245 it { is_expected.not_to include(ci_registry_image) } 3246 end 3247 3248 context 'and is enabled for project' do 3249 before do 3250 project.project_feature.update_column(:container_registry_access_level, ProjectFeature::ENABLED) 3251 end 3252 3253 it { is_expected.to include(ci_registry) } 3254 it { is_expected.to include(ci_registry_image) } 3255 end 3256 3257 context 'and is private for project' do 3258 before do 3259 project.project_feature.update_column(:container_registry_access_level, ProjectFeature::PRIVATE) 3260 end 3261 3262 it { is_expected.to include(ci_registry) } 3263 it { is_expected.to include(ci_registry_image) } 3264 end 3265 end 3266 3267 context 'when runner is assigned to build' do 3268 let(:runner) { create(:ci_runner, description: 'description', tag_list: %w(docker linux)) } 3269 3270 before do 3271 build.update!(runner: runner) 3272 end 3273 3274 it { is_expected.to include({ key: 'CI_RUNNER_ID', value: runner.id.to_s, public: true, masked: false }) } 3275 it { is_expected.to include({ key: 'CI_RUNNER_DESCRIPTION', value: 'description', public: true, masked: false }) } 3276 it { is_expected.to include({ key: 'CI_RUNNER_TAGS', value: 'docker, linux', public: true, masked: false }) } 3277 end 3278 3279 context 'when build is for a deployment' do 3280 let(:deployment_variable) { { key: 'KUBERNETES_TOKEN', value: 'TOKEN', public: false, masked: false } } 3281 3282 before do 3283 build.environment = 'production' 3284 3285 allow_any_instance_of(Project) 3286 .to receive(:deployment_variables) 3287 .and_return([deployment_variable]) 3288 end 3289 3290 it { is_expected.to include(deployment_variable) } 3291 end 3292 3293 context 'when project has default CI config path' do 3294 let(:ci_config_path) { { key: 'CI_CONFIG_PATH', value: '.gitlab-ci.yml', public: true, masked: false } } 3295 3296 it { is_expected.to include(ci_config_path) } 3297 end 3298 3299 context 'when project has custom CI config path' do 3300 let(:ci_config_path) { { key: 'CI_CONFIG_PATH', value: 'custom', public: true, masked: false } } 3301 3302 before do 3303 project.update!(ci_config_path: 'custom') 3304 end 3305 3306 it { is_expected.to include(ci_config_path) } 3307 end 3308 3309 context 'when pipeline variable overrides build variable' do 3310 let(:build) do 3311 create(:ci_build, pipeline: pipeline, yaml_variables: [{ key: 'MYVAR', value: 'myvar', public: true }]) 3312 end 3313 3314 before do 3315 pipeline.variables.build(key: 'MYVAR', value: 'pipeline value') 3316 end 3317 3318 it 'overrides YAML variable using a pipeline variable' do 3319 variables = subject.to_runner_variables.reverse.uniq { |variable| variable[:key] }.reverse 3320 3321 expect(variables) 3322 .not_to include(key: 'MYVAR', value: 'myvar', public: true, masked: false) 3323 expect(variables) 3324 .to include(key: 'MYVAR', value: 'pipeline value', public: false, masked: false) 3325 end 3326 end 3327 3328 context 'when build is parallelized' do 3329 shared_examples 'parallelized jobs config' do 3330 let(:index) { 3 } 3331 let(:total) { 5 } 3332 3333 before do 3334 build.options[:parallel] = config 3335 build.options[:instance] = index 3336 end 3337 3338 it 'includes CI_NODE_INDEX' do 3339 is_expected.to include( 3340 { key: 'CI_NODE_INDEX', value: index.to_s, public: true, masked: false } 3341 ) 3342 end 3343 3344 it 'includes correct CI_NODE_TOTAL' do 3345 is_expected.to include( 3346 { key: 'CI_NODE_TOTAL', value: total.to_s, public: true, masked: false } 3347 ) 3348 end 3349 end 3350 3351 context 'when parallel is a number' do 3352 let(:config) { 5 } 3353 3354 it_behaves_like 'parallelized jobs config' 3355 end 3356 3357 context 'when parallel is hash with the total key' do 3358 let(:config) { { total: 5 } } 3359 3360 it_behaves_like 'parallelized jobs config' 3361 end 3362 3363 context 'when parallel is nil' do 3364 let(:config) {} 3365 3366 it_behaves_like 'parallelized jobs config' do 3367 let(:total) { 1 } 3368 end 3369 end 3370 end 3371 3372 context 'when build has not been persisted yet' do 3373 let(:build) do 3374 described_class.new( 3375 name: 'rspec', 3376 stage: 'test', 3377 ref: 'feature', 3378 project: project, 3379 pipeline: pipeline 3380 ) 3381 end 3382 3383 let(:pipeline) { create(:ci_pipeline, project: project, ref: 'feature') } 3384 3385 it 'returns static predefined variables' do 3386 expect(build.variables.size).to be >= 28 3387 expect(build.variables) 3388 .to include(key: 'CI_COMMIT_REF_NAME', value: 'feature', public: true, masked: false) 3389 expect(build).not_to be_persisted 3390 end 3391 end 3392 3393 context 'for deploy tokens' do 3394 let(:deploy_token) { create(:deploy_token, :gitlab_deploy_token) } 3395 3396 let(:deploy_token_variables) do 3397 [ 3398 { key: 'CI_DEPLOY_USER', value: deploy_token.username, public: true, masked: false }, 3399 { key: 'CI_DEPLOY_PASSWORD', value: deploy_token.token, public: false, masked: true } 3400 ] 3401 end 3402 3403 context 'when gitlab-deploy-token exists' do 3404 before do 3405 project.deploy_tokens << deploy_token 3406 end 3407 3408 it 'includes deploy token variables' do 3409 is_expected.to include(*deploy_token_variables) 3410 end 3411 end 3412 3413 context 'when gitlab-deploy-token does not exist' do 3414 it 'does not include deploy token variables' do 3415 expect(subject.find { |v| v[:key] == 'CI_DEPLOY_USER'}).to be_nil 3416 expect(subject.find { |v| v[:key] == 'CI_DEPLOY_PASSWORD'}).to be_nil 3417 end 3418 end 3419 end 3420 3421 context 'when build has dependency which has dotenv variable' do 3422 let!(:prepare) { create(:ci_build, pipeline: pipeline, stage_idx: 0) } 3423 let!(:build) { create(:ci_build, pipeline: pipeline, stage_idx: 1, options: { dependencies: [prepare.name] }) } 3424 3425 let!(:job_variable) { create(:ci_job_variable, :dotenv_source, job: prepare) } 3426 3427 it { is_expected.to include(key: job_variable.key, value: job_variable.value, public: false, masked: false) } 3428 end 3429 end 3430 3431 describe '#scoped_variables' do 3432 it 'records a prometheus metric' do 3433 histogram = double(:histogram) 3434 expect(::Gitlab::Ci::Pipeline::Metrics).to receive(:pipeline_builder_scoped_variables_histogram) 3435 .and_return(histogram) 3436 3437 expect(histogram).to receive(:observe) 3438 .with({}, a_kind_of(ActiveSupport::Duration)) 3439 3440 build.scoped_variables 3441 end 3442 3443 shared_examples 'calculates scoped_variables' do 3444 context 'when build has not been persisted yet' do 3445 let(:build) do 3446 described_class.new( 3447 name: 'rspec', 3448 stage: 'test', 3449 ref: 'feature', 3450 project: project, 3451 pipeline: pipeline, 3452 scheduling_type: :stage 3453 ) 3454 end 3455 3456 let(:pipeline) { create(:ci_pipeline, project: project, ref: 'feature') } 3457 3458 it 'does not persist the build' do 3459 expect(build).to be_valid 3460 expect(build).not_to be_persisted 3461 3462 build.scoped_variables 3463 3464 expect(build).not_to be_persisted 3465 end 3466 3467 it 'returns static predefined variables' do 3468 keys = %w[CI_JOB_NAME 3469 CI_COMMIT_SHA 3470 CI_COMMIT_SHORT_SHA 3471 CI_COMMIT_REF_NAME 3472 CI_COMMIT_REF_SLUG 3473 CI_JOB_STAGE] 3474 3475 variables = build.scoped_variables 3476 3477 variables.map { |env| env[:key] }.tap do |names| 3478 expect(names).to include(*keys) 3479 end 3480 3481 expect(variables) 3482 .to include(key: 'CI_COMMIT_REF_NAME', value: 'feature', public: true, masked: false) 3483 end 3484 3485 it 'does not return prohibited variables' do 3486 keys = %w[CI_JOB_ID 3487 CI_JOB_URL 3488 CI_JOB_TOKEN 3489 CI_BUILD_ID 3490 CI_BUILD_TOKEN 3491 CI_REGISTRY_USER 3492 CI_REGISTRY_PASSWORD 3493 CI_REPOSITORY_URL 3494 CI_ENVIRONMENT_URL 3495 CI_DEPLOY_USER 3496 CI_DEPLOY_PASSWORD] 3497 3498 build.scoped_variables.map { |env| env[:key] }.tap do |names| 3499 expect(names).not_to include(*keys) 3500 end 3501 end 3502 end 3503 3504 context 'with dependency variables' do 3505 let!(:prepare) { create(:ci_build, name: 'prepare', pipeline: pipeline, stage_idx: 0) } 3506 let!(:build) { create(:ci_build, pipeline: pipeline, stage_idx: 1, options: { dependencies: ['prepare'] }) } 3507 3508 let!(:job_variable) { create(:ci_job_variable, :dotenv_source, job: prepare) } 3509 3510 it 'inherits dependent variables' do 3511 expect(build.scoped_variables.to_hash).to include(job_variable.key => job_variable.value) 3512 end 3513 end 3514 end 3515 3516 it_behaves_like 'calculates scoped_variables' 3517 3518 it 'delegates to the variable builders' do 3519 expect_next_instance_of(Gitlab::Ci::Variables::Builder) do |builder| 3520 expect(builder) 3521 .to receive(:scoped_variables).with(build, hash_including(:environment, :dependencies)) 3522 .and_call_original 3523 3524 expect(builder).to receive(:predefined_variables).and_call_original 3525 end 3526 3527 build.scoped_variables 3528 end 3529 end 3530 3531 describe '#simple_variables_without_dependencies' do 3532 it 'does not load dependencies' do 3533 expect(build).not_to receive(:dependency_variables) 3534 3535 build.simple_variables_without_dependencies 3536 end 3537 end 3538 3539 shared_examples "secret CI variables" do 3540 context 'when ref is branch' do 3541 let(:build) { create(:ci_build, ref: 'master', tag: false, project: project) } 3542 3543 context 'when ref is protected' do 3544 before do 3545 create(:protected_branch, :developers_can_merge, name: 'master', project: project) 3546 end 3547 3548 it { is_expected.to include(variable) } 3549 end 3550 3551 context 'when ref is not protected' do 3552 it { is_expected.not_to include(variable) } 3553 end 3554 end 3555 3556 context 'when ref is tag' do 3557 let(:build) { create(:ci_build, ref: 'v1.1.0', tag: true, project: project) } 3558 3559 context 'when ref is protected' do 3560 before do 3561 create(:protected_tag, project: project, name: 'v*') 3562 end 3563 3564 it { is_expected.to include(variable) } 3565 end 3566 3567 context 'when ref is not protected' do 3568 it { is_expected.not_to include(variable) } 3569 end 3570 end 3571 3572 context 'when ref is merge request' do 3573 let(:merge_request) { create(:merge_request, :with_detached_merge_request_pipeline) } 3574 let(:pipeline) { merge_request.pipelines_for_merge_request.first } 3575 let(:build) { create(:ci_build, ref: merge_request.source_branch, tag: false, pipeline: pipeline, project: project) } 3576 3577 context 'when ref is protected' do 3578 before do 3579 create(:protected_branch, :developers_can_merge, name: merge_request.source_branch, project: project) 3580 end 3581 3582 it 'does not return protected variables as it is not supported for merge request pipelines' do 3583 is_expected.not_to include(variable) 3584 end 3585 end 3586 3587 context 'when ref is not protected' do 3588 it { is_expected.not_to include(variable) } 3589 end 3590 end 3591 end 3592 3593 describe '#secret_instance_variables' do 3594 subject { build.secret_instance_variables } 3595 3596 let_it_be(:variable) { create(:ci_instance_variable, protected: true) } 3597 3598 include_examples "secret CI variables" 3599 end 3600 3601 describe '#secret_group_variables' do 3602 subject { build.secret_group_variables } 3603 3604 let_it_be(:variable) { create(:ci_group_variable, protected: true, group: group) } 3605 3606 include_examples "secret CI variables" 3607 end 3608 3609 describe '#secret_project_variables' do 3610 subject { build.secret_project_variables } 3611 3612 let_it_be(:variable) { create(:ci_variable, protected: true, project: project) } 3613 3614 include_examples "secret CI variables" 3615 end 3616 3617 describe '#kubernetes_variables' do 3618 let(:build) { create(:ci_build) } 3619 let(:service) { double(execute: template) } 3620 let(:template) { double(to_yaml: 'example-kubeconfig', valid?: template_valid) } 3621 let(:template_valid) { true } 3622 3623 subject { build.kubernetes_variables } 3624 3625 before do 3626 allow(Ci::GenerateKubeconfigService).to receive(:new).with(build).and_return(service) 3627 end 3628 3629 it { is_expected.to include(key: 'KUBECONFIG', value: 'example-kubeconfig', public: false, file: true) } 3630 3631 context 'generated config is invalid' do 3632 let(:template_valid) { false } 3633 3634 it { is_expected.not_to include(key: 'KUBECONFIG', value: 'example-kubeconfig', public: false, file: true) } 3635 end 3636 end 3637 3638 describe '#deployment_variables' do 3639 let(:build) { create(:ci_build, environment: environment) } 3640 let(:environment) { 'production' } 3641 let(:kubernetes_namespace) { 'namespace' } 3642 let(:project_variables) { double } 3643 3644 subject { build.deployment_variables(environment: environment) } 3645 3646 before do 3647 allow(build).to receive(:expanded_kubernetes_namespace) 3648 .and_return(kubernetes_namespace) 3649 3650 allow(build.project).to receive(:deployment_variables) 3651 .with(environment: environment, kubernetes_namespace: kubernetes_namespace) 3652 .and_return(project_variables) 3653 end 3654 3655 it { is_expected.to eq(project_variables) } 3656 3657 context 'environment is nil' do 3658 let(:environment) { nil } 3659 3660 it { is_expected.to be_empty } 3661 end 3662 end 3663 3664 describe '#any_unmet_prerequisites?' do 3665 let(:build) { create(:ci_build, :created) } 3666 3667 subject { build.any_unmet_prerequisites? } 3668 3669 before do 3670 allow(build).to receive(:prerequisites).and_return(prerequisites) 3671 end 3672 3673 context 'build has prerequisites' do 3674 let(:prerequisites) { [double] } 3675 3676 it { is_expected.to be_truthy } 3677 end 3678 3679 context 'build does not have prerequisites' do 3680 let(:prerequisites) { [] } 3681 3682 it { is_expected.to be_falsey } 3683 end 3684 end 3685 3686 describe '#yaml_variables' do 3687 let(:build) { create(:ci_build, pipeline: pipeline, yaml_variables: variables) } 3688 3689 let(:variables) do 3690 [ 3691 { 'key' => :VARIABLE, 'value' => 'my value' }, 3692 { 'key' => 'VARIABLE2', 'value' => 'my value 2' } 3693 ] 3694 end 3695 3696 shared_examples 'having consistent representation' do 3697 it 'allows to access using symbols' do 3698 expect(build.reload.yaml_variables.first[:key]).to eq('VARIABLE') 3699 expect(build.reload.yaml_variables.first[:value]).to eq('my value') 3700 expect(build.reload.yaml_variables.second[:key]).to eq('VARIABLE2') 3701 expect(build.reload.yaml_variables.second[:value]).to eq('my value 2') 3702 end 3703 end 3704 3705 it_behaves_like 'having consistent representation' 3706 3707 it 'persist data in build metadata' do 3708 expect(build.metadata.read_attribute(:config_variables)).not_to be_nil 3709 end 3710 3711 it 'does not persist data in build' do 3712 expect(build.read_attribute(:yaml_variables)).to be_nil 3713 end 3714 end 3715 3716 describe '#dependency_variables' do 3717 subject { build.dependency_variables } 3718 3719 context 'when using dependencies' do 3720 let!(:prepare1) { create(:ci_build, name: 'prepare1', pipeline: pipeline, stage_idx: 0) } 3721 let!(:prepare2) { create(:ci_build, name: 'prepare2', pipeline: pipeline, stage_idx: 0) } 3722 let!(:build) { create(:ci_build, pipeline: pipeline, stage_idx: 1, options: { dependencies: ['prepare1'] }) } 3723 3724 let!(:job_variable_1) { create(:ci_job_variable, :dotenv_source, job: prepare1) } 3725 let!(:job_variable_2) { create(:ci_job_variable, job: prepare1) } 3726 let!(:job_variable_3) { create(:ci_job_variable, :dotenv_source, job: prepare2) } 3727 3728 it 'inherits only dependent variables' do 3729 expect(subject.to_hash).to eq(job_variable_1.key => job_variable_1.value) 3730 end 3731 end 3732 3733 context 'when using needs' do 3734 let!(:prepare1) { create(:ci_build, name: 'prepare1', pipeline: pipeline, stage_idx: 0) } 3735 let!(:prepare2) { create(:ci_build, name: 'prepare2', pipeline: pipeline, stage_idx: 0) } 3736 let!(:prepare3) { create(:ci_build, name: 'prepare3', pipeline: pipeline, stage_idx: 0) } 3737 let!(:build) { create(:ci_build, pipeline: pipeline, stage_idx: 1, scheduling_type: 'dag') } 3738 let!(:build_needs_prepare1) { create(:ci_build_need, build: build, name: 'prepare1', artifacts: true) } 3739 let!(:build_needs_prepare2) { create(:ci_build_need, build: build, name: 'prepare2', artifacts: false) } 3740 3741 let!(:job_variable_1) { create(:ci_job_variable, :dotenv_source, job: prepare1) } 3742 let!(:job_variable_2) { create(:ci_job_variable, :dotenv_source, job: prepare2) } 3743 let!(:job_variable_3) { create(:ci_job_variable, :dotenv_source, job: prepare3) } 3744 3745 it 'inherits only needs with artifacts variables' do 3746 expect(subject.to_hash).to eq(job_variable_1.key => job_variable_1.value) 3747 end 3748 end 3749 end 3750 3751 describe 'state transition: any => [:preparing]' do 3752 let(:build) { create(:ci_build, :created) } 3753 3754 before do 3755 allow(build).to receive(:prerequisites).and_return([double]) 3756 end 3757 3758 it 'queues BuildPrepareWorker' do 3759 expect(Ci::BuildPrepareWorker).to receive(:perform_async).with(build.id) 3760 3761 build.enqueue 3762 end 3763 end 3764 3765 describe 'state transition: any => [:pending]' do 3766 let(:build) { create(:ci_build, :created) } 3767 3768 it 'queues BuildQueueWorker' do 3769 expect(BuildQueueWorker).to receive(:perform_async).with(build.id) 3770 3771 build.enqueue 3772 end 3773 3774 it 'queues BuildHooksWorker' do 3775 expect(BuildHooksWorker).to receive(:perform_async).with(build.id) 3776 3777 build.enqueue 3778 end 3779 end 3780 3781 describe 'state transition: pending: :running' do 3782 let(:runner) { create(:ci_runner) } 3783 let(:job) { create(:ci_build, :pending, runner: runner) } 3784 3785 before do 3786 job.project.update_attribute(:build_timeout, 1800) 3787 end 3788 3789 def run_job_without_exception 3790 job.run! 3791 rescue StateMachines::InvalidTransition 3792 end 3793 3794 context 'for pipeline ref existence' do 3795 it 'ensures pipeline ref creation' do 3796 expect(job.pipeline.persistent_ref).to receive(:create).once 3797 3798 run_job_without_exception 3799 end 3800 3801 it 'ensures that it is not run in database transaction' do 3802 expect(job.pipeline.persistent_ref).to receive(:create) do 3803 expect(ApplicationRecord).not_to be_inside_transaction 3804 end 3805 3806 run_job_without_exception 3807 end 3808 end 3809 3810 shared_examples 'saves data on transition' do 3811 it 'saves timeout' do 3812 expect { job.run! }.to change { job.reload.ensure_metadata.timeout }.from(nil).to(expected_timeout) 3813 end 3814 3815 it 'saves timeout_source' do 3816 expect { job.run! }.to change { job.reload.ensure_metadata.timeout_source }.from('unknown_timeout_source').to(expected_timeout_source) 3817 end 3818 3819 context 'when Ci::BuildMetadata#update_timeout_state fails update' do 3820 before do 3821 allow_any_instance_of(Ci::BuildMetadata).to receive(:update_timeout_state).and_return(false) 3822 end 3823 3824 it "doesn't save timeout" do 3825 expect { run_job_without_exception }.not_to change { job.reload.ensure_metadata.timeout } 3826 end 3827 3828 it "doesn't save timeout_source" do 3829 expect { run_job_without_exception }.not_to change { job.reload.ensure_metadata.timeout_source } 3830 end 3831 end 3832 end 3833 3834 context 'when runner timeout overrides project timeout' do 3835 let(:expected_timeout) { 900 } 3836 let(:expected_timeout_source) { 'runner_timeout_source' } 3837 3838 before do 3839 runner.update_attribute(:maximum_timeout, 900) 3840 end 3841 3842 it_behaves_like 'saves data on transition' 3843 end 3844 3845 context "when runner timeout doesn't override project timeout" do 3846 let(:expected_timeout) { 1800 } 3847 let(:expected_timeout_source) { 'project_timeout_source' } 3848 3849 before do 3850 runner.update_attribute(:maximum_timeout, 3600) 3851 end 3852 3853 it_behaves_like 'saves data on transition' 3854 end 3855 end 3856 3857 describe '#has_valid_build_dependencies?' do 3858 shared_examples 'validation is active' do 3859 context 'when depended job has not been completed yet' do 3860 let!(:pre_stage_job) { create(:ci_build, :manual, pipeline: pipeline, name: 'test', stage_idx: 0) } 3861 3862 it { expect(job).to have_valid_build_dependencies } 3863 end 3864 3865 context 'when artifacts of depended job has been expired' do 3866 let!(:pre_stage_job) { create(:ci_build, :success, :expired, pipeline: pipeline, name: 'test', stage_idx: 0) } 3867 3868 context 'when pipeline is not locked' do 3869 before do 3870 build.pipeline.unlocked! 3871 end 3872 3873 it { expect(job).not_to have_valid_build_dependencies } 3874 end 3875 3876 context 'when pipeline is locked' do 3877 before do 3878 build.pipeline.artifacts_locked! 3879 end 3880 3881 it { expect(job).to have_valid_build_dependencies } 3882 end 3883 end 3884 3885 context 'when artifacts of depended job has been erased' do 3886 let!(:pre_stage_job) { create(:ci_build, :success, pipeline: pipeline, name: 'test', stage_idx: 0, erased_at: 1.minute.ago) } 3887 3888 before do 3889 pre_stage_job.erase 3890 end 3891 3892 it { expect(job).not_to have_valid_build_dependencies } 3893 end 3894 end 3895 3896 shared_examples 'validation is not active' do 3897 context 'when depended job has not been completed yet' do 3898 let!(:pre_stage_job) { create(:ci_build, :manual, pipeline: pipeline, name: 'test', stage_idx: 0) } 3899 3900 it { expect(job).to have_valid_build_dependencies } 3901 end 3902 3903 context 'when artifacts of depended job has been expired' do 3904 let!(:pre_stage_job) { create(:ci_build, :success, :expired, pipeline: pipeline, name: 'test', stage_idx: 0) } 3905 3906 it { expect(job).to have_valid_build_dependencies } 3907 end 3908 3909 context 'when artifacts of depended job has been erased' do 3910 let!(:pre_stage_job) { create(:ci_build, :success, pipeline: pipeline, name: 'test', stage_idx: 0, erased_at: 1.minute.ago) } 3911 3912 before do 3913 pre_stage_job.erase 3914 end 3915 3916 it { expect(job).to have_valid_build_dependencies } 3917 end 3918 end 3919 3920 let!(:job) { create(:ci_build, :pending, pipeline: pipeline, stage_idx: 1, options: options) } 3921 let!(:pre_stage_job) { create(:ci_build, :success, pipeline: pipeline, name: 'test', stage_idx: 0) } 3922 3923 context 'when "dependencies" keyword is not defined' do 3924 let(:options) { {} } 3925 3926 it { expect(job).to have_valid_build_dependencies } 3927 end 3928 3929 context 'when "dependencies" keyword is empty' do 3930 let(:options) { { dependencies: [] } } 3931 3932 it { expect(job).to have_valid_build_dependencies } 3933 end 3934 3935 context 'when "dependencies" keyword is specified' do 3936 let(:options) { { dependencies: ['test'] } } 3937 3938 it_behaves_like 'validation is active' 3939 end 3940 end 3941 3942 describe 'state transition when build fails' do 3943 let(:service) { ::MergeRequests::AddTodoWhenBuildFailsService.new(project: project, current_user: user) } 3944 3945 before do 3946 allow(::MergeRequests::AddTodoWhenBuildFailsService).to receive(:new).and_return(service) 3947 allow(service).to receive(:close) 3948 end 3949 3950 context 'when build is configured to be retried' do 3951 subject { create(:ci_build, :running, options: { script: ["ls -al"], retry: 3 }, project: project, user: user) } 3952 3953 it 'retries build and assigns the same user to it' do 3954 expect(described_class).to receive(:retry) 3955 .with(subject, user) 3956 3957 subject.drop! 3958 end 3959 3960 it 'does not try to create a todo' do 3961 project.add_developer(user) 3962 3963 expect(service).not_to receive(:pipeline_merge_requests) 3964 3965 subject.drop! 3966 end 3967 3968 context 'when retry service raises Gitlab::Access::AccessDeniedError exception' do 3969 let(:retry_service) { Ci::RetryBuildService.new(subject.project, subject.user) } 3970 3971 before do 3972 allow_any_instance_of(Ci::RetryBuildService) 3973 .to receive(:execute) 3974 .with(subject) 3975 .and_raise(Gitlab::Access::AccessDeniedError) 3976 allow(Gitlab::AppLogger).to receive(:error) 3977 end 3978 3979 it 'handles raised exception' do 3980 expect { subject.drop! }.not_to raise_error 3981 end 3982 3983 it 'logs the error' do 3984 subject.drop! 3985 3986 expect(Gitlab::AppLogger) 3987 .to have_received(:error) 3988 .with(a_string_matching("Unable to auto-retry job #{subject.id}")) 3989 end 3990 3991 it 'fails the job' do 3992 subject.drop! 3993 expect(subject.failed?).to be_truthy 3994 end 3995 end 3996 end 3997 3998 context 'when build is not configured to be retried' do 3999 subject { create(:ci_build, :running, project: project, user: user, pipeline: pipeline) } 4000 4001 let(:pipeline) do 4002 create(:ci_pipeline, 4003 project: project, 4004 ref: 'feature', 4005 sha: merge_request.diff_head_sha, 4006 merge_requests_as_head_pipeline: [merge_request]) 4007 end 4008 4009 let(:merge_request) do 4010 create(:merge_request, :opened, 4011 source_branch: 'feature', 4012 source_project: project, 4013 target_branch: 'master', 4014 target_project: project) 4015 end 4016 4017 it 'does not retry build' do 4018 expect(described_class).not_to receive(:retry) 4019 4020 subject.drop! 4021 end 4022 4023 it 'does not count retries when not necessary' do 4024 expect(described_class).not_to receive(:retry) 4025 expect_any_instance_of(described_class) 4026 .not_to receive(:retries_count) 4027 4028 subject.drop! 4029 end 4030 4031 it 'creates a todo async', :sidekiq_inline do 4032 project.add_developer(user) 4033 4034 expect_next_instance_of(TodoService) do |todo_service| 4035 expect(todo_service) 4036 .to receive(:merge_request_build_failed).with(merge_request) 4037 end 4038 4039 subject.drop! 4040 end 4041 end 4042 4043 context 'when associated deployment failed to update its status' do 4044 let(:build) { create(:ci_build, :running, pipeline: pipeline) } 4045 let!(:deployment) { create(:deployment, deployable: build) } 4046 4047 before do 4048 allow_any_instance_of(Deployment) 4049 .to receive(:drop!).and_raise('Unexpected error') 4050 end 4051 4052 it 'can drop the build' do 4053 expect(Gitlab::ErrorTracking).to receive(:track_exception) 4054 4055 expect { build.drop! }.not_to raise_error 4056 4057 expect(build).to be_failed 4058 end 4059 end 4060 end 4061 4062 describe '.matches_tag_ids' do 4063 let_it_be(:build, reload: true) { create(:ci_build, project: project, user: user) } 4064 4065 let(:tag_ids) { ::ActsAsTaggableOn::Tag.named_any(tag_list).ids } 4066 4067 subject { described_class.where(id: build).matches_tag_ids(tag_ids) } 4068 4069 before do 4070 build.update!(tag_list: build_tag_list) 4071 end 4072 4073 context 'when have different tags' do 4074 let(:build_tag_list) { %w(A B) } 4075 let(:tag_list) { %w(C D) } 4076 4077 it "does not match a build" do 4078 is_expected.not_to contain_exactly(build) 4079 end 4080 end 4081 4082 context 'when have a subset of tags' do 4083 let(:build_tag_list) { %w(A B) } 4084 let(:tag_list) { %w(A B C D) } 4085 4086 it "does match a build" do 4087 is_expected.to contain_exactly(build) 4088 end 4089 end 4090 4091 context 'when build does not have tags' do 4092 let(:build_tag_list) { [] } 4093 let(:tag_list) { %w(C D) } 4094 4095 it "does match a build" do 4096 is_expected.to contain_exactly(build) 4097 end 4098 end 4099 4100 context 'when does not have a subset of tags' do 4101 let(:build_tag_list) { %w(A B C) } 4102 let(:tag_list) { %w(C D) } 4103 4104 it "does not match a build" do 4105 is_expected.not_to contain_exactly(build) 4106 end 4107 end 4108 end 4109 4110 describe '.matches_tags' do 4111 let_it_be(:build, reload: true) { create(:ci_build, project: project, user: user) } 4112 4113 subject { described_class.where(id: build).with_any_tags } 4114 4115 before do 4116 build.update!(tag_list: tag_list) 4117 end 4118 4119 context 'when does have tags' do 4120 let(:tag_list) { %w(A B) } 4121 4122 it "does match a build" do 4123 is_expected.to contain_exactly(build) 4124 end 4125 end 4126 4127 context 'when does not have tags' do 4128 let(:tag_list) { [] } 4129 4130 it "does not match a build" do 4131 is_expected.not_to contain_exactly(build) 4132 end 4133 end 4134 end 4135 4136 describe 'pages deployments' do 4137 let_it_be(:build, reload: true) { create(:ci_build, project: project, user: user) } 4138 4139 context 'when job is "pages"' do 4140 before do 4141 build.name = 'pages' 4142 end 4143 4144 context 'when pages are enabled' do 4145 before do 4146 allow(Gitlab.config.pages).to receive_messages(enabled: true) 4147 end 4148 4149 it 'is marked as pages generator' do 4150 expect(build).to be_pages_generator 4151 end 4152 4153 context 'job succeeds' do 4154 it "calls pages worker" do 4155 expect(PagesWorker).to receive(:perform_async).with(:deploy, build.id) 4156 4157 build.success! 4158 end 4159 end 4160 4161 context 'job fails' do 4162 it "does not call pages worker" do 4163 expect(PagesWorker).not_to receive(:perform_async) 4164 4165 build.drop! 4166 end 4167 end 4168 end 4169 4170 context 'when pages are disabled' do 4171 before do 4172 allow(Gitlab.config.pages).to receive_messages(enabled: false) 4173 end 4174 4175 it 'is not marked as pages generator' do 4176 expect(build).not_to be_pages_generator 4177 end 4178 4179 context 'job succeeds' do 4180 it "does not call pages worker" do 4181 expect(PagesWorker).not_to receive(:perform_async) 4182 4183 build.success! 4184 end 4185 end 4186 end 4187 end 4188 4189 context 'when job is not "pages"' do 4190 before do 4191 build.name = 'other-job' 4192 end 4193 4194 it 'is not marked as pages generator' do 4195 expect(build).not_to be_pages_generator 4196 end 4197 4198 context 'job succeeds' do 4199 it "does not call pages worker" do 4200 expect(PagesWorker).not_to receive(:perform_async) 4201 4202 build.success 4203 end 4204 end 4205 end 4206 end 4207 4208 describe '#has_terminal?' do 4209 let(:states) { described_class.state_machines[:status].states.keys - [:running] } 4210 4211 subject { build.has_terminal? } 4212 4213 it 'returns true if the build is running and it has a runner_session_url' do 4214 build.build_runner_session(url: 'whatever') 4215 build.status = :running 4216 4217 expect(subject).to be_truthy 4218 end 4219 4220 context 'returns false' do 4221 it 'when runner_session_url is empty' do 4222 build.status = :running 4223 4224 expect(subject).to be_falsey 4225 end 4226 4227 context 'unless the build is running' do 4228 before do 4229 build.build_runner_session(url: 'whatever') 4230 end 4231 4232 it do 4233 states.each do |state| 4234 build.status = state 4235 4236 is_expected.to be_falsey 4237 end 4238 end 4239 end 4240 end 4241 end 4242 4243 describe '#collect_test_reports!' do 4244 subject { build.collect_test_reports!(test_reports) } 4245 4246 let(:test_reports) { Gitlab::Ci::Reports::TestReports.new } 4247 4248 it { expect(test_reports.get_suite(build.name).total_count).to eq(0) } 4249 4250 context 'when build has a test report' do 4251 context 'when there is a JUnit test report from rspec test suite' do 4252 before do 4253 create(:ci_job_artifact, :junit, job: build, project: build.project) 4254 end 4255 4256 it 'parses blobs and add the results to the test suite' do 4257 expect { subject }.not_to raise_error 4258 4259 expect(test_reports.get_suite(build.name).total_count).to eq(4) 4260 expect(test_reports.get_suite(build.name).success_count).to be(2) 4261 expect(test_reports.get_suite(build.name).failed_count).to be(2) 4262 end 4263 end 4264 4265 context 'when there is a JUnit test report from java ant test suite' do 4266 before do 4267 create(:ci_job_artifact, :junit_with_ant, job: build, project: build.project) 4268 end 4269 4270 it 'parses blobs and add the results to the test suite' do 4271 expect { subject }.not_to raise_error 4272 4273 expect(test_reports.get_suite(build.name).total_count).to eq(3) 4274 expect(test_reports.get_suite(build.name).success_count).to be(3) 4275 expect(test_reports.get_suite(build.name).failed_count).to be(0) 4276 end 4277 end 4278 4279 context 'when there is a corrupted JUnit test report' do 4280 before do 4281 create(:ci_job_artifact, :junit_with_corrupted_data, job: build, project: build.project) 4282 end 4283 4284 it 'returns no test data and includes a suite_error message' do 4285 expect { subject }.not_to raise_error 4286 4287 expect(test_reports.get_suite(build.name).total_count).to eq(0) 4288 expect(test_reports.get_suite(build.name).success_count).to eq(0) 4289 expect(test_reports.get_suite(build.name).failed_count).to eq(0) 4290 expect(test_reports.get_suite(build.name).suite_error).to eq('JUnit XML parsing failed: 1:1: FATAL: Document is empty') 4291 end 4292 end 4293 end 4294 end 4295 4296 describe '#collect_accessibility_reports!' do 4297 subject { build.collect_accessibility_reports!(accessibility_report) } 4298 4299 let(:accessibility_report) { Gitlab::Ci::Reports::AccessibilityReports.new } 4300 4301 it { expect(accessibility_report.urls).to eq({}) } 4302 4303 context 'when build has an accessibility report' do 4304 context 'when there is an accessibility report with errors' do 4305 before do 4306 create(:ci_job_artifact, :accessibility, job: build, project: build.project) 4307 end 4308 4309 it 'parses blobs and add the results to the accessibility report' do 4310 expect { subject }.not_to raise_error 4311 4312 expect(accessibility_report.urls.keys).to match_array(['https://about.gitlab.com/']) 4313 expect(accessibility_report.errors_count).to eq(10) 4314 expect(accessibility_report.scans_count).to eq(1) 4315 expect(accessibility_report.passes_count).to eq(0) 4316 end 4317 end 4318 4319 context 'when there is an accessibility report without errors' do 4320 before do 4321 create(:ci_job_artifact, :accessibility_without_errors, job: build, project: build.project) 4322 end 4323 4324 it 'parses blobs and add the results to the accessibility report' do 4325 expect { subject }.not_to raise_error 4326 4327 expect(accessibility_report.urls.keys).to match_array(['https://pa11y.org/']) 4328 expect(accessibility_report.errors_count).to eq(0) 4329 expect(accessibility_report.scans_count).to eq(1) 4330 expect(accessibility_report.passes_count).to eq(1) 4331 end 4332 end 4333 4334 context 'when there is an accessibility report with an invalid url' do 4335 before do 4336 create(:ci_job_artifact, :accessibility_with_invalid_url, job: build, project: build.project) 4337 end 4338 4339 it 'parses blobs and add the results to the accessibility report' do 4340 expect { subject }.not_to raise_error 4341 4342 expect(accessibility_report.urls).to be_empty 4343 expect(accessibility_report.errors_count).to eq(0) 4344 expect(accessibility_report.scans_count).to eq(0) 4345 expect(accessibility_report.passes_count).to eq(0) 4346 end 4347 end 4348 end 4349 end 4350 4351 describe '#collect_coverage_reports!' do 4352 subject { build.collect_coverage_reports!(coverage_report) } 4353 4354 let(:coverage_report) { Gitlab::Ci::Reports::CoverageReports.new } 4355 4356 it { expect(coverage_report.files).to eq({}) } 4357 4358 context 'when build has a coverage report' do 4359 context 'when there is a Cobertura coverage report from simplecov-cobertura' do 4360 before do 4361 create(:ci_job_artifact, :cobertura, job: build, project: build.project) 4362 end 4363 4364 it 'parses blobs and add the results to the coverage report' do 4365 expect { subject }.not_to raise_error 4366 4367 expect(coverage_report.files.keys).to match_array(['app/controllers/abuse_reports_controller.rb']) 4368 expect(coverage_report.files['app/controllers/abuse_reports_controller.rb'].count).to eq(23) 4369 end 4370 end 4371 4372 context 'when there is a Cobertura coverage report from gocov-xml' do 4373 before do 4374 create(:ci_job_artifact, :coverage_gocov_xml, job: build, project: build.project) 4375 end 4376 4377 it 'parses blobs and add the results to the coverage report' do 4378 expect { subject }.not_to raise_error 4379 4380 expect(coverage_report.files.keys).to match_array(['auth/token.go', 'auth/rpccredentials.go']) 4381 expect(coverage_report.files['auth/token.go'].count).to eq(49) 4382 expect(coverage_report.files['auth/rpccredentials.go'].count).to eq(10) 4383 end 4384 end 4385 4386 context 'when there is a Cobertura coverage report with class filename paths not relative to project root' do 4387 before do 4388 allow(build.project).to receive(:full_path).and_return('root/javademo') 4389 allow(build.pipeline).to receive(:all_worktree_paths).and_return(['src/main/java/com/example/javademo/User.java']) 4390 4391 create(:ci_job_artifact, :coverage_with_paths_not_relative_to_project_root, job: build, project: build.project) 4392 end 4393 4394 it 'parses blobs and add the results to the coverage report with corrected paths' do 4395 expect { subject }.not_to raise_error 4396 4397 expect(coverage_report.files.keys).to match_array(['src/main/java/com/example/javademo/User.java']) 4398 end 4399 end 4400 4401 context 'when there is a corrupted Cobertura coverage report' do 4402 before do 4403 create(:ci_job_artifact, :coverage_with_corrupted_data, job: build, project: build.project) 4404 end 4405 4406 it 'raises an error' do 4407 expect { subject }.to raise_error(Gitlab::Ci::Parsers::Coverage::Cobertura::InvalidLineInformationError) 4408 end 4409 end 4410 end 4411 end 4412 4413 describe '#collect_codequality_reports!' do 4414 subject(:codequality_report) { build.collect_codequality_reports!(Gitlab::Ci::Reports::CodequalityReports.new) } 4415 4416 it { expect(codequality_report.degradations).to eq({}) } 4417 4418 context 'when build has a codequality report' do 4419 context 'when there is a codequality report' do 4420 before do 4421 create(:ci_job_artifact, :codequality, job: build, project: build.project) 4422 end 4423 4424 it 'parses blobs and add the results to the codequality report' do 4425 expect { codequality_report }.not_to raise_error 4426 4427 expect(codequality_report.degradations_count).to eq(3) 4428 end 4429 end 4430 4431 context 'when there is an codequality report without errors' do 4432 before do 4433 create(:ci_job_artifact, :codequality_without_errors, job: build, project: build.project) 4434 end 4435 4436 it 'parses blobs and add the results to the codequality report' do 4437 expect { codequality_report }.not_to raise_error 4438 4439 expect(codequality_report.degradations_count).to eq(0) 4440 end 4441 end 4442 end 4443 end 4444 4445 describe '#collect_terraform_reports!' do 4446 let(:terraform_reports) { Gitlab::Ci::Reports::TerraformReports.new } 4447 4448 it 'returns an empty hash' do 4449 expect(build.collect_terraform_reports!(terraform_reports).plans).to eq({}) 4450 end 4451 4452 context 'when build has a terraform report' do 4453 context 'when there is a valid tfplan.json' do 4454 before do 4455 create(:ci_job_artifact, :terraform, job: build, project: build.project) 4456 end 4457 4458 it 'parses blobs and add the results to the terraform report' do 4459 expect { build.collect_terraform_reports!(terraform_reports) }.not_to raise_error 4460 4461 terraform_reports.plans.each do |key, hash_value| 4462 expect(hash_value.keys).to match_array(%w[create delete job_id job_name job_path update]) 4463 end 4464 4465 expect(terraform_reports.plans).to match( 4466 a_hash_including( 4467 build.id.to_s => a_hash_including( 4468 'create' => 0, 4469 'update' => 1, 4470 'delete' => 0, 4471 'job_name' => build.name 4472 ) 4473 ) 4474 ) 4475 end 4476 end 4477 4478 context 'when there is an invalid tfplan.json' do 4479 before do 4480 create(:ci_job_artifact, :terraform_with_corrupted_data, job: build, project: build.project) 4481 end 4482 4483 it 'adds invalid plan report' do 4484 expect { build.collect_terraform_reports!(terraform_reports) }.not_to raise_error 4485 4486 terraform_reports.plans.each do |key, hash_value| 4487 expect(hash_value.keys).to match_array(%w[job_id job_name job_path tf_report_error]) 4488 end 4489 4490 expect(terraform_reports.plans).to match( 4491 a_hash_including( 4492 build.id.to_s => a_hash_including( 4493 'tf_report_error' => :invalid_json_format 4494 ) 4495 ) 4496 ) 4497 end 4498 end 4499 end 4500 end 4501 4502 describe '#report_artifacts' do 4503 subject { build.report_artifacts } 4504 4505 context 'when the build has reports' do 4506 let!(:report) { create(:ci_job_artifact, :codequality, job: build) } 4507 4508 it 'returns the artifacts with reports' do 4509 expect(subject).to contain_exactly(report) 4510 end 4511 end 4512 end 4513 4514 describe '#artifacts_metadata_entry' do 4515 let_it_be(:build) { create(:ci_build, project: project) } 4516 4517 let(:path) { 'other_artifacts_0.1.2/another-subdirectory/banana_sample.gif' } 4518 4519 around do |example| 4520 freeze_time { example.run } 4521 end 4522 4523 before do 4524 stub_artifacts_object_storage 4525 end 4526 4527 subject { build.artifacts_metadata_entry(path) } 4528 4529 context 'when using local storage' do 4530 let!(:metadata) { create(:ci_job_artifact, :metadata, job: build) } 4531 4532 context 'for existing file' do 4533 it 'does exist' do 4534 is_expected.to be_exists 4535 end 4536 end 4537 4538 context 'for non-existing file' do 4539 let(:path) { 'invalid-file' } 4540 4541 it 'does not exist' do 4542 is_expected.not_to be_exists 4543 end 4544 end 4545 end 4546 4547 context 'when using remote storage' do 4548 include HttpIOHelpers 4549 4550 let!(:metadata) { create(:ci_job_artifact, :remote_store, :metadata, job: build) } 4551 let(:file_path) { expand_fixture_path('ci_build_artifacts_metadata.gz') } 4552 4553 before do 4554 stub_remote_url_206(metadata.file.url, file_path) 4555 end 4556 4557 context 'for existing file' do 4558 it 'does exist' do 4559 is_expected.to be_exists 4560 end 4561 end 4562 4563 context 'for non-existing file' do 4564 let(:path) { 'invalid-file' } 4565 4566 it 'does not exist' do 4567 is_expected.not_to be_exists 4568 end 4569 end 4570 end 4571 end 4572 4573 describe '#publishes_artifacts_reports?' do 4574 let(:build) { create(:ci_build, options: options) } 4575 4576 subject { build.publishes_artifacts_reports? } 4577 4578 context 'when artifacts reports are defined' do 4579 let(:options) do 4580 { artifacts: { reports: { junit: "junit.xml" } } } 4581 end 4582 4583 it { is_expected.to be_truthy } 4584 end 4585 4586 context 'when artifacts reports missing defined' do 4587 let(:options) do 4588 { artifacts: { paths: ["file.txt"] } } 4589 end 4590 4591 it { is_expected.to be_falsey } 4592 end 4593 4594 context 'when options are missing' do 4595 let(:options) { nil } 4596 4597 it { is_expected.to be_falsey } 4598 end 4599 end 4600 4601 describe '#runner_required_feature_names' do 4602 let(:build) { create(:ci_build, options: options) } 4603 4604 subject { build.runner_required_feature_names } 4605 4606 context 'when artifacts reports are defined' do 4607 let(:options) do 4608 { artifacts: { reports: { junit: "junit.xml" } } } 4609 end 4610 4611 it { is_expected.to include(:upload_multiple_artifacts) } 4612 end 4613 4614 context 'when artifacts exclude is defined' do 4615 let(:options) do 4616 { artifacts: { exclude: %w[something] } } 4617 end 4618 4619 it { is_expected.to include(:artifacts_exclude) } 4620 end 4621 end 4622 4623 describe '#supported_runner?' do 4624 let_it_be_with_refind(:build) { create(:ci_build) } 4625 4626 subject { build.supported_runner?(runner_features) } 4627 4628 context 'when `upload_multiple_artifacts` feature is required by build' do 4629 before do 4630 expect(build).to receive(:runner_required_feature_names) do 4631 [:upload_multiple_artifacts] 4632 end 4633 end 4634 4635 context 'when runner provides given feature' do 4636 let(:runner_features) do 4637 { upload_multiple_artifacts: true } 4638 end 4639 4640 it { is_expected.to be_truthy } 4641 end 4642 4643 context 'when runner does not provide given feature' do 4644 let(:runner_features) do 4645 {} 4646 end 4647 4648 it { is_expected.to be_falsey } 4649 end 4650 end 4651 4652 context 'when `refspecs` feature is required by build' do 4653 before do 4654 allow(build).to receive(:merge_request_ref?) { true } 4655 end 4656 4657 context 'when runner provides given feature' do 4658 let(:runner_features) { { refspecs: true } } 4659 4660 it { is_expected.to be_truthy } 4661 end 4662 4663 context 'when runner does not provide given feature' do 4664 let(:runner_features) { {} } 4665 4666 it { is_expected.to be_falsey } 4667 end 4668 end 4669 4670 context 'when `multi_build_steps` feature is required by build' do 4671 before do 4672 expect(build).to receive(:runner_required_feature_names) do 4673 [:multi_build_steps] 4674 end 4675 end 4676 4677 context 'when runner provides given feature' do 4678 let(:runner_features) { { multi_build_steps: true } } 4679 4680 it { is_expected.to be_truthy } 4681 end 4682 4683 context 'when runner does not provide given feature' do 4684 let(:runner_features) { {} } 4685 4686 it { is_expected.to be_falsey } 4687 end 4688 end 4689 4690 context 'when `return_exit_code` feature is required by build' do 4691 let(:options) { { allow_failure_criteria: { exit_codes: [1] } } } 4692 4693 before do 4694 build.update!(options: options) 4695 end 4696 4697 context 'when runner provides given feature' do 4698 let(:runner_features) { { return_exit_code: true } } 4699 4700 it { is_expected.to be_truthy } 4701 end 4702 4703 context 'when runner does not provide given feature' do 4704 let(:runner_features) { {} } 4705 4706 it { is_expected.to be_falsey } 4707 end 4708 4709 context 'when the runner does not provide all of the required features' do 4710 let(:options) do 4711 { 4712 allow_failure_criteria: { exit_codes: [1] }, 4713 artifacts: { reports: { junit: "junit.xml" } } 4714 } 4715 end 4716 4717 let(:runner_features) { { return_exit_code: true } } 4718 4719 it 'requires `upload_multiple_artifacts` too' do 4720 is_expected.to be_falsey 4721 end 4722 end 4723 end 4724 end 4725 4726 describe '#deployment_status' do 4727 before do 4728 allow_any_instance_of(described_class).to receive(:create_deployment) 4729 end 4730 4731 context 'when build is a last deployment' do 4732 let(:build) { create(:ci_build, :success, environment: 'production', pipeline: pipeline, project: project) } 4733 let(:environment) { create(:environment, name: 'production', project: build.project) } 4734 let!(:deployment) { create(:deployment, :success, environment: environment, project: environment.project, deployable: build) } 4735 4736 it { expect(build.deployment_status).to eq(:last) } 4737 end 4738 4739 context 'when there is a newer build with deployment' do 4740 let(:build) { create(:ci_build, :success, environment: 'production', pipeline: pipeline, project: project) } 4741 let(:environment) { create(:environment, name: 'production', project: build.project) } 4742 let!(:deployment) { create(:deployment, :success, environment: environment, project: environment.project, deployable: build) } 4743 let!(:last_deployment) { create(:deployment, :success, environment: environment, project: environment.project) } 4744 4745 it { expect(build.deployment_status).to eq(:out_of_date) } 4746 end 4747 4748 context 'when build with deployment has failed' do 4749 let(:build) { create(:ci_build, :failed, environment: 'production', pipeline: pipeline, project: project) } 4750 let(:environment) { create(:environment, name: 'production', project: build.project) } 4751 let!(:deployment) { create(:deployment, :success, environment: environment, project: environment.project, deployable: build) } 4752 4753 it { expect(build.deployment_status).to eq(:failed) } 4754 end 4755 4756 context 'when build with deployment is running' do 4757 let(:build) { create(:ci_build, environment: 'production', pipeline: pipeline, project: project) } 4758 let(:environment) { create(:environment, name: 'production', project: build.project) } 4759 let!(:deployment) { create(:deployment, :success, environment: environment, project: environment.project, deployable: build) } 4760 4761 it { expect(build.deployment_status).to eq(:creating) } 4762 end 4763 end 4764 4765 describe '#degenerated?' do 4766 context 'when build is degenerated' do 4767 subject { create(:ci_build, :degenerated) } 4768 4769 it { is_expected.to be_degenerated } 4770 end 4771 4772 context 'when build is valid' do 4773 subject { create(:ci_build) } 4774 4775 it { is_expected.not_to be_degenerated } 4776 4777 context 'and becomes degenerated' do 4778 before do 4779 subject.degenerate! 4780 end 4781 4782 it { is_expected.to be_degenerated } 4783 end 4784 end 4785 end 4786 4787 describe 'degenerate!' do 4788 let(:build) { create(:ci_build) } 4789 4790 subject { build.degenerate! } 4791 4792 before do 4793 build.ensure_metadata 4794 build.needs.create!(name: 'another-job') 4795 end 4796 4797 it 'drops metadata' do 4798 subject 4799 4800 expect(build.reload).to be_degenerated 4801 expect(build.metadata).to be_nil 4802 expect(build.needs).to be_empty 4803 end 4804 end 4805 4806 describe '#archived?' do 4807 context 'when build is degenerated' do 4808 subject { create(:ci_build, :degenerated) } 4809 4810 it { is_expected.to be_archived } 4811 end 4812 4813 context 'for old build' do 4814 subject { create(:ci_build, created_at: 1.day.ago) } 4815 4816 context 'when archive_builds_in is set' do 4817 before do 4818 stub_application_setting(archive_builds_in_seconds: 3600) 4819 end 4820 4821 it { is_expected.to be_archived } 4822 end 4823 4824 context 'when archive_builds_in is not set' do 4825 before do 4826 stub_application_setting(archive_builds_in_seconds: nil) 4827 end 4828 4829 it { is_expected.not_to be_archived } 4830 end 4831 end 4832 end 4833 4834 describe '#read_metadata_attribute' do 4835 let(:build) { create(:ci_build, :degenerated) } 4836 let(:build_options) { { key: "build" } } 4837 let(:metadata_options) { { key: "metadata" } } 4838 let(:default_options) { { key: "default" } } 4839 4840 subject { build.send(:read_metadata_attribute, :options, :config_options, default_options) } 4841 4842 context 'when build and metadata options is set' do 4843 before do 4844 build.write_attribute(:options, build_options) 4845 build.ensure_metadata.write_attribute(:config_options, metadata_options) 4846 end 4847 4848 it 'prefers build options' do 4849 is_expected.to eq(build_options) 4850 end 4851 end 4852 4853 context 'when only metadata options is set' do 4854 before do 4855 build.write_attribute(:options, nil) 4856 build.ensure_metadata.write_attribute(:config_options, metadata_options) 4857 end 4858 4859 it 'returns metadata options' do 4860 is_expected.to eq(metadata_options) 4861 end 4862 end 4863 4864 context 'when none is set' do 4865 it 'returns default value' do 4866 is_expected.to eq(default_options) 4867 end 4868 end 4869 end 4870 4871 describe '#write_metadata_attribute' do 4872 let(:build) { create(:ci_build, :degenerated) } 4873 let(:options) { { key: "new options" } } 4874 let(:existing_options) { { key: "existing options" } } 4875 4876 subject { build.send(:write_metadata_attribute, :options, :config_options, options) } 4877 4878 context 'when data in build is already set' do 4879 before do 4880 build.write_attribute(:options, existing_options) 4881 end 4882 4883 it 'does set metadata options' do 4884 subject 4885 4886 expect(build.metadata.read_attribute(:config_options)).to eq(options) 4887 end 4888 4889 it 'does reset build options' do 4890 subject 4891 4892 expect(build.read_attribute(:options)).to be_nil 4893 end 4894 end 4895 end 4896 4897 describe '#invalid_dependencies' do 4898 let!(:pre_stage_job_valid) { create(:ci_build, :manual, pipeline: pipeline, name: 'test1', stage_idx: 0) } 4899 let!(:pre_stage_job_invalid) { create(:ci_build, :success, :expired, pipeline: pipeline, name: 'test2', stage_idx: 1) } 4900 let!(:job) { create(:ci_build, :pending, pipeline: pipeline, stage_idx: 2, options: { dependencies: %w(test1 test2) }) } 4901 4902 context 'when pipeline is locked' do 4903 before do 4904 build.pipeline.unlocked! 4905 end 4906 4907 it 'returns invalid dependencies when expired' do 4908 expect(job.invalid_dependencies).to eq([pre_stage_job_invalid]) 4909 end 4910 end 4911 4912 context 'when pipeline is not locked' do 4913 before do 4914 build.pipeline.artifacts_locked! 4915 end 4916 4917 it 'returns no invalid dependencies when expired' do 4918 expect(job.invalid_dependencies).to eq([]) 4919 end 4920 end 4921 end 4922 4923 describe '#execute_hooks' do 4924 before do 4925 build.clear_memoization(:build_data) 4926 end 4927 4928 context 'with project hooks' do 4929 let(:build_data) { double(:BuildData, dup: double(:DupedData)) } 4930 4931 before do 4932 create(:project_hook, project: project, job_events: true) 4933 end 4934 4935 it 'calls project.execute_hooks(build_data, :job_hooks)' do 4936 expect(::Gitlab::DataBuilder::Build) 4937 .to receive(:build).with(build).and_return(build_data) 4938 expect(build.project) 4939 .to receive(:execute_hooks).with(build_data.dup, :job_hooks) 4940 4941 build.execute_hooks 4942 end 4943 end 4944 4945 context 'without project hooks' do 4946 it 'does not call project.execute_hooks' do 4947 expect(build.project).not_to receive(:execute_hooks) 4948 4949 build.execute_hooks 4950 end 4951 end 4952 4953 context 'with project services' do 4954 before do 4955 create(:integration, active: true, job_events: true, project: project) 4956 end 4957 4958 it 'executes services' do 4959 allow_next_found_instance_of(Integration) do |integration| 4960 expect(integration).to receive(:async_execute) 4961 end 4962 4963 build.execute_hooks 4964 end 4965 end 4966 4967 context 'without relevant project services' do 4968 before do 4969 create(:integration, active: true, job_events: false, project: project) 4970 end 4971 4972 it 'does not execute services' do 4973 allow_next_found_instance_of(Integration) do |integration| 4974 expect(integration).not_to receive(:async_execute) 4975 end 4976 4977 build.execute_hooks 4978 end 4979 end 4980 end 4981 4982 describe '#environment_auto_stop_in' do 4983 subject { build.environment_auto_stop_in } 4984 4985 context 'when build option has environment auto_stop_in' do 4986 let(:build) { create(:ci_build, options: { environment: { name: 'test', auto_stop_in: '1 day' } }) } 4987 4988 it { is_expected.to eq('1 day') } 4989 end 4990 4991 context 'when build option does not have environment auto_stop_in' do 4992 let(:build) { create(:ci_build) } 4993 4994 it { is_expected.to be_nil } 4995 end 4996 end 4997 4998 describe '#degradation_threshold' do 4999 subject { build.degradation_threshold } 5000 5001 context 'when threshold variable is defined' do 5002 before do 5003 build.yaml_variables = [ 5004 { key: 'SOME_VAR_1', value: 'SOME_VAL_1' }, 5005 { key: 'DEGRADATION_THRESHOLD', value: '5' }, 5006 { key: 'SOME_VAR_2', value: 'SOME_VAL_2' } 5007 ] 5008 end 5009 5010 it { is_expected.to eq(5) } 5011 end 5012 5013 context 'when threshold variable is not defined' do 5014 before do 5015 build.yaml_variables = [ 5016 { key: 'SOME_VAR_1', value: 'SOME_VAL_1' }, 5017 { key: 'SOME_VAR_2', value: 'SOME_VAL_2' } 5018 ] 5019 end 5020 5021 it { is_expected.to be_nil } 5022 end 5023 end 5024 5025 describe '#run_on_status_commit' do 5026 it 'runs provided hook after status commit' do 5027 action = spy('action') 5028 5029 build.run_on_status_commit { action.perform! } 5030 build.success! 5031 5032 expect(action).to have_received(:perform!).once 5033 end 5034 5035 it 'does not run hooks when status has not changed' do 5036 action = spy('action') 5037 5038 build.run_on_status_commit { action.perform! } 5039 build.save! 5040 5041 expect(action).not_to have_received(:perform!) 5042 end 5043 end 5044 5045 describe '#debug_mode?' do 5046 subject { build.debug_mode? } 5047 5048 context 'when CI_DEBUG_TRACE=true is in variables' do 5049 context 'when in instance variables' do 5050 before do 5051 create(:ci_instance_variable, key: 'CI_DEBUG_TRACE', value: 'true') 5052 end 5053 5054 it { is_expected.to eq true } 5055 end 5056 5057 context 'when in group variables' do 5058 before do 5059 create(:ci_group_variable, key: 'CI_DEBUG_TRACE', value: 'true', group: project.group) 5060 end 5061 5062 it { is_expected.to eq true } 5063 end 5064 5065 context 'when in pipeline variables' do 5066 before do 5067 create(:ci_pipeline_variable, key: 'CI_DEBUG_TRACE', value: 'true', pipeline: pipeline) 5068 end 5069 5070 it { is_expected.to eq true } 5071 end 5072 5073 context 'when in project variables' do 5074 before do 5075 create(:ci_variable, key: 'CI_DEBUG_TRACE', value: 'true', project: project) 5076 end 5077 5078 it { is_expected.to eq true } 5079 end 5080 5081 context 'when in job variables' do 5082 before do 5083 create(:ci_job_variable, key: 'CI_DEBUG_TRACE', value: 'true', job: build) 5084 end 5085 5086 it { is_expected.to eq true } 5087 end 5088 5089 context 'when in yaml variables' do 5090 before do 5091 build.update!(yaml_variables: [{ key: :CI_DEBUG_TRACE, value: 'true' }]) 5092 end 5093 5094 it { is_expected.to eq true } 5095 end 5096 end 5097 5098 context 'when CI_DEBUG_TRACE is not in variables' do 5099 it { is_expected.to eq false } 5100 end 5101 end 5102 5103 describe '#drop_with_exit_code!' do 5104 let(:exit_code) { 1 } 5105 let(:options) { {} } 5106 5107 before do 5108 build.options.merge!(options) 5109 build.save! 5110 end 5111 5112 subject(:drop_with_exit_code) do 5113 build.drop_with_exit_code!(:unknown_failure, exit_code) 5114 end 5115 5116 shared_examples 'drops the build without changing allow_failure' do 5117 it 'does not change allow_failure' do 5118 expect { drop_with_exit_code } 5119 .not_to change { build.reload.allow_failure } 5120 end 5121 5122 it 'drops the build' do 5123 expect { drop_with_exit_code } 5124 .to change { build.reload.failed? } 5125 end 5126 end 5127 5128 context 'when exit_codes are not defined' do 5129 it_behaves_like 'drops the build without changing allow_failure' 5130 end 5131 5132 context 'when allow_failure_criteria is nil' do 5133 let(:options) { { allow_failure_criteria: nil } } 5134 5135 it_behaves_like 'drops the build without changing allow_failure' 5136 end 5137 5138 context 'when exit_codes is nil' do 5139 let(:options) do 5140 { 5141 allow_failure_criteria: { 5142 exit_codes: nil 5143 } 5144 } 5145 end 5146 5147 it_behaves_like 'drops the build without changing allow_failure' 5148 end 5149 5150 context 'when exit_codes do not match' do 5151 let(:options) do 5152 { 5153 allow_failure_criteria: { 5154 exit_codes: [2, 3, 4] 5155 } 5156 } 5157 end 5158 5159 it_behaves_like 'drops the build without changing allow_failure' 5160 end 5161 5162 context 'with matching exit codes' do 5163 let(:options) do 5164 { allow_failure_criteria: { exit_codes: [1, 2, 3] } } 5165 end 5166 5167 it 'changes allow_failure' do 5168 expect { drop_with_exit_code } 5169 .to change { build.reload.allow_failure } 5170 end 5171 5172 it 'drops the build' do 5173 expect { drop_with_exit_code } 5174 .to change { build.reload.failed? } 5175 end 5176 5177 it 'is executed inside a transaction' do 5178 expect(build).to receive(:drop!) 5179 .with(:unknown_failure) 5180 .and_raise(ActiveRecord::Rollback) 5181 5182 expect(build).to receive(:conditionally_allow_failure!) 5183 .with(1) 5184 .and_call_original 5185 5186 expect { drop_with_exit_code } 5187 .not_to change { build.reload.allow_failure } 5188 end 5189 5190 context 'when exit_code is nil' do 5191 let(:exit_code) {} 5192 5193 it_behaves_like 'drops the build without changing allow_failure' 5194 end 5195 end 5196 end 5197 5198 describe '#exit_codes_defined?' do 5199 let(:options) { {} } 5200 5201 before do 5202 build.options.merge!(options) 5203 end 5204 5205 subject(:exit_codes_defined) do 5206 build.exit_codes_defined? 5207 end 5208 5209 context 'without allow_failure_criteria' do 5210 it { is_expected.to be_falsey } 5211 end 5212 5213 context 'when exit_codes is nil' do 5214 let(:options) do 5215 { 5216 allow_failure_criteria: { 5217 exit_codes: nil 5218 } 5219 } 5220 end 5221 5222 it { is_expected.to be_falsey } 5223 end 5224 5225 context 'when exit_codes is an empty array' do 5226 let(:options) do 5227 { 5228 allow_failure_criteria: { 5229 exit_codes: [] 5230 } 5231 } 5232 end 5233 5234 it { is_expected.to be_falsey } 5235 end 5236 5237 context 'when exit_codes are defined' do 5238 let(:options) do 5239 { 5240 allow_failure_criteria: { 5241 exit_codes: [5, 6] 5242 } 5243 } 5244 end 5245 5246 it { is_expected.to be_truthy } 5247 end 5248 end 5249 5250 describe '.build_matchers' do 5251 let_it_be(:pipeline) { create(:ci_pipeline, :protected) } 5252 5253 subject(:matchers) { pipeline.builds.build_matchers(pipeline.project) } 5254 5255 context 'when the pipeline is empty' do 5256 it 'does not throw errors' do 5257 is_expected.to eq([]) 5258 end 5259 end 5260 5261 context 'when the pipeline has builds' do 5262 let_it_be(:build_without_tags) do 5263 create(:ci_build, pipeline: pipeline) 5264 end 5265 5266 let_it_be(:build_with_tags) do 5267 create(:ci_build, pipeline: pipeline, tag_list: %w[tag1 tag2]) 5268 end 5269 5270 let_it_be(:other_build_with_tags) do 5271 create(:ci_build, pipeline: pipeline, tag_list: %w[tag2 tag1]) 5272 end 5273 5274 it { expect(matchers.size).to eq(2) } 5275 5276 it 'groups build ids' do 5277 expect(matchers.map(&:build_ids)).to match_array([ 5278 [build_without_tags.id], 5279 match_array([build_with_tags.id, other_build_with_tags.id]) 5280 ]) 5281 end 5282 5283 it { expect(matchers.map(&:tag_list)).to match_array([[], %w[tag1 tag2]]) } 5284 5285 it { expect(matchers.map(&:protected?)).to all be_falsey } 5286 5287 context 'when the builds are protected' do 5288 before do 5289 pipeline.builds.update_all(protected: true) 5290 end 5291 5292 it { expect(matchers).to all be_protected } 5293 end 5294 end 5295 end 5296 5297 describe '#build_matcher' do 5298 let_it_be(:build) do 5299 build_stubbed(:ci_build, tag_list: %w[tag1 tag2]) 5300 end 5301 5302 subject(:matcher) { build.build_matcher } 5303 5304 it { expect(matcher.build_ids).to eq([build.id]) } 5305 5306 it { expect(matcher.tag_list).to match_array(%w[tag1 tag2]) } 5307 5308 it { expect(matcher.protected?).to eq(build.protected?) } 5309 5310 it { expect(matcher.project).to eq(build.project) } 5311 end 5312 5313 describe '#shared_runner_build?' do 5314 context 'when build does not have a runner assigned' do 5315 it 'is not a shared runner build' do 5316 expect(build.runner).to be_nil 5317 5318 expect(build).not_to be_shared_runner_build 5319 end 5320 end 5321 5322 context 'when build has a project runner assigned' do 5323 before do 5324 build.runner = create(:ci_runner, :project) 5325 end 5326 5327 it 'is not a shared runner build' do 5328 expect(build).not_to be_shared_runner_build 5329 end 5330 end 5331 5332 context 'when build has an instance runner assigned' do 5333 before do 5334 build.runner = create(:ci_runner, :instance_type) 5335 end 5336 5337 it 'is a shared runner build' do 5338 expect(build).to be_shared_runner_build 5339 end 5340 end 5341 end 5342 5343 describe '.with_project_and_metadata' do 5344 it 'does not join across databases' do 5345 with_cross_joins_prevented do 5346 ::Ci::Build.with_project_and_metadata.to_a 5347 end 5348 end 5349 end 5350 5351 describe '.without_coverage' do 5352 let!(:build_with_coverage) { create(:ci_build, pipeline: pipeline, coverage: 100.0) } 5353 5354 it 'returns builds without coverage values' do 5355 expect(described_class.without_coverage).to eq([build]) 5356 end 5357 end 5358 5359 describe '.with_coverage_regex' do 5360 let!(:build_with_coverage_regex) { create(:ci_build, pipeline: pipeline, coverage_regex: '\d') } 5361 5362 it 'returns builds with coverage regex values' do 5363 expect(described_class.with_coverage_regex).to eq([build_with_coverage_regex]) 5364 end 5365 end 5366 5367 describe '#ensure_trace_metadata!' do 5368 it 'delegates to Ci::BuildTraceMetadata' do 5369 expect(Ci::BuildTraceMetadata) 5370 .to receive(:find_or_upsert_for!) 5371 .with(build.id) 5372 5373 build.ensure_trace_metadata! 5374 end 5375 end 5376 5377 describe '#doom!' do 5378 subject { build.doom! } 5379 5380 let_it_be(:build) { create(:ci_build, :queued) } 5381 5382 it 'updates status and failure_reason', :aggregate_failures do 5383 subject 5384 5385 expect(build.status).to eq("failed") 5386 expect(build.failure_reason).to eq("data_integrity_failure") 5387 end 5388 5389 it 'drops associated pending build' do 5390 subject 5391 5392 expect(build.reload.queuing_entry).not_to be_present 5393 end 5394 end 5395 5396 it 'does not generate cross DB queries when a record is created via FactoryBot' do 5397 with_cross_database_modification_prevented do 5398 create(:ci_build) 5399 end 5400 end 5401 5402 describe '#runner_features' do 5403 subject do 5404 build.save! 5405 build.cancel_gracefully? 5406 end 5407 5408 let_it_be(:build) { create(:ci_build, pipeline: pipeline) } 5409 5410 it 'cannot cancel gracefully' do 5411 expect(subject).to be false 5412 end 5413 5414 it 'can cancel gracefully' do 5415 build.set_cancel_gracefully 5416 5417 expect(subject).to be true 5418 end 5419 end 5420 5421 it_behaves_like 'it has loose foreign keys' do 5422 let(:factory_name) { :ci_build } 5423 end 5424 5425 it_behaves_like 'cleanup by a loose foreign key' do 5426 let!(:model) { create(:ci_build, user: create(:user)) } 5427 let!(:parent) { model.user } 5428 end 5429end 5430