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 @@
}
]
},
"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": {
"$ref": "#/definitions/string_file_list",
"description": "Path to file or list of files with code quality report(s) (such as Code Climate)."
......
......@@ -64,35 +64,50 @@ module Ci
def create_archive(artifacts)
return unless artifacts[:untracked] || artifacts[:paths]
archive = {
artifact_type: :archive,
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
BuildArtifact.for_archive(artifacts).to_h.tap do |artifact|
artifact.delete(:exclude) unless artifact[:exclude].present?
end
end
def create_reports(reports, expire_in:)
return unless reports&.any?
reports.map do |report_type, report_paths|
{
artifact_type: report_type.to_sym,
artifact_format: ::Ci::JobArtifact::TYPE_AND_FORMAT_PAIRS.fetch(report_type.to_sym),
name: ::Ci::JobArtifact::DEFAULT_FILE_NAMES.fetch(report_type.to_sym),
paths: report_paths,
reports.map { |report| BuildArtifact.for_report(report, expire_in).to_h.compact }
end
BuildArtifact = Struct.new(:name, :untracked, :paths, :exclude, :when, :expire_in, :artifact_type, :artifact_format, keyword_init: true) do
def self.for_archive(artifacts)
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',
expire_in: expire_in
}
)
end
end
......
......@@ -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 [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 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
Cobertura was originally developed for Java, but there are many third-party ports for other languages such as
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`
> [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.
For the coverage analysis to work, you have to provide a properly formatted
[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
for other languages have plugins to add support for it, like:
......
......@@ -6,7 +6,7 @@ module API
module JobRequest
class Artifacts < Grape::Entity
expose :name
expose :untracked
expose :untracked, expose_nil: false
expose :paths
expose :exclude, expose_nil: false
expose :when
......
......@@ -58,6 +58,16 @@ module QA
artifacts:
paths:
- 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
}
]
......@@ -71,7 +81,8 @@ module QA
'test-success': 'passed',
'test-failure': 'failed',
'test-tags-mismatch': 'pending',
'test-artifacts': 'passed'
'test-artifacts': 'passed',
'test-coverage-report': 'passed'
}.each do |job, status|
Page::Project::Pipeline::Show.perform do |pipeline|
pipeline.click_job(job)
......
......@@ -497,6 +497,22 @@ FactoryBot.define do
options { {} }
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
# https://gitlab.com/gitlab-org/gitlab/-/issues/210486
trait :dast do
......
# frozen_string_literal: true
require 'fast_spec_helper'
require 'spec_helper'
RSpec.describe Gitlab::Ci::Config::Entry::Reports::CoverageReport do
let(:entry) { described_class.new(config) }
......
......@@ -78,16 +78,72 @@ RSpec.describe Ci::BuildRunnerPresenter do
artifact_format: Ci::JobArtifact::TYPE_AND_FORMAT_PAIRS.fetch(file_type),
paths: [filename],
when: 'always'
}
}.compact
end
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
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
let(:report) { { junit: ['junit.xml'] } }
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
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
let(:expected_variables) do
[{ '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