diff options
Diffstat (limited to 'spec/models/ci/build_spec.rb')
-rw-r--r-- | spec/models/ci/build_spec.rb | 353 |
1 files changed, 295 insertions, 58 deletions
diff --git a/spec/models/ci/build_spec.rb b/spec/models/ci/build_spec.rb index a4f3fa518c6..6605866d9c0 100644 --- a/spec/models/ci/build_spec.rb +++ b/spec/models/ci/build_spec.rb @@ -106,10 +106,14 @@ describe Ci::Build do end end - describe '.with_artifacts_archive' do - subject { described_class.with_artifacts_archive } + describe '.with_downloadable_artifacts' do + subject { described_class.with_downloadable_artifacts } - context 'when job does not have an archive' do + before do + stub_feature_flags(drop_license_management_artifact: false) + end + + context 'when job does not have a downloadable artifact' do let!(:job) { create(:ci_build) } it 'does not return the job' do @@ -117,15 +121,23 @@ describe Ci::Build do end end - context 'when job has a job artifact archive' do - let!(:job) { create(:ci_build, :artifacts) } + ::Ci::JobArtifact::DOWNLOADABLE_TYPES.each do |type| + context "when job has a #{type} artifact" do + it 'returns the job' do + job = create(:ci_build) + create( + :ci_job_artifact, + file_format: ::Ci::JobArtifact::TYPE_AND_FORMAT_PAIRS[type.to_sym], + file_type: type, + job: job + ) - it 'returns the job' do - is_expected.to include(job) + is_expected.to include(job) + end end end - context 'when job has a job artifact trace' do + context 'when job has a non-downloadable artifact' do let!(:job) { create(:ci_build, :trace_artifact) } it 'does not return the job' do @@ -1419,6 +1431,8 @@ describe Ci::Build do subject { build.erase_erasable_artifacts! } before do + stub_feature_flags(drop_license_management_artifact: false) + Ci::JobArtifact.file_types.keys.each do |file_type| create(:ci_job_artifact, job: build, file_type: file_type, file_format: Ci::JobArtifact::TYPE_AND_FORMAT_PAIRS[file_type.to_sym]) end @@ -2367,12 +2381,14 @@ describe Ci::Build do let(:pipeline_pre_var) { { key: 'pipeline', value: 'value', public: true, masked: false } } let(:build_yaml_var) { { key: 'yaml', value: 'value', public: true, masked: false } } let(:job_jwt_var) { { key: 'CI_JOB_JWT', value: 'ci.job.jwt', public: false, masked: true } } + let(:job_dependency_var) { { key: 'job_dependency', value: 'value', public: true, masked: false } } before do allow(build).to receive(:predefined_variables) { [build_pre_var] } allow(build).to receive(:yaml_variables) { [build_yaml_var] } allow(build).to receive(:persisted_variables) { [] } allow(build).to receive(:job_jwt_variables) { [job_jwt_var] } + allow(build).to receive(:dependency_variables) { [job_dependency_var] } allow_any_instance_of(Project) .to receive(:predefined_variables) { [project_pre_var] } @@ -2390,6 +2406,7 @@ describe Ci::Build do project_pre_var, pipeline_pre_var, build_yaml_var, + job_dependency_var, { key: 'secret', value: 'value', public: false, masked: false }]) end end @@ -2884,6 +2901,19 @@ describe Ci::Build do it { is_expected.to include(deployment_variable) } end + context 'when build has a freeze period' do + let(:freeze_variable) { { key: 'CI_DEPLOY_FREEZE', value: 'true', masked: false, public: true } } + + before do + expect_next_instance_of(Ci::FreezePeriodStatus) do |freeze_period| + expect(freeze_period).to receive(:execute) + .and_return(true) + end + end + + it { is_expected.to include(freeze_variable) } + end + context 'when project has default CI config path' do let(:ci_config_path) { { key: 'CI_CONFIG_PATH', value: '.gitlab-ci.yml', public: true, masked: false } } @@ -2987,6 +3017,15 @@ describe Ci::Build do end end end + + context 'when build has dependency which has dotenv variable' do + let!(:prepare) { create(:ci_build, pipeline: pipeline, stage_idx: 0) } + let!(:build) { create(:ci_build, pipeline: pipeline, stage_idx: 1, options: { dependencies: [prepare.name] }) } + + let!(:job_variable) { create(:ci_job_variable, :dotenv_source, job: prepare) } + + it { is_expected.to include(key: job_variable.key, value: job_variable.value, public: false, masked: false) } + end end describe '#scoped_variables' do @@ -3049,71 +3088,36 @@ describe Ci::Build do end end end - end - describe '#secret_group_variables' do - subject { build.secret_group_variables } - - let!(:variable) { create(:ci_group_variable, protected: true, group: group) } + context 'with dependency variables' do + let!(:prepare) { create(:ci_build, name: 'prepare', pipeline: pipeline, stage_idx: 0) } + let!(:build) { create(:ci_build, pipeline: pipeline, stage_idx: 1, options: { dependencies: ['prepare'] }) } - context 'when ref is branch' do - let(:build) { create(:ci_build, ref: 'master', tag: false, project: project) } + let!(:job_variable) { create(:ci_job_variable, :dotenv_source, job: prepare) } - context 'when ref is protected' do + context 'FF ci_dependency_variables is enabled' do before do - create(:protected_branch, :developers_can_merge, name: 'master', project: project) + stub_feature_flags(ci_dependency_variables: true) end - it { is_expected.to include(variable) } - end - - context 'when ref is not protected' do - it { is_expected.not_to include(variable) } - end - end - - context 'when ref is tag' do - let(:build) { create(:ci_build, ref: 'v1.1.0', tag: true, project: project) } - - context 'when ref is protected' do - before do - create(:protected_tag, project: project, name: 'v*') + it 'inherits dependent variables' do + expect(build.scoped_variables.to_hash).to include(job_variable.key => job_variable.value) end - - it { is_expected.to include(variable) } - end - - context 'when ref is not protected' do - it { is_expected.not_to include(variable) } end - end - context 'when ref is merge request' do - let(:merge_request) { create(:merge_request, :with_detached_merge_request_pipeline) } - let(:pipeline) { merge_request.pipelines_for_merge_request.first } - let(:build) { create(:ci_build, ref: merge_request.source_branch, tag: false, pipeline: pipeline, project: project) } - - context 'when ref is protected' do + context 'FF ci_dependency_variables is disabled' do before do - create(:protected_branch, :developers_can_merge, name: merge_request.source_branch, project: project) + stub_feature_flags(ci_dependency_variables: false) end - it 'does not return protected variables as it is not supported for merge request pipelines' do - is_expected.not_to include(variable) + it 'does not inherit dependent variables' do + expect(build.scoped_variables.to_hash).not_to include(job_variable.key => job_variable.value) end end - - context 'when ref is not protected' do - it { is_expected.not_to include(variable) } - end end end - describe '#secret_project_variables' do - subject { build.secret_project_variables } - - let!(:variable) { create(:ci_variable, protected: true, project: project) } - + shared_examples "secret CI variables" do context 'when ref is branch' do let(:build) { create(:ci_build, ref: 'master', tag: false, project: project) } @@ -3167,6 +3171,30 @@ describe Ci::Build do end end + describe '#secret_instance_variables' do + subject { build.secret_instance_variables } + + let_it_be(:variable) { create(:ci_instance_variable, protected: true) } + + include_examples "secret CI variables" + end + + describe '#secret_group_variables' do + subject { build.secret_group_variables } + + let_it_be(:variable) { create(:ci_group_variable, protected: true, group: group) } + + include_examples "secret CI variables" + end + + describe '#secret_project_variables' do + subject { build.secret_project_variables } + + let_it_be(:variable) { create(:ci_variable, protected: true, project: project) } + + include_examples "secret CI variables" + end + describe '#deployment_variables' do let(:build) { create(:ci_build, environment: environment) } let(:environment) { 'production' } @@ -3217,6 +3245,29 @@ describe Ci::Build do expect(build.scoped_variables_hash).not_to include('MY_VAR': 'myvar') end end + + context 'when overriding CI instance variables' do + before do + create(:ci_instance_variable, key: 'MY_VAR', value: 'my value 1') + group.variables.create!(key: 'MY_VAR', value: 'my value 2') + end + + it 'returns a regular hash created using valid ordering' do + expect(build.scoped_variables_hash).to include('MY_VAR': 'my value 2') + expect(build.scoped_variables_hash).not_to include('MY_VAR': 'my value 1') + end + end + + context 'when CI instance variables are disabled' do + before do + create(:ci_instance_variable, key: 'MY_VAR', value: 'my value 1') + stub_feature_flags(ci_instance_level_variables: false) + end + + it 'does not include instance level variables' do + expect(build.scoped_variables_hash).not_to include('MY_VAR': 'my value 1') + end + end end describe '#any_unmet_prerequisites?' do @@ -3293,6 +3344,41 @@ describe Ci::Build do end end + describe '#dependency_variables' do + subject { build.dependency_variables } + + context 'when using dependencies' do + let!(:prepare1) { create(:ci_build, name: 'prepare1', pipeline: pipeline, stage_idx: 0) } + let!(:prepare2) { create(:ci_build, name: 'prepare2', pipeline: pipeline, stage_idx: 0) } + let!(:build) { create(:ci_build, pipeline: pipeline, stage_idx: 1, options: { dependencies: ['prepare1'] }) } + + let!(:job_variable_1) { create(:ci_job_variable, :dotenv_source, job: prepare1) } + let!(:job_variable_2) { create(:ci_job_variable, job: prepare1) } + let!(:job_variable_3) { create(:ci_job_variable, :dotenv_source, job: prepare2) } + + it 'inherits only dependent variables' do + expect(subject.to_hash).to eq(job_variable_1.key => job_variable_1.value) + end + end + + context 'when using needs' do + let!(:prepare1) { create(:ci_build, name: 'prepare1', pipeline: pipeline, stage_idx: 0) } + let!(:prepare2) { create(:ci_build, name: 'prepare2', pipeline: pipeline, stage_idx: 0) } + let!(:prepare3) { create(:ci_build, name: 'prepare3', pipeline: pipeline, stage_idx: 0) } + let!(:build) { create(:ci_build, pipeline: pipeline, stage_idx: 1, scheduling_type: 'dag') } + let!(:build_needs_prepare1) { create(:ci_build_need, build: build, name: 'prepare1', artifacts: true) } + let!(:build_needs_prepare2) { create(:ci_build_need, build: build, name: 'prepare2', artifacts: false) } + + let!(:job_variable_1) { create(:ci_job_variable, :dotenv_source, job: prepare1) } + let!(:job_variable_2) { create(:ci_job_variable, :dotenv_source, job: prepare2) } + let!(:job_variable_3) { create(:ci_job_variable, :dotenv_source, job: prepare3) } + + it 'inherits only needs with artifacts variables' do + expect(subject.to_hash).to eq(job_variable_1.key => job_variable_1.value) + end + end + end + describe 'state transition: any => [:preparing]' do let(:build) { create(:ci_build, :created) } @@ -3822,8 +3908,68 @@ describe Ci::Build do create(:ci_job_artifact, :junit_with_corrupted_data, job: build, project: build.project) end - it 'raises an error' do - expect { subject }.to raise_error(Gitlab::Ci::Parsers::Test::Junit::JunitParserError) + it 'returns no test data and includes a suite_error message' do + expect { subject }.not_to raise_error + + expect(test_reports.get_suite(build.name).total_count).to eq(0) + expect(test_reports.get_suite(build.name).success_count).to eq(0) + expect(test_reports.get_suite(build.name).failed_count).to eq(0) + expect(test_reports.get_suite(build.name).suite_error).to eq('JUnit XML parsing failed: 1:1: FATAL: Document is empty') + end + end + end + end + + describe '#collect_accessibility_reports!' do + subject { build.collect_accessibility_reports!(accessibility_report) } + + let(:accessibility_report) { Gitlab::Ci::Reports::AccessibilityReports.new } + + it { expect(accessibility_report.urls).to eq({}) } + + context 'when build has an accessibility report' do + context 'when there is an accessibility report with errors' do + before do + create(:ci_job_artifact, :accessibility, job: build, project: build.project) + end + + it 'parses blobs and add the results to the accessibility report' do + expect { subject }.not_to raise_error + + expect(accessibility_report.urls.keys).to match_array(['https://about.gitlab.com/']) + expect(accessibility_report.errors_count).to eq(10) + expect(accessibility_report.scans_count).to eq(1) + expect(accessibility_report.passes_count).to eq(0) + end + end + + context 'when there is an accessibility report without errors' do + before do + create(:ci_job_artifact, :accessibility_without_errors, job: build, project: build.project) + end + + it 'parses blobs and add the results to the accessibility report' do + expect { subject }.not_to raise_error + + expect(accessibility_report.urls.keys).to match_array(['https://pa11y.org/']) + expect(accessibility_report.errors_count).to eq(0) + expect(accessibility_report.scans_count).to eq(1) + expect(accessibility_report.passes_count).to eq(1) + end + end + + context 'when there is an accessibility report with an invalid url' do + before do + create(:ci_job_artifact, :accessibility_with_invalid_url, job: build, project: build.project) + end + + it 'parses blobs and add the results to the accessibility report' do + expect { subject }.not_to raise_error + + expect(accessibility_report.urls).to be_empty + expect(accessibility_report.errors_count).to eq(0) + expect(accessibility_report.scans_count).to eq(0) + expect(accessibility_report.passes_count).to eq(0) end end end @@ -3876,6 +4022,48 @@ describe Ci::Build do end end + describe '#collect_terraform_reports!' do + let(:terraform_reports) { Gitlab::Ci::Reports::TerraformReports.new } + + it 'returns an empty hash' do + expect(build.collect_terraform_reports!(terraform_reports).plans).to eq({}) + end + + context 'when build has a terraform report' do + context 'when there is a valid tfplan.json' do + before do + create(:ci_job_artifact, :terraform, job: build, project: build.project) + end + + it 'parses blobs and add the results to the terraform report' do + expect { build.collect_terraform_reports!(terraform_reports) }.not_to raise_error + + expect(terraform_reports.plans).to match( + a_hash_including( + 'tfplan.json' => a_hash_including( + 'create' => 0, + 'update' => 1, + 'delete' => 0 + ) + ) + ) + end + end + + context 'when there is an invalid tfplan.json' do + before do + create(:ci_job_artifact, :terraform_with_corrupted_data, job: build, project: build.project) + end + + it 'raises an error' do + expect { build.collect_terraform_reports!(terraform_reports) }.to raise_error( + Gitlab::Ci::Parsers::Terraform::Tfplan::TfplanParserError + ) + end + end + end + end + describe '#report_artifacts' do subject { build.report_artifacts } @@ -3986,6 +4174,28 @@ describe Ci::Build do it { is_expected.to include(:upload_multiple_artifacts) } end + + context 'when artifacts exclude is defined and the is feature enabled' do + let(:options) do + { artifacts: { exclude: %w[something] } } + end + + context 'when a feature flag is enabled' do + before do + stub_feature_flags(ci_artifacts_exclude: true) + end + + it { is_expected.to include(:artifacts_exclude) } + end + + context 'when a feature flag is disabled' do + before do + stub_feature_flags(ci_artifacts_exclude: false) + end + + it { is_expected.not_to include(:artifacts_exclude) } + end + end end describe '#supported_runner?' do @@ -4312,4 +4522,31 @@ describe Ci::Build do it { is_expected.to be_nil } end end + + describe '#degradation_threshold' do + subject { build.degradation_threshold } + + context 'when threshold variable is defined' do + before do + build.yaml_variables = [ + { key: 'SOME_VAR_1', value: 'SOME_VAL_1' }, + { key: 'DEGRADATION_THRESHOLD', value: '5' }, + { key: 'SOME_VAR_2', value: 'SOME_VAL_2' } + ] + end + + it { is_expected.to eq(5) } + end + + context 'when threshold variable is not defined' do + before do + build.yaml_variables = [ + { key: 'SOME_VAR_1', value: 'SOME_VAL_1' }, + { key: 'SOME_VAR_2', value: 'SOME_VAL_2' } + ] + end + + it { is_expected.to be_nil } + end + end end |