coverage
cobertura-plugin
[!NOTE|label:references:]
libs
def showCoverageReport( String xmlPath, Map targets = [:], Boolean failNoReports = true ) {
Map<String, String> benchmarks = [
conditional : '70, 0, 0' ,
line : '80, 0, 0' ,
method : '80, 0, 0' ,
branch : '60, 0, 0'
]
benchmarks = benchmarks << targets
def file = findFiles( glob: xmlPath )
if ( file.size() ) {
String report = file.first().path
println "coverage report file found in: ${report}"
cobertura coberturaReportFile: report ,
conditionalCoverageTargets: benchmarks.conditional ,
lineCoverageTargets: benchmarks.line ,
methodCoverageTargets: benchmarks.method ,
failNoReports: failNoReports ,
failUnhealthy: false,
failUnstable: false,
onlyStable: false,
autoUpdateStability: true,
autoUpdateHealth: false,
enableNewApi: false
} else {
error "Could not find cobertura xml report in pattern: ${xmlPath}"
}
}
Jenkinsfile
recordCoverage qualityGates: [ [criticality: 'NOTE' , integerThreshold: 30 , metric: 'MODULE' , threshold: 30.0] , [criticality: 'NOTE' , integerThreshold: 30 , metric: 'PACKAGE' , threshold: 30.0] , [criticality: 'NOTE' , integerThreshold: 30 , metric: 'FILE' , threshold: 30.0] , [criticality: 'NOTE' , integerThreshold: 30 , metric: 'CLASS' , threshold: 30.0] , [criticality: 'NOTE' , integerThreshold: 30 , metric: 'METHOD' , threshold: 30.0] , [criticality: 'NOTE' , integerThreshold: 30 , metric: 'LINE' , threshold: 30.0] , [criticality: 'NOTE' , integerThreshold: 30 , metric: 'BRANCH' , threshold: 30.0] , [criticality: 'NOTE' , integerThreshold: 30 , metric: 'INSTRUCTION' , threshold: 30.0] , [criticality: 'NOTE' , integerThreshold: 30 , metric: 'MUTATION' , threshold: 30.0] , [criticality: 'NOTE' , integerThreshold: 30 , metric: 'TEST_STRENGTH' , threshold: 30.0] , [criticality: 'NOTE' , integerThreshold: 30 , metric: 'COMPLEXITY' , threshold: 30.0] , [criticality: 'NOTE' , integerThreshold: 30 , metric: 'COMPLEXITY_MAXIMUM' , threshold: 30.0] , [criticality: 'NOTE' , integerThreshold: 30 , metric: 'LOC' , threshold: 30.0] , [criticality: 'NOTE' , integerThreshold: 30 , metric: 'TESTS' , threshold: 30.0] ], tools: [[parser: 'COBERTURA', pattern: '*.xml']]
recordCoverage checksAnnotationScope: 'ALL_LINES', enabledForFailure: true, ignoreParsingErrors: true, qualityGates: [ [baseline: 'MODIFIED_LINES', criticality: 'NOTE', metric: 'LINE', threshold: 0.001] ], skipSymbolicLinks: true, sourceCodeRetention: 'EVERY_BUILD', sourceDirectories: [[path: '../Src']], tools: [[parser: 'COBERTURA', pattern: 'a.xml']]
Cobertura code coverage report for jenkins pipeline jobs
step([
$class: 'CoberturaPublisher',
autoUpdateHealth: false,
autoUpdateStability: false,
coberturaReportFile: '**/coverage.xml',
failUnhealthy: false,
failUnstable: false,
maxNumberOfBuilds: 0,
onlyStable: false,
sourceEncoding: 'ASCII',
zoomCoverageChart: false
])
When coverage goes down threshold is updated and the build is not marked as unstable
node('my-node') {
[...]
stage('Publish coverage report') {
archive "coverage.xml"
cobertura(
coberturaReportFile: "coverage.xml",
onlyStable: false,
failNoReports: true,
failUnhealthy: false,
failUnstable: false,
autoUpdateHealth: true,
autoUpdateStability: true,
zoomCoverageChart: true,
maxNumberOfBuilds: 0,
lineCoverageTargets: '80, 80, 80',
conditionalCoverageTargets: '80, 80, 80',
classCoverageTargets: '80, 80, 80',
fileCoverageTargets: '80, 80, 80',
)
}
}
pipeline-library/vars/buildPlugin.groovy
if (!skipTests) {
junit('**/target/surefire-reports/**/*.xml,**/target/failsafe-reports/**/*.xml,**/target/invoker-reports/**/*.xml')
if (first) {
discoverReferenceBuild()
// Default configuration for JaCoCo can be overwritten using a `jacoco` parameter (map).
// Configuration see: https://www.jenkins.io/doc/pipeline/steps/code-coverage-api/#recordcoverage-record-code-coverage-results
Map jacocoArguments = [tools: [[parser: 'JACOCO', pattern: '**/jacoco/jacoco.xml']], sourceCodeRetention: 'MODIFIED']
if (params?.jacoco) {
jacocoArguments.putAll(params.jacoco as Map)
}
recordCoverage jacocoArguments
if (pit) {
Map pitArguments = [tools: [[parser: 'PIT', pattern: '**/pit-reports/mutations.xml']], id: 'pit', name: 'Mutation Coverage', sourceCodeRetention: 'MODIFIED']
pitArguments.putAll(pit)
pitArguments.remove('skip')
recordCoverage(pitArguments)
}
}
}
}
tips
unstable build when thresholds decreased
[!NOTE|label:references:]
cobertura(
...
autoUpdateStability: true,
autoUpdateHealth: true,
)
result
[Pipeline] cobertura
22:37:43 [Cobertura] Publishing Cobertura coverage report...
22:37:43
22:37:44 [Cobertura] Publishing Cobertura coverage results...
22:37:44
22:37:44 [Cobertura] Cobertura coverage report found.
22:37:44
22:37:45 [Cobertura] Code coverage enforcement failed for the following metrics:
22:37:45
22:37:45 [Cobertura] Conditionals's stability is 41.83 and set mininum stability is 41.85.
22:37:45
22:37:45 [Cobertura] Setting Build to unstable.
22:37:45
[Pipeline] }
[!NOTE|label:references:]
sample
libs
def showCoverageReport( String xmlPath, String sourcePath = '**/src', Map targets = [:] ) {
Map<String, String> benchmarks = [
conditional : '70, 0, 0' ,
line : '80, 0, 0' ,
method : '80, 0, 0' ,
branch : '60, 0, 0'
]
benchmarks = benchmarks << targets
def file = findFiles( glob: xmlPath )
if ( file.size() ) {
String report = file.first().path
println "coverage report file found: ${report}"
discoverReferenceBuild()
recordCoverage( name: 'Cobertura Coverage',
id: 'coverage',
tools: [[ parser: 'COBERTURA', pattern: xmlPath ]],
sourceDirectories: [[ path: sourcePath ]],
ignoreParsingErrors: true,
skipSymbolicLinks: false,
calculateDiffForChangeRequests: true,
failBuildIfCoverageDecreasedInChangeRequest: true,
sourceCodeRetention: 'EVERY_BUILD',
checksAnnotationScope: 'ALL_LINES',
qualityGates: [
[ threshold: benchmarks.line.split(',').first() , metric: 'LINE' , baseline: 'PROJECT' , criticality: 'UNSTABLE' ] ,
[ threshold: 0.01 , metric: 'LINE' , baseline: 'MODIFIED_LINES' , criticality: 'UNSTABLE' ] ,
[ threshold: benchmarks.branch.split(',').first() , metric: 'BRANCH' , baseline: 'PROJECT' , criticality: 'UNSTABLE' ]
]
)
} else {
error( "Could not find cobertura xml report in pattern: ${xmlPath}" )
}
}
Jenkinsfile
[!NOTE|label:references:]
jenkinsci/jenkins/Jenkinsfile
recordCoverage(tools: [[parser: 'JACOCO', pattern: 'coverage/target/site/jacoco-aggregate/jacoco.xml']],
sourceCodeRetention: 'MODIFIED', sourceDirectories: [[path: 'core/src/main/java']])
prototyp-feedbackcomponent/Jenkinsfile
// New Coverage Tool: Cobertura + Coverage Plugin
recordCoverage qualityGates: [[metric: 'LINE', threshold: 1.0], [metric: 'BRANCH', threshold: 1.0]], tools: [[parser: 'COBERTURA', pattern: 'src/output/test/coverage/cobertura-coverage.xml']]
deprecated
[!NOTE|label:references:]
Code Coverage
def publishCoverage( String xmlPath ) {
def file = findFiles( glob: xmlPath )
if ( file.size() ) {
String report = file.first().path
color.info( "coverage report file: ${report}" )
archiveArtifacts artifacts: report
publishCoverage adapters: [
cobertura( path: xmlPath, thresholds: [[ thresholdTarget: 'Conditional', unstableThreshold: 30.0 ]] )
],
applyThresholdRecursively: true,
failBuildIfCoverageDecreasedInChangeRequest: false,
failNoReports: false,
failUnhealthy: false,
failUnstable: false,
skipPublishingChecks: false,
globalThresholds: [[ thresholdTarget: 'Conditional', unstableThreshold: 30.0 ]],
sourceDirectories: [[ path: '../Src' ]],
sourceFileResolver: sourceFiles('STORE_ALL_BUILD')
} else {
error( "Could not find cobertura xml report in pattern ${xmlPath}" )
}
}
#657 Stored source code files should use a unique path
publishCoverage(
adapters: [coberturaReportAdapter(mergeToOneReport: true, path: '**/*cobertura*.xml')],
calculateDiffForChangeRequests: true,
failNoReports: true,
globalThresholds: [[thresholdTarget: 'Line', unstableThreshold: 89.0]]
)
jenkins declerative pipeline - fail build when coverage drops
def coverage = [
'applyThresholdRecursively':true,
'failBuildIfCoverageDecreasedInChangeRequest':true,
/* ... etc ... */
]
coverage.globalThresholds = [[
failUnhealthy: false,
thresholdTarget: 'File',
unhealthyThreshold: 1.0,
unstableThreshold: 0.0
]] //use your own values here
def coverageFilePath = 'path-to-your-coverage-file'
publishCoverage(
adapters: [ coberturaAdapter(mergeToOneReport: true, path: coverageFilePath) ],
applyThresholdRecursively: coverage.applyThresholdRecursively,
failBuildIfCoverageDecreasedInChangeRequest: coverage.failBuildIfCoverageDecreasedInChangeRequest,
failNoReports: coverage.failNoReports,
failUnhealthy: coverage.failUnhealthy,
failUnstable: coverage.failUnstable,
globalThresholds: coverage.globalThresholds
)
Last updated