Commit 2a0874b3 authored by Marius Bobin's avatar Marius Bobin

Merge branch '344533-add-ci-config-entry-coverage_report' into 'master'

Create new CI report keyword `coverage_report`

See merge request gitlab-org/gitlab!82671
parents b2a22b6e c7c632e4
......@@ -8,6 +8,7 @@ module Gitlab
# Entry that represents a configuration of job artifacts.
#
class Reports < ::Gitlab::Config::Entry::Node
include ::Gitlab::Config::Entry::Configurable
include ::Gitlab::Config::Entry::Validatable
include ::Gitlab::Config::Entry::Attributable
......@@ -15,10 +16,13 @@ module Gitlab
%i[junit codequality sast secret_detection dependency_scanning container_scanning
dast performance browser_performance load_performance license_scanning metrics lsif
dotenv cobertura terraform accessibility cluster_applications
requirements coverage_fuzzing api_fuzzing cluster_image_scanning].freeze
requirements coverage_fuzzing api_fuzzing cluster_image_scanning
coverage_report].freeze
attributes ALLOWED_KEYS
entry :coverage_report, Reports::CoverageReport, description: 'Coverage report configuration.'
validations do
validates :config, type: Hash
validates :config, allowed_keys: ALLOWED_KEYS
......@@ -47,10 +51,18 @@ module Gitlab
validates :cluster_applications, array_of_strings_or_string: true # DEPRECATED: https://gitlab.com/gitlab-org/gitlab/-/issues/333441
validates :requirements, array_of_strings_or_string: true
end
validates :config, mutually_exclusive_keys: [:coverage_report, :cobertura]
end
def value
@config.transform_values { |v| Array(v) }
@config.transform_values do |value|
if value.is_a?(Hash)
value
else
Array(value)
end
end
end
end
end
......
# frozen_string_literal: true
module Gitlab
module Ci
class Config
module Entry
class Reports
class CoverageReport < ::Gitlab::Config::Entry::Node
include ::Gitlab::Config::Entry::Validatable
include ::Gitlab::Config::Entry::Attributable
ALLOWED_KEYS = %i[coverage_format path].freeze
SUPPORTED_COVERAGE = %w[cobertura].freeze
attributes ALLOWED_KEYS
validations do
validates :config, type: Hash
validates :config, allowed_keys: ALLOWED_KEYS
with_options(presence: true) do
validates :coverage_format, inclusion: { in: SUPPORTED_COVERAGE, message: "must be one of supported formats: #{SUPPORTED_COVERAGE.join(', ')}." }
validates :path, type: String
end
end
end
end
end
end
end
end
......@@ -39,6 +39,17 @@ module Gitlab
end
end
class MutuallyExclusiveKeysValidator < ActiveModel::EachValidator
def validate_each(record, attribute, value)
mutually_exclusive_keys = value.try(:keys).to_a & options[:in]
if mutually_exclusive_keys.length > 1
record.errors.add(attribute, "please use only one the following keys: " +
mutually_exclusive_keys.join(', '))
end
end
end
class AllowedValuesValidator < ActiveModel::EachValidator
def validate_each(record, attribute, value)
unless options[:in].include?(value.to_s)
......
# frozen_string_literal: true
require 'fast_spec_helper'
RSpec.describe Gitlab::Ci::Config::Entry::Reports::CoverageReport do
let(:entry) { described_class.new(config) }
describe 'validations' do
context 'when it is valid' do
let(:config) { { coverage_format: 'cobertura', path: 'cobertura-coverage.xml' } }
it { expect(entry).to be_valid }
it { expect(entry.value).to eq(config) }
end
context 'with unsupported coverage format' do
let(:config) { { coverage_format: 'jacoco', path: 'jacoco.xml' } }
it { expect(entry).not_to be_valid }
it { expect(entry.errors).to include /format must be one of supported formats/ }
end
context 'without coverage format' do
let(:config) { { path: 'cobertura-coverage.xml' } }
it { expect(entry).not_to be_valid }
it { expect(entry.errors).to include /format can't be blank/ }
end
context 'without path' do
let(:config) { { coverage_format: 'cobertura' } }
it { expect(entry).not_to be_valid }
it { expect(entry.errors).to include /path can't be blank/ }
end
context 'with invalid path' do
let(:config) { { coverage_format: 'cobertura', path: 123 } }
it { expect(entry).not_to be_valid }
it { expect(entry.errors).to include /path should be a string/ }
end
context 'with unknown keys' do
let(:config) { { coverage_format: 'cobertura', path: 'cobertura-coverage.xml', foo: :bar } }
it { expect(entry).not_to be_valid }
it { expect(entry.errors).to include /contains unknown keys/ }
end
end
end
......@@ -6,12 +6,8 @@ RSpec.describe Gitlab::Ci::Config::Entry::Reports do
let(:entry) { described_class.new(config) }
describe 'validates ALLOWED_KEYS' do
let(:artifact_file_types) { Ci::JobArtifact.file_types }
described_class::ALLOWED_KEYS.each do |keyword, _|
it "expects #{keyword} to be an artifact file_type" do
expect(artifact_file_types).to include(keyword)
end
it "expects ALLOWED_KEYS to be an artifact file_type or coverage_report" do
expect(Ci::JobArtifact.file_types.keys.map(&:to_sym) + [:coverage_report]).to include(*described_class::ALLOWED_KEYS)
end
end
......@@ -68,6 +64,45 @@ RSpec.describe Gitlab::Ci::Config::Entry::Reports do
it_behaves_like 'a valid entry', params[:keyword], params[:file]
end
end
context 'when coverage_report is specified' do
let(:coverage_format) { :cobertura }
let(:filename) { 'cobertura-coverage.xml' }
let(:coverage_report) { { path: filename, coverage_format: coverage_format } }
let(:config) { { coverage_report: coverage_report } }
it 'is valid' do
expect(entry).to be_valid
end
it 'returns artifacts configuration' do
expect(entry.value).to eq(config)
end
context 'and another report is specified' do
let(:config) { { coverage_report: coverage_report, dast: 'gl-dast-report.json' } }
it 'is valid' do
expect(entry).to be_valid
end
it 'returns artifacts configuration' do
expect(entry.value).to eq({ coverage_report: coverage_report, dast: ['gl-dast-report.json'] })
end
end
context 'and a direct coverage report format is specified' do
let(:config) { { coverage_report: coverage_report, cobertura: 'cobertura-coverage.xml' } }
it 'is not valid' do
expect(entry).not_to be_valid
end
it 'reports error' do
expect(entry.errors).to include /please use only one the following keys: coverage_report, cobertura/
end
end
end
end
context 'when entry value is not correct' do
......
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Gitlab::Config::Entry::Validators do
let(:klass) do
Class.new do
include ActiveModel::Validations
include Gitlab::Config::Entry::Validators
end
end
let(:instance) { klass.new }
describe described_class::MutuallyExclusiveKeysValidator do
using RSpec::Parameterized::TableSyntax
before do
klass.instance_eval do
validates :config, mutually_exclusive_keys: [:foo, :bar]
end
allow(instance).to receive(:config).and_return(config)
end
where(:context, :config, :valid_result) do
'with mutually exclusive keys' | { foo: 1, bar: 2 } | false
'without mutually exclusive keys' | { foo: 1 } | true
'without mutually exclusive keys' | { bar: 1 } | true
'with other keys' | { foo: 1, baz: 2 } | true
end
with_them do
it 'validates the instance' do
expect(instance.valid?).to be(valid_result)
unless valid_result
expect(instance.errors.messages_for(:config)).to include /please use only one the following keys: foo, bar/
end
end
end
end
end
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Ci::CreatePipelineService do
let_it_be(:project) { create(:project, :repository) }
let_it_be(:user) { project.first_owner }
let(:ref) { 'refs/heads/master' }
let(:source) { :push }
let(:service) { described_class.new(project, user, { ref: ref }) }
let(:pipeline) { service.execute(source).payload }
describe 'artifacts:' do
before do
stub_ci_pipeline_yaml_file(config)
allow_next_instance_of(Ci::BuildScheduleWorker) do |instance|
allow(instance).to receive(:perform).and_return(true)
end
end
describe 'reports:' do
context 'with valid config' do
let(:config) do
<<~YAML
test-job:
script: "echo 'hello world' > cobertura.xml"
artifacts:
reports:
coverage_report:
coverage_format: 'cobertura'
path: 'cobertura.xml'
dependency-scanning-job:
script: "echo 'hello world' > gl-dependency-scanning-report.json"
artifacts:
reports:
dependency_scanning: 'gl-dependency-scanning-report.json'
YAML
end
it 'creates pipeline with builds' do
expect(pipeline).to be_persisted
expect(pipeline).not_to have_yaml_errors
expect(pipeline.builds.pluck(:name)).to contain_exactly('test-job', 'dependency-scanning-job')
end
end
context 'with invalid config' do
let(:config) do
<<~YAML
test-job:
script: "echo 'hello world' > cobertura.xml"
artifacts:
reports:
foo: 'bar'
YAML
end
it 'creates pipeline with yaml errors' do
expect(pipeline).to be_persisted
expect(pipeline).to have_yaml_errors
end
end
end
end
end
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment