diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2020-03-06 15:08:05 +0000 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2020-03-06 15:08:05 +0000 |
commit | f78257cbddd711e18cbce93ad740a4aa0acac347 (patch) | |
tree | 7f018abe3ce1c0010879cc480f348a35e616fabb /spec | |
parent | f500600a43b531e2e7a5858b74bd35312b02c349 (diff) | |
download | gitlab-ce-f78257cbddd711e18cbce93ad740a4aa0acac347.tar.gz |
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'spec')
24 files changed, 769 insertions, 115 deletions
diff --git a/spec/fixtures/ci_build_artifacts.zip b/spec/fixtures/ci_build_artifacts.zip Binary files differindex dae976d918e..91ec9f7dcd0 100644 --- a/spec/fixtures/ci_build_artifacts.zip +++ b/spec/fixtures/ci_build_artifacts.zip diff --git a/spec/lib/backup/manager_spec.rb b/spec/lib/backup/manager_spec.rb index cee299522ce..b86e92d5969 100644 --- a/spec/lib/backup/manager_spec.rb +++ b/spec/lib/backup/manager_spec.rb @@ -319,18 +319,21 @@ describe Backup::Manager do context 'when there is a non-tarred backup in the directory' do before do - allow(Dir).to receieve(:glob).and_return( + allow(Dir).to receive(:glob).and_return( [ 'backup_information.yml' ] ) + allow(File).to receive(:exist?).and_return(true) + end + + it 'selects the non-tarred backup to restore from' do + expect(Kernel).not_to receive(:system) - it 'selects the non-tarred backup to restore from' do - expect { subject.unpack }.to output.to_stdout - expect(progress).to have_received(:puts) - .with(a_string_matching('Non tarred backup found ')) - expect(Kernel).not_to receive(:system) - end + subject.unpack + + expect(progress).to have_received(:puts) + .with(a_string_matching('Non tarred backup found ')) end end end diff --git a/spec/lib/gitlab/ci/artifact_file_reader_spec.rb b/spec/lib/gitlab/ci/artifact_file_reader_spec.rb new file mode 100644 index 00000000000..04017b9ae3e --- /dev/null +++ b/spec/lib/gitlab/ci/artifact_file_reader_spec.rb @@ -0,0 +1,100 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Gitlab::Ci::ArtifactFileReader do + let(:job) { create(:ci_build) } + let(:path) { 'generated.yml' } # included in the ci_build_artifacts.zip + + describe '#read' do + subject { described_class.new(job).read(path) } + + context 'when job has artifacts and metadata' do + let!(:artifacts) { create(:ci_job_artifact, :archive, job: job) } + let!(:metadata) { create(:ci_job_artifact, :metadata, job: job) } + + it 'returns the content at the path' do + is_expected.to be_present + expect(YAML.safe_load(subject).keys).to contain_exactly('rspec', 'time', 'custom') + end + + context 'when path does not exist' do + let(:path) { 'file/does/not/exist.txt' } + let(:expected_error) do + "Path `#{path}` does not exist inside the `#{job.name}` artifacts archive!" + end + + it 'raises an error' do + expect { subject }.to raise_error(described_class::Error, expected_error) + end + end + + context 'when path points to a directory' do + let(:path) { 'other_artifacts_0.1.2' } + let(:expected_error) do + "Path `#{path}` was expected to be a file but it was a directory!" + end + + it 'raises an error' do + expect { subject }.to raise_error(described_class::Error, expected_error) + end + end + + context 'when path is nested' do + # path exists in ci_build_artifacts.zip + let(:path) { 'other_artifacts_0.1.2/doc_sample.txt' } + + it 'returns the content at the nested path' do + is_expected.to be_present + end + end + + context 'when artifact archive size is greater than the limit' do + let(:expected_error) do + "Artifacts archive for job `#{job.name}` is too large: max 1 KB" + end + + before do + stub_const("#{described_class}::MAX_ARCHIVE_SIZE", 1.kilobyte) + end + + it 'raises an error' do + expect { subject }.to raise_error(described_class::Error, expected_error) + end + end + + context 'when metadata entry shows size greater than the limit' do + let(:expected_error) do + "Artifacts archive for job `#{job.name}` is too large: max 5 MB" + end + + before do + expect_next_instance_of(Gitlab::Ci::Build::Artifacts::Metadata::Entry) do |entry| + expect(entry).to receive(:total_size).and_return(10.megabytes) + end + end + + it 'raises an error' do + expect { subject }.to raise_error(described_class::Error, expected_error) + end + end + end + + context 'when job does not have metadata artifacts' do + let!(:artifacts) { create(:ci_job_artifact, :archive, job: job) } + let(:expected_error) do + "Job `#{job.name}` has missing artifacts metadata and cannot be extracted!" + end + + it 'raises an error' do + expect { subject }.to raise_error(described_class::Error, expected_error) + end + end + + context 'when job does not have artifacts' do + it 'raises ArgumentError' do + expect { subject }.to raise_error(ArgumentError, 'Job does not have artifacts') + end + end + end +end diff --git a/spec/lib/gitlab/ci/config/entry/bridge_spec.rb b/spec/lib/gitlab/ci/config/entry/bridge_spec.rb index ad388886681..d08ce30618d 100644 --- a/spec/lib/gitlab/ci/config/entry/bridge_spec.rb +++ b/spec/lib/gitlab/ci/config/entry/bridge_spec.rb @@ -106,6 +106,7 @@ describe Gitlab::Ci::Config::Entry::Bridge do ignore: false, stage: 'test', only: { refs: %w[branches tags] }, + variables: {}, scheduling_type: :stage) end end @@ -128,6 +129,7 @@ describe Gitlab::Ci::Config::Entry::Bridge do ignore: false, stage: 'test', only: { refs: %w[branches tags] }, + variables: {}, scheduling_type: :stage) end end diff --git a/spec/lib/gitlab/ci/config/entry/job_spec.rb b/spec/lib/gitlab/ci/config/entry/job_spec.rb index 313b504ab59..7df0eccb3ed 100644 --- a/spec/lib/gitlab/ci/config/entry/job_spec.rb +++ b/spec/lib/gitlab/ci/config/entry/job_spec.rb @@ -6,6 +6,7 @@ describe Gitlab::Ci::Config::Entry::Job do let(:entry) { described_class.new(config, name: :rspec) } it_behaves_like 'with inheritable CI config' do + let(:config) { { script: 'echo' } } let(:inheritable_key) { 'default' } let(:inheritable_class) { Gitlab::Ci::Config::Entry::Default } @@ -15,6 +16,10 @@ describe Gitlab::Ci::Config::Entry::Job do let(:ignored_inheritable_columns) do %i[] end + + before do + allow(entry).to receive_message_chain(:inherit_entry, :default_value).and_return(true) + end end describe '.nodes' do @@ -24,7 +29,8 @@ describe Gitlab::Ci::Config::Entry::Job do let(:result) do %i[before_script script stage type after_script cache image services only except rules needs variables artifacts - environment coverage retry interruptible timeout release tags] + environment coverage retry interruptible timeout release tags + inherit] end it { is_expected.to match_array result } @@ -500,7 +506,13 @@ describe Gitlab::Ci::Config::Entry::Job do let(:unspecified) { double('unspecified', 'specified?' => false) } let(:default) { double('default', '[]' => unspecified) } let(:workflow) { double('workflow', 'has_rules?' => false) } - let(:deps) { double('deps', 'default' => default, '[]' => unspecified, 'workflow' => workflow) } + + let(:deps) do + double('deps', + 'default_entry' => default, + 'workflow_entry' => workflow, + 'variables_value' => nil) + end context 'when job config overrides default config' do before do diff --git a/spec/lib/gitlab/ci/config/entry/jobs_spec.rb b/spec/lib/gitlab/ci/config/entry/jobs_spec.rb index c8c188d71bf..203342ab620 100644 --- a/spec/lib/gitlab/ci/config/entry/jobs_spec.rb +++ b/spec/lib/gitlab/ci/config/entry/jobs_spec.rb @@ -99,6 +99,7 @@ describe Gitlab::Ci::Config::Entry::Jobs do only: { refs: %w[branches tags] }, stage: 'test', trigger: { project: 'my/project' }, + variables: {}, scheduling_type: :stage }, regular_job: { diff --git a/spec/lib/gitlab/ci/config/entry/processable_spec.rb b/spec/lib/gitlab/ci/config/entry/processable_spec.rb index 410aef1cd53..5c2c6520f25 100644 --- a/spec/lib/gitlab/ci/config/entry/processable_spec.rb +++ b/spec/lib/gitlab/ci/config/entry/processable_spec.rb @@ -7,6 +7,10 @@ describe Gitlab::Ci::Config::Entry::Processable do Class.new(::Gitlab::Config::Entry::Node) do include Gitlab::Ci::Config::Entry::Processable + entry :tags, ::Gitlab::Config::Entry::ArrayOfStrings, + description: 'Set the default tags.', + inherit: true + def self.name 'job' end @@ -189,14 +193,17 @@ describe Gitlab::Ci::Config::Entry::Processable do end describe '#compose!' do - let(:specified) do - double('specified', 'specified?' => true, value: 'specified') - end - let(:unspecified) { double('unspecified', 'specified?' => false) } let(:default) { double('default', '[]' => unspecified) } let(:workflow) { double('workflow', 'has_rules?' => false) } - let(:deps) { double('deps', 'default' => default, '[]' => unspecified, 'workflow' => workflow) } + let(:variables) { } + + let(:deps) do + double('deps', + default_entry: default, + workflow_entry: workflow, + variables_value: variables) + end context 'with workflow rules' do using RSpec::Parameterized::TableSyntax @@ -240,6 +247,84 @@ describe Gitlab::Ci::Config::Entry::Processable do end end end + + context 'with inheritance' do + context 'of variables' do + let(:config) do + { variables: { A: 'job', B: 'job' } } + end + + before do + entry.compose!(deps) + end + + context 'with only job variables' do + it 'does return defined variables' do + expect(entry.value).to include( + variables: { 'A' => 'job', 'B' => 'job' } + ) + end + end + + context 'when root yaml variables are used' do + let(:variables) do + Gitlab::Ci::Config::Entry::Variables.new( + A: 'root', C: 'root' + ).value + end + + it 'does return all variables and overwrite them' do + expect(entry.value).to include( + variables: { 'A' => 'job', 'B' => 'job', 'C' => 'root' } + ) + end + + context 'when inherit of defaults is disabled' do + let(:config) do + { + variables: { A: 'job', B: 'job' }, + inherit: { variables: false } + } + end + + it 'does return only job variables' do + expect(entry.value).to include( + variables: { 'A' => 'job', 'B' => 'job' } + ) + end + end + end + end + + context 'of default:tags' do + using RSpec::Parameterized::TableSyntax + + where(:default_tags, :tags, :inherit_default, :result) do + nil | %w[a b] | nil | %w[a b] + nil | %w[a b] | true | %w[a b] + nil | %w[a b] | false | %w[a b] + %w[b c] | %w[a b] | nil | %w[a b] + %w[b c] | %w[a b] | true | %w[a b] + %w[b c] | %w[a b] | false | %w[a b] + %w[b c] | nil | nil | %w[b c] + %w[b c] | nil | true | %w[b c] + %w[b c] | nil | false | nil + end + + with_them do + let(:config) { { tags: tags, inherit: { default: inherit_default } } } + let(:default_specified_tags) { double('tags', 'specified?' => true, 'valid?' => true, 'value' => default_tags) } + + before do + allow(default).to receive('[]').with(:tags).and_return(default_specified_tags) + + entry.compose!(deps) + end + + it { expect(entry.tags_value).to eq(result) } + end + end + end end context 'when composed' do @@ -254,10 +339,12 @@ describe Gitlab::Ci::Config::Entry::Processable do end it 'returns correct value' do - expect(entry.value) - .to eq(name: :rspec, - stage: 'test', - only: { refs: %w[branches tags] }) + expect(entry.value).to eq( + name: :rspec, + stage: 'test', + only: { refs: %w[branches tags] }, + variables: {} + ) end end end diff --git a/spec/lib/gitlab/ci/config/entry/root_spec.rb b/spec/lib/gitlab/ci/config/entry/root_spec.rb index cf0a3cfa963..c3871b6b3cf 100644 --- a/spec/lib/gitlab/ci/config/entry/root_spec.rb +++ b/spec/lib/gitlab/ci/config/entry/root_spec.rb @@ -32,7 +32,7 @@ describe Gitlab::Ci::Config::Entry::Root do image: 'ruby:2.2', default: {}, services: ['postgres:9.1', 'mysql:5.5'], - variables: { VAR: 'value' }, + variables: { VAR: 'root' }, after_script: ['make clean'], stages: %w(build pages release), cache: { key: 'k', untracked: true, paths: ['public/'] }, @@ -42,6 +42,7 @@ describe Gitlab::Ci::Config::Entry::Root do stage: 'release', before_script: [], after_script: [], + variables: { 'VAR' => 'job' }, script: ["make changelog | tee release_changelog.txt"], release: { tag_name: 'v0.06', @@ -127,7 +128,7 @@ describe Gitlab::Ci::Config::Entry::Root do services: [{ name: 'postgres:9.1' }, { name: 'mysql:5.5' }], stage: 'test', cache: { key: 'k', untracked: true, paths: ['public/'], policy: 'pull-push' }, - variables: {}, + variables: { 'VAR' => 'root' }, ignore: false, after_script: ['make clean'], only: { refs: %w[branches tags] }, @@ -141,7 +142,7 @@ describe Gitlab::Ci::Config::Entry::Root do services: [{ name: 'postgres:9.1' }, { name: 'mysql:5.5' }], stage: 'test', cache: { key: 'k', untracked: true, paths: ['public/'], policy: 'pull-push' }, - variables: {}, + variables: { 'VAR' => 'root' }, ignore: false, after_script: ['make clean'], only: { refs: %w[branches tags] }, @@ -157,7 +158,7 @@ describe Gitlab::Ci::Config::Entry::Root do services: [{ name: "postgres:9.1" }, { name: "mysql:5.5" }], cache: { key: "k", untracked: true, paths: ["public/"], policy: "pull-push" }, only: { refs: %w(branches tags) }, - variables: {}, + variables: { 'VAR' => 'job' }, after_script: [], ignore: false, scheduling_type: :stage } @@ -175,11 +176,11 @@ describe Gitlab::Ci::Config::Entry::Root do image: 'ruby:2.1', services: ['postgres:9.1', 'mysql:5.5'] }, - variables: { VAR: 'value' }, + variables: { VAR: 'root' }, stages: %w(build pages), cache: { key: 'k', untracked: true, paths: ['public/'] }, rspec: { script: %w[rspec ls] }, - spinach: { before_script: [], variables: { VAR: 'AA' }, script: 'spinach' } } + spinach: { before_script: [], variables: { VAR: 'job' }, script: 'spinach' } } end context 'when composed' do @@ -203,7 +204,7 @@ describe Gitlab::Ci::Config::Entry::Root do services: [{ name: 'postgres:9.1' }, { name: 'mysql:5.5' }], stage: 'test', cache: { key: 'k', untracked: true, paths: ['public/'], policy: "pull-push" }, - variables: {}, + variables: { 'VAR' => 'root' }, ignore: false, after_script: ['make clean'], only: { refs: %w[branches tags] }, @@ -215,7 +216,7 @@ describe Gitlab::Ci::Config::Entry::Root do services: [{ name: 'postgres:9.1' }, { name: 'mysql:5.5' }], stage: 'test', cache: { key: 'k', untracked: true, paths: ['public/'], policy: "pull-push" }, - variables: { 'VAR' => 'AA' }, + variables: { 'VAR' => 'job' }, ignore: false, after_script: ['make clean'], only: { refs: %w[branches tags] }, diff --git a/spec/lib/gitlab/ci/config/external/file/artifact_spec.rb b/spec/lib/gitlab/ci/config/external/file/artifact_spec.rb new file mode 100644 index 00000000000..a8eb13c47bc --- /dev/null +++ b/spec/lib/gitlab/ci/config/external/file/artifact_spec.rb @@ -0,0 +1,167 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Gitlab::Ci::Config::External::File::Artifact do + let(:parent_pipeline) { create(:ci_pipeline) } + let(:context) do + Gitlab::Ci::Config::External::Context.new(parent_pipeline: parent_pipeline) + end + + let(:external_file) { described_class.new(params, context) } + + describe '#matching?' do + context 'when params contain artifact location' do + let(:params) { { artifact: 'generated.yml' } } + + it 'returns true' do + expect(external_file).to be_matching + end + end + + context 'when params does not contain artifact location' do + let(:params) { {} } + + it 'returns false' do + expect(external_file).not_to be_matching + end + end + end + + describe '#valid?' do + shared_examples 'is invalid' do + it 'is not valid' do + expect(external_file).not_to be_valid + end + + it 'sets the expected error' do + expect(external_file.errors) + .to contain_exactly(expected_error) + end + end + + describe 'when used in non child pipeline context' do + let(:parent_pipeline) { nil } + let(:params) { { artifact: 'generated.yml' } } + + let(:expected_error) do + 'Including configs from artifacts is only allowed when triggering child pipelines' + end + + it_behaves_like 'is invalid' + end + + context 'when used in child pipeline context' do + let(:parent_pipeline) { create(:ci_pipeline) } + + context 'when job is not provided' do + let(:params) { { artifact: 'generated.yml' } } + + let(:expected_error) do + 'Job must be provided when including configs from artifacts' + end + + it_behaves_like 'is invalid' + end + + context 'when job is provided' do + let(:params) { { artifact: 'generated.yml', job: 'generator' } } + + context 'when job does not exist in the parent pipeline' do + let(:expected_error) do + 'Job `generator` not found in parent pipeline or does not have artifacts!' + end + + it_behaves_like 'is invalid' + end + + context 'when job exists in the parent pipeline' do + let!(:generator_job) { create(:ci_build, name: 'generator', pipeline: parent_pipeline) } + + context 'when job does not have artifacts' do + let(:expected_error) do + 'Job `generator` not found in parent pipeline or does not have artifacts!' + end + + it_behaves_like 'is invalid' + end + + context 'when job has archive artifacts' do + let!(:artifacts) do + create(:ci_job_artifact, :archive, + job: generator_job, + file: fixture_file_upload(Rails.root.join('spec/fixtures/pages.zip'), 'application/zip')) + end + + let(:expected_error) do + 'Job `generator` has missing artifacts metadata and cannot be extracted!' + end + + it_behaves_like 'is invalid' + + context 'when job has artifacts exceeding the max allowed size' do + let(:expected_error) do + "Artifacts archive for job `generator` is too large: max 1 KB" + end + + before do + stub_const("#{Gitlab::Ci::ArtifactFileReader}::MAX_ARCHIVE_SIZE", 1.kilobyte) + end + + it_behaves_like 'is invalid' + end + + context 'when job has artifacts metadata' do + let!(:metadata) do + create(:ci_job_artifact, :metadata, job: generator_job) + end + + let(:expected_error) do + 'Path `generated.yml` does not exist inside the `generator` artifacts archive!' + end + + it_behaves_like 'is invalid' + + context 'when file is found in metadata' do + let!(:artifacts) { create(:ci_job_artifact, :archive, job: generator_job) } + let!(:metadata) { create(:ci_job_artifact, :metadata, job: generator_job) } + + context 'when file is empty' do + before do + allow_next_instance_of(Gitlab::Ci::ArtifactFileReader) do |reader| + allow(reader).to receive(:read).and_return('') + end + end + + let(:expected_error) do + 'File `generated.yml` is empty!' + end + + it_behaves_like 'is invalid' + end + + context 'when file is not empty' do + it 'is valid' do + expect(external_file).to be_valid + expect(external_file.content).to be_present + end + + it 'propagates parent_pipeline to nested includes' do + expected_attrs = { + parent_pipeline: parent_pipeline, + project: anything, + sha: anything, + user: anything + } + expect(context).to receive(:mutate).with(expected_attrs).and_call_original + external_file.content + end + end + end + end + end + end + end + end + end +end diff --git a/spec/lib/gitlab/ci/config/external/file/local_spec.rb b/spec/lib/gitlab/ci/config/external/file/local_spec.rb index 53205a18762..c9851239859 100644 --- a/spec/lib/gitlab/ci/config/external/file/local_spec.rb +++ b/spec/lib/gitlab/ci/config/external/file/local_spec.rb @@ -6,10 +6,19 @@ describe Gitlab::Ci::Config::External::File::Local do let_it_be(:project) { create(:project, :repository) } let_it_be(:user) { create(:user) } let(:sha) { '12345' } - let(:context_params) { { project: project, sha: sha, user: user } } let(:context) { Gitlab::Ci::Config::External::Context.new(**context_params) } let(:params) { { local: location } } let(:local_file) { described_class.new(params, context) } + let(:parent_pipeline) { double(:parent_pipeline) } + + let(:context_params) do + { + project: project, + sha: sha, + user: user, + parent_pipeline: parent_pipeline + } + end before do allow_any_instance_of(Gitlab::Ci::Config::External::Context) @@ -117,7 +126,11 @@ describe Gitlab::Ci::Config::External::File::Local do subject { local_file.send(:expand_context_attrs) } it 'inherits project, user and sha' do - is_expected.to include(user: user, project: project, sha: sha) + is_expected.to include( + user: user, + project: project, + sha: sha, + parent_pipeline: parent_pipeline) end end diff --git a/spec/lib/gitlab/ci/config/external/file/project_spec.rb b/spec/lib/gitlab/ci/config/external/file/project_spec.rb index 77a71f9972b..b2924ae9d91 100644 --- a/spec/lib/gitlab/ci/config/external/file/project_spec.rb +++ b/spec/lib/gitlab/ci/config/external/file/project_spec.rb @@ -7,10 +7,19 @@ describe Gitlab::Ci::Config::External::File::Project do let_it_be(:project) { create(:project, :repository) } let_it_be(:user) { create(:user) } let(:context_user) { user } - let(:context_params) { { project: context_project, sha: '12345', user: context_user } } + let(:parent_pipeline) { double(:parent_pipeline) } let(:context) { Gitlab::Ci::Config::External::Context.new(**context_params) } let(:project_file) { described_class.new(params, context) } + let(:context_params) do + { + project: context_project, + sha: '12345', + user: context_user, + parent_pipeline: parent_pipeline + } + end + before do project.add_developer(user) @@ -152,7 +161,11 @@ describe Gitlab::Ci::Config::External::File::Project do subject { project_file.send(:expand_context_attrs) } it 'inherits user, and target project and sha' do - is_expected.to include(user: user, project: project, sha: project.commit('master').id) + is_expected.to include( + user: user, + project: project, + sha: project.commit('master').id, + parent_pipeline: parent_pipeline) end end diff --git a/spec/lib/gitlab/ci/config_spec.rb b/spec/lib/gitlab/ci/config_spec.rb index 525335cfea9..82ca8a29c5a 100644 --- a/spec/lib/gitlab/ci/config_spec.rb +++ b/spec/lib/gitlab/ci/config_spec.rb @@ -376,23 +376,6 @@ describe Gitlab::Ci::Config do end end - context 'when context expansion timeout is disabled' do - before do - allow_next_instance_of(Gitlab::Ci::Config::External::Context) do |instance| - allow(instance).to receive(:check_execution_time!).and_call_original - end - - allow(Feature) - .to receive(:enabled?) - .with(:ci_limit_yaml_expansion, project, default_enabled: true) - .and_return(false) - end - - it 'does not raises errors' do - expect { config }.not_to raise_error - end - end - describe 'external file version' do context 'when external local file SHA is defined' do it 'is using a defined value' do @@ -541,5 +524,76 @@ describe Gitlab::Ci::Config do end end end + + context 'when including file from artifact' do + let(:config) do + described_class.new( + gitlab_ci_yml, + project: nil, + sha: nil, + user: nil, + parent_pipeline: parent_pipeline) + end + + let(:gitlab_ci_yml) do + <<~HEREDOC + include: + - artifact: generated.yml + job: rspec + HEREDOC + end + + let(:parent_pipeline) { nil } + + context 'when used in the context of a child pipeline' do + # This job has ci_build_artifacts.zip artifact archive which + # contains generated.yml + let!(:job) { create(:ci_build, :artifacts, name: 'rspec', pipeline: parent_pipeline) } + let(:parent_pipeline) { create(:ci_pipeline) } + + it 'returns valid config' do + expect(config).to be_valid + end + + context 'when job key is missing' do + let(:gitlab_ci_yml) do + <<~HEREDOC + include: + - artifact: generated.yml + HEREDOC + end + + it 'raises an error' do + expect { config }.to raise_error( + described_class::ConfigError, + 'Job must be provided when including configs from artifacts' + ) + end + end + + context 'when artifact key is missing' do + let(:gitlab_ci_yml) do + <<~HEREDOC + include: + - job: rspec + HEREDOC + end + + it 'raises an error' do + expect { config }.to raise_error( + described_class::ConfigError, + /needs to match exactly one accessor!/ + ) + end + end + end + + it 'disallows the use in parent pipelines' do + expect { config }.to raise_error( + described_class::ConfigError, + 'Including configs from artifacts is only allowed when triggering child pipelines' + ) + end + end end end diff --git a/spec/lib/gitlab/ci/yaml_processor_spec.rb b/spec/lib/gitlab/ci/yaml_processor_spec.rb index e303557bd00..5c85a136972 100644 --- a/spec/lib/gitlab/ci/yaml_processor_spec.rb +++ b/spec/lib/gitlab/ci/yaml_processor_spec.rb @@ -509,28 +509,44 @@ module Gitlab describe "before_script" do context "in global context" do - let(:config) do - { - before_script: ["global script"], - test: { script: ["script"] } - } + using RSpec::Parameterized::TableSyntax + + where(:inherit, :result) do + nil | ["global script"] + { default: false } | nil + { default: true } | ["global script"] end - it "return commands with scripts concatenated" do - expect(subject[:options][:before_script]).to eq(["global script"]) + with_them do + let(:config) do + { + before_script: ["global script"], + test: { script: ["script"], inherit: inherit } + } + end + + it { expect(subject[:options][:before_script]).to eq(result) } end end context "in default context" do - let(:config) do - { - default: { before_script: ["global script"] }, - test: { script: ["script"] } - } + using RSpec::Parameterized::TableSyntax + + where(:inherit, :result) do + nil | ["global script"] + { default: false } | nil + { default: true } | ["global script"] end - it "return commands with scripts concatenated" do - expect(subject[:options][:before_script]).to eq(["global script"]) + with_them do + let(:config) do + { + default: { before_script: ["global script"] }, + test: { script: ["script"], inherit: inherit } + } + end + + it { expect(subject[:options][:before_script]).to eq(result) } end end @@ -793,7 +809,7 @@ module Gitlab context 'when job and global variables are defined' do let(:global_variables) do - { 'VAR1' => 'global1', 'VAR3' => 'global3' } + { 'VAR1' => 'global1', 'VAR3' => 'global3', 'VAR4' => 'global4' } end let(:job_variables) do { 'VAR1' => 'value1', 'VAR2' => 'value2' } @@ -802,16 +818,32 @@ module Gitlab { before_script: ['pwd'], variables: global_variables, - rspec: { script: 'rspec', variables: job_variables } + rspec: { script: 'rspec', variables: job_variables, inherit: inherit } } end - it 'returns all unique variables' do - expect(subject).to contain_exactly( - { key: 'VAR3', value: 'global3', public: true }, - { key: 'VAR1', value: 'value1', public: true }, - { key: 'VAR2', value: 'value2', public: true } - ) + context 'when no inheritance is specified' do + let(:inherit) { } + + it 'returns all unique variables' do + expect(subject).to contain_exactly( + { key: 'VAR4', value: 'global4', public: true }, + { key: 'VAR3', value: 'global3', public: true }, + { key: 'VAR1', value: 'value1', public: true }, + { key: 'VAR2', value: 'value2', public: true } + ) + end + end + + context 'when inheritance is disabled' do + let(:inherit) { { variables: false } } + + it 'does not inherit variables' do + expect(subject).to contain_exactly( + { key: 'VAR1', value: 'value1', public: true }, + { key: 'VAR2', value: 'value2', public: true } + ) + end end end diff --git a/spec/lib/gitlab/config/entry/attributable_spec.rb b/spec/lib/gitlab/config/entry/attributable_spec.rb index bc29a194181..64a4670f483 100644 --- a/spec/lib/gitlab/config/entry/attributable_spec.rb +++ b/spec/lib/gitlab/config/entry/attributable_spec.rb @@ -59,7 +59,7 @@ describe Gitlab::Config::Entry::Attributable do end end - expectation.to raise_error(ArgumentError, 'Method already defined: length') + expectation.to raise_error(ArgumentError, /Method 'length' already defined in/) end end end diff --git a/spec/models/ci/job_artifact_spec.rb b/spec/models/ci/job_artifact_spec.rb index 8f56d735f36..0a7a44b225c 100644 --- a/spec/models/ci/job_artifact_spec.rb +++ b/spec/models/ci/job_artifact_spec.rb @@ -25,7 +25,7 @@ describe Ci::JobArtifact do end it_behaves_like 'UpdateProjectStatistics' do - subject { build(:ci_job_artifact, :archive, size: 106365) } + subject { build(:ci_job_artifact, :archive, size: 107464) } end end @@ -35,7 +35,7 @@ describe Ci::JobArtifact do end it_behaves_like 'UpdateProjectStatistics' do - subject { build(:ci_job_artifact, :archive, size: 106365) } + subject { build(:ci_job_artifact, :archive, size: 107464) } end end @@ -173,7 +173,7 @@ describe Ci::JobArtifact do let(:artifact) { create(:ci_job_artifact, :archive, project: project) } it 'sets the size from the file size' do - expect(artifact.size).to eq(106365) + expect(artifact.size).to eq(107464) end end diff --git a/spec/models/ci/pipeline_spec.rb b/spec/models/ci/pipeline_spec.rb index 76051ecb177..f775906a545 100644 --- a/spec/models/ci/pipeline_spec.rb +++ b/spec/models/ci/pipeline_spec.rb @@ -2553,6 +2553,19 @@ describe Ci::Pipeline, :mailer do end end + describe '#find_job_with_archive_artifacts' do + let!(:old_job) { create(:ci_build, name: 'rspec', retried: true, pipeline: pipeline) } + let!(:job_without_artifacts) { create(:ci_build, name: 'rspec', pipeline: pipeline) } + let!(:expected_job) { create(:ci_build, :artifacts, name: 'rspec', pipeline: pipeline ) } + let!(:different_job) { create(:ci_build, name: 'deploy', pipeline: pipeline) } + + subject { pipeline.find_job_with_archive_artifacts('rspec') } + + it 'finds the expected job' do + expect(subject).to eq(expected_job) + end + end + describe '#latest_builds_with_artifacts' do let!(:fresh_build) { create(:ci_build, :success, :artifacts, pipeline: pipeline) } let!(:stale_build) { create(:ci_build, :success, :expired, :artifacts, pipeline: pipeline) } diff --git a/spec/models/snippet_spec.rb b/spec/models/snippet_spec.rb index 87ace7b51f4..95807f5f0c1 100644 --- a/spec/models/snippet_spec.rb +++ b/spec/models/snippet_spec.rb @@ -511,6 +511,32 @@ describe Snippet do end end + describe '#blobs' do + let(:snippet) { create(:snippet) } + + context 'when repository does not exist' do + it 'returns empty array' do + expect(snippet.blobs).to be_empty + end + end + + context 'when repository exists' do + let(:snippet) { create(:snippet, :repository) } + + it 'returns array of blobs' do + expect(snippet.blobs).to all(be_a(Blob)) + end + end + + it 'returns a blob representing the snippet data' do + blob = snippet.blob + + expect(blob).to be_a(Blob) + expect(blob.path).to eq(snippet.file_name) + expect(blob.data).to eq(snippet.content) + end + end + describe '#to_json' do let(:snippet) { build(:snippet) } diff --git a/spec/requests/api/runner_spec.rb b/spec/requests/api/runner_spec.rb index e76ab8d409b..6e5c2088ee7 100644 --- a/spec/requests/api/runner_spec.rb +++ b/spec/requests/api/runner_spec.rb @@ -756,7 +756,7 @@ describe API::Runner, :clean_gitlab_redis_shared_state do expect(json_response['dependencies'].count).to eq(1) expect(json_response['dependencies']).to include( { 'id' => job.id, 'name' => job.name, 'token' => job.token, - 'artifacts_file' => { 'filename' => 'ci_build_artifacts.zip', 'size' => 106365 } }) + 'artifacts_file' => { 'filename' => 'ci_build_artifacts.zip', 'size' => 107464 } }) end end diff --git a/spec/services/ci/create_pipeline_service/custom_config_content_spec.rb b/spec/services/ci/create_pipeline_service/custom_config_content_spec.rb index 2657f1d300a..112b19fcbc5 100644 --- a/spec/services/ci/create_pipeline_service/custom_config_content_spec.rb +++ b/spec/services/ci/create_pipeline_service/custom_config_content_spec.rb @@ -4,30 +4,77 @@ require 'spec_helper' describe Ci::CreatePipelineService do let_it_be(:project) { create(:project, :repository) } let_it_be(:user) { create(:admin) } - let(:upstream_pipeline) { create(:ci_pipeline) } let(:ref) { 'refs/heads/master' } let(:service) { described_class.new(project, user, { ref: ref }) } + let(:upstream_pipeline) { create(:ci_pipeline, project: project) } + let(:bridge) { create(:ci_bridge, pipeline: upstream_pipeline) } + + subject { service.execute(:push, bridge: bridge) } + context 'custom config content' do let(:bridge) do create(:ci_bridge, status: 'running', pipeline: upstream_pipeline, project: upstream_pipeline.project).tap do |bridge| - allow(bridge).to receive(:yaml_for_downstream).and_return( - <<~YML - rspec: - script: rspec - custom: - script: custom - YML - ) + allow(bridge).to receive(:yaml_for_downstream).and_return(config_from_bridge) end end - subject { service.execute(:push, bridge: bridge) } + let(:config_from_bridge) do + <<~YML + rspec: + script: rspec + custom: + script: custom + YML + end + + before do + allow(bridge).to receive(:yaml_for_downstream).and_return config_from_bridge + end it 'creates a pipeline using the content passed in as param' do expect(subject).to be_persisted expect(subject.builds.map(&:name)).to eq %w[rspec custom] expect(subject.config_source).to eq 'bridge_source' end + + context 'when bridge includes yaml from artifact' do + # the generated.yml is available inside the ci_build_artifacts.zip associated + # to the generator_job + let(:config_from_bridge) do + <<~YML + include: + - artifact: generated.yml + job: generator + YML + end + + context 'when referenced job exists' do + let!(:generator_job) do + create(:ci_build, :artifacts, + project: project, + pipeline: upstream_pipeline, + name: 'generator') + end + + it 'created a pipeline using the content passed in as param and download the artifact' do + expect(subject).to be_persisted + expect(subject.builds.pluck(:name)).to eq %w[rspec time custom] + expect(subject.config_source).to eq 'bridge_source' + end + end + + context 'when referenced job does not exist' do + it 'creates an empty pipeline' do + expect(subject).to be_persisted + expect(subject).to be_failed + expect(subject.errors.full_messages) + .to contain_exactly( + 'Job `generator` not found in parent pipeline or does not have artifacts!') + expect(subject.builds.pluck(:name)).to be_empty + expect(subject.config_source).to eq 'bridge_source' + end + end + end end end diff --git a/spec/services/projects/fork_service_spec.rb b/spec/services/projects/fork_service_spec.rb index 12c51d01d63..cbf7a135c41 100644 --- a/spec/services/projects/fork_service_spec.rb +++ b/spec/services/projects/fork_service_spec.rb @@ -315,6 +315,7 @@ describe Projects::ForkService do # Stub everything required to move a project to a Gitaly shard that does not exist stub_storage_settings('test_second_storage' => { 'path' => 'tmp/tests/second_storage' }) allow_any_instance_of(Gitlab::Git::Repository).to receive(:fetch_repository_as_mirror).and_return(true) + allow_any_instance_of(Gitlab::Git::Repository).to receive(:checksum).and_return(::Gitlab::Git::BLANK_SHA) Projects::UpdateRepositoryStorageService.new(project).execute('test_second_storage') fork_after_move = fork_project(project) diff --git a/spec/services/projects/update_repository_storage_service_spec.rb b/spec/services/projects/update_repository_storage_service_spec.rb index a0917f718e6..2e9a4626abb 100644 --- a/spec/services/projects/update_repository_storage_service_spec.rb +++ b/spec/services/projects/update_repository_storage_service_spec.rb @@ -16,6 +16,15 @@ describe Projects::UpdateRepositoryStorageService do context 'without wiki and design repository' do let(:project) { create(:project, :repository, repository_read_only: true, wiki_enabled: false) } + let!(:checksum) { project.repository.checksum } + let(:project_repository_double) { double(:repository) } + + before do + allow(Gitlab::Git::Repository).to receive(:new).and_call_original + allow(Gitlab::Git::Repository).to receive(:new) + .with('test_second_storage', project.repository.raw.relative_path, project.repository.gl_repository, project.repository.full_path) + .and_return(project_repository_double) + end context 'when the move succeeds' do it 'moves the repository to the new storage and unmarks the repository as read only' do @@ -23,10 +32,14 @@ describe Projects::UpdateRepositoryStorageService do project.repository.path_to_repo end - expect_any_instance_of(Gitlab::Git::Repository).to receive(:fetch_repository_as_mirror) + expect(project_repository_double).to receive(:fetch_repository_as_mirror) .with(project.repository.raw).and_return(true) + expect(project_repository_double).to receive(:checksum) + .and_return(checksum) + + result = subject.execute('test_second_storage') - subject.execute('test_second_storage') + expect(result[:status]).to eq(:success) expect(project).not_to be_repository_read_only expect(project.repository_storage).to eq('test_second_storage') expect(gitlab_shell.repository_exists?('default', old_path)).to be(false) @@ -44,16 +57,50 @@ describe Projects::UpdateRepositoryStorageService do context 'when the move fails' do it 'unmarks the repository as read-only without updating the repository storage' do - expect_any_instance_of(Gitlab::Git::Repository).to receive(:fetch_repository_as_mirror) + expect(project_repository_double).to receive(:fetch_repository_as_mirror) .with(project.repository.raw).and_return(false) expect(GitlabShellWorker).not_to receive(:perform_async) - subject.execute('test_second_storage') + result = subject.execute('test_second_storage') + expect(result[:status]).to eq(:error) expect(project).not_to be_repository_read_only expect(project.repository_storage).to eq('default') end end + + context 'when the checksum does not match' do + it 'unmarks the repository as read-only without updating the repository storage' do + expect(project_repository_double).to receive(:fetch_repository_as_mirror) + .with(project.repository.raw).and_return(true) + expect(project_repository_double).to receive(:checksum) + .and_return('not matching checksum') + expect(GitlabShellWorker).not_to receive(:perform_async) + + result = subject.execute('test_second_storage') + + expect(result[:status]).to eq(:error) + expect(project).not_to be_repository_read_only + expect(project.repository_storage).to eq('default') + end + end + + context 'when a object pool was joined' do + let!(:pool) { create(:pool_repository, :ready, source_project: project) } + + it 'leaves the pool' do + expect(project_repository_double).to receive(:fetch_repository_as_mirror) + .with(project.repository.raw).and_return(true) + expect(project_repository_double).to receive(:checksum) + .and_return(checksum) + + result = subject.execute('test_second_storage') + + expect(result[:status]).to eq(:success) + expect(project.repository_storage).to eq('test_second_storage') + expect(project.reload_pool_repository).to be_nil + end + end end context 'with wiki repository' do @@ -66,18 +113,5 @@ describe Projects::UpdateRepositoryStorageService do end end end - - context 'when a object pool was joined' do - let(:project) { create(:project, :repository, wiki_enabled: false, repository_read_only: true) } - let(:pool) { create(:pool_repository, :ready, source_project: project) } - - it 'leaves the pool' do - allow_any_instance_of(Gitlab::Git::Repository).to receive(:fetch_repository_as_mirror).and_return(true) - - subject.execute('test_second_storage') - - expect(project.reload_pool_repository).to be_nil - end - end end end diff --git a/spec/support/shared_examples/lib/gitlab/config/inheritable_shared_examples.rb b/spec/support/shared_examples/lib/gitlab/config/inheritable_shared_examples.rb index 556d81133bc..95772b1774a 100644 --- a/spec/support/shared_examples/lib/gitlab/config/inheritable_shared_examples.rb +++ b/spec/support/shared_examples/lib/gitlab/config/inheritable_shared_examples.rb @@ -53,7 +53,7 @@ RSpec.shared_examples 'with inheritable CI config' do let(:deps) do if inheritable_key - double('deps', inheritable_key => inheritable, '[]' => unspecified) + double('deps', "#{inheritable_key}_entry" => inheritable, '[]' => unspecified) else inheritable end @@ -68,7 +68,7 @@ RSpec.shared_examples 'with inheritable CI config' do it 'does inherit value' do expect(inheritable).to receive('[]').with(entry_key).and_return(specified) - entry.compose!(deps) + entry.send(:inherit!, deps) expect(entry[entry_key]).to eq(specified) end @@ -86,7 +86,7 @@ RSpec.shared_examples 'with inheritable CI config' do expect do # we ignore exceptions as `#overwrite_entry` # can raise exception on duplicates - entry.compose!(deps) rescue described_class::InheritError + entry.send(:inherit!, deps) rescue described_class::InheritError end.not_to change { entry[entry_key] } end end @@ -94,7 +94,7 @@ RSpec.shared_examples 'with inheritable CI config' do context 'when inheritable does not specify' do it 'does not inherit value' do - entry.compose!(deps) + entry.send(:inherit!, deps) expect(entry[entry_key]).to be_a( Gitlab::Config::Entry::Undefined) diff --git a/spec/support/shared_examples/services/projects/update_repository_storage_service_shared_examples.rb b/spec/support/shared_examples/services/projects/update_repository_storage_service_shared_examples.rb index f222dff60ab..6f83f52d54b 100644 --- a/spec/support/shared_examples/services/projects/update_repository_storage_service_shared_examples.rb +++ b/spec/support/shared_examples/services/projects/update_repository_storage_service_shared_examples.rb @@ -2,7 +2,10 @@ RSpec.shared_examples 'moves repository to another storage' do |repository_type| let(:project_repository_double) { double(:repository) } + let!(:project_repository_checksum) { project.repository.checksum } + let(:repository_double) { double(:repository) } + let(:repository_checksum) { repository.checksum } before do # Default stub for non-specified params @@ -19,15 +22,16 @@ RSpec.shared_examples 'moves repository to another storage' do |repository_type| context 'when the move succeeds', :clean_gitlab_redis_shared_state do before do - allow(project_repository_double) - .to receive(:fetch_repository_as_mirror) + allow(project_repository_double).to receive(:fetch_repository_as_mirror) .with(project.repository.raw) .and_return(true) + allow(project_repository_double).to receive(:checksum) + .and_return(project_repository_checksum) - allow(repository_double) - .to receive(:fetch_repository_as_mirror) - .with(repository.raw) - .and_return(true) + allow(repository_double).to receive(:fetch_repository_as_mirror) + .with(repository.raw).and_return(true) + allow(repository_double).to receive(:checksum) + .and_return(repository_checksum) end it "moves the project and its #{repository_type} repository to the new storage and unmarks the repository as read only" do @@ -37,8 +41,9 @@ RSpec.shared_examples 'moves repository to another storage' do |repository_type| old_repository_path = repository.full_path - subject.execute('test_second_storage') + result = subject.execute('test_second_storage') + expect(result[:status]).to eq(:success) expect(project).not_to be_repository_read_only expect(project.repository_storage).to eq('test_second_storage') expect(gitlab_shell.repository_exists?('default', old_project_repository_path)).to be(false) @@ -87,13 +92,38 @@ RSpec.shared_examples 'moves repository to another storage' do |repository_type| it 'unmarks the repository as read-only without updating the repository storage' do allow(project_repository_double).to receive(:fetch_repository_as_mirror) .with(project.repository.raw).and_return(true) + allow(project_repository_double).to receive(:checksum) + .and_return(project_repository_checksum) allow(repository_double).to receive(:fetch_repository_as_mirror) .with(repository.raw).and_return(false) expect(GitlabShellWorker).not_to receive(:perform_async) - subject.execute('test_second_storage') + result = subject.execute('test_second_storage') + + expect(result[:status]).to eq(:error) + expect(project).not_to be_repository_read_only + expect(project.repository_storage).to eq('default') + end + end + + context "when the checksum of the #{repository_type} repository does not match" do + it 'unmarks the repository as read-only without updating the repository storage' do + allow(project_repository_double).to receive(:fetch_repository_as_mirror) + .with(project.repository.raw).and_return(true) + allow(project_repository_double).to receive(:checksum) + .and_return(project_repository_checksum) + + allow(repository_double).to receive(:fetch_repository_as_mirror) + .with(repository.raw).and_return(true) + allow(repository_double).to receive(:checksum) + .and_return('not matching checksum') + + expect(GitlabShellWorker).not_to receive(:perform_async) + + result = subject.execute('test_second_storage') + expect(result[:status]).to eq(:error) expect(project).not_to be_repository_read_only expect(project.repository_storage).to eq('default') end diff --git a/spec/workers/post_receive_spec.rb b/spec/workers/post_receive_spec.rb index 72e423db611..2a2d0f5a857 100644 --- a/spec/workers/post_receive_spec.rb +++ b/spec/workers/post_receive_spec.rb @@ -421,17 +421,35 @@ describe PostReceive do perform end end + + it 'updates the snippet db information' do + blob = snippet.blobs.first + + expect(snippet).to receive(:update).with(file_name: blob.path, content: blob.data) + + perform + end + + context 'when snippet does not have any blob' do + it 'does not update snippet db information' do + allow(snippet).to receive(:blobs).and_return([]) + + expect(snippet).not_to receive(:update) + + perform + end + end end end context 'with PersonalSnippet' do - let!(:snippet) { create(:personal_snippet, author: project.owner) } + let!(:snippet) { create(:personal_snippet, :repository, author: project.owner) } it_behaves_like 'snippet changes actions' end context 'with ProjectSnippet' do - let!(:snippet) { create(:project_snippet, project: project, author: project.owner) } + let!(:snippet) { create(:project_snippet, :repository, project: project, author: project.owner) } it_behaves_like 'snippet changes actions' end |