# coverage

* [coverage](#coverage)
  * [cobertura-plugin](#cobertura-plugin)
    * [libs](#libs)
    * [Jenkinsfile](#jenkinsfile)
    * [tips](#tips)
  * [coverage-plugin](#coverage-plugin)
    * [sample](#sample)
    * [libs](#libs-1)
    * [Jenkinsfile](#jenkinsfile-1)
* [deprecated](#deprecated)

## coverage

### cobertura-plugin

> \[!NOTE|label:references:]
>
> * [steps: Cobertura Plugin](https://www.jenkins.io/doc/pipeline/steps/cobertura/)
> * [#89 When coverage goes down threshold is updated and the build is not marked as unstable](https://github.com/jenkinsci/cobertura-plugin/issues/89#issuecomment-453506691)
> * [Jenkins Cobertura Plugin - reset Ratcheting / autoUpdateHealth values](https://stackoverflow.com/a/66161051/2940319) | [#124 Cannot reset ratcheted targets](https://github.com/jenkinsci/cobertura-plugin/issues/124)
> * [src/main/java/hudson/plugins/cobertura/CoberturaPublisher.java](https://github.com/jenkinsci/cobertura-plugin/blob/master/src/main/java/hudson/plugins/cobertura/CoberturaPublisher.java#L213)
>   * `PACKAGES`
>   * `FILES`
>   * `CLASSES`
>   * `CONDITIONAL`
>   * `LINE`
>   * `METHOD`
> * [Jenkins Pipeline With Python](https://skamalakannan.dev/posts/jenkins-pipeline-python/)
> * [Static Code Analysis Using SonarQube and Jenkins](https://www.opensourceforu.com/2021/08/static-code-analysis-using-sonarqube-and-jenkins/)

#### libs

```groovy
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

```groovy
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](https://stackoverflow.com/a/44024599/2940319)

    ```groovy
    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](https://github.com/jenkinsci/cobertura-plugin/issues/89)

    ```groovy
    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](https://github.com/jenkins-infra/pipeline-library/blob/master/vars/buildPlugin.groovy#L147C19-L166C18)

    ```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:]
    >
    > * [Jenkins Cobertura Plugin - reset Ratcheting / autoUpdateHealth values](https://stackoverflow.com/q/65133756/2940319)
    > * [#89 When coverage goes down threshold is updated and the build is not marked as unstable](https://github.com/jenkinsci/cobertura-plugin/issues/89#issuecomment-453506691)
    > * [#124 Cannot reset ratcheted targets](https://github.com/jenkinsci/cobertura-plugin/issues/124)

    ```groovy
    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] }
        ```

        [![cobertura decrease unstable](https://1999659500-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-MHqOSW6on5cHbtwcSH6%2Fuploads%2Fgit-blob-23b57464cf54c208fd342fdc4b845ab11774e050%2Fjenkins-cobertura-decrease-unstable.png?alt=media)](https://github.com/marslo/ibook/blob/marslo/docs/screenshot/jenkins/jenkins-cobertura-decrease-unstable.png)

### [coverage-plugin](https://github.com/jenkinsci/coverage-plugin)

> \[!NOTE|label:references:]
>
> * [Steps: Coverage Plugin](https://www.jenkins.io/doc/pipeline/steps/coverage/)
> * [jenkinsci/coverage-model](https://github.com/jenkinsci/coverage-model)
>   * [Index of incrementals/edu/hm/hafner](https://repo.jenkins-ci.org/incrementals/edu/hm/hafner/)
>   * [`public enum Metric`: src/main/java/edu/hm/hafner/coverage/Metric.java](https://github.com/jenkinsci/coverage-model/blob/main/src/main/java/edu/hm/hafner/coverage/Metric.java) | [qualityGates](https://www.jenkins.io/doc/pipeline/steps/coverage/)
>     * nodes that can have children
>       * `CONTAINER`
>       * `MODULE`
>       * `PACKAGE`
>       * `FILE`
>       * `CLASS`
>       * `METHOD`
>     * coverage values without children
>       * `LINE`
>       * `BRANCH`
>       * `INSTRUCTION`
>     * additional metrics without children
>       * `MUTATION`
>       * `TEST_STRENGTH`
>       * `COMPLEXITY`
>       * `COMPLEXITY_MAXIMUM`
>       * `COMPLEXITY_DENSITY`
>       * `LOC`
>       * `TESTS`
>   * [`src/main/java/edu/hm/hafner/coverage/registry/ParserRegistry.java`](https://github.com/jenkinsci/coverage-model/blob/main/src/main/java/edu/hm/hafner/coverage/registry/ParserRegistry.java)
>     * parser type:
>       * `COBERTURA`
>       * `JACOCO`
>       * `JUNIT`
>       * `PIT`
>       * `NUNIT`
>       * `OPENCOVER`
>       * `JUNIT`
>       * `XUNIT`
> * [sample workflow](https://github.com/jenkinsci/coverage-model/actions/runs/7781672728/workflow)

#### sample

* [ci.jenkins.io: Core » jenkins » master #6073](https://ci.jenkins.io/job/Core/job/jenkins/job/master/6073/coverage/) | [#9194 Format 'admin' differently in the setup wizard for clarity](https://github.com/jenkinsci/jenkins/pull/9194) | [checkers](https://github.com/jenkinsci/jenkins/pull/9194/checks?check_run_id=24275010829)
* [app.codecov.io/gh/jenkinsci/coverage-plugin](https://app.codecov.io/gh/jenkinsci/coverage-plugin?search=\&trend=12%20months)

#### libs

```groovy

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:]
>
> * [EXPLAIN/Prototype feedback-component/Jenkinsfile](https://www.uni-hildesheim.de/gitlab/explain/prototyp-feedbackcomponent/-/blob/main/Jenkinsfile?ref_type=heads#L87)
> * [jenkins-jenkins-2.455/Jenkinsfile](https://fossies.org/linux/jenkins/Jenkinsfile)

*   [jenkinsci/jenkins/Jenkinsfile](https://github.com/jenkinsci/jenkins/blob/master/Jenkinsfile)

    ```groovy
    recordCoverage(tools: [[parser: 'JACOCO', pattern: 'coverage/target/site/jacoco-aggregate/jacoco.xml']],
    sourceCodeRetention: 'MODIFIED', sourceDirectories: [[path: 'core/src/main/java']])
    ```
*   [prototyp-feedbackcomponent/Jenkinsfile](https://www.uni-hildesheim.de/gitlab/explain/prototyp-feedbackcomponent/-/blob/main/Jenkinsfile?ref_type=heads#L87)

    ```groovy
    // 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:]
>
> * [Steps: Code Coverage Plugin](https://www.jenkins.io/doc/pipeline/steps/code-coverage-api/#code-coverage-plugin)
> * [failBuildIfCoverageDecreasedInChangeRequest](https://github.com/jenkinsci/code-coverage-api-plugin/pull/372)

*   [Code Coverage](https://plugins.jenkins.io/code-coverage-api/)

    ```groovy
    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](https://github.com/jenkinsci/code-coverage-api-plugin/issues/657)

        ```groovy
        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](https://stackoverflow.com/a/76436571/2940319)

        ```groovy
        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
        )
        ```
