Commit 56943486 authored by Albert Salim's avatar Albert Salim Committed by Marius Bobin

Include code_coverage in presented build for runner

Changelog: added
parent 6fe7f25b
...@@ -187,6 +187,21 @@ ...@@ -187,6 +187,21 @@
} }
] ]
}, },
"coverage_report": {
"type": "object",
"description": "Used to collect coverage reports from the job.",
"properties": {
"coverage_format": {
"description": "Code coverage format used by the test framework.",
"enum": ["cobertura"]
},
"path": {
"description": "Path to the coverage report file that should be parsed.",
"type": "string",
"minLength": 1
}
}
},
"codequality": { "codequality": {
"$ref": "#/definitions/string_file_list", "$ref": "#/definitions/string_file_list",
"description": "Path to file or list of files with code quality report(s) (such as Code Climate)." "description": "Path to file or list of files with code quality report(s) (such as Code Climate)."
......
...@@ -64,35 +64,50 @@ module Ci ...@@ -64,35 +64,50 @@ module Ci
def create_archive(artifacts) def create_archive(artifacts)
return unless artifacts[:untracked] || artifacts[:paths] return unless artifacts[:untracked] || artifacts[:paths]
archive = { BuildArtifact.for_archive(artifacts).to_h.tap do |artifact|
artifact_type: :archive, artifact.delete(:exclude) unless artifact[:exclude].present?
artifact_format: :zip,
name: artifacts[:name],
untracked: artifacts[:untracked],
paths: artifacts[:paths],
when: artifacts[:when],
expire_in: artifacts[:expire_in]
}
if artifacts.dig(:exclude).present?
archive.merge(exclude: artifacts[:exclude])
else
archive
end end
end end
def create_reports(reports, expire_in:) def create_reports(reports, expire_in:)
return unless reports&.any? return unless reports&.any?
reports.map do |report_type, report_paths| reports.map { |report| BuildArtifact.for_report(report, expire_in).to_h.compact }
{ end
artifact_type: report_type.to_sym,
artifact_format: ::Ci::JobArtifact::TYPE_AND_FORMAT_PAIRS.fetch(report_type.to_sym), BuildArtifact = Struct.new(:name, :untracked, :paths, :exclude, :when, :expire_in, :artifact_type, :artifact_format, keyword_init: true) do
name: ::Ci::JobArtifact::DEFAULT_FILE_NAMES.fetch(report_type.to_sym), def self.for_archive(artifacts)
paths: report_paths, self.new(
artifact_type: :archive,
artifact_format: :zip,
name: artifacts[:name],
untracked: artifacts[:untracked],
paths: artifacts[:paths],
when: artifacts[:when],
expire_in: artifacts[:expire_in],
exclude: artifacts[:exclude]
)
end
def self.for_report(report, expire_in)
type, params = report
if type == :coverage_report
artifact_type = params[:coverage_format].to_sym
paths = [params[:path]]
else
artifact_type = type
paths = params
end
self.new(
artifact_type: artifact_type,
artifact_format: ::Ci::JobArtifact::TYPE_AND_FORMAT_PAIRS.fetch(artifact_type),
name: ::Ci::JobArtifact::DEFAULT_FILE_NAMES.fetch(artifact_type),
paths: paths,
when: 'always', when: 'always',
expire_in: expire_in expire_in: expire_in
} )
end end
end end
......
...@@ -80,9 +80,14 @@ GitLab can display the results of one or more reports in: ...@@ -80,9 +80,14 @@ GitLab can display the results of one or more reports in:
- The [security dashboard](../../user/application_security/security_dashboard/index.md). - The [security dashboard](../../user/application_security/security_dashboard/index.md).
- The [Project Vulnerability report](../../user/application_security/vulnerability_report/index.md). - The [Project Vulnerability report](../../user/application_security/vulnerability_report/index.md).
## `artifacts:reports:cobertura` ## `artifacts:reports:cobertura` (DEPRECATED)
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/3708) in GitLab 12.9. > - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/3708) in GitLab 12.9.
> - [Deprecated](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/78132) in GitLab 14.9.
WARNING:
This feature is in its end-of-life process. It is [deprecated](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/78132) for use in GitLab
14.8 and replaced with `artifacts:reports:coverage_report`.
The `cobertura` report collects [Cobertura coverage XML files](../../user/project/merge_requests/test_coverage_visualization.md). The `cobertura` report collects [Cobertura coverage XML files](../../user/project/merge_requests/test_coverage_visualization.md).
The collected Cobertura coverage reports upload to GitLab as an artifact. The collected Cobertura coverage reports upload to GitLab as an artifact.
...@@ -93,6 +98,28 @@ GitLab can display the results of one or more reports in the merge request ...@@ -93,6 +98,28 @@ GitLab can display the results of one or more reports in the merge request
Cobertura was originally developed for Java, but there are many third-party ports for other languages such as Cobertura was originally developed for Java, but there are many third-party ports for other languages such as
JavaScript, Python, and Ruby. JavaScript, Python, and Ruby.
## `artifacts:reports:coverage_report`
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/344533) in GitLab 14.9.
Use `coverage_report` to collect coverage report in Cobertura format, similar to `artifacts:reports:cobertura`.
NOTE:
`artifacts:reports:coverage_report` cannot be used at the same time with `artifacts:reports:cobertura`.
```yaml
artifacts:
reports:
coverage_report:
coverage_format: cobertura
path: coverage/cobertura-coverage.xml
```
The collected coverage report is uploaded to GitLab as an artifact.
GitLab can display the results of coverage report in the merge request
[diff annotations](../../user/project/merge_requests/test_coverage_visualization.md).
## `artifacts:reports:codequality` ## `artifacts:reports:codequality`
> [Moved](https://gitlab.com/gitlab-org/gitlab/-/issues/212499) to GitLab Free in 13.2. > [Moved](https://gitlab.com/gitlab-org/gitlab/-/issues/212499) to GitLab Free in 13.2.
......
...@@ -28,7 +28,7 @@ between pipeline completion and the visualization loading on the page. ...@@ -28,7 +28,7 @@ between pipeline completion and the visualization loading on the page.
For the coverage analysis to work, you have to provide a properly formatted For the coverage analysis to work, you have to provide a properly formatted
[Cobertura XML](https://cobertura.github.io/cobertura/) report to [Cobertura XML](https://cobertura.github.io/cobertura/) report to
[`artifacts:reports:cobertura`](../../../ci/yaml/artifacts_reports.md#artifactsreportscobertura). [`artifacts:reports:cobertura`](../../../ci/yaml/artifacts_reports.md#artifactsreportscobertura-deprecated).
This format was originally developed for Java, but most coverage analysis frameworks This format was originally developed for Java, but most coverage analysis frameworks
for other languages have plugins to add support for it, like: for other languages have plugins to add support for it, like:
......
...@@ -6,7 +6,7 @@ module API ...@@ -6,7 +6,7 @@ module API
module JobRequest module JobRequest
class Artifacts < Grape::Entity class Artifacts < Grape::Entity
expose :name expose :name
expose :untracked expose :untracked, expose_nil: false
expose :paths expose :paths
expose :exclude, expose_nil: false expose :exclude, expose_nil: false
expose :when expose :when
......
...@@ -58,6 +58,16 @@ module QA ...@@ -58,6 +58,16 @@ module QA
artifacts: artifacts:
paths: paths:
- my-artifacts/ - my-artifacts/
test-coverage-report:
tags:
- #{executor}
script: mkdir coverage; echo "CONTENTS" > coverage/cobertura.xml
artifacts:
reports:
coverage_report:
coverage_format: cobertura
path: coverage/cobertura.xml
YAML YAML
} }
] ]
...@@ -71,7 +81,8 @@ module QA ...@@ -71,7 +81,8 @@ module QA
'test-success': 'passed', 'test-success': 'passed',
'test-failure': 'failed', 'test-failure': 'failed',
'test-tags-mismatch': 'pending', 'test-tags-mismatch': 'pending',
'test-artifacts': 'passed' 'test-artifacts': 'passed',
'test-coverage-report': 'passed'
}.each do |job, status| }.each do |job, status|
Page::Project::Pipeline::Show.perform do |pipeline| Page::Project::Pipeline::Show.perform do |pipeline|
pipeline.click_job(job) pipeline.click_job(job)
......
...@@ -497,6 +497,22 @@ FactoryBot.define do ...@@ -497,6 +497,22 @@ FactoryBot.define do
options { {} } options { {} }
end end
trait :coverage_report_cobertura do
options do
{
artifacts: {
expire_in: '7d',
reports: {
coverage_report: {
coverage_format: 'cobertura',
path: 'cobertura.xml'
}
}
}
}
end
end
# TODO: move Security traits to ee_ci_build # TODO: move Security traits to ee_ci_build
# https://gitlab.com/gitlab-org/gitlab/-/issues/210486 # https://gitlab.com/gitlab-org/gitlab/-/issues/210486
trait :dast do trait :dast do
......
# frozen_string_literal: true # frozen_string_literal: true
require 'fast_spec_helper' require 'spec_helper'
RSpec.describe Gitlab::Ci::Config::Entry::Reports::CoverageReport do RSpec.describe Gitlab::Ci::Config::Entry::Reports::CoverageReport do
let(:entry) { described_class.new(config) } let(:entry) { described_class.new(config) }
......
...@@ -78,16 +78,72 @@ RSpec.describe Ci::BuildRunnerPresenter do ...@@ -78,16 +78,72 @@ RSpec.describe Ci::BuildRunnerPresenter do
artifact_format: Ci::JobArtifact::TYPE_AND_FORMAT_PAIRS.fetch(file_type), artifact_format: Ci::JobArtifact::TYPE_AND_FORMAT_PAIRS.fetch(file_type),
paths: [filename], paths: [filename],
when: 'always' when: 'always'
} }.compact
end end
it 'presents correct hash' do it 'presents correct hash' do
expect(presenter.artifacts.first).to include(report_expectation) expect(presenter.artifacts).to contain_exactly(report_expectation)
end end
end end
end end
end end
context 'when a specific coverage_report type is given' do
let(:coverage_format) { :cobertura }
let(:filename) { 'cobertura-coverage.xml' }
let(:coverage_report) { { path: filename, coverage_format: coverage_format } }
let(:report) { { coverage_report: coverage_report } }
let(:build) { create(:ci_build, options: { artifacts: { reports: report } }) }
let(:expected_coverage_report) do
{
name: filename,
artifact_type: coverage_format,
artifact_format: Ci::JobArtifact::TYPE_AND_FORMAT_PAIRS.fetch(coverage_format),
paths: [filename],
when: 'always'
}
end
it 'presents the coverage report hash with the coverage format' do
expect(presenter.artifacts).to contain_exactly(expected_coverage_report)
end
end
context 'when a specific coverage_report type is given with another report type' do
let(:coverage_format) { :cobertura }
let(:coverage_filename) { 'cobertura-coverage.xml' }
let(:coverage_report) { { path: coverage_filename, coverage_format: coverage_format } }
let(:ds_filename) { 'gl-dependency-scanning-report.json' }
let(:report) { { coverage_report: coverage_report, dependency_scanning: [ds_filename] } }
let(:build) { create(:ci_build, options: { artifacts: { reports: report } }) }
let(:expected_coverage_report) do
{
name: coverage_filename,
artifact_type: coverage_format,
artifact_format: Ci::JobArtifact::TYPE_AND_FORMAT_PAIRS.fetch(coverage_format),
paths: [coverage_filename],
when: 'always'
}
end
let(:expected_ds_report) do
{
name: ds_filename,
artifact_type: :dependency_scanning,
artifact_format: Ci::JobArtifact::TYPE_AND_FORMAT_PAIRS.fetch(:dependency_scanning),
paths: [ds_filename],
when: 'always'
}
end
it 'presents both reports' do
expect(presenter.artifacts).to contain_exactly(expected_coverage_report, expected_ds_report)
end
end
context "when option has both archive and reports specification" do context "when option has both archive and reports specification" do
let(:report) { { junit: ['junit.xml'] } } let(:report) { { junit: ['junit.xml'] } }
let(:build) { create(:ci_build, options: { script: 'echo', artifacts: { **archive, reports: report } }) } let(:build) { create(:ci_build, options: { script: 'echo', artifacts: { **archive, reports: report } }) }
......
...@@ -611,6 +611,40 @@ RSpec.describe API::Ci::Runner, :clean_gitlab_redis_shared_state do ...@@ -611,6 +611,40 @@ RSpec.describe API::Ci::Runner, :clean_gitlab_redis_shared_state do
end end
end end
context 'when job has code coverage report' do
let(:job) do
create(:ci_build, :pending, :queued, :coverage_report_cobertura,
pipeline: pipeline, name: 'spinach', stage: 'test', stage_idx: 0)
end
let(:expected_artifacts) do
[
{
'name' => 'cobertura-coverage.xml',
'paths' => ['cobertura.xml'],
'when' => 'always',
'expire_in' => '7d',
"artifact_type" => "cobertura",
"artifact_format" => "gzip"
}
]
end
it 'returns job with the correct artifact specification', :aggregate_failures do
request_job info: { platform: :darwin, features: { upload_multiple_artifacts: true } }
expect(response).to have_gitlab_http_status(:created)
expect(response.headers['Content-Type']).to eq('application/json')
expect(response.headers).not_to have_key('X-GitLab-Last-Update')
expect(runner.reload.platform).to eq('darwin')
expect(json_response['id']).to eq(job.id)
expect(json_response['token']).to eq(job.token)
expect(json_response['job_info']).to eq(expected_job_info)
expect(json_response['git_info']).to eq(expected_git_info)
expect(json_response['artifacts']).to eq(expected_artifacts)
end
end
context 'when triggered job is available' do context 'when triggered job is available' do
let(:expected_variables) do let(:expected_variables) do
[{ 'key' => 'CI_JOB_NAME', 'value' => 'spinach', 'public' => true, 'masked' => false }, [{ 'key' => 'CI_JOB_NAME', 'value' => 'spinach', 'public' => true, 'masked' => false },
......
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