├── src ├── main │ ├── resources │ │ ├── coverage │ │ │ ├── taglib │ │ │ ├── coverage-table.properties │ │ │ ├── configuration.properties │ │ │ ├── configuration.jelly │ │ │ └── deprecated-coverage-table.jelly │ │ ├── io │ │ │ └── jenkins │ │ │ │ └── plugins │ │ │ │ └── coverage │ │ │ │ ├── detector │ │ │ │ ├── Messages.properties │ │ │ │ └── AntPathReportDetector │ │ │ │ │ └── config.jelly │ │ │ │ ├── CoveragePublisher │ │ │ │ ├── help-failNoReports.html │ │ │ │ ├── help-failUnstable.html │ │ │ │ ├── help-failUnhealthy.html │ │ │ │ ├── help-calculateDiffForChangeRequests.html │ │ │ │ ├── help-failBuildIfCoverageDecreasedInChangeRequest.html │ │ │ │ ├── help-checksName.html │ │ │ │ ├── help-skipPublishingChecks.html │ │ │ │ ├── help-applyThresholdRecursively.html │ │ │ │ ├── config.properties │ │ │ │ ├── help-sourceCodeEncoding.html │ │ │ │ ├── help-sourceDirectories.html │ │ │ │ └── config.jelly │ │ │ │ ├── adapter │ │ │ │ ├── Messages.properties │ │ │ │ ├── CoverageReportAdapterDescriptor │ │ │ │ │ └── config.jelly │ │ │ │ ├── istanbul-cobertura-to-standard.xsl │ │ │ │ └── cobertura-to-standard.xsl │ │ │ │ ├── model │ │ │ │ ├── visualization │ │ │ │ │ └── dashboard │ │ │ │ │ │ └── CoverageColumn │ │ │ │ │ │ ├── columnHeader.jelly │ │ │ │ │ │ ├── config.properties │ │ │ │ │ │ ├── config.jelly │ │ │ │ │ │ └── column.jelly │ │ │ │ ├── CoverageJobAction │ │ │ │ │ └── floatingBox.jelly │ │ │ │ ├── SourceViewModel │ │ │ │ │ ├── index.properties │ │ │ │ │ └── index.jelly │ │ │ │ ├── CoverageViewModel │ │ │ │ │ └── index.properties │ │ │ │ └── Messages.properties │ │ │ │ ├── source │ │ │ │ ├── Messages.properties │ │ │ │ ├── SourceFileResolver │ │ │ │ │ └── config.jelly │ │ │ │ └── Messages_zh_CN.properties │ │ │ │ ├── targets │ │ │ │ └── CoverageResult │ │ │ │ │ └── index.properties │ │ │ │ ├── CoverageColumn │ │ │ │ └── column.jelly │ │ │ │ ├── Messages_zh_CN.properties │ │ │ │ ├── Messages.properties │ │ │ │ ├── CoveragePullRequestMonitoringPortlet │ │ │ │ └── monitor.jelly │ │ │ │ ├── CoverageProjectAction │ │ │ │ ├── nodata.jelly │ │ │ │ └── floatingBox.jelly │ │ │ │ ├── CoverageAction │ │ │ │ └── summary.jelly │ │ │ │ └── threshold │ │ │ │ └── Threshold │ │ │ │ └── config.jelly │ │ ├── index.jelly │ │ └── META-INF │ │ │ └── hudson.remoting.ClassFilter │ ├── webapp │ │ ├── images │ │ │ └── tab.png │ │ ├── js │ │ │ └── colors.js │ │ └── css │ │ │ └── style.css │ └── java │ │ └── io │ │ └── jenkins │ │ └── plugins │ │ └── coverage │ │ ├── CoverageFeatureConstants.java │ │ ├── model │ │ ├── visualization │ │ │ ├── package-info.java │ │ │ ├── dashboard │ │ │ │ ├── package-info.java │ │ │ │ ├── ProjectCoverage.java │ │ │ │ ├── ProjectCoverageDelta.java │ │ │ │ ├── ChangeCoverage.java │ │ │ │ ├── ChangeCoverageDelta.java │ │ │ │ └── IndirectCoverageChanges.java │ │ │ ├── colorization │ │ │ │ ├── package-info.java │ │ │ │ ├── ColorId.java │ │ │ │ ├── ColorScheme.java │ │ │ │ ├── CoverageColorJenkinsId.java │ │ │ │ ├── CoverageChangeTendency.java │ │ │ │ ├── CoverageColorPalette.java │ │ │ │ └── CoverageChangeLevel.java │ │ │ └── charts │ │ │ │ └── CoverageSeriesBuilder.java │ │ ├── package-info.java │ │ ├── exception │ │ │ └── CodeDeltaException.java │ │ ├── PackageCoverageNode.java │ │ ├── SourceViewModel.java │ │ ├── MethodCoverageNode.java │ │ ├── CoverageLeaf.java │ │ ├── FilePathValidator.java │ │ ├── CoverageJobAction.java │ │ └── SafeFraction.java │ │ ├── targets │ │ ├── RestResultWrapper.java │ │ ├── CoverageTrend.java │ │ ├── CoverageAggregationRule.java │ │ ├── CoverageTrendTree.java │ │ ├── HasName.java │ │ ├── Chartable.java │ │ ├── CoverageTreeElement.java │ │ ├── CoverageElementRegister.java │ │ └── CoverageElement.java │ │ ├── detector │ │ ├── Detectable.java │ │ └── AntPathReportDetector.java │ │ ├── adapter │ │ ├── CoverageAdapter.java │ │ ├── CoverageAdapterDescriptor.java │ │ ├── converter │ │ │ ├── JSONDocumentConverter.java │ │ │ └── DocumentConverter.java │ │ ├── JavaXMLCoverageReportAdapter.java │ │ ├── CoverageReportAdapterDescriptor.java │ │ ├── JSONCoverageReportAdapter.java │ │ ├── JavaCoverageReportAdapterDescriptor.java │ │ ├── XMLCoverageReportAdapter.java │ │ └── CoberturaReportAdapter.java │ │ ├── exception │ │ └── CoverageException.java │ │ ├── CoverageElementInitializer.java │ │ ├── BuildUtils.java │ │ ├── source │ │ └── SourceFileResolver.java │ │ ├── CompatibleObjectInputStream.java │ │ └── CoverageProjectAction.java └── test │ ├── resources │ ├── io │ │ └── jenkins │ │ │ └── plugins │ │ │ └── coverage │ │ │ ├── Security2376Test │ │ │ └── jobs │ │ │ │ └── fs │ │ │ │ ├── builds │ │ │ │ └── 1 │ │ │ │ │ ├── coverage-report │ │ │ │ │ └── build.xml │ │ │ │ └── config.xml │ │ │ ├── model │ │ │ ├── column-dsl.yaml │ │ │ ├── cobertura-lower-coverage.xml │ │ │ ├── AcuCobolParser.java.txt │ │ │ ├── cobertura-higher-coverage.xml │ │ │ └── cobertura-package-root.xml │ │ │ ├── metrics │ │ │ ├── steps │ │ │ │ ├── column-metric-dsl.yaml │ │ │ │ ├── coverage-publisher-details.checks-expected-result │ │ │ │ ├── coverage-publisher-summary.checks-expected-result-pit │ │ │ │ ├── coverage-publisher-summary.checks-expected-result │ │ │ │ ├── coverage-publisher-quality-gate.checks-expected-result │ │ │ │ ├── cobertura-lower-coverage.xml │ │ │ │ ├── cobertura-higher-coverage.xml │ │ │ │ └── job-dsl.yaml │ │ │ ├── source │ │ │ │ ├── tooltip.html │ │ │ │ └── AcuCobolParser.java.txt │ │ │ └── charts │ │ │ │ └── chart.json │ │ │ ├── sec1699.xml │ │ │ └── cobertura-coverage.xml │ └── design.puml │ └── java │ └── io │ └── jenkins │ └── plugins │ └── coverage │ ├── model │ ├── visualization │ │ ├── colorization │ │ │ ├── CoverageColorJenkinsIdTest.java │ │ │ ├── CoverageChangeTendencyTest.java │ │ │ ├── CoverageLevelTest.java │ │ │ └── CoverageChangeLevelTest.java │ │ └── dashboard │ │ │ ├── ChangeCoverageTest.java │ │ │ ├── ProjectCoverageTest.java │ │ │ ├── ChangeCoverageDeltaTest.java │ │ │ ├── ProjectCoverageDeltaTest.java │ │ │ └── IndirectCoverageChangesTest.java │ ├── CoverageLeafTest.java │ ├── SafeFractionTest.java │ ├── DeclarativePipelineSupportITest.java │ ├── JobDslITest.java │ ├── PackageCoverageNodeTest.java │ ├── testutil │ │ └── JobStubs.java │ ├── MethodCoverageNodeTest.java │ ├── CoverageMetricTest.java │ └── PluginArchitectureTest.java │ ├── Security2376Test.java │ ├── CoverageProcessorTest.java │ └── CoverageCornerCaseTest.java ├── .mvn ├── maven.config └── extensions.xml ├── img.png ├── images ├── step.png ├── summary.png ├── all-metrics.png ├── quality-gates.png ├── reportCC_screen.PNG ├── dashboard_screen.PNG ├── reportFile_screen.PNG ├── reportICC_screen.PNG ├── reportTree_screen.PNG ├── pit-coverage-checks.png ├── quality-gates-result.png ├── jacoco-coverage-checks.png ├── reportOverview_screen.PNG ├── pit-coverage-checks-annotations.png └── jacoco-coverage-checks-annotations.png ├── .github ├── auto_assign.yml ├── workflows │ ├── assign-pr.yml │ ├── run-release-drafter.yml │ ├── check-md-links.yml │ ├── sync-labels.yml │ ├── enforce-labels.yml │ └── ci.yml ├── check-md-links.json ├── labels.yml └── release-drafter.yml ├── bin ├── go.sh ├── skip.sh ├── clean.sh ├── release.sh └── deploy.sh ├── .gitignore ├── README.md ├── etc ├── assertj-templates │ ├── soft_assertions_entry_point_class_template.txt │ ├── assertions_entry_point_class_template.txt │ └── has_assertion_template.txt └── Jenkinsfile.analysis ├── Jenkinsfile └── LICENSE /src/main/resources/coverage/taglib: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.mvn/maven.config: -------------------------------------------------------------------------------- 1 | -Pconsume-incrementals 2 | -Pmight-produce-incrementals 3 | -------------------------------------------------------------------------------- /img.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jenkinsci/code-coverage-api-plugin/HEAD/img.png -------------------------------------------------------------------------------- /images/step.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jenkinsci/code-coverage-api-plugin/HEAD/images/step.png -------------------------------------------------------------------------------- /images/summary.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jenkinsci/code-coverage-api-plugin/HEAD/images/summary.png -------------------------------------------------------------------------------- /images/all-metrics.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jenkinsci/code-coverage-api-plugin/HEAD/images/all-metrics.png -------------------------------------------------------------------------------- /images/quality-gates.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jenkinsci/code-coverage-api-plugin/HEAD/images/quality-gates.png -------------------------------------------------------------------------------- /images/reportCC_screen.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jenkinsci/code-coverage-api-plugin/HEAD/images/reportCC_screen.PNG -------------------------------------------------------------------------------- /src/main/resources/io/jenkins/plugins/coverage/detector/Messages.properties: -------------------------------------------------------------------------------- 1 | AntPathDetector.displayName=Ant Path Detector -------------------------------------------------------------------------------- /images/dashboard_screen.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jenkinsci/code-coverage-api-plugin/HEAD/images/dashboard_screen.PNG -------------------------------------------------------------------------------- /images/reportFile_screen.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jenkinsci/code-coverage-api-plugin/HEAD/images/reportFile_screen.PNG -------------------------------------------------------------------------------- /images/reportICC_screen.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jenkinsci/code-coverage-api-plugin/HEAD/images/reportICC_screen.PNG -------------------------------------------------------------------------------- /images/reportTree_screen.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jenkinsci/code-coverage-api-plugin/HEAD/images/reportTree_screen.PNG -------------------------------------------------------------------------------- /images/pit-coverage-checks.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jenkinsci/code-coverage-api-plugin/HEAD/images/pit-coverage-checks.png -------------------------------------------------------------------------------- /images/quality-gates-result.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jenkinsci/code-coverage-api-plugin/HEAD/images/quality-gates-result.png -------------------------------------------------------------------------------- /src/main/resources/io/jenkins/plugins/coverage/CoveragePublisher/help-failNoReports.html: -------------------------------------------------------------------------------- 1 | Fail the build if no coverage reports found. -------------------------------------------------------------------------------- /src/main/webapp/images/tab.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jenkinsci/code-coverage-api-plugin/HEAD/src/main/webapp/images/tab.png -------------------------------------------------------------------------------- /images/jacoco-coverage-checks.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jenkinsci/code-coverage-api-plugin/HEAD/images/jacoco-coverage-checks.png -------------------------------------------------------------------------------- /images/reportOverview_screen.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jenkinsci/code-coverage-api-plugin/HEAD/images/reportOverview_screen.PNG -------------------------------------------------------------------------------- /src/main/resources/io/jenkins/plugins/coverage/CoveragePublisher/help-failUnstable.html: -------------------------------------------------------------------------------- 1 | Fail the build if coverage is lower than stable threshold. -------------------------------------------------------------------------------- /src/main/resources/io/jenkins/plugins/coverage/CoveragePublisher/help-failUnhealthy.html: -------------------------------------------------------------------------------- 1 | Fail the step if coverage is lower than healthy threshold. 2 | -------------------------------------------------------------------------------- /images/pit-coverage-checks-annotations.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jenkinsci/code-coverage-api-plugin/HEAD/images/pit-coverage-checks-annotations.png -------------------------------------------------------------------------------- /images/jacoco-coverage-checks-annotations.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jenkinsci/code-coverage-api-plugin/HEAD/images/jacoco-coverage-checks-annotations.png -------------------------------------------------------------------------------- /.github/auto_assign.yml: -------------------------------------------------------------------------------- 1 | addReviewers: false 2 | addAssignees: true 3 | 4 | assignees: 5 | - uhafner 6 | 7 | skipKeywords: 8 | - wip 9 | 10 | numberOfAssignees: 0 11 | -------------------------------------------------------------------------------- /bin/go.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | (cd plugin; mvn clean install -DskipITs || { echo "Build failed"; exit 1; }) 6 | 7 | $(dirname "$0")/deploy.sh code-coverage-api 8 | 9 | -------------------------------------------------------------------------------- /bin/skip.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | (cd plugin; mvn clean install -Pskip || { echo "Build failed"; exit 1; }) 6 | 7 | $(dirname "$0")/deploy.sh code-coverage-api 8 | 9 | -------------------------------------------------------------------------------- /src/main/resources/io/jenkins/plugins/coverage/CoveragePublisher/help-calculateDiffForChangeRequests.html: -------------------------------------------------------------------------------- 1 | If it is a change request build, calculate code coverage diff with a target branch build -------------------------------------------------------------------------------- /src/main/resources/io/jenkins/plugins/coverage/CoveragePublisher/help-failBuildIfCoverageDecreasedInChangeRequest.html: -------------------------------------------------------------------------------- 1 | Fail the build in case if code coverage decreased in comparison with the target branch -------------------------------------------------------------------------------- /bin/clean.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | (cd plugin; mvn clean install -Djenkins.test.timeout=1000 || { echo "Build failed"; exit 1; }) 6 | 7 | $(dirname "$0")/deploy.sh code-coverage-api 8 | 9 | 10 | -------------------------------------------------------------------------------- /src/main/resources/coverage/coverage-table.properties: -------------------------------------------------------------------------------- 1 | changed.files=Show only changed files 2 | select.row=Please select a file in the table to open the source code 3 | no.sourcecode=No source code available for this file 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node 2 | node_modules 3 | target 4 | *.iml 5 | .classpath 6 | .project 7 | .settings 8 | pom.xml.versionsBackup 9 | pom.xml.releaseBackup 10 | release.properties 11 | .DS_Store 12 | .idea 13 | /package-lock.json 14 | -------------------------------------------------------------------------------- /src/main/resources/io/jenkins/plugins/coverage/adapter/Messages.properties: -------------------------------------------------------------------------------- 1 | CoberturaReportAdapter.displayName=Cobertura 2 | JacocoReportAdapter.displayName=Jacoco 3 | IstanbulCoberturaReportAdapter.displayName=Istanbul (Cobertura reporter) -------------------------------------------------------------------------------- /src/main/resources/io/jenkins/plugins/coverage/model/visualization/dashboard/CoverageColumn/columnHeader.jelly: -------------------------------------------------------------------------------- 1 | 2 | 3 | ${it.columnName} 4 | 5 | -------------------------------------------------------------------------------- /src/main/resources/io/jenkins/plugins/coverage/source/Messages.properties: -------------------------------------------------------------------------------- 1 | SourceFileResolver.neverSave=never save source files 2 | SourceFileResolver.saveLast=save last build source files 3 | SourceFileResolver.saveAll=save all build source files 4 | -------------------------------------------------------------------------------- /bin/release.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | git pull 4 | git push 5 | cd plugin 6 | mvn -B clean build-helper:parse-version release:prepare release:perform -DdevelopmentVersion=\${parsedVersion.majorVersion}.\${parsedVersion.nextMinorVersion}.0-SNAPSHOT 7 | 8 | 9 | -------------------------------------------------------------------------------- /src/test/resources/io/jenkins/plugins/coverage/Security2376Test/jobs/fs/builds/1/coverage-report: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jenkinsci/code-coverage-api-plugin/HEAD/src/test/resources/io/jenkins/plugins/coverage/Security2376Test/jobs/fs/builds/1/coverage-report -------------------------------------------------------------------------------- /src/main/resources/io/jenkins/plugins/coverage/source/SourceFileResolver/config.jelly: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /src/main/resources/io/jenkins/plugins/coverage/CoveragePublisher/help-checksName.html: -------------------------------------------------------------------------------- 1 |
2 | If provided, and publishing checks enabled, the plugin will use this name when publishing results to corresponding 3 | SCM hosting platforms. If not, the default of "Code Coverage" will be used. 4 |
-------------------------------------------------------------------------------- /src/main/resources/io/jenkins/plugins/coverage/model/CoverageJobAction/floatingBox.jelly: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /.github/workflows/assign-pr.yml: -------------------------------------------------------------------------------- 1 | name: 'Auto Assign PR' 2 | 3 | on: pull_request_target 4 | 5 | jobs: 6 | add-reviews: 7 | runs-on: ubuntu-latest 8 | steps: 9 | - uses: kentaro-m/auto-assign-action@v1.2.5 10 | with: 11 | repo-token: "${{ secrets.GITHUB_TOKEN }}" 12 | -------------------------------------------------------------------------------- /src/main/resources/io/jenkins/plugins/coverage/CoveragePublisher/help-skipPublishingChecks.html: -------------------------------------------------------------------------------- 1 | If unchecked, the plugin will automatically publish the coverage to corresponding SCM platforms. 2 | For example, if you are using this feature for a GitHub organization project, the coverage will be published to GitHub through the Checks API. -------------------------------------------------------------------------------- /src/main/java/io/jenkins/plugins/coverage/CoverageFeatureConstants.java: -------------------------------------------------------------------------------- 1 | package io.jenkins.plugins.coverage; 2 | 3 | 4 | // this class will list all extra features 5 | // TODO may need a better solution :( 6 | public class CoverageFeatureConstants { 7 | public static final String FEATURE_SOURCE_FILE_PATH = "source-file-path"; 8 | } 9 | -------------------------------------------------------------------------------- /src/main/resources/io/jenkins/plugins/coverage/source/Messages_zh_CN.properties: -------------------------------------------------------------------------------- 1 | SourceFileResolver.neverSave=\u6c38\u8fdc\u4e0d\u4fdd\u5b58\u6e90\u6587\u4ef6 2 | SourceFileResolver.saveLast=\u4fdd\u5b58\u4e0a\u4e00\u6b21\u6784\u5efa\u7684\u6e90\u6587\u4ef6 3 | SourceFileResolver.saveAll=\u4fdd\u5b58\u6240\u6709\u6784\u5efa\u7684\u6e90\u6587\u4ef6 4 | -------------------------------------------------------------------------------- /src/main/resources/io/jenkins/plugins/coverage/targets/CoverageResult/index.properties: -------------------------------------------------------------------------------- 1 | reason.1=\ 2 | You have specified source file storing level to never save. 3 | 4 | reason.2=\ 5 | Code Coverage API plugin cannot found source files. 6 | 7 | reason.3=\ 8 | You do not have sufficient permissions to view this file. 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /src/main/java/io/jenkins/plugins/coverage/model/visualization/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Contains logic and models for visualizing code coverage. 3 | */ 4 | @DefaultAnnotation(NonNull.class) 5 | package io.jenkins.plugins.coverage.model.visualization; 6 | 7 | import edu.umd.cs.findbugs.annotations.DefaultAnnotation; 8 | import edu.umd.cs.findbugs.annotations.NonNull; 9 | -------------------------------------------------------------------------------- /src/main/resources/io/jenkins/plugins/coverage/model/SourceViewModel/index.properties: -------------------------------------------------------------------------------- 1 | reason.1=\ 2 | You did not enable storing of source files (see parameter 'sourceFiles'). 3 | 4 | reason.2=\ 5 | Code Coverage API plugin did not find the source files. 6 | 7 | reason.3=\ 8 | You do not have sufficient permissions to view source files. 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /src/main/resources/io/jenkins/plugins/coverage/CoveragePublisher/help-applyThresholdRecursively.html: -------------------------------------------------------------------------------- 1 | By default, this plugins will only apply threshold at report level. If this option is set to true, it will apply threshold to all levels. 2 | 3 | For example, if this option is true, and if you have specified a `Line` threshold, this plugin will check all modules, directories, files... -------------------------------------------------------------------------------- /.github/workflows/run-release-drafter.yml: -------------------------------------------------------------------------------- 1 | name: 'Invoke Release Drafter' 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | - main 8 | 9 | jobs: 10 | update_release_draft: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: release-drafter/release-drafter@v5.25.0 14 | env: 15 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 16 | -------------------------------------------------------------------------------- /src/main/resources/index.jelly: -------------------------------------------------------------------------------- 1 | 2 |
3 | The Code Coverage API plug-in reached end-of-life. All functionality has been integrated 4 | into the Coverage Plugin. 5 | Please migrate your steps to the new recordCoverage step provided by the Coverage Plugin. 6 |
7 | -------------------------------------------------------------------------------- /src/main/resources/io/jenkins/plugins/coverage/CoveragePublisher/config.properties: -------------------------------------------------------------------------------- 1 | title.sourceCodeEncoding=Source Code Encoding 2 | description.sourceCodeEncoding=Encoding of your source code. 3 | title.sourceDirectories=Source Directories 4 | description.sourceDirectories=Additional paths to the source code if not in the root of the workspace \ 5 | (or outside the workspace). 6 | 7 | -------------------------------------------------------------------------------- /src/main/resources/io/jenkins/plugins/coverage/model/CoverageViewModel/index.properties: -------------------------------------------------------------------------------- 1 | coverage.details.title=Coverage details by {0} 2 | reason.1=\ 3 | You did not enable storing of source files (see parameter 'sourceFiles'). 4 | reason.2=\ 5 | Code Coverage API plugin did not find the source files. 6 | reason.3=\ 7 | You do not have sufficient permissions to view source files. 8 | -------------------------------------------------------------------------------- /.github/check-md-links.json: -------------------------------------------------------------------------------- 1 | { 2 | "httpHeaders": [ 3 | { 4 | "urls": ["https://github.com/", "https://guides.github.com/", "https://help.github.com/", "https://docs.github.com/", "https://classroom.github.com"], 5 | "headers": { 6 | "Accept-Encoding": "zstd, br, gzip, deflate" 7 | } 8 | } 9 | ], 10 | "aliveStatusCodes": [200, 500, 503] 11 | } 12 | -------------------------------------------------------------------------------- /src/main/java/io/jenkins/plugins/coverage/targets/RestResultWrapper.java: -------------------------------------------------------------------------------- 1 | package io.jenkins.plugins.coverage.targets; 2 | 3 | import hudson.model.Api; 4 | 5 | public class RestResultWrapper { 6 | private Object o; 7 | 8 | public RestResultWrapper(Object o) { 9 | this.o = o; 10 | } 11 | 12 | public Api getApi() { 13 | return new Api(o); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/main/resources/io/jenkins/plugins/coverage/model/visualization/dashboard/CoverageColumn/config.properties: -------------------------------------------------------------------------------- 1 | title.columnName=Column Name 2 | title.coverageType=Coverage Type 3 | description.coverageType=Choose which code coverage type should be shown in the column.. 4 | title.coverageMetric=Coverage Metric 5 | description.coverageMetric=Choose which code coverage metric should be shown in the column. 6 | -------------------------------------------------------------------------------- /.github/workflows/check-md-links.yml: -------------------------------------------------------------------------------- 1 | name: 'Check Markdown links' 2 | 3 | on: push 4 | 5 | jobs: 6 | markdown-link-check: 7 | runs-on: ubuntu-latest 8 | steps: 9 | - uses: actions/checkout@v4 10 | - uses: gaurav-nelson/github-action-markdown-link-check@v1 11 | with: 12 | check-modified-files-only: 'no' 13 | config-file: '.github/check-md-links.json' 14 | -------------------------------------------------------------------------------- /src/main/java/io/jenkins/plugins/coverage/model/visualization/dashboard/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Contains logic and models for visualizing code coverage within a dashboard column. 3 | */ 4 | @DefaultAnnotation(NonNull.class) 5 | package io.jenkins.plugins.coverage.model.visualization.dashboard; 6 | 7 | import edu.umd.cs.findbugs.annotations.DefaultAnnotation; 8 | import edu.umd.cs.findbugs.annotations.NonNull; 9 | -------------------------------------------------------------------------------- /src/main/java/io/jenkins/plugins/coverage/model/visualization/colorization/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Provides colors and operations on them to be used in order to visualize coverage. 3 | */ 4 | @DefaultAnnotation(NonNull.class) 5 | package io.jenkins.plugins.coverage.model.visualization.colorization; 6 | 7 | import edu.umd.cs.findbugs.annotations.DefaultAnnotation; 8 | import edu.umd.cs.findbugs.annotations.NonNull; 9 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## This plugin reached end-of-life. 2 | 3 | All functionality has been integrated into the [Coverage Plugin](https://github.com/jenkinsci/coverage-plugin). Please use that plugin instead. It is available in the Jenkins update center. 4 | 5 | If you are still using the old and deprecated step `publishCoverage` then you still can use this plugin. But please migrate to the new step `recordCoverage` as soon as possible. 6 | -------------------------------------------------------------------------------- /src/main/java/io/jenkins/plugins/coverage/model/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * New coverage model that tries to replace all the existing functionality with a more versatile model. 3 | * 4 | * @author Ullrich Hafner 5 | */ 6 | @DefaultAnnotation(NonNull.class) 7 | package io.jenkins.plugins.coverage.model; 8 | 9 | import edu.umd.cs.findbugs.annotations.DefaultAnnotation; 10 | import edu.umd.cs.findbugs.annotations.NonNull; 11 | -------------------------------------------------------------------------------- /src/main/resources/io/jenkins/plugins/coverage/CoverageColumn/column.jelly: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | ${it.getCoverage(job)} 6 | 7 | 8 | ${it.getCoverage(job)} 9 | 10 | 11 | -------------------------------------------------------------------------------- /etc/assertj-templates/soft_assertions_entry_point_class_template.txt: -------------------------------------------------------------------------------- 1 | package ${package}; 2 | 3 | /** 4 | * Entry point for soft assertions of different data types. 5 | */ 6 | @edu.umd.cs.findbugs.annotations.SuppressFBWarnings("NM") 7 | @javax.annotation.Generated(value="assertj-assertions-generator") 8 | public class SoftAssertions extends org.assertj.core.api.AutoCloseableSoftAssertions { 9 | ${all_assertions_entry_points} 10 | } 11 | -------------------------------------------------------------------------------- /src/main/resources/io/jenkins/plugins/coverage/CoveragePublisher/help-sourceCodeEncoding.html: -------------------------------------------------------------------------------- 1 |
2 | In order to correctly show all your affected source code files in the detail views, 3 | the plugin must open these files with the correct character encoding (UTF-8, ISO-8859-1, etc.). 4 | If you leave this field empty then the default encoding of the platform will be used. This might work but 5 | is not recommended. 6 |
7 | -------------------------------------------------------------------------------- /.mvn/extensions.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | io.jenkins.tools.incrementals 4 | git-changelist-maven-extension 5 | 1.7 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/test/resources/io/jenkins/plugins/coverage/model/column-dsl.yaml: -------------------------------------------------------------------------------- 1 | jobs: 2 | - script: > 3 | listView("dsl-view") { 4 | jobs { 5 | regex("^bar") 6 | } 7 | recurse(true) 8 | columns { 9 | coverageColumn() 10 | status() 11 | weather() 12 | name() 13 | lastSuccess() 14 | lastFailure() 15 | lastDuration() 16 | testResult(1) 17 | buildButton() 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /.github/workflows/sync-labels.yml: -------------------------------------------------------------------------------- 1 | name: Sync labels 2 | on: 3 | push: 4 | branches: 5 | - master 6 | - main 7 | paths: 8 | - .github/labels.yml 9 | - .github/workflows/sync-labels.yml 10 | 11 | jobs: 12 | build: 13 | runs-on: ubuntu-latest 14 | steps: 15 | - uses: actions/checkout@v4 16 | - uses: micnncim/action-label-syncer@v1.3.0 17 | env: 18 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 19 | with: 20 | manifest: .github/labels.yml 21 | -------------------------------------------------------------------------------- /src/main/resources/io/jenkins/plugins/coverage/Messages_zh_CN.properties: -------------------------------------------------------------------------------- 1 | CoverageColumn.ColumnName=\u8986\u76d6\u7387 2 | CoveragePublisher.displayName=\u53d1\u5e03\u4ee3\u7801\u8986\u76d6\u7387\u62a5\u544a 3 | CoverageAction.displayName=\u4ee3\u7801\u8986\u76d6\u7387\u62a5\u544a 4 | CoverageProjectAction.displayName=\u4ee3\u7801\u8986\u76d6\u7387\u62a5\u544a 5 | CoverageProcessor.healthReportDescriptionTemplate=\u8986\u76d6\u7387\u5206\u6570\u4e3a {0}% 6 | CoverageColumn.Caption=\u884c\u8986\u76d6\u7387 7 | CoverageColumn.CoverageEmpty=none -------------------------------------------------------------------------------- /src/main/webapp/js/colors.js: -------------------------------------------------------------------------------- 1 | getJenkinsColors = function (colors) { 2 | // TODO: also handle HSL colors and parse them to hex in order to use dark mode colors 3 | const colorHexMapping = new Map; 4 | colors.forEach(function (jenkinsId) { 5 | const colorHex = getComputedStyle(document.body).getPropertyValue(jenkinsId); 6 | if (colorHex.match(/^#[a-fA-F0-9]{6}$/) !== null) { 7 | colorHexMapping.set(jenkinsId, colorHex); 8 | } 9 | }) 10 | return colorHexMapping; 11 | }; 12 | 13 | -------------------------------------------------------------------------------- /src/test/resources/io/jenkins/plugins/coverage/metrics/steps/column-metric-dsl.yaml: -------------------------------------------------------------------------------- 1 | jobs: 2 | - script: > 3 | listView("dsl-view") { 4 | jobs { 5 | regex("^bar") 6 | } 7 | recurse(true) 8 | columns { 9 | coverageMetricColumn() 10 | status() 11 | weather() 12 | name() 13 | lastSuccess() 14 | lastFailure() 15 | lastDuration() 16 | testResult(1) 17 | buildButton() 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /bin/deploy.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | JENKINS_HOME=../docker/volumes/jenkins-home 6 | 7 | echo "Installing plugin ${1} in $JENKINS_HOME" 8 | rm -rf $JENKINS_HOME/plugins/${1}* 9 | cp -fv plugin/target/${1}.hpi $JENKINS_HOME/plugins/${1}.jpi 10 | 11 | CURRENT_UID="$(id -u):$(id -g)" 12 | export CURRENT_UID 13 | IS_RUNNING=$(docker-compose ps -q jenkins-controller) 14 | if [[ "$IS_RUNNING" != "" ]]; then 15 | docker-compose restart 16 | echo "Restarting Jenkins (docker compose with user ID ${CURRENT_UID}) ..." 17 | fi 18 | 19 | -------------------------------------------------------------------------------- /src/main/java/io/jenkins/plugins/coverage/model/visualization/colorization/ColorId.java: -------------------------------------------------------------------------------- 1 | package io.jenkins.plugins.coverage.model.visualization.colorization; 2 | 3 | /** 4 | * Provides IDs for colors which are used within this plugin in order to separate the color palette from the logic. 5 | * 6 | * @author Florian Orendi 7 | */ 8 | public enum ColorId { 9 | INSUFFICIENT, 10 | VERY_BAD, 11 | BAD, 12 | INADEQUATE, 13 | AVERAGE, 14 | GOOD, 15 | VERY_GOOD, 16 | EXCELLENT, 17 | 18 | BLACK, 19 | WHITE 20 | } 21 | -------------------------------------------------------------------------------- /src/main/resources/io/jenkins/plugins/coverage/detector/AntPathReportDetector/config.jelly: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 10 | 11 |
7 | 8 | 9 |
12 |
13 |
14 | -------------------------------------------------------------------------------- /.github/workflows/enforce-labels.yml: -------------------------------------------------------------------------------- 1 | name: Enforce PR labels 2 | on: 3 | pull_request: 4 | types: [opened, labeled, unlabeled, synchronize] 5 | jobs: 6 | label: 7 | runs-on: ubuntu-latest 8 | permissions: 9 | issues: write 10 | pull-requests: write 11 | steps: 12 | - uses: mheap/github-action-required-labels@v5 13 | with: 14 | mode: minimum 15 | count: 1 16 | labels: "bug,feature,enhancement,breaking,tests,documentation,internal,dependencies" 17 | message: "Maintainer needs to assign at least one label before merge" 18 | -------------------------------------------------------------------------------- /src/main/java/io/jenkins/plugins/coverage/detector/Detectable.java: -------------------------------------------------------------------------------- 1 | package io.jenkins.plugins.coverage.detector; 2 | 3 | import io.jenkins.plugins.coverage.adapter.CoverageReportAdapter; 4 | 5 | import java.io.File; 6 | 7 | /** 8 | * Coverage report will be able to be automatically found and match. 9 | *

10 | * It should be implemented by Descriptor of {@link CoverageReportAdapter} 11 | *

12 | */ 13 | public interface Detectable { 14 | /** 15 | * @param file file be detected 16 | * @return true if file match the rule 17 | */ 18 | boolean detect(File file); 19 | } 20 | -------------------------------------------------------------------------------- /src/main/resources/io/jenkins/plugins/coverage/Messages.properties: -------------------------------------------------------------------------------- 1 | CoveragePublisher.displayName=Publish Coverage Report [deprecated] 2 | CoverageAction.displayName=Coverage Report 3 | CoverageProjectAction.displayName=Coverage Report 4 | CoverageProcessor.healthReportDescriptionTemplate=Coverage Healthy score is {0}% 5 | CoverageColumn.Caption=Line Coverage 6 | CoverageColumn.ColumnName=Code Coverage 7 | CoverageColumn.CoverageEmpty=none 8 | FieldValidator.Error.DefaultEncoding=Encoding must be a supported encoding of the Java platform, see: \ 9 | java.nio.charset.Charset 10 | -------------------------------------------------------------------------------- /etc/assertj-templates/assertions_entry_point_class_template.txt: -------------------------------------------------------------------------------- 1 | package ${package}; 2 | 3 | /** 4 | * Entry point for assertions of different data types. Each method in this class is a static factory for the 5 | * type-specific assertion objects. 6 | */ 7 | @edu.umd.cs.findbugs.annotations.SuppressFBWarnings("NM") 8 | @javax.annotation.Generated(value="assertj-assertions-generator") 9 | public class Assertions extends org.assertj.core.api.Assertions { 10 | ${all_assertions_entry_points} 11 | /** 12 | * Creates a new {@link Assertions}. 13 | */ 14 | protected Assertions() { 15 | // empty 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/main/java/io/jenkins/plugins/coverage/adapter/CoverageAdapter.java: -------------------------------------------------------------------------------- 1 | package io.jenkins.plugins.coverage.adapter; 2 | 3 | import hudson.ExtensionPoint; 4 | import hudson.model.Describable; 5 | import hudson.model.Descriptor; 6 | import jenkins.model.Jenkins; 7 | 8 | import java.io.Serializable; 9 | 10 | public abstract class CoverageAdapter implements ExtensionPoint, Describable, Serializable { 11 | 12 | 13 | @SuppressWarnings("unchecked") 14 | @Override 15 | public Descriptor getDescriptor() { 16 | return Jenkins.get().getDescriptorOrDie(getClass()); 17 | } 18 | 19 | } 20 | -------------------------------------------------------------------------------- /src/main/resources/io/jenkins/plugins/coverage/CoveragePublisher/help-sourceDirectories.html: -------------------------------------------------------------------------------- 1 |
2 | Since the plugin also reads the affected source code files it needs to copy these files from the agent to the 3 | controller. If these files are not part of the workspace (or checked out into a sub folder of the workspace) they 4 | are not automatically found. So you can add one or more source code directories where the plugin tries to find 5 | these files. Note, that due to security restrictions additional paths outside the workspace need to be registered 6 | in Jenkins system configuration before they can be used here. 7 |
8 | -------------------------------------------------------------------------------- /src/main/resources/io/jenkins/plugins/coverage/model/visualization/dashboard/CoverageColumn/config.jelly: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /src/main/java/io/jenkins/plugins/coverage/model/exception/CodeDeltaException.java: -------------------------------------------------------------------------------- 1 | package io.jenkins.plugins.coverage.model.exception; 2 | 3 | /** 4 | * Exception which is thrown when preprocessing the code delta failed. 5 | * 6 | * @author Florian Orendi 7 | */ 8 | public class CodeDeltaException extends Exception { 9 | private static final long serialVersionUID = -7255072653278584604L; 10 | 11 | /** 12 | * Constructor which creates an exception with a message. 13 | * 14 | * @param message 15 | * The message 16 | */ 17 | public CodeDeltaException(final String message) { 18 | super(message); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/io/jenkins/plugins/coverage/adapter/CoverageAdapterDescriptor.java: -------------------------------------------------------------------------------- 1 | package io.jenkins.plugins.coverage.adapter; 2 | 3 | import hudson.DescriptorExtensionList; 4 | import hudson.model.Descriptor; 5 | import jenkins.model.Jenkins; 6 | 7 | public class CoverageAdapterDescriptor extends Descriptor { 8 | 9 | public CoverageAdapterDescriptor(Class clazz) { 10 | super(clazz); 11 | } 12 | 13 | public static DescriptorExtensionList> all() { 14 | return Jenkins.get().getDescriptorList(CoverageAdapter.class); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/io/jenkins/plugins/coverage/model/visualization/colorization/ColorScheme.java: -------------------------------------------------------------------------------- 1 | package io.jenkins.plugins.coverage.model.visualization.colorization; 2 | 3 | /** 4 | * Represents different types of color schemes that can be selected in order to load the matching color palette. 5 | * 6 | * @author Florian Orendi 7 | */ 8 | public enum ColorScheme { 9 | 10 | /** 11 | * The default colors. 12 | */ 13 | DEFAULT, 14 | /** 15 | * Colors which are used if the dark mode is activated. 16 | */ 17 | DARK_MODE, 18 | /** 19 | * Colors which are usable in case of color blindness. 20 | */ 21 | COLOR_BLINDNESS 22 | } 23 | -------------------------------------------------------------------------------- /src/test/resources/io/jenkins/plugins/coverage/Security2376Test/jobs/fs/config.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | false 6 | 7 | 8 | true 9 | false 10 | false 11 | false 12 | 13 | false 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /src/main/resources/io/jenkins/plugins/coverage/model/Messages.properties: -------------------------------------------------------------------------------- 1 | Coverage.Not.Available=n/a 2 | Coverage.Link.Name=Coverage Report 3 | Coverage.Title=Coverage of ''{0}'' 4 | Coverage_Column=Coverage [deprecated] 5 | Project_Coverage_Type=Project Coverage 6 | Project_Coverage_Delta_Type=Project Coverage Delta 7 | Change_Coverage_Type=Change Coverage 8 | Change_Coverage_Delta_Type=Change Coverage Delta 9 | Indirect_Coverage_Changes_Type=Indirect Coverage Changes 10 | 11 | Column.File=File 12 | Column.Package=Package 13 | Column.LineCoverage=Line 14 | Column.DeltaLineCoverage=Line {0} 15 | Column.BranchCoverage=Branch 16 | Column.DeltaBranchCoverage=Branch {0} 17 | Column.LinesOfCode=LOC 18 | -------------------------------------------------------------------------------- /src/test/resources/design.puml: -------------------------------------------------------------------------------- 1 | @startuml 2 | 3 | skinparam componentStyle uml2 4 | skinparam component { 5 | BorderColor #a0a0a0 6 | BackgroundColor #f8f8f8 7 | } 8 | 9 | [Steps] <<..metrics.steps>> 10 | [Color] <<..metrics.color>> 11 | [Source] <<..metrics.source>> 12 | [Charts] <<..metrics.charts>> 13 | [Model] <<..metrics.model>> 14 | [Restapi] <<..metrics.restapi>> 15 | 16 | [Restapi] --> [Model] 17 | [Restapi] --> [Source] 18 | 19 | [Steps] --> [Model] 20 | [Steps] --> [Color] 21 | [Steps] --> [Source] 22 | [Steps] --> [Charts] 23 | [Steps] --> [Restapi] 24 | 25 | [Charts] --> [Color] 26 | [Charts] --> [Model] 27 | 28 | [Source] --> [Model] 29 | 30 | [Model] --> [Color] 31 | 32 | 33 | @enduml 34 | -------------------------------------------------------------------------------- /src/main/java/io/jenkins/plugins/coverage/exception/CoverageException.java: -------------------------------------------------------------------------------- 1 | package io.jenkins.plugins.coverage.exception; 2 | 3 | public class CoverageException extends Exception { 4 | 5 | public CoverageException() { 6 | } 7 | 8 | public CoverageException(String message) { 9 | super(message); 10 | } 11 | 12 | public CoverageException(String message, Throwable cause) { 13 | super(message, cause); 14 | } 15 | 16 | public CoverageException(Throwable cause) { 17 | super(cause); 18 | } 19 | 20 | public CoverageException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) { 21 | super(message, cause, enableSuppression, writableStackTrace); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/main/resources/io/jenkins/plugins/coverage/CoveragePullRequestMonitoringPortlet/monitor.jelly: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
10 | 11 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /src/main/java/io/jenkins/plugins/coverage/adapter/converter/JSONDocumentConverter.java: -------------------------------------------------------------------------------- 1 | package io.jenkins.plugins.coverage.adapter.converter; 2 | 3 | import org.w3c.dom.Document; 4 | 5 | import com.fasterxml.jackson.databind.JsonNode; 6 | 7 | import io.jenkins.plugins.coverage.exception.CoverageException; 8 | 9 | public abstract class JSONDocumentConverter extends DocumentConverter { 10 | 11 | 12 | /** 13 | * 14 | * @param report JSON format report 15 | * @param document document that the report will convert to 16 | * @return standard format document converted by JSON format report 17 | */ 18 | @Override 19 | protected abstract Document convert(JsonNode report, Document document) throws CoverageException; 20 | 21 | } 22 | -------------------------------------------------------------------------------- /src/test/resources/io/jenkins/plugins/coverage/sec1699.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | ]> 5 | 6 | 7 | fedepell 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | &xxe; 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /src/main/java/io/jenkins/plugins/coverage/adapter/JavaXMLCoverageReportAdapter.java: -------------------------------------------------------------------------------- 1 | package io.jenkins.plugins.coverage.adapter; 2 | 3 | import org.w3c.dom.Document; 4 | 5 | import io.jenkins.plugins.coverage.adapter.parser.JavaCoverageParser; 6 | import io.jenkins.plugins.coverage.exception.CoverageException; 7 | import io.jenkins.plugins.coverage.targets.CoverageResult; 8 | 9 | public abstract class JavaXMLCoverageReportAdapter extends XMLCoverageReportAdapter { 10 | 11 | 12 | public JavaXMLCoverageReportAdapter(final String path) { 13 | super(path); 14 | } 15 | 16 | @Override 17 | public CoverageResult parseToResult(final Document document, final String reportName) throws CoverageException { 18 | return new JavaCoverageParser(reportName).parse(document); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/main/resources/io/jenkins/plugins/coverage/CoverageProjectAction/nodata.jelly: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 |

No valid coverage data available

7 | Some Reasons are there: 8 |
    9 |
  1. Only coverage data of last succeed build will be shown in there. 10 | If you previous builds are all unstable or failed, you won't be able to see anything. 11 | You can go to the build's page to see coverage data for each build.
  2. 12 |
13 |
14 |
15 |
16 | 17 | -------------------------------------------------------------------------------- /src/test/java/io/jenkins/plugins/coverage/model/visualization/colorization/CoverageColorJenkinsIdTest.java: -------------------------------------------------------------------------------- 1 | package io.jenkins.plugins.coverage.model.visualization.colorization; 2 | 3 | import org.junit.jupiter.api.Test; 4 | 5 | import static io.jenkins.plugins.coverage.model.visualization.colorization.CoverageColorJenkinsId.*; 6 | import static org.assertj.core.api.Assertions.*; 7 | 8 | /** 9 | * Test class for {@link CoverageColorJenkinsId}. 10 | * 11 | * @author Florian Orendi 12 | */ 13 | class CoverageColorJenkinsIdTest { 14 | 15 | @Test 16 | void shouldGetAllIds() { 17 | assertThat(getAll().size()).isEqualTo(values().length); 18 | } 19 | 20 | @Test 21 | void shouldGetColorId() { 22 | assertThat(GREEN.getJenkinsColorId()).isEqualTo("--green"); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/test/resources/io/jenkins/plugins/coverage/metrics/source/tooltip.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
Killed Mutations: 4 |
    5 |
  • changed conditional boundary 6 | (org.pitest.mutationtest.engine.gregor.mutators.ConditionalsBoundaryMutator) 7 |
  • 8 |
  • negated conditional (org.pitest.mutationtest.engine.gregor.mutators.NegateConditionalsMutator)
  • 9 |
10 |
11 |
12 |
13 |
Survived Mutations: 14 |
    15 |
  • Replaced integer addition with subtraction 16 | (org.pitest.mutationtest.engine.gregor.mutators.MathMutator) 17 |
  • 18 |
19 |
20 |
21 |
22 | -------------------------------------------------------------------------------- /Jenkinsfile: -------------------------------------------------------------------------------- 1 | def configurations = [ 2 | [ platform: "linux", jdk: "21" ], 3 | [ platform: "windows", jdk: "17" ] 4 | ] 5 | 6 | def params = [ 7 | failFast: false, 8 | configurations: configurations, 9 | checkstyle: [qualityGates: [[threshold: 1, type: 'NEW', unstable: true]], 10 | filters:[includePackage('io.jenkins.plugins.coverage.metrics')]], 11 | pmd: [qualityGates: [[threshold: 1, type: 'NEW', unstable: true]], 12 | filters:[includePackage('io.jenkins.plugins.coverage.metrics')]], 13 | spotbugs: [qualityGates: [[threshold: 1, type: 'NEW', unstable: true]], 14 | filters:[includePackage('io.jenkins.plugins.coverage.metrics')]], 15 | jacoco: [sourceCodeRetention: 'MODIFIED', sourceDirectories: [[path: 'plugin/src/main/java']]] 16 | ] 17 | 18 | buildPlugin(params) 19 | -------------------------------------------------------------------------------- /src/main/java/io/jenkins/plugins/coverage/targets/CoverageTrend.java: -------------------------------------------------------------------------------- 1 | package io.jenkins.plugins.coverage.targets; 2 | 3 | import org.kohsuke.stapler.export.Exported; 4 | import org.kohsuke.stapler.export.ExportedBean; 5 | 6 | import java.io.Serializable; 7 | import java.util.List; 8 | 9 | @ExportedBean 10 | public class CoverageTrend implements Serializable { 11 | 12 | private String buildName; 13 | private List elements; 14 | 15 | 16 | public CoverageTrend(String buildName, List elements) { 17 | this.buildName = buildName; 18 | this.elements = elements; 19 | } 20 | 21 | @Exported 22 | public String getBuildName() { 23 | return buildName; 24 | } 25 | 26 | @Exported 27 | public List getElements() { 28 | return elements; 29 | } 30 | 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/io/jenkins/plugins/coverage/adapter/CoverageReportAdapterDescriptor.java: -------------------------------------------------------------------------------- 1 | package io.jenkins.plugins.coverage.adapter; 2 | 3 | 4 | import io.jenkins.plugins.coverage.targets.CoverageElement; 5 | 6 | import java.util.Collections; 7 | import java.util.List; 8 | 9 | 10 | public class CoverageReportAdapterDescriptor 11 | extends CoverageAdapterDescriptor { 12 | 13 | public CoverageReportAdapterDescriptor(Class clazz) { 14 | super(clazz); 15 | } 16 | 17 | public List getCoverageElements() { 18 | return Collections.emptyList(); 19 | } 20 | 21 | public String getCoverageElementType() { 22 | return CoverageElement.COVERAGE_ELEMENT_TYPE_NONE; 23 | } 24 | 25 | public boolean defaultMergeToOneReport() { 26 | return false; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/main/resources/coverage/configuration.properties: -------------------------------------------------------------------------------- 1 | parser.title=Code Coverage Tools 2 | parser.add=Add Tool 3 | 4 | qualityGates.add=Add Quality Gate 5 | qualityGates.title=Quality gates 6 | qualityGates.description=You can define an arbitrary number of quality gates that will be evaluated after a build. \ 7 | If a quality gate is not passed then the build can be set to unstable or failed, respectively. 8 | 9 | title.id=Custom ID 10 | title.name=Custom Name 11 | skipPublishingChecks.title=Skip publishing of checks to SCM hosting platforms 12 | checksName.title=Checks name 13 | checksAnnotationScope.title=Select the scope of source code annotations 14 | failOnError.title=Fail the step if errors have been reported during the execution 15 | title.enabledForFailure=Enable recording for failed builds 16 | title.skipSymbolicLinks=Skip symbolic links when searching for files 17 | sourceCodeRetention.title=Source Code Retention Strategy 18 | 19 | -------------------------------------------------------------------------------- /src/main/java/io/jenkins/plugins/coverage/model/PackageCoverageNode.java: -------------------------------------------------------------------------------- 1 | package io.jenkins.plugins.coverage.model; 2 | 3 | /** 4 | * A {@link CoverageNode} for a specific package. It converts a package structure to a corresponding path structure. 5 | * 6 | * @author Ullrich Hafner 7 | */ 8 | public class PackageCoverageNode extends CoverageNode { 9 | private static final long serialVersionUID = 8236436628673022634L; 10 | 11 | /** 12 | * Creates a new coverage item node with the given name. 13 | * 14 | * @param name 15 | * the human-readable name of the node 16 | */ 17 | public PackageCoverageNode(final String name) { 18 | super(CoverageMetric.PACKAGE, name); 19 | } 20 | 21 | @Override 22 | public String getPath() { 23 | return mergePath(getName().replaceAll("\\.", "/")); 24 | } 25 | 26 | @Override 27 | protected CoverageNode copyEmpty() { 28 | return new PackageCoverageNode(getName()); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/main/resources/META-INF/hudson.remoting.ClassFilter: -------------------------------------------------------------------------------- 1 | gnu.trove.impl.hash.THash 2 | gnu.trove.impl.hash.TIntHash 3 | gnu.trove.impl.hash.TPrimitiveHash 4 | gnu.trove.map.hash.TIntObjectHashMap 5 | 6 | org.apache.commons.lang3.math.Fraction 7 | 8 | edu.hm.hafner.coverage.ClassNode 9 | edu.hm.hafner.coverage.ContainerNode 10 | edu.hm.hafner.coverage.Coverage 11 | edu.hm.hafner.coverage.CyclomaticComplexity 12 | edu.hm.hafner.coverage.FileNode 13 | edu.hm.hafner.coverage.FractionValue 14 | edu.hm.hafner.coverage.IntegerValue 15 | edu.hm.hafner.coverage.LinesOfCode 16 | edu.hm.hafner.coverage.MethodNode 17 | edu.hm.hafner.coverage.Metric 18 | edu.hm.hafner.coverage.ModuleNode 19 | edu.hm.hafner.coverage.Mutation 20 | edu.hm.hafner.coverage.MutationStatus 21 | edu.hm.hafner.coverage.Mutator 22 | edu.hm.hafner.coverage.Node 23 | edu.hm.hafner.coverage.PackageNode 24 | edu.hm.hafner.coverage.Percentage 25 | edu.hm.hafner.coverage.SafeFraction 26 | edu.hm.hafner.coverage.Value 27 | 28 | java.util.ImmutableCollections$ListN 29 | java.util.ImmutableCollections$SetN 30 | java.util.ImmutableCollections$Set12 31 | -------------------------------------------------------------------------------- /src/test/java/io/jenkins/plugins/coverage/Security2376Test.java: -------------------------------------------------------------------------------- 1 | package io.jenkins.plugins.coverage; 2 | 3 | import hudson.model.FreeStyleProject; 4 | import org.junit.Rule; 5 | import org.junit.Test; 6 | import org.jvnet.hudson.test.JenkinsRule; 7 | import org.jvnet.hudson.test.recipes.LocalData; 8 | 9 | import java.io.IOException; 10 | 11 | public class Security2376Test { 12 | @Rule 13 | public JenkinsRule j = new JenkinsRule(); 14 | 15 | @LocalData 16 | @Test(expected = SecurityException.class) 17 | public void testDeserialization() throws IOException, ClassNotFoundException { 18 | // coverage-report is just a serialized empty IdentityHashMap, not on the serialization allowlist as of Jenkins 2.303 19 | final FreeStyleProject fs = (FreeStyleProject) j.jenkins.getItemByFullName("fs"); 20 | CoverageProcessor.recoverCoverageResult(fs.getBuild("1")); 21 | // Without the fix, this fails with: 22 | // Caused by: java.lang.ClassCastException: java.util.IdentityHashMap cannot be cast to io.jenkins.plugins.coverage.targets.CoverageResult 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/test/resources/io/jenkins/plugins/coverage/metrics/steps/coverage-publisher-details.checks-expected-result: -------------------------------------------------------------------------------- 1 | #### Project coverage details 2 | 3 | ||Module Coverage|Package Coverage|File Coverage|Class Coverage|Method Coverage|Line Coverage|Branch Coverage|Instruction Coverage 4 | |:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---: 5 | :feet: **Overall project**|100.00% (1/1)|100.00% (4/4)|70.00% (7/10)|83.33% (15/18)|95.10% (97/102)|91.02% (294/323)|93.97% (109/116)|93.33% (1260/1350) 6 | :chart_with_upwards_trend: **Overall project (difference to reference job)**|+0.00%|-50.00% :arrow_down:|n/a|n/a|n/a|+50.00% :arrow_up:|n/a|n/a 7 | :feet: **Modified files**|n/a|n/a|n/a|n/a|n/a|50.00% (1/2)|n/a|n/a 8 | :chart_with_upwards_trend: **Modified files (difference to overall project)**|n/a|n/a|n/a|n/a|n/a|+50.00% :arrow_up:|n/a|n/a 9 | :feet: **Modified code lines**|n/a|n/a|n/a|n/a|n/a|50.00% (1/2)|n/a|n/a 10 | :chart_with_upwards_trend: **Modified code lines (difference to modified files)**|n/a|n/a|n/a|n/a|n/a|+50.00% :arrow_up:|n/a|n/a 11 | :feet: **Indirect changes**|n/a|n/a|n/a|n/a|n/a|50.00% (1/2)|n/a|n/a 12 | -------------------------------------------------------------------------------- /src/main/resources/io/jenkins/plugins/coverage/adapter/CoverageReportAdapterDescriptor/config.jelly: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 10 | 11 |
7 | 8 | 9 |
12 |
13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 |
26 | -------------------------------------------------------------------------------- /src/test/resources/io/jenkins/plugins/coverage/metrics/charts/chart.json: -------------------------------------------------------------------------------- 1 | { 2 | "domainAxisLabels": [ 3 | "#1", "#2" 4 | ], 5 | "buildNumbers": [ 6 | 1, 2 7 | ], 8 | "series": [ 9 | { 10 | "name": "Line Coverage", 11 | "type": "line", 12 | "symbol": "circle", 13 | "data": [ 14 | 50.0, 25.0 15 | ], 16 | "itemStyle": { 17 | "color": "--green", 18 | "borderColor": "#ffffff", 19 | "borderWidth": 0 20 | }, 21 | "stack": "", 22 | "areaStyle": { 23 | "normal": true 24 | } 25 | }, 26 | { 27 | "name": "Branch Coverage", 28 | "type": "line", 29 | "symbol": "circle", 30 | "data": [ 31 | 75.0, 25.0 32 | ], 33 | "itemStyle": { 34 | "color": "--dark-green", 35 | "borderColor": "#ffffff", 36 | "borderWidth": 0 37 | }, 38 | "stack": "", 39 | "areaStyle": { 40 | "normal": true 41 | } 42 | } 43 | ], 44 | "domainAxisItemName": "Build", 45 | "integerRangeAxis": false, 46 | "rangeMax": 100.0, 47 | "rangeMin": 25.0 48 | } 49 | -------------------------------------------------------------------------------- /src/test/resources/io/jenkins/plugins/coverage/metrics/steps/coverage-publisher-summary.checks-expected-result-pit: -------------------------------------------------------------------------------- 1 | #### Summary for modified lines 2 | - 1 line has been modified 3 | - all lines are covered 4 | - 1 mutation survived (of 3) 5 | #### Overview by baseline 6 | 7 | * **[Overall project (difference to reference job)](http://127.0.0.1:8080/job/pipeline-coding-style/job/5/coverage#overview)** 8 | * Line Coverage: 93.84% (198/211) - Delta: +50.00% 9 | * Mutation Coverage: 90.24% (222/246) 10 | * Lines of Code: 211 11 | * **[Modified files (difference to overall project)](http://127.0.0.1:8080/job/pipeline-coding-style/job/5/coverage#modifiedFilesCoverage)** 12 | * Line Coverage: 50.00% (1/2) - Delta: +50.00% 13 | * **[Modified code lines (difference to modified files)](http://127.0.0.1:8080/job/pipeline-coding-style/job/5/coverage#modifiedLinesCoverage)** 14 | * Line Coverage: 50.00% (1/2) - Delta: +50.00% 15 | * **[Indirect changes](http://127.0.0.1:8080/job/pipeline-coding-style/job/5/coverage#indirectCoverage)** 16 | * Line Coverage: 50.00% (1/2) 17 | 18 | #### Quality Gates Summary 19 | 20 | No active quality gates. 21 | -------------------------------------------------------------------------------- /src/main/java/io/jenkins/plugins/coverage/model/visualization/charts/CoverageSeriesBuilder.java: -------------------------------------------------------------------------------- 1 | package io.jenkins.plugins.coverage.model.visualization.charts; 2 | 3 | import java.util.HashMap; 4 | import java.util.Map; 5 | 6 | import edu.hm.hafner.echarts.SeriesBuilder; 7 | 8 | import io.jenkins.plugins.coverage.model.CoverageBuildAction; 9 | 10 | /** 11 | * Builds one x-axis point for the series of a line chart showing the line and branch coverage of a project. 12 | * 13 | * @author Ullrich Hafner 14 | */ 15 | public class CoverageSeriesBuilder extends SeriesBuilder { 16 | static final String LINE_COVERAGE = "line"; 17 | static final String BRANCH_COVERAGE = "branch"; 18 | 19 | @Override 20 | protected Map computeSeries(final CoverageBuildAction coverageBuildAction) { 21 | Map series = new HashMap<>(); 22 | 23 | series.put(LINE_COVERAGE, coverageBuildAction.getLineCoverage().getRoundedPercentage()); 24 | series.put(BRANCH_COVERAGE, coverageBuildAction.getBranchCoverage().getRoundedPercentage()); 25 | 26 | return series; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/test/resources/io/jenkins/plugins/coverage/Security2376Test/jobs/fs/builds/1/build.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | admin 9 | 10 | 1 11 | 12 | 13 | 14 | 15 | 2 16 | 1627289739464 17 | 1627289739514 18 | SUCCESS 19 | 52 20 | UTF-8 21 | false 22 | 23 | /var/jenkins_home/workspace/fs 24 | 2.303 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /.github/labels.yml: -------------------------------------------------------------------------------- 1 | - name: bug 2 | description: Bugs or performance problems 3 | color: CC0000 4 | - name: feature 5 | color: 1d76db 6 | description: New features 7 | - name: enhancement 8 | description: Enhancement of existing functionality 9 | color: 0366d6 10 | - name: breaking 11 | description: Breaking Changes 12 | color: e4b21d 13 | - name: tests 14 | description: Enhancement of tests 15 | color: 0e8a16 16 | - name: documentation 17 | description: Enhancement of documentation 18 | color: bfafea 19 | - name: internal 20 | description: Internal changes without user or API impact 21 | color: e6e6e6 22 | - name: dependencies 23 | description: Update of dependencies 24 | color: e6e6e6 25 | - name: java 26 | description: Pull requests that update Maven Java dependencies 27 | color: b6b6b6 28 | - name: javascript 29 | description: Pull requests that update Node Javascript dependencies 30 | color: b6b6b6 31 | - name: github_actions 32 | description: Pull requests that update Github Actions workflows 33 | color: 909090 34 | - name: hacktoberfest 35 | description: Pull requests that participate in Hacktoberfest 36 | color: 7057ff 37 | -------------------------------------------------------------------------------- /.github/release-drafter.yml: -------------------------------------------------------------------------------- 1 | name-template: 'v$RESOLVED_VERSION 🎁' 2 | tag-template: 'v$RESOLVED_VERSION' 3 | 4 | template: | 5 | $CHANGES 6 | 7 | # Emoji reference: https://gitmoji.carloscuesta.me/ 8 | categories: 9 | - title: 💥 Breaking Changes 10 | label: breaking 11 | - title: 🚀 New Features 12 | label: feature 13 | - title: ✨ Improvements 14 | label: enhancement 15 | - title: 🐛 Bug Fixes 16 | label: bug 17 | - title: 📝 Documentation 18 | label: documentation 19 | - title: 📦 Dependency Updates 20 | label: dependencies 21 | - title: 🔧 Internal Changes 22 | label: internal 23 | - title: 🚦 Tests 24 | label: tests 25 | 26 | version-resolver: 27 | major: 28 | labels: 29 | - 'removed' 30 | minor: 31 | labels: 32 | - 'feature' 33 | - 'enhancement' 34 | - 'deprecated' 35 | - 'dependencies' 36 | - 'documentation' 37 | - 'tests' 38 | - 'internal' 39 | patch: 40 | labels: 41 | - 'bug' 42 | default: minor 43 | 44 | replacers: 45 | - search: '/\[*JENKINS-(\d+)\]*\s*-*\s*/g' 46 | replace: '[JENKINS-$1](https://issues.jenkins.io/browse/JENKINS-$1) - ' 47 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Shenyu Zheng and other Jenkins contributors 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/test/resources/io/jenkins/plugins/coverage/metrics/steps/coverage-publisher-summary.checks-expected-result: -------------------------------------------------------------------------------- 1 | #### Summary for modified lines 2 | 3 | - 3 lines have been modified 4 | - 2 lines are not covered 5 | - 1 line is covered only partially 6 | 7 | #### Overview by baseline 8 | 9 | * **[Overall project (difference to reference job)](http://127.0.0.1:8080/job/pipeline-coding-style/job/5/coverage#overview)** 10 | * Line Coverage: 91.02% (294/323) - Delta: +50.00% 11 | * Branch Coverage: 93.97% (109/116) 12 | * Complexity Density: 0.50% 13 | * Lines of Code: 323 14 | * **[Modified files (difference to overall project)](http://127.0.0.1:8080/job/pipeline-coding-style/job/5/coverage#modifiedFilesCoverage)** 15 | * Line Coverage: 50.00% (1/2) - Delta: +50.00% 16 | * **[Modified code lines (difference to modified files)](http://127.0.0.1:8080/job/pipeline-coding-style/job/5/coverage#modifiedLinesCoverage)** 17 | * Line Coverage: 50.00% (1/2) - Delta: +50.00% 18 | * **[Indirect changes](http://127.0.0.1:8080/job/pipeline-coding-style/job/5/coverage#indirectCoverage)** 19 | * Line Coverage: 50.00% (1/2) 20 | 21 | #### Quality Gates Summary 22 | 23 | No active quality gates. 24 | -------------------------------------------------------------------------------- /src/test/java/io/jenkins/plugins/coverage/model/CoverageLeafTest.java: -------------------------------------------------------------------------------- 1 | package io.jenkins.plugins.coverage.model; 2 | 3 | import org.junit.jupiter.api.Test; 4 | 5 | import nl.jqno.equalsverifier.EqualsVerifier; 6 | 7 | import io.jenkins.plugins.coverage.model.Coverage.CoverageBuilder; 8 | 9 | import static io.jenkins.plugins.coverage.metrics.Assertions.*; 10 | 11 | /** 12 | * Tests the class {@link CoverageLeaf}. 13 | * 14 | * @author Ullrich Hafner 15 | */ 16 | class CoverageLeafTest extends AbstractCoverageTest { 17 | private static final Coverage COVERED = new Coverage.CoverageBuilder().setCovered(1).setMissed(0).build(); 18 | 19 | @Test 20 | void shouldCreateLeaf() { 21 | CoverageLeaf coverageLeaf = new CoverageLeaf(LINE, COVERED); 22 | 23 | assertThat(coverageLeaf).hasMetric(LINE).hasToString("[Line]: 100.00% (1/1)"); 24 | assertThat(coverageLeaf.getCoverage(LINE)).isEqualTo(COVERED); 25 | assertThat(coverageLeaf.getCoverage(CoverageMetric.MODULE)).isEqualTo(CoverageBuilder.NO_COVERAGE); 26 | } 27 | 28 | @Test 29 | void shouldObeyEqualsContract() { 30 | EqualsVerifier.forClass(CoverageLeaf.class).verify(); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /etc/assertj-templates/has_assertion_template.txt: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * Verifies that the actual ${class_to_assert}'s ${property} is equal to the given one. 4 | * @param ${property_safe} the given ${property} to compare the actual ${class_to_assert}'s ${property} to. 5 | * @return this assertion object. 6 | * @throws AssertionError - if the actual ${class_to_assert}'s ${property} is not equal to the given one.${throws_javadoc} 7 | */ 8 | public ${self_type} has${Property}(${propertyType} ${property_safe}) ${throws}{ 9 | // check that actual ${class_to_assert} we want to make assertions on is not null. 10 | isNotNull(); 11 | 12 | // overrides the default error message with a more explicit one 13 | String assertjErrorMessage = "\nExpecting ${property} of:\n <%s>\nto be:\n <%s>\nbut was:\n <%s>"; 14 | 15 | // null safe check 16 | ${propertyType} actual${Property} = actual.${getter}(); 17 | if (!java.util.Objects.deepEquals(actual${Property}, ${property_safe})) { 18 | failWithMessage(assertjErrorMessage, actual, ${property_safe}, actual${Property}); 19 | } 20 | 21 | // return the current assertion for method chaining 22 | return ${myself}; 23 | } 24 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: 'GitHub CI' 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | - main 8 | pull_request: 9 | 10 | jobs: 11 | build: 12 | 13 | strategy: 14 | matrix: 15 | platform: [ubuntu-latest, macos-latest, windows-latest] 16 | jdk: [17] 17 | include: 18 | - platform: ubuntu-latest 19 | jdk: 11 20 | - platform: macos-latest 21 | jdk: 11 22 | 23 | runs-on: ${{ matrix.platform }} 24 | name: on ${{ matrix.platform }} with JDK ${{ matrix.jdk }} 25 | 26 | steps: 27 | - uses: actions/checkout@v4 28 | - name: Set up Maven 29 | uses: stCarolas/setup-maven@v4.5 30 | with: 31 | maven-version: 3.9.1 32 | - name: Set up JDK ${{ matrix.jdk }} 33 | uses: actions/setup-java@v3 34 | with: 35 | distribution: 'temurin' 36 | java-version: '${{ matrix.jdk }}' 37 | check-latest: true 38 | cache: 'maven' 39 | - name: Build with Maven 40 | env: 41 | BROWSER: chrome-container 42 | run: mvn -V --color always -ntp clean verify --file pom.xml '-Djenkins.test.timeout=5000' '-Dgpg.skip' 43 | -------------------------------------------------------------------------------- /src/main/java/io/jenkins/plugins/coverage/adapter/JSONCoverageReportAdapter.java: -------------------------------------------------------------------------------- 1 | package io.jenkins.plugins.coverage.adapter; 2 | 3 | import com.fasterxml.jackson.databind.ObjectMapper; 4 | import io.jenkins.plugins.coverage.adapter.converter.JSONDocumentConverter; 5 | import io.jenkins.plugins.coverage.exception.CoverageException; 6 | import org.w3c.dom.Document; 7 | 8 | import java.io.File; 9 | import java.io.IOException; 10 | 11 | public abstract class JSONCoverageReportAdapter extends CoverageReportAdapter { 12 | 13 | /** 14 | * @param path Ant-style path of report files. 15 | */ 16 | public JSONCoverageReportAdapter(String path) { 17 | super(path); 18 | } 19 | 20 | @Override 21 | protected Document convert(File source) throws CoverageException { 22 | try { 23 | return getConverter().convert(new ObjectMapper().readTree(source)); 24 | } catch (IOException e) { 25 | e.printStackTrace(); 26 | throw new CoverageException(e); 27 | } 28 | } 29 | 30 | 31 | /** 32 | * @return converter which convert JSONObject to DOM Document. 33 | */ 34 | protected abstract JSONDocumentConverter getConverter(); 35 | 36 | } 37 | -------------------------------------------------------------------------------- /src/main/java/io/jenkins/plugins/coverage/CoverageElementInitializer.java: -------------------------------------------------------------------------------- 1 | package io.jenkins.plugins.coverage; 2 | 3 | import hudson.DescriptorExtensionList; 4 | import hudson.init.InitMilestone; 5 | import hudson.init.Initializer; 6 | 7 | import io.jenkins.plugins.coverage.adapter.CoverageAdapter; 8 | import io.jenkins.plugins.coverage.adapter.CoverageAdapterDescriptor; 9 | import io.jenkins.plugins.coverage.adapter.CoverageReportAdapterDescriptor; 10 | import io.jenkins.plugins.coverage.targets.CoverageElementRegister; 11 | 12 | @SuppressWarnings("unchecked") 13 | public class CoverageElementInitializer { 14 | 15 | @Initializer(after = InitMilestone.PLUGINS_STARTED) 16 | @SuppressWarnings("unused") 17 | public static void registerAllCoverageElement() { 18 | DescriptorExtensionList> reportDescriptors = CoverageAdapterDescriptor.all(); 19 | 20 | reportDescriptors.stream() 21 | .filter(d -> d instanceof CoverageReportAdapterDescriptor) 22 | .map(d -> (CoverageReportAdapterDescriptor) d) 23 | .forEach(d -> CoverageElementRegister.addCoverageElements(d.getCoverageElementType(), d.getCoverageElements())); 24 | 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/main/java/io/jenkins/plugins/coverage/model/visualization/colorization/CoverageColorJenkinsId.java: -------------------------------------------------------------------------------- 1 | package io.jenkins.plugins.coverage.model.visualization.colorization; 2 | 3 | import java.util.Arrays; 4 | import java.util.Set; 5 | import java.util.stream.Collectors; 6 | 7 | /** 8 | * Contains color IDs which represent the keys of a JSON object that is dynamically filled with the currently set 9 | * Jenkins colors. 10 | * 11 | * @author Florian Orendi 12 | */ 13 | public enum CoverageColorJenkinsId { 14 | 15 | GREEN("--green"), 16 | LIGHT_GREEN("--light-green"), 17 | YELLOW("--yellow"), 18 | LIGHT_YELLOW("--light-yellow"), 19 | ORANGE("--orange"), 20 | LIGHT_ORANGE("--light-orange"), 21 | RED("--red"), 22 | LIGHT_RED("--light-red"); 23 | 24 | private final String jenkinsColorId; 25 | 26 | CoverageColorJenkinsId(final String colorId) { 27 | this.jenkinsColorId = colorId; 28 | } 29 | 30 | public String getJenkinsColorId() { 31 | return jenkinsColorId; 32 | } 33 | 34 | public static Set getAll() { 35 | return Arrays.stream(values()) 36 | .map(id -> id.jenkinsColorId) 37 | .collect(Collectors.toSet()); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/main/java/io/jenkins/plugins/coverage/targets/CoverageAggregationRule.java: -------------------------------------------------------------------------------- 1 | package io.jenkins.plugins.coverage.targets; 2 | 3 | 4 | import java.util.Map; 5 | import java.util.TreeMap; 6 | 7 | 8 | public class CoverageAggregationRule { 9 | 10 | public static Map aggregate(CoverageElement source, 11 | CoverageElement input, 12 | Ratio inputResult, 13 | Map runningTotal) { 14 | Map result = new TreeMap<>(runningTotal); 15 | 16 | Ratio prevTotal = result.get(input); 17 | if (prevTotal == null) { 18 | prevTotal = Ratio.create(0, 0); 19 | } 20 | 21 | Ratio r = Ratio.create(inputResult.numerator + prevTotal.numerator, inputResult.denominator + prevTotal.denominator); 22 | result.put(input, r); 23 | 24 | return result; 25 | } 26 | 27 | 28 | public static Ratio combine(CoverageElement element, Ratio existingResult, Ratio additionalResult) { 29 | return Ratio.create(existingResult.numerator + additionalResult.numerator, existingResult.denominator + additionalResult.denominator); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/io/jenkins/plugins/coverage/adapter/JavaCoverageReportAdapterDescriptor.java: -------------------------------------------------------------------------------- 1 | package io.jenkins.plugins.coverage.adapter; 2 | 3 | import java.util.List; 4 | 5 | import com.google.common.collect.Lists; 6 | 7 | import io.jenkins.plugins.coverage.targets.CoverageElement; 8 | 9 | public class JavaCoverageReportAdapterDescriptor extends CoverageReportAdapterDescriptor { 10 | /** A Java package. */ 11 | public static final CoverageElement PACKAGE = new CoverageElement("Package", 1); 12 | /** A Java class. */ 13 | public static final CoverageElement CLASS = new CoverageElement("Class", 3); 14 | /** A Java method. */ 15 | public static final CoverageElement METHOD = new CoverageElement("Method", 4); 16 | 17 | public JavaCoverageReportAdapterDescriptor(final Class clazz) { 18 | super(clazz); 19 | } 20 | 21 | @Override 22 | public List getCoverageElements() { 23 | return Lists.newArrayList(new CoverageElement("Group", 0), 24 | PACKAGE, 25 | CoverageElement.FILE, 26 | CLASS, 27 | METHOD); 28 | } 29 | 30 | @Override 31 | public String getCoverageElementType() { 32 | return CoverageElement.COVERAGE_ELEMENT_TYPE_JAVA; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/main/java/io/jenkins/plugins/coverage/targets/CoverageTrendTree.java: -------------------------------------------------------------------------------- 1 | package io.jenkins.plugins.coverage.targets; 2 | 3 | import org.kohsuke.stapler.export.Exported; 4 | import org.kohsuke.stapler.export.ExportedBean; 5 | 6 | import java.util.LinkedList; 7 | import java.util.List; 8 | import java.util.Map; 9 | 10 | @ExportedBean 11 | public class CoverageTrendTree { 12 | 13 | private String name; 14 | private List trends; 15 | private Map children; 16 | 17 | public CoverageTrendTree(String name, List trends, Map children) { 18 | this.name = name; 19 | this.trends = trends; 20 | this.children = children; 21 | } 22 | 23 | @Exported 24 | public String getName() { 25 | return name; 26 | } 27 | 28 | @Exported 29 | public List getTrends() { 30 | return trends; 31 | } 32 | 33 | @Exported 34 | public List getChildren() { 35 | List childrenTrends = new LinkedList<>(); 36 | 37 | for (Map.Entry e : children.entrySet()) { 38 | childrenTrends.add(new CoverageTrendTree(e.getKey(), e.getValue().getCoverageTrends(), e.getValue().getChildrenReal())); 39 | } 40 | return childrenTrends; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/test/resources/io/jenkins/plugins/coverage/metrics/steps/coverage-publisher-quality-gate.checks-expected-result: -------------------------------------------------------------------------------- 1 | #### Quality Gates Summary 2 | 3 | Overall result: Failed 4 | - Overall project - File Coverage: ≪Failed≫ - (Actual value: 75.00%, Quality gate: 76.00) 5 | - Overall project - Line Coverage: ≪Failed≫ - (Actual value: 50.00%, Quality gate: 51.00) 6 | - Modified code lines - File Coverage: ≪Failed≫ - (Actual value: 75.00%, Quality gate: 76.00) 7 | - Modified code lines - Line Coverage: ≪Failed≫ - (Actual value: 50.00%, Quality gate: 51.00) 8 | - Modified files - File Coverage: ≪Failed≫ - (Actual value: 75.00%, Quality gate: 76.00) 9 | - Modified files - Line Coverage: ≪Failed≫ - (Actual value: 50.00%, Quality gate: 51.00) 10 | - Overall project (difference to reference job) - File Coverage: ≪Failed≫ - (Actual value: -10.00%, Quality gate: 10.00) 11 | - Overall project (difference to reference job) - Line Coverage: ≪Failed≫ - (Actual value: +5.00%, Quality gate: 10.00) 12 | - Modified code lines (difference to modified files) - File Coverage: ≪Failed≫ - (Actual value: -10.00%, Quality gate: 10.00) 13 | - Modified code lines (difference to modified files) - Line Coverage: ≪Failed≫ - (Actual value: +5.00%, Quality gate: 10.00) 14 | - Modified files (difference to overall project) - File Coverage: ≪Failed≫ - (Actual value: -10.00%, Quality gate: 10.00) 15 | - Modified files (difference to overall project) - Line Coverage: ≪Failed≫ - (Actual value: +5.00%, Quality gate: 10.00) 16 | -------------------------------------------------------------------------------- /src/test/resources/io/jenkins/plugins/coverage/model/cobertura-lower-coverage.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | /Users/leobalter/dev/testing/solutions/3 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /src/test/resources/io/jenkins/plugins/coverage/metrics/steps/cobertura-lower-coverage.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | /Users/leobalter/dev/testing/solutions/3 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /src/test/java/io/jenkins/plugins/coverage/model/visualization/colorization/CoverageChangeTendencyTest.java: -------------------------------------------------------------------------------- 1 | package io.jenkins.plugins.coverage.model.visualization.colorization; 2 | 3 | import org.junit.jupiter.api.Test; 4 | 5 | import static org.assertj.core.api.Assertions.*; 6 | 7 | /** 8 | * Test class for {@link CoverageChangeTendency}. 9 | * 10 | * @author Florian Orendi 11 | */ 12 | class CoverageChangeTendencyTest { 13 | 14 | private static final ColorProvider COLOR_PROVIDER = ColorProviderFactory.createDefaultColorProvider(); 15 | 16 | @Test 17 | void shouldGetDisplayColorsForTendency() { 18 | assertThat(CoverageChangeTendency.getDisplayColorsForTendency(1.0, COLOR_PROVIDER)) 19 | .isEqualTo(COLOR_PROVIDER.getDisplayColorsOf(ColorId.EXCELLENT)); 20 | assertThat(CoverageChangeTendency.getDisplayColorsForTendency(0.0, COLOR_PROVIDER)) 21 | .isEqualTo(COLOR_PROVIDER.getDisplayColorsOf(ColorId.AVERAGE)); 22 | assertThat(CoverageChangeTendency.getDisplayColorsForTendency(-1.0, COLOR_PROVIDER)) 23 | .isEqualTo(COLOR_PROVIDER.getDisplayColorsOf(ColorId.INSUFFICIENT)); 24 | assertThat(CoverageChangeTendency.getDisplayColorsForTendency(null, COLOR_PROVIDER)) 25 | .isEqualTo(COLOR_PROVIDER.getDisplayColorsOf(ColorId.WHITE)); 26 | } 27 | 28 | @Test 29 | void shouldGetColorizationId() { 30 | assertThat(CoverageChangeTendency.INCREASED.getColorizationId()).isEqualTo(ColorId.EXCELLENT); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/main/resources/io/jenkins/plugins/coverage/model/SourceViewModel/index.jelly: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
11 | 12 |
13 |
14 | 15 | 16 | 17 |
18 | 19 | 20 |
21 |
22 |
23 | 24 |

25 | ${%Source code is unavailable.} 26 | ${%Some possible reasons are:} 27 |

    28 |
  • ${%reason.1}
  • 29 |
  • ${%reason.2}
  • 30 |
  • ${%reason.3}
  • 31 |
32 |

33 |
34 |
35 | 36 |
37 |
38 |
39 | 40 |
41 | 42 | 43 | 44 |
45 | 46 |
47 | -------------------------------------------------------------------------------- /src/test/resources/io/jenkins/plugins/coverage/model/AcuCobolParser.java.txt: -------------------------------------------------------------------------------- 1 | package edu.hm.hafner.analysis.parser; 2 | 3 | import java.util.Optional; 4 | import java.util.regex.Matcher; 5 | 6 | import edu.hm.hafner.analysis.Issue; 7 | import edu.hm.hafner.analysis.IssueBuilder; 8 | import edu.hm.hafner.analysis.LookaheadParser; 9 | import edu.hm.hafner.util.LookaheadStream; 10 | 11 | import static edu.hm.hafner.analysis.Categories.*; 12 | 13 | /** 14 | * A parser for the Acu Cobol compile. 15 | * 16 | * @author jerryshea 17 | */ 18 | public class AcuCobolParser extends LookaheadParser { 19 | private static final long serialVersionUID = -894639209290549425L; 20 | 21 | private static final String ACU_COBOL_WARNING_PATTERN = "^\\s*(\\[.*\\])?\\s*?(.*), line ([0-9]*): Warning: (.*)$"; 22 | 23 | /** 24 | * Creates a new instance of {@link AcuCobolParser}. 25 | */ 26 | public AcuCobolParser() { 27 | super(ACU_COBOL_WARNING_PATTERN); 28 | } 29 | 30 | @Override 31 | protected boolean isLineInteresting(final String line) { 32 | return line.contains("Warning"); 33 | } 34 | 35 | @Override 36 | protected Optional createIssue(final Matcher matcher, final LookaheadStream lookahead, 37 | final IssueBuilder builder) { 38 | return builder.setFileName(matcher.group(2)) 39 | .setLineStart(matcher.group(3)) 40 | .setCategory(guessCategory(matcher.group(4))) 41 | .setMessage(matcher.group(4)) 42 | .buildOptional(); 43 | } 44 | } 45 | 46 | -------------------------------------------------------------------------------- /src/test/resources/io/jenkins/plugins/coverage/metrics/source/AcuCobolParser.java.txt: -------------------------------------------------------------------------------- 1 | package edu.hm.hafner.analysis.parser; 2 | 3 | import java.util.Optional; 4 | import java.util.regex.Matcher; 5 | 6 | import edu.hm.hafner.analysis.Issue; 7 | import edu.hm.hafner.analysis.IssueBuilder; 8 | import edu.hm.hafner.analysis.LookaheadParser; 9 | import edu.hm.hafner.util.LookaheadStream; 10 | 11 | import static edu.hm.hafner.analysis.Categories.*; 12 | 13 | /** 14 | * A parser for the Acu Cobol compile. 15 | * 16 | * @author jerryshea 17 | */ 18 | public class AcuCobolParser extends LookaheadParser { 19 | private static final long serialVersionUID = -894639209290549425L; 20 | 21 | private static final String ACU_COBOL_WARNING_PATTERN = "^\\s*(\\[.*\\])?\\s*?(.*), line ([0-9]*): Warning: (.*)$"; 22 | 23 | /** 24 | * Creates a new instance of {@link AcuCobolParser}. 25 | */ 26 | public AcuCobolParser() { 27 | super(ACU_COBOL_WARNING_PATTERN); 28 | } 29 | 30 | @Override 31 | protected boolean isLineInteresting(final String line) { 32 | return line.contains("Warning"); 33 | } 34 | 35 | @Override 36 | protected Optional createIssue(final Matcher matcher, final LookaheadStream lookahead, 37 | final IssueBuilder builder) { 38 | return builder.setFileName(matcher.group(2)) 39 | .setLineStart(matcher.group(3)) 40 | .setCategory(guessCategory(matcher.group(4))) 41 | .setMessage(matcher.group(4)) 42 | .buildOptional(); 43 | } 44 | } 45 | 46 | -------------------------------------------------------------------------------- /etc/Jenkinsfile.analysis: -------------------------------------------------------------------------------- 1 | node('java-agent') { 2 | stage ('Checkout') { 3 | checkout scm 4 | } 5 | 6 | stage ('Git mining') { 7 | discoverGitReferenceBuild() 8 | mineRepository() 9 | gitDiffStat() 10 | } 11 | 12 | stage ('Build, Test, and Static Analysis') { 13 | withMaven(mavenLocalRepo: '/var/data/m2repository', mavenOpts: '-Xmx768m -Xms512m') { 14 | sh 'mvn -V -e clean verify -Dmaven.test.failure.ignore -Dgpg.skip' 15 | } 16 | 17 | recordIssues tools: [java(), javaDoc()], aggregatingResults: 'true', id: 'java', name: 'Java', filters:[excludeFile('.*Assert.java')] 18 | recordIssues tool: errorProne(), healthy: 1, unhealthy: 20 19 | 20 | junit testResults: '**/target/*-reports/TEST-*.xml' 21 | recordCoverage tools: [[parser: 'JACOCO', pattern: '**/jacoco/jacoco.xml']], sourceCodeRetention: 'EVERY_BUILD', 22 | qualityGates: [ [threshold: 90.0, metric: 'LINE', baseline: 'PROJECT', criticality: 'UNSTABLE']], 23 | sourceDirectories: [[path: 'plugin/src/main/java']] 24 | recordIssues tools: [checkStyle(pattern: 'target/checkstyle-result.xml'), 25 | spotBugs(pattern: 'target/spotbugsXml.xml'), 26 | pmdParser(pattern: 'target/pmd.xml'), 27 | cpd(pattern: 'target/cpd.xml'), 28 | revApi(pattern: 'target/revapi-result.json'), 29 | taskScanner(highTags:'FIXME', normalTags:'TODO', includePattern: '**/*.java', excludePattern: 'target/**/*')] 30 | } 31 | 32 | stage ('Collect Maven Warnings') { 33 | recordIssues tool: mavenConsole() 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/main/resources/io/jenkins/plugins/coverage/CoveragePublisher/config.jelly: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /src/main/java/io/jenkins/plugins/coverage/targets/HasName.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2007-2018 manolo, Seiji Sogabe, Michael Barrientos and Jenkins contributors 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | * THE SOFTWARE. 21 | */ 22 | package io.jenkins.plugins.coverage.targets; 23 | 24 | // Code adopted from Cobertura Plugin https://github.com/jenkinsci/cobertura-plugin/ 25 | /** 26 | * Interface for elements which have a display name 27 | * 28 | * @author manolo 29 | * @since 20-Mar-2010 08:59:50 30 | */ 31 | public interface HasName { 32 | 33 | public String getName(); 34 | } 35 | -------------------------------------------------------------------------------- /src/main/java/io/jenkins/plugins/coverage/targets/Chartable.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2007-2018 Michael Barrientos, Seiji Sogabe and Jenkins contributors 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | * THE SOFTWARE. 21 | */ 22 | package io.jenkins.plugins.coverage.targets; 23 | 24 | import hudson.model.Run; 25 | 26 | 27 | import java.util.Map; 28 | 29 | // Code adopted from Cobertura Plugin https://github.com/jenkinsci/cobertura-plugin/ 30 | public interface Chartable { 31 | 32 | Chartable getPreviousResult(); 33 | 34 | Map getResults(); 35 | 36 | Run getOwner(); 37 | 38 | } 39 | -------------------------------------------------------------------------------- /src/main/resources/io/jenkins/plugins/coverage/model/visualization/dashboard/CoverageColumn/column.jelly: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 28 | 29 | 30 | 31 | 32 | ${coverageText} 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /src/test/resources/io/jenkins/plugins/coverage/model/cobertura-higher-coverage.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | /Users/leobalter/dev/testing/solutions/3 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /src/test/resources/io/jenkins/plugins/coverage/metrics/steps/cobertura-higher-coverage.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | /Users/leobalter/dev/testing/solutions/3 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /src/test/resources/io/jenkins/plugins/coverage/model/cobertura-package-root.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | /home/user/dev/python/package 7 | 8 | 9 | 10 | 11 | 12 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /src/main/java/io/jenkins/plugins/coverage/detector/AntPathReportDetector.java: -------------------------------------------------------------------------------- 1 | package io.jenkins.plugins.coverage.detector; 2 | 3 | import edu.umd.cs.findbugs.annotations.NonNull; 4 | import hudson.Extension; 5 | import hudson.FilePath; 6 | import hudson.model.Run; 7 | import hudson.model.TaskListener; 8 | import io.jenkins.plugins.coverage.adapter.CoverageAdapterDescriptor; 9 | import io.jenkins.plugins.coverage.exception.CoverageException; 10 | import org.jenkinsci.Symbol; 11 | import org.kohsuke.stapler.DataBoundConstructor; 12 | 13 | import java.io.IOException; 14 | import java.util.Arrays; 15 | import java.util.List; 16 | 17 | public class AntPathReportDetector extends ReportDetector { 18 | 19 | private String path; 20 | 21 | @DataBoundConstructor 22 | public AntPathReportDetector(String path) { 23 | this.path = path; 24 | } 25 | 26 | @Override 27 | public List findFiles(Run run, FilePath workspace, TaskListener listener) throws CoverageException { 28 | try { 29 | return Arrays.asList(workspace.list(path)); 30 | } catch (IOException | InterruptedException e) { 31 | throw new CoverageException(e); 32 | } 33 | } 34 | 35 | public String getPath() { 36 | return path; 37 | } 38 | 39 | @Symbol("antPath") 40 | @Extension 41 | public static final class AntPathReportDetectorDescriptor extends CoverageAdapterDescriptor { 42 | 43 | public AntPathReportDetectorDescriptor() { 44 | super(AntPathReportDetector.class); 45 | } 46 | 47 | @NonNull 48 | @Override 49 | public String getDisplayName() { 50 | return Messages.AntPathDetector_displayName(); 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/main/java/io/jenkins/plugins/coverage/model/visualization/dashboard/ProjectCoverage.java: -------------------------------------------------------------------------------- 1 | package io.jenkins.plugins.coverage.model.visualization.dashboard; 2 | 3 | import java.util.Locale; 4 | import java.util.Optional; 5 | 6 | import io.jenkins.plugins.coverage.model.CoverageBuildAction; 7 | import io.jenkins.plugins.coverage.model.CoverageMetric; 8 | import io.jenkins.plugins.coverage.model.CoveragePercentage; 9 | import io.jenkins.plugins.coverage.model.Messages; 10 | import io.jenkins.plugins.coverage.model.visualization.colorization.ColorProvider.DisplayColors; 11 | import io.jenkins.plugins.coverage.model.visualization.colorization.CoverageLevel; 12 | 13 | /** 14 | * Concrete implementation of {@link CoverageColumnType} which represents the project coverage. 15 | * 16 | * @author Florian Orendi 17 | */ 18 | public class ProjectCoverage extends CoverageColumnType { 19 | 20 | /** 21 | * Creates a column type to be used for representing the project coverage. 22 | */ 23 | public ProjectCoverage() { 24 | super(Messages._Project_Coverage_Type()); 25 | } 26 | 27 | @Override 28 | public Optional getCoverage(final CoverageBuildAction action, final CoverageMetric metric) { 29 | if (action.hasCoverage(metric)) { 30 | return Optional.of(action.getCoverage(metric).getCoveredPercentage()); 31 | } 32 | return Optional.empty(); 33 | } 34 | 35 | @Override 36 | public DisplayColors getDisplayColors(final CoveragePercentage coverage) { 37 | return CoverageLevel.getDisplayColorsOfCoverageLevel(coverage.getDoubleValue(), getColorProvider()); 38 | } 39 | 40 | @Override 41 | public String formatCoverage(final CoveragePercentage coverage, final Locale locale) { 42 | return coverage.formatPercentage(locale); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/main/java/io/jenkins/plugins/coverage/model/visualization/dashboard/ProjectCoverageDelta.java: -------------------------------------------------------------------------------- 1 | package io.jenkins.plugins.coverage.model.visualization.dashboard; 2 | 3 | import java.util.Locale; 4 | import java.util.Optional; 5 | 6 | import io.jenkins.plugins.coverage.model.CoverageBuildAction; 7 | import io.jenkins.plugins.coverage.model.CoverageMetric; 8 | import io.jenkins.plugins.coverage.model.CoveragePercentage; 9 | import io.jenkins.plugins.coverage.model.Messages; 10 | import io.jenkins.plugins.coverage.model.visualization.colorization.ColorProvider.DisplayColors; 11 | import io.jenkins.plugins.coverage.model.visualization.colorization.CoverageChangeTendency; 12 | 13 | /** 14 | * Concrete implementation of {@link CoverageColumnType} which represents the project coverage delta. 15 | * 16 | * @author Florian Orendi 17 | */ 18 | public class ProjectCoverageDelta extends CoverageColumnType { 19 | 20 | /** 21 | * Creates a column type to be used for representing the project coverage delta. 22 | */ 23 | public ProjectCoverageDelta() { 24 | super(Messages._Project_Coverage_Delta_Type()); 25 | } 26 | 27 | @Override 28 | public Optional getCoverage(final CoverageBuildAction action, final CoverageMetric metric) { 29 | if (action.hasDelta(metric)) { 30 | return Optional.of(action.getDifference().get(metric)); 31 | } 32 | return Optional.empty(); 33 | } 34 | 35 | @Override 36 | public DisplayColors getDisplayColors(final CoveragePercentage coverage) { 37 | return CoverageChangeTendency.getDisplayColorsForTendency(coverage.getDoubleValue(), getColorProvider()); 38 | } 39 | 40 | @Override 41 | public String formatCoverage(final CoveragePercentage coverage, final Locale locale) { 42 | return coverage.formatDeltaPercentage(locale); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/test/java/io/jenkins/plugins/coverage/model/SafeFractionTest.java: -------------------------------------------------------------------------------- 1 | package io.jenkins.plugins.coverage.model; 2 | 3 | import org.apache.commons.lang3.math.Fraction; 4 | import org.junit.jupiter.api.Test; 5 | 6 | import static org.assertj.core.api.Assertions.*; 7 | 8 | /** 9 | * Tests the class {@link SafeFraction}. 10 | * 11 | * @author Ullrich Hafner 12 | */ 13 | class SafeFractionTest { 14 | @Test 15 | void shouldDelegateToFraction() { 16 | Fraction ten = Fraction.getFraction(10, 1); 17 | SafeFraction safeFraction = new SafeFraction(ten); 18 | assertThat(safeFraction.multiplyBy(ten).doubleValue()).isEqualTo(100.0); 19 | assertThat(safeFraction.subtract(ten).doubleValue()).isEqualTo(0); 20 | assertThat(safeFraction.add(ten).doubleValue()).isEqualTo(20.0); 21 | } 22 | 23 | @Test 24 | void shouldHandleOverflowForMultiply() { 25 | Fraction fraction = Fraction.getFraction(Integer.MAX_VALUE - 1, Integer.MAX_VALUE - 1); 26 | SafeFraction safeFraction = new SafeFraction(fraction); 27 | assertThat(safeFraction.multiplyBy(Fraction.getFraction("100.0")).doubleValue()).isEqualTo(100.0); 28 | } 29 | 30 | @Test 31 | void shouldHandleOverflowForSubtract() { 32 | Fraction fraction = Fraction.getFraction(Integer.MAX_VALUE - 1, Integer.MAX_VALUE - 1); 33 | SafeFraction safeFraction = new SafeFraction(fraction); 34 | assertThat(safeFraction.subtract(Fraction.getFraction("100.0")).doubleValue()).isEqualTo(-99.0); 35 | } 36 | 37 | @Test 38 | void shouldHandleOverflowForAdd() { 39 | Fraction fraction = Fraction.getFraction(Integer.MAX_VALUE - 1, Integer.MAX_VALUE - 1); 40 | SafeFraction safeFraction = new SafeFraction(fraction); 41 | assertThat(safeFraction.add(Fraction.getFraction("100.0")).doubleValue()).isEqualTo(101.0); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/test/java/io/jenkins/plugins/coverage/model/DeclarativePipelineSupportITest.java: -------------------------------------------------------------------------------- 1 | package io.jenkins.plugins.coverage.model; 2 | 3 | import org.junit.jupiter.api.Test; 4 | 5 | import org.jenkinsci.plugins.workflow.cps.CpsFlowDefinition; 6 | import org.jenkinsci.plugins.workflow.job.WorkflowJob; 7 | import hudson.model.Run; 8 | 9 | import io.jenkins.plugins.util.IntegrationTestWithJenkinsPerSuite; 10 | 11 | import static org.assertj.core.api.Assertions.*; 12 | 13 | /** 14 | * Integration test to check if declarative pipelines are supported. 15 | */ 16 | class DeclarativePipelineSupportITest extends IntegrationTestWithJenkinsPerSuite { 17 | private static final String JACOCO_ANALYSIS_MODEL_XML = "jacoco-analysis-model.xml"; 18 | 19 | /** 20 | * Check if code coverage is supported in declarative pipelines. 21 | */ 22 | @Test 23 | void declarativePipelineSupportJacoco() { 24 | WorkflowJob job = createPipelineWithWorkspaceFiles(JACOCO_ANALYSIS_MODEL_XML); 25 | 26 | job.setDefinition(new CpsFlowDefinition("pipeline {\n" 27 | + " agent any\n" 28 | + " stages {\n" 29 | + " stage('Test') {\n" 30 | + " steps {\n" 31 | + " publishCoverage(\n" 32 | + " adapters: [\n" 33 | + " jacocoAdapter('" + JACOCO_ANALYSIS_MODEL_XML + "')\n" 34 | + " ],\n" 35 | + " )}\n" 36 | + " }\n" 37 | + " }\n" 38 | + "}", true)); 39 | Run build = buildSuccessfully(job); 40 | assertThat(build.getAction(CoverageBuildAction.class).getLineCoverage()) 41 | .isEqualTo(new Coverage.CoverageBuilder().setCovered(6083).setMissed(6368 - 6083).build()); 42 | } 43 | } 44 | 45 | 46 | -------------------------------------------------------------------------------- /src/main/java/io/jenkins/plugins/coverage/model/visualization/colorization/CoverageChangeTendency.java: -------------------------------------------------------------------------------- 1 | package io.jenkins.plugins.coverage.model.visualization.colorization; 2 | 3 | import edu.umd.cs.findbugs.annotations.NonNull; 4 | 5 | import io.jenkins.plugins.coverage.model.visualization.colorization.ColorProvider.DisplayColors; 6 | 7 | /** 8 | * Provides the colorization for different coverage change tendencies. 9 | * 10 | * @author Florian Orendi 11 | */ 12 | public enum CoverageChangeTendency { 13 | 14 | INCREASED(ColorId.EXCELLENT), 15 | EQUALS(ColorId.AVERAGE), 16 | DECREASED(ColorId.INSUFFICIENT), 17 | NA(ColorId.WHITE); 18 | 19 | private final ColorId colorizationId; 20 | 21 | CoverageChangeTendency(final ColorId colorizationId) { 22 | this.colorizationId = colorizationId; 23 | } 24 | 25 | /** 26 | * Provides the {@link DisplayColors display colors} which match with the passed coverage change tendency. 27 | * 28 | * @param change 29 | * The coverage change 30 | * @param colorProvider 31 | * The {@link ColorProvider color provider} to be used 32 | * 33 | * @return the matching change level 34 | */ 35 | public static DisplayColors getDisplayColorsForTendency(final Double change, 36 | @NonNull final ColorProvider colorProvider) { 37 | ColorId colorId; 38 | if (change == null || change.isNaN()) { 39 | colorId = NA.colorizationId; 40 | } 41 | else if (change > 0) { 42 | colorId = INCREASED.colorizationId; 43 | } 44 | else if (change < 0) { 45 | colorId = DECREASED.colorizationId; 46 | } 47 | else { 48 | colorId = EQUALS.colorizationId; 49 | } 50 | return colorProvider.getDisplayColorsOf(colorId); 51 | } 52 | 53 | public ColorId getColorizationId() { 54 | return colorizationId; 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/main/java/io/jenkins/plugins/coverage/model/visualization/dashboard/ChangeCoverage.java: -------------------------------------------------------------------------------- 1 | package io.jenkins.plugins.coverage.model.visualization.dashboard; 2 | 3 | import java.util.Locale; 4 | import java.util.Optional; 5 | 6 | import io.jenkins.plugins.coverage.model.CoverageBuildAction; 7 | import io.jenkins.plugins.coverage.model.CoverageMetric; 8 | import io.jenkins.plugins.coverage.model.CoveragePercentage; 9 | import io.jenkins.plugins.coverage.model.Messages; 10 | import io.jenkins.plugins.coverage.model.visualization.colorization.ColorProvider.DisplayColors; 11 | import io.jenkins.plugins.coverage.model.visualization.colorization.CoverageLevel; 12 | 13 | /** 14 | * Concrete implementation of {@link CoverageColumnType} which represents the change coverage. 15 | * 16 | * @author Florian Orendi 17 | */ 18 | public class ChangeCoverage extends CoverageColumnType { 19 | 20 | /** 21 | * Creates a column type to be used for representing the change coverage. 22 | */ 23 | public ChangeCoverage() { 24 | super(Messages._Change_Coverage_Type()); 25 | } 26 | 27 | @Override 28 | public Optional getCoverage(final CoverageBuildAction action, final CoverageMetric metric) { 29 | if (action.hasChangeCoverage(metric)) { 30 | return Optional.of(action.getChangeCoverage(metric).getCoveredPercentage()); 31 | } 32 | return Optional.empty(); 33 | } 34 | 35 | @Override 36 | public DisplayColors getDisplayColors(final CoveragePercentage coverage) { 37 | return CoverageLevel.getDisplayColorsOfCoverageLevel(coverage.getDoubleValue(), getColorProvider()); 38 | } 39 | 40 | @Override 41 | public String formatCoverage(final CoveragePercentage coverage, final Locale locale) { 42 | return coverage.formatPercentage(locale); 43 | } 44 | 45 | @Override 46 | public String getAnchor() { 47 | return "#changeCoverage"; 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/main/webapp/css/style.css: -------------------------------------------------------------------------------- 1 | table.source { 2 | border-spacing: 0; 3 | border-collapse: collapse; 4 | border: 1px solid #bbb; 5 | width: 100%; 6 | } 7 | 8 | table.source td { 9 | border-spacing: 0; 10 | border-collapse: collapse; 11 | border: 0 solid #bbb; 12 | padding-left: 0.2em; 13 | padding-right: 0.2em; 14 | font-family: monospace; 15 | } 16 | 17 | table.source .text { 18 | margin-top: -1px; 19 | } 20 | 21 | table.source td.line { 22 | text-align: right; 23 | border-width: 0 1px 0 0; 24 | } 25 | 26 | table.source td.hits { 27 | text-align: right; 28 | border-width: 0px 1px 0px 0px; 29 | } 30 | 31 | table.source th { 32 | padding-left: 0.5em; 33 | font-weight: bold; 34 | font-family: serif; 35 | background-color: #f0f0f0; 36 | border-width: 1px 1px 1px 1px; 37 | text-align: left; 38 | } 39 | 40 | table.source tr.coverFull { 41 | background-color: #dfd; 42 | } 43 | 44 | table.source tr.coverPart { 45 | background-color: #ff9; 46 | } 47 | 48 | table.source tr.coverPart td.hits, table.source tr.coverNone td.hits { 49 | font-weight: bold; 50 | } 51 | 52 | table.source tr.coverNone { 53 | background-color: #fdd; 54 | } 55 | 56 | table.source tr.noCover { 57 | background-color: #fff; 58 | } 59 | 60 | table.source tr.coverSkip { 61 | background-color: #b4b4b4; 62 | } 63 | 64 | .center { 65 | text-align: center; 66 | } 67 | 68 | /* Bar graph in table */ 69 | .bar-graph { 70 | float: left; 71 | color: transparent; 72 | } 73 | 74 | .missed { 75 | background-color: #FFCDD2; 76 | } 77 | 78 | .missed--hover:hover { 79 | background-color: #EF9A9A; 80 | } 81 | 82 | .covered { 83 | background-color: #C8E6C9; 84 | } 85 | 86 | .covered--hover:hover { 87 | background-color: #A5D6A7; 88 | } 89 | 90 | /* Table Delta Badge */ 91 | .badge-delta { 92 | width: 50px !important; 93 | vertical-align: text-top !important; 94 | } 95 | 96 | -------------------------------------------------------------------------------- /src/main/java/io/jenkins/plugins/coverage/model/visualization/colorization/CoverageColorPalette.java: -------------------------------------------------------------------------------- 1 | package io.jenkins.plugins.coverage.model.visualization.colorization; 2 | 3 | import java.awt.*; 4 | 5 | /** 6 | * Provides a color palette which can be used as a plugin internal fallback if no other color schemes have been defined. 7 | * The defined colors correspond to the Jenkins Design 8 | * Library. 9 | * 10 | * @author Florian Orendi 11 | */ 12 | public enum CoverageColorPalette { 13 | 14 | WHITE(ColorId.WHITE, new Color(255, 255, 255), new Color(0, 0, 0)), 15 | BLACK(ColorId.BLACK, new Color(0, 0, 0), new Color(255, 255, 255)), 16 | 17 | RED(ColorId.INSUFFICIENT, new Color(230, 0, 31), new Color(255, 255, 255)), 18 | LIGHT_RED(ColorId.VERY_BAD, new Color(255, 77, 101), new Color(255, 255, 255)), 19 | 20 | ORANGE(ColorId.BAD, new Color(254, 130, 10), new Color(0, 0, 0)), 21 | LIGHT_ORANGE(ColorId.INADEQUATE, new Color(254, 182, 112), new Color(0, 0, 0)), 22 | 23 | YELLOW(ColorId.AVERAGE, new Color(255, 204, 0), new Color(0, 0, 0)), 24 | LIGHT_YELLOW(ColorId.GOOD, new Color(255, 224, 102), new Color(0, 0, 0)), 25 | 26 | LIGHT_GREEN(ColorId.VERY_GOOD, new Color(75, 223, 124), new Color(0, 0, 0)), 27 | GREEN(ColorId.EXCELLENT, new Color(30, 166, 75), new Color(255, 255, 255)); 28 | 29 | private final ColorId colorId; 30 | private final Color fillColor; 31 | private final Color lineColor; 32 | 33 | CoverageColorPalette(final ColorId colorId, final Color fillColor, final Color lineColor) { 34 | this.colorId = colorId; 35 | this.fillColor = fillColor; 36 | this.lineColor = lineColor; 37 | } 38 | 39 | public ColorId getColorId() { 40 | return colorId; 41 | } 42 | 43 | public Color getFillColor() { 44 | return fillColor; 45 | } 46 | 47 | public Color getLineColor() { 48 | return lineColor; 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/test/resources/io/jenkins/plugins/coverage/metrics/steps/job-dsl.yaml: -------------------------------------------------------------------------------- 1 | jobs: 2 | - script: > 3 | freeStyleJob('dsl-freestyle-job') { 4 | publishers { 5 | recordCoverage { 6 | tools { 7 | coverageTool { 8 | parser('JACOCO') 9 | pattern('jacoco-pattern.*') 10 | } 11 | coverageTool { 12 | parser('COBERTURA') 13 | pattern('cobertura-pattern.*') 14 | } 15 | } 16 | qualityGates { 17 | coverageQualityGate { 18 | threshold(70) 19 | metric('LINE') 20 | baseline('PROJECT') 21 | criticality('UNSTABLE') 22 | } 23 | coverageQualityGate { 24 | threshold(80) 25 | metric('BRANCH') 26 | baseline('MODIFIED_LINES') 27 | criticality('FAILURE') 28 | } 29 | } 30 | id('my-coverage') 31 | name('My Coverage') 32 | enabledForFailure(true) 33 | skipPublishingChecks(true) 34 | failOnError(true) 35 | skipSymbolicLinks(true) 36 | scm('my-git') 37 | sourceCodeEncoding('UTF-8') 38 | sourceCodeRetention('EVERY_BUILD') 39 | sourceDirectories { 40 | sourceCodeDirectory { 41 | path('directory-1') 42 | } 43 | sourceCodeDirectory { 44 | path('directory-2') 45 | } 46 | } 47 | } 48 | } 49 | }; 50 | -------------------------------------------------------------------------------- /src/main/java/io/jenkins/plugins/coverage/model/visualization/dashboard/ChangeCoverageDelta.java: -------------------------------------------------------------------------------- 1 | package io.jenkins.plugins.coverage.model.visualization.dashboard; 2 | 3 | import java.util.Locale; 4 | import java.util.Optional; 5 | 6 | import io.jenkins.plugins.coverage.model.CoverageBuildAction; 7 | import io.jenkins.plugins.coverage.model.CoverageMetric; 8 | import io.jenkins.plugins.coverage.model.CoveragePercentage; 9 | import io.jenkins.plugins.coverage.model.Messages; 10 | import io.jenkins.plugins.coverage.model.visualization.colorization.ColorProvider.DisplayColors; 11 | import io.jenkins.plugins.coverage.model.visualization.colorization.CoverageChangeTendency; 12 | 13 | /** 14 | * Concrete implementation of {@link CoverageColumnType} which represents the change coverage delta. 15 | * 16 | * @author Florian Orendi 17 | */ 18 | public class ChangeCoverageDelta extends CoverageColumnType { 19 | 20 | /** 21 | * Creates a column type to be used for representing the change coverage delta. 22 | */ 23 | public ChangeCoverageDelta() { 24 | super(Messages._Change_Coverage_Delta_Type()); 25 | } 26 | 27 | @Override 28 | public Optional getCoverage(final CoverageBuildAction action, final CoverageMetric metric) { 29 | if (action.hasChangeCoverageDifference(metric)) { 30 | return Optional.of(action.getChangeCoverageDifference(metric)); 31 | } 32 | return Optional.empty(); 33 | } 34 | 35 | @Override 36 | public DisplayColors getDisplayColors(final CoveragePercentage coverage) { 37 | return CoverageChangeTendency.getDisplayColorsForTendency(coverage.getDoubleValue(), getColorProvider()); 38 | } 39 | 40 | @Override 41 | public String formatCoverage(final CoveragePercentage coverage, final Locale locale) { 42 | return coverage.formatDeltaPercentage(locale); 43 | } 44 | 45 | @Override 46 | public String getAnchor() { 47 | return "#changeCoverage"; 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/main/java/io/jenkins/plugins/coverage/BuildUtils.java: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | * THE SOFTWARE. 21 | */ 22 | 23 | package io.jenkins.plugins.coverage; 24 | 25 | import hudson.model.Run; 26 | 27 | 28 | public final class BuildUtils { 29 | 30 | /** 31 | * BuildUtils cannot be instantiated. 32 | */ 33 | private BuildUtils() { 34 | } 35 | 36 | /** 37 | * Get previous not failed completed build. 38 | * 39 | * @param b current build 40 | * @return previous not failed completed build 41 | */ 42 | public static Run getPreviousNotFailedCompletedBuild(Run b) { 43 | while (true) { 44 | b = b.getPreviousNotFailedBuild(); 45 | if (b == null) { 46 | return null; 47 | } 48 | if (!b.isBuilding()) { 49 | return b; 50 | } 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/test/resources/io/jenkins/plugins/coverage/cobertura-coverage.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | /Users/leobalter/dev/testing/solutions/3 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /src/main/java/io/jenkins/plugins/coverage/model/visualization/dashboard/IndirectCoverageChanges.java: -------------------------------------------------------------------------------- 1 | package io.jenkins.plugins.coverage.model.visualization.dashboard; 2 | 3 | import java.util.Locale; 4 | import java.util.Optional; 5 | 6 | import io.jenkins.plugins.coverage.model.CoverageBuildAction; 7 | import io.jenkins.plugins.coverage.model.CoverageMetric; 8 | import io.jenkins.plugins.coverage.model.CoveragePercentage; 9 | import io.jenkins.plugins.coverage.model.Messages; 10 | import io.jenkins.plugins.coverage.model.visualization.colorization.ColorProvider.DisplayColors; 11 | import io.jenkins.plugins.coverage.model.visualization.colorization.CoverageLevel; 12 | 13 | /** 14 | * Concrete implementation of {@link CoverageColumnType} which represents the indirect coverage changes. 15 | * 16 | * @author Florian Orendi 17 | */ 18 | public class IndirectCoverageChanges extends CoverageColumnType { 19 | 20 | /** 21 | * Creates a column type to be used for representing the indirect coverage changes. 22 | */ 23 | public IndirectCoverageChanges() { 24 | super(Messages._Indirect_Coverage_Changes_Type()); 25 | } 26 | 27 | @Override 28 | public Optional getCoverage(final CoverageBuildAction action, final CoverageMetric metric) { 29 | if (action.hasIndirectCoverageChanges(metric)) { 30 | return Optional.of(action.getIndirectCoverageChanges(metric).getCoveredPercentage()); 31 | } 32 | return Optional.empty(); 33 | } 34 | 35 | @Override 36 | public DisplayColors getDisplayColors(final CoveragePercentage coverage) { 37 | return CoverageLevel.getDisplayColorsOfCoverageLevel(coverage.getDoubleValue(), getColorProvider()); 38 | } 39 | 40 | @Override 41 | public String formatCoverage(final CoveragePercentage coverage, final Locale locale) { 42 | return coverage.formatPercentage(locale); 43 | } 44 | 45 | @Override 46 | public String getAnchor() { 47 | return "#indirectCoverage"; 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/main/resources/io/jenkins/plugins/coverage/adapter/istanbul-cobertura-to-standard.xsl: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | istanbul 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | -------------------------------------------------------------------------------- /src/main/java/io/jenkins/plugins/coverage/model/SourceViewModel.java: -------------------------------------------------------------------------------- 1 | package io.jenkins.plugins.coverage.model; 2 | 3 | import java.io.File; 4 | import java.io.IOException; 5 | 6 | import org.apache.commons.lang3.exception.ExceptionUtils; 7 | 8 | import hudson.model.Run; 9 | import hudson.util.TextFile; 10 | 11 | import io.jenkins.plugins.coverage.model.visualization.code.SourceCodeFacade; 12 | 13 | /** 14 | * Server side model that provides the data for the source code view of the coverage results. The layout of the 15 | * associated view is defined corresponding jelly view 'index.jelly'. 16 | * 17 | * @author Ullrich Hafner 18 | */ 19 | public class SourceViewModel extends CoverageViewModel { 20 | /** 21 | * Creates a new source view model instance. 22 | * 23 | * @param owner 24 | * the owner of this view 25 | * @param fileNode 26 | * the selected file node of the coverage tree 27 | */ 28 | public SourceViewModel(final Run owner, final CoverageNode fileNode) { 29 | super(owner, fileNode); 30 | } 31 | 32 | /** 33 | * Returns the source file rendered in HTML. 34 | * 35 | * @return {@code true} if the source file is available, {@code false} otherwise 36 | */ 37 | public String getSourceFileContent() { 38 | try { 39 | File rootDir = getOwner().getRootDir(); 40 | if (isSourceFileInNewFormatAvailable(getNode())) { 41 | return new SourceCodeFacade().read(rootDir, getId(), getNode().getPath()); 42 | } 43 | if (isSourceFileInOldFormatAvailable(getNode())) { 44 | return new TextFile(getFileForBuildsWithOldVersion(rootDir, getNode().getName())).read(); // fallback with sources persisted using the < 2.1.0 serialization 45 | } 46 | return Messages.Coverage_Not_Available(); 47 | } 48 | catch (IOException | InterruptedException exception) { 49 | return ExceptionUtils.getStackTrace(exception); 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/test/java/io/jenkins/plugins/coverage/model/visualization/colorization/CoverageLevelTest.java: -------------------------------------------------------------------------------- 1 | package io.jenkins.plugins.coverage.model.visualization.colorization; 2 | 3 | import java.awt.*; 4 | 5 | import org.junit.jupiter.api.Test; 6 | 7 | import io.jenkins.plugins.coverage.model.visualization.colorization.ColorProvider.DisplayColors; 8 | 9 | import static org.assertj.core.api.Assertions.*; 10 | 11 | /** 12 | * Test class for {@link CoverageLevel}. 13 | * 14 | * @author Florian Orendi 15 | */ 16 | class CoverageLevelTest { 17 | 18 | private static final ColorProvider COLOR_PROVIDER = ColorProviderFactory.createDefaultColorProvider(); 19 | 20 | @Test 21 | void shouldHaveWorkingGetters() { 22 | CoverageLevel coverageLevel = CoverageLevel.LVL_0; 23 | assertThat(coverageLevel.getLevel()).isEqualTo(0.0); 24 | assertThat(coverageLevel.getColorizationId()).isEqualTo(ColorId.INSUFFICIENT); 25 | } 26 | 27 | @Test 28 | void shouldGetDisplayColorsOfCoveragePercentage() { 29 | Color blendedColor = ColorProvider.blendColors( 30 | COLOR_PROVIDER.getDisplayColorsOf(CoverageLevel.LVL_60.getColorizationId()).getFillColor(), 31 | COLOR_PROVIDER.getDisplayColorsOf(CoverageLevel.LVL_70.getColorizationId()).getFillColor()); 32 | 33 | assertThat(CoverageLevel.getDisplayColorsOfCoverageLevel(65.0, COLOR_PROVIDER)) 34 | .isEqualTo(new DisplayColors(COLOR_PROVIDER.getDisplayColorsOf(ColorId.BLACK).getFillColor(), 35 | blendedColor)); 36 | assertThat(CoverageLevel.getDisplayColorsOfCoverageLevel(96.0, COLOR_PROVIDER)) 37 | .isEqualTo(COLOR_PROVIDER.getDisplayColorsOf(ColorId.EXCELLENT)); 38 | assertThat(CoverageLevel.getDisplayColorsOfCoverageLevel(50.0, COLOR_PROVIDER)) 39 | .isEqualTo(COLOR_PROVIDER.getDisplayColorsOf(ColorId.VERY_BAD)); 40 | assertThat(CoverageLevel.getDisplayColorsOfCoverageLevel(-2.0, COLOR_PROVIDER)) 41 | .isEqualTo(COLOR_PROVIDER.getDisplayColorsOf(ColorId.WHITE)); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/main/java/io/jenkins/plugins/coverage/model/MethodCoverageNode.java: -------------------------------------------------------------------------------- 1 | package io.jenkins.plugins.coverage.model; 2 | 3 | import java.util.Objects; 4 | 5 | /** 6 | * A {@link CoverageNode} for a specific method. 7 | * 8 | * @author Florian Orendi 9 | */ 10 | public class MethodCoverageNode extends CoverageNode { 11 | 12 | private static final long serialVersionUID = -5765205034179396434L; 13 | 14 | /** 15 | * The line number where the code of method begins (not including the method head). 16 | */ 17 | private final int lineNumber; 18 | 19 | /** 20 | * Creates a new coverage item node with the given name. 21 | * 22 | * @param name 23 | * The human-readable name of the node 24 | * @param lineNumber 25 | * The line number where the method begins (not including the method head) 26 | */ 27 | public MethodCoverageNode(final String name, final int lineNumber) { 28 | super(CoverageMetric.METHOD, name); 29 | this.lineNumber = lineNumber; 30 | } 31 | 32 | /** 33 | * Checks whether the line number is valid. 34 | * 35 | * @return {@code true} if the line number is valid, else {@code false} 36 | */ 37 | public boolean hasValidLineNumber() { 38 | return lineNumber > 0; 39 | } 40 | 41 | public int getLineNumber() { 42 | return lineNumber; 43 | } 44 | 45 | @Override 46 | protected CoverageNode copyEmpty() { 47 | return new MethodCoverageNode(getName(), getLineNumber()); 48 | } 49 | 50 | @Override 51 | public boolean equals(final Object o) { 52 | if (this == o) { 53 | return true; 54 | } 55 | if (o == null || getClass() != o.getClass()) { 56 | return false; 57 | } 58 | if (!super.equals(o)) { 59 | return false; 60 | } 61 | MethodCoverageNode that = (MethodCoverageNode) o; 62 | return lineNumber == that.lineNumber; 63 | } 64 | 65 | @Override 66 | public int hashCode() { 67 | return Objects.hash(super.hashCode(), lineNumber); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/main/resources/io/jenkins/plugins/coverage/adapter/cobertura-to-standard.xsl: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | cobertura 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | -------------------------------------------------------------------------------- /src/test/java/io/jenkins/plugins/coverage/model/visualization/dashboard/ChangeCoverageTest.java: -------------------------------------------------------------------------------- 1 | package io.jenkins.plugins.coverage.model.visualization.dashboard; 2 | 3 | import java.util.Optional; 4 | 5 | import org.junit.jupiter.api.Test; 6 | 7 | import io.jenkins.plugins.coverage.model.CoverageBuildAction; 8 | import io.jenkins.plugins.coverage.model.CoverageMetric; 9 | import io.jenkins.plugins.coverage.model.CoveragePercentage; 10 | import io.jenkins.plugins.coverage.model.visualization.colorization.ColorId; 11 | import io.jenkins.plugins.coverage.model.visualization.colorization.ColorProvider.DisplayColors; 12 | 13 | import static org.assertj.core.api.Assertions.*; 14 | 15 | /** 16 | * Test class for {@link ProjectCoverage}. 17 | * 18 | * @author Florian Orendi 19 | */ 20 | class ChangeCoverageTest extends CoverageColumnTypeTest { 21 | 22 | @Test 23 | void shouldGetCoverage() { 24 | CoverageBuildAction action = createCoverageBuildAction(); 25 | Optional coverage = CHANGE_COVERAGE.getCoverage(action, COVERAGE_METRIC); 26 | 27 | assertThat(coverage).isNotEmpty().satisfies(value -> assertThat(value.get()).isEqualTo(COVERAGE_PERCENTAGE)); 28 | } 29 | 30 | @Test 31 | void shouldNotReturnNonExistentCoverage() { 32 | CoverageBuildAction action = createCoverageBuildAction(); 33 | Optional coverage = CHANGE_COVERAGE.getCoverage(action, CoverageMetric.FILE); 34 | 35 | assertThat(coverage).isEmpty(); 36 | } 37 | 38 | @Test 39 | void shouldGetDisplayColors() { 40 | DisplayColors color = CHANGE_COVERAGE.getDisplayColors(COVERAGE_PERCENTAGE); 41 | assertThat(color).isEqualTo(COLOR_PROVIDER.getDisplayColorsOf(ColorId.VERY_BAD)); 42 | } 43 | 44 | @Test 45 | void shouldFormatCoverage() { 46 | String formattedCoverage = CHANGE_COVERAGE.formatCoverage(COVERAGE_PERCENTAGE, LOCALE); 47 | assertThat(formattedCoverage).isEqualTo("50,00%"); 48 | } 49 | 50 | @Test 51 | void shouldProvideReportUrl() { 52 | assertThat(CHANGE_COVERAGE.getAnchor()).isEqualTo("#changeCoverage"); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/test/java/io/jenkins/plugins/coverage/model/visualization/dashboard/ProjectCoverageTest.java: -------------------------------------------------------------------------------- 1 | package io.jenkins.plugins.coverage.model.visualization.dashboard; 2 | 3 | import java.util.Optional; 4 | 5 | import org.junit.jupiter.api.Test; 6 | 7 | import io.jenkins.plugins.coverage.model.CoverageBuildAction; 8 | import io.jenkins.plugins.coverage.model.CoverageMetric; 9 | import io.jenkins.plugins.coverage.model.CoveragePercentage; 10 | import io.jenkins.plugins.coverage.model.visualization.colorization.ColorId; 11 | import io.jenkins.plugins.coverage.model.visualization.colorization.ColorProvider.DisplayColors; 12 | 13 | import static org.assertj.core.api.Assertions.*; 14 | 15 | /** 16 | * Test class for {@link ProjectCoverage}. 17 | * 18 | * @author Florian Orendi 19 | */ 20 | class ProjectCoverageTest extends CoverageColumnTypeTest { 21 | 22 | @Test 23 | void shouldGetCoverage() { 24 | CoverageBuildAction action = createCoverageBuildAction(); 25 | Optional coverage = PROJECT_COVERAGE.getCoverage(action, COVERAGE_METRIC); 26 | 27 | assertThat(coverage).isNotEmpty().satisfies(value -> assertThat(value.get()).isEqualTo(COVERAGE_PERCENTAGE)); 28 | } 29 | 30 | @Test 31 | void shouldNotReturnNonExistentCoverage() { 32 | CoverageBuildAction action = createCoverageBuildAction(); 33 | Optional coverage = PROJECT_COVERAGE.getCoverage(action, CoverageMetric.FILE); 34 | 35 | assertThat(coverage).isEmpty(); 36 | } 37 | 38 | @Test 39 | void shouldGetDisplayColors() { 40 | DisplayColors color = PROJECT_COVERAGE.getDisplayColors(COVERAGE_PERCENTAGE); 41 | assertThat(color).isEqualTo(COLOR_PROVIDER.getDisplayColorsOf(ColorId.VERY_BAD)); 42 | } 43 | 44 | @Test 45 | void shouldFormatCoverage() { 46 | String formattedCoverage = PROJECT_COVERAGE.formatCoverage(COVERAGE_PERCENTAGE, LOCALE); 47 | assertThat(formattedCoverage).isEqualTo("50,00%"); 48 | } 49 | 50 | @Test 51 | void shouldProvideReportUrl() { 52 | assertThat(PROJECT_COVERAGE.getAnchor()).isEqualTo("#overview"); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/main/java/io/jenkins/plugins/coverage/adapter/XMLCoverageReportAdapter.java: -------------------------------------------------------------------------------- 1 | package io.jenkins.plugins.coverage.adapter; 2 | 3 | import edu.umd.cs.findbugs.annotations.CheckForNull; 4 | import edu.umd.cs.findbugs.annotations.Nullable; 5 | import io.jenkins.plugins.coverage.adapter.util.XMLUtils; 6 | import io.jenkins.plugins.coverage.exception.CoverageException; 7 | import org.apache.commons.lang.StringUtils; 8 | import org.w3c.dom.Document; 9 | 10 | import javax.xml.transform.stream.StreamSource; 11 | import java.io.File; 12 | import java.io.FileNotFoundException; 13 | 14 | public abstract class XMLCoverageReportAdapter extends CoverageReportAdapter { 15 | 16 | 17 | public XMLCoverageReportAdapter(String path) { 18 | super(path); 19 | } 20 | 21 | /** 22 | * @return XSL file that convert report into standard format 23 | */ 24 | @CheckForNull 25 | public abstract String getXSL(); 26 | 27 | /** 28 | * If return null, report will not be validate. 29 | * 30 | * @return XSD file to validate report 31 | */ 32 | @Nullable 33 | public abstract String getXSD(); 34 | 35 | /** 36 | * convert source xml file according to xsl file 37 | * 38 | * @param source source xml file 39 | */ 40 | @Override 41 | public Document convert(File source) throws CoverageException { 42 | try { 43 | StreamSource xsl = getRealXSL(); 44 | return XMLUtils.getInstance().convertToDocumentWithXSL(xsl, source); 45 | } catch (FileNotFoundException e) { 46 | e.printStackTrace(); 47 | throw new CoverageException(e); 48 | } 49 | } 50 | 51 | @SuppressWarnings("WeakerAccess") 52 | protected StreamSource getRealXSL() throws FileNotFoundException { 53 | String xsl = getXSL(); 54 | if (StringUtils.isEmpty(xsl)) { 55 | throw new FileNotFoundException("Cannot found xsl file, xsl path must be no-empty"); 56 | } 57 | return new StreamSource(getXSLResourceClass().getResourceAsStream(xsl)); 58 | } 59 | 60 | private Class getXSLResourceClass() { 61 | return this.getClass(); 62 | } 63 | 64 | } 65 | -------------------------------------------------------------------------------- /src/test/java/io/jenkins/plugins/coverage/model/JobDslITest.java: -------------------------------------------------------------------------------- 1 | package io.jenkins.plugins.coverage.model; 2 | 3 | import org.junit.jupiter.api.Test; 4 | 5 | import hudson.model.View; 6 | import hudson.views.ListViewColumn; 7 | 8 | import io.jenkins.plugins.casc.ConfigurationAsCode; 9 | import io.jenkins.plugins.casc.ConfiguratorException; 10 | import io.jenkins.plugins.coverage.model.visualization.dashboard.CoverageColumn; 11 | import io.jenkins.plugins.util.IntegrationTestWithJenkinsPerTest; 12 | 13 | import static io.jenkins.plugins.coverage.metrics.Assertions.*; 14 | 15 | /** 16 | * Tests the Job DSL Plugin. 17 | * 18 | * @author Ullrich Hafner 19 | */ 20 | class JobDslITest extends IntegrationTestWithJenkinsPerTest { 21 | /** 22 | * Creates a freestyle job from a YAML file and verifies that issue recorder finds warnings. 23 | */ 24 | @Test 25 | void shouldCreateFreestyleJobUsingJobDslAndVerifyIssueRecorderWithDefaultConfiguration() { 26 | configureJenkins("column-dsl.yaml"); 27 | 28 | View view = getJenkins().getInstance().getView("dsl-view"); 29 | 30 | assertThat(view).isNotNull(); 31 | 32 | assertThat(view.getColumns()) 33 | .extracting(ListViewColumn::getColumnCaption) 34 | .contains(new CoverageColumn().getColumnCaption()); 35 | 36 | assertThat(view.getColumns()).first() 37 | .isInstanceOfSatisfying(CoverageColumn.class, 38 | c -> { 39 | assertThat(c).hasColumnCaption(Messages.Coverage_Column()) 40 | .hasCoverageMetric(CoverageMetric.LINE.getName()); 41 | }); 42 | } 43 | 44 | /** 45 | * Helper method to get jenkins configuration file. 46 | * 47 | * @param fileName 48 | * file with configuration. 49 | */ 50 | private void configureJenkins(final String fileName) { 51 | try { 52 | ConfigurationAsCode.get().configure(getResourceAsFile(fileName).toUri().toString()); 53 | } 54 | catch (ConfiguratorException e) { 55 | throw new AssertionError(e); 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/test/java/io/jenkins/plugins/coverage/model/visualization/dashboard/ChangeCoverageDeltaTest.java: -------------------------------------------------------------------------------- 1 | package io.jenkins.plugins.coverage.model.visualization.dashboard; 2 | 3 | import java.util.Optional; 4 | 5 | import org.junit.jupiter.api.Test; 6 | 7 | import io.jenkins.plugins.coverage.model.CoverageBuildAction; 8 | import io.jenkins.plugins.coverage.model.CoverageMetric; 9 | import io.jenkins.plugins.coverage.model.CoveragePercentage; 10 | import io.jenkins.plugins.coverage.model.visualization.colorization.ColorId; 11 | import io.jenkins.plugins.coverage.model.visualization.colorization.ColorProvider.DisplayColors; 12 | 13 | import static org.assertj.core.api.Assertions.*; 14 | 15 | /** 16 | * Test class for {@link ProjectCoverage}. 17 | * 18 | * @author Florian Orendi 19 | */ 20 | class ChangeCoverageDeltaTest extends CoverageColumnTypeTest { 21 | 22 | @Test 23 | void shouldGetCoverage() { 24 | CoverageBuildAction action = createCoverageBuildAction(); 25 | Optional coverage = CHANGE_COVERAGE_DELTA.getCoverage(action, COVERAGE_METRIC); 26 | 27 | assertThat(coverage).isNotEmpty().satisfies(value -> assertThat(value.get()).isEqualTo(COVERAGE_PERCENTAGE)); 28 | } 29 | 30 | @Test 31 | void shouldNotReturnNonExistentCoverage() { 32 | CoverageBuildAction action = createCoverageBuildAction(); 33 | Optional coverage = CHANGE_COVERAGE_DELTA.getCoverage(action, CoverageMetric.FILE); 34 | 35 | assertThat(coverage).isEmpty(); 36 | } 37 | 38 | @Test 39 | void shouldGetDisplayColors() { 40 | DisplayColors color = CHANGE_COVERAGE_DELTA.getDisplayColors(COVERAGE_PERCENTAGE); 41 | assertThat(color).isEqualTo(COLOR_PROVIDER.getDisplayColorsOf(ColorId.EXCELLENT)); 42 | } 43 | 44 | @Test 45 | void shouldFormatCoverage() { 46 | String formattedCoverage = CHANGE_COVERAGE_DELTA.formatCoverage(COVERAGE_PERCENTAGE, LOCALE); 47 | assertThat(formattedCoverage).isEqualTo("+50,00%"); 48 | } 49 | 50 | @Test 51 | void shouldProvideReportUrl() { 52 | assertThat(CHANGE_COVERAGE_DELTA.getAnchor()).isEqualTo("#changeCoverage"); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/test/java/io/jenkins/plugins/coverage/model/PackageCoverageNodeTest.java: -------------------------------------------------------------------------------- 1 | package io.jenkins.plugins.coverage.model; 2 | 3 | import org.junit.jupiter.api.Test; 4 | 5 | import nl.jqno.equalsverifier.EqualsVerifier; 6 | import nl.jqno.equalsverifier.Warning; 7 | 8 | import io.jenkins.plugins.coverage.model.Coverage.CoverageBuilder; 9 | 10 | import static org.assertj.core.api.Assertions.*; 11 | 12 | /** 13 | * Test class for {@link PackageCoverageNode}. 14 | * 15 | * @author Florian Orendi 16 | */ 17 | class PackageCoverageNodeTest { 18 | 19 | private static final String PACKAGE = "test.path"; 20 | private static final String PATH = "test/path"; 21 | 22 | @Test 23 | void shouldGetPath() { 24 | PackageCoverageNode node = createPackageCoverageNode(); 25 | assertThat(node.getPath()).isEqualTo(PATH); 26 | } 27 | 28 | @Test 29 | void shouldCopyTree() { 30 | PackageCoverageNode node = createPackageCoverageNode(); 31 | CoverageNode root = node.getParent().copyTree(); 32 | assertThat(root.getChildren()).containsExactly(node); 33 | } 34 | 35 | @Test 36 | void shouldObeyEqualsContract() { 37 | EqualsVerifier.forClass(PackageCoverageNode.class) 38 | .withPrefabValues(CoverageNode.class, 39 | new PackageCoverageNode(PACKAGE), 40 | new CoverageNode(CoverageMetric.FILE, "file.txt")) 41 | .suppress(Warning.NONFINAL_FIELDS) 42 | .usingGetClass() 43 | .withIgnoredFields("parent") 44 | .verify(); 45 | } 46 | 47 | /** 48 | * Creates an instance of {@link PackageCoverageNode}. 49 | * 50 | * @return the created instance 51 | */ 52 | private PackageCoverageNode createPackageCoverageNode() { 53 | CoverageNode parent = new CoverageNode(CoverageMetric.MODULE, ""); 54 | CoverageLeaf leaf = new CoverageLeaf(CoverageMetric.LINE, CoverageBuilder.NO_COVERAGE); 55 | PackageCoverageNode node = new PackageCoverageNode(PACKAGE); 56 | node.setParent(parent); 57 | parent.add(node); 58 | node.add(leaf); 59 | return node; 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/test/java/io/jenkins/plugins/coverage/model/visualization/dashboard/ProjectCoverageDeltaTest.java: -------------------------------------------------------------------------------- 1 | package io.jenkins.plugins.coverage.model.visualization.dashboard; 2 | 3 | import java.util.Optional; 4 | 5 | import org.junit.jupiter.api.Test; 6 | 7 | import io.jenkins.plugins.coverage.model.CoverageBuildAction; 8 | import io.jenkins.plugins.coverage.model.CoverageMetric; 9 | import io.jenkins.plugins.coverage.model.CoveragePercentage; 10 | import io.jenkins.plugins.coverage.model.visualization.colorization.ColorId; 11 | import io.jenkins.plugins.coverage.model.visualization.colorization.ColorProvider.DisplayColors; 12 | 13 | import static org.assertj.core.api.Assertions.*; 14 | 15 | /** 16 | * Test class for {@link ProjectCoverageDelta}. 17 | * 18 | * @author Florian Orendi 19 | */ 20 | class ProjectCoverageDeltaTest extends CoverageColumnTypeTest { 21 | 22 | @Test 23 | void shouldGetCoverage() { 24 | CoverageBuildAction action = createCoverageBuildAction(); 25 | Optional coverage = PROJECT_COVERAGE_DELTA.getCoverage(action, COVERAGE_METRIC); 26 | 27 | assertThat(coverage).isNotEmpty().satisfies(value -> assertThat(value.get()).isEqualTo(COVERAGE_PERCENTAGE)); 28 | } 29 | 30 | @Test 31 | void shouldNotReturnNonExistentCoverage() { 32 | CoverageBuildAction action = createCoverageBuildAction(); 33 | Optional coverage = PROJECT_COVERAGE_DELTA.getCoverage(action, CoverageMetric.FILE); 34 | 35 | assertThat(coverage).isEmpty(); 36 | } 37 | 38 | @Test 39 | void shouldGetDisplayColors() { 40 | DisplayColors color = PROJECT_COVERAGE_DELTA.getDisplayColors(COVERAGE_PERCENTAGE); 41 | assertThat(color).isEqualTo(COLOR_PROVIDER.getDisplayColorsOf(ColorId.EXCELLENT)); 42 | } 43 | 44 | @Test 45 | void shouldFormatCoverage() { 46 | String formattedCoverage = PROJECT_COVERAGE_DELTA.formatCoverage(COVERAGE_PERCENTAGE, LOCALE); 47 | assertThat(formattedCoverage).isEqualTo("+50,00%"); 48 | } 49 | 50 | @Test 51 | void shouldProvideReportUrl() { 52 | assertThat(PROJECT_COVERAGE_DELTA.getAnchor()).isEqualTo("#overview"); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/test/java/io/jenkins/plugins/coverage/model/visualization/dashboard/IndirectCoverageChangesTest.java: -------------------------------------------------------------------------------- 1 | package io.jenkins.plugins.coverage.model.visualization.dashboard; 2 | 3 | import java.util.Optional; 4 | 5 | import org.junit.jupiter.api.Test; 6 | 7 | import io.jenkins.plugins.coverage.model.CoverageBuildAction; 8 | import io.jenkins.plugins.coverage.model.CoverageMetric; 9 | import io.jenkins.plugins.coverage.model.CoveragePercentage; 10 | import io.jenkins.plugins.coverage.model.visualization.colorization.ColorId; 11 | import io.jenkins.plugins.coverage.model.visualization.colorization.ColorProvider.DisplayColors; 12 | 13 | import static org.assertj.core.api.Assertions.*; 14 | 15 | /** 16 | * Test class for {@link ProjectCoverage}. 17 | * 18 | * @author Florian Orendi 19 | */ 20 | class IndirectCoverageChangesTest extends CoverageColumnTypeTest { 21 | 22 | @Test 23 | void shouldGetCoverage() { 24 | CoverageBuildAction action = createCoverageBuildAction(); 25 | Optional coverage = INDIRECT_COVERAGE_CHANGES.getCoverage(action, COVERAGE_METRIC); 26 | 27 | assertThat(coverage).isNotEmpty().satisfies(value -> assertThat(value.get()).isEqualTo(COVERAGE_PERCENTAGE)); 28 | } 29 | 30 | @Test 31 | void shouldNotReturnNonExistentCoverage() { 32 | CoverageBuildAction action = createCoverageBuildAction(); 33 | Optional coverage = INDIRECT_COVERAGE_CHANGES.getCoverage(action, CoverageMetric.FILE); 34 | 35 | assertThat(coverage).isEmpty(); 36 | } 37 | 38 | @Test 39 | void shouldGetDisplayColors() { 40 | DisplayColors color = INDIRECT_COVERAGE_CHANGES.getDisplayColors(COVERAGE_PERCENTAGE); 41 | assertThat(color).isEqualTo(COLOR_PROVIDER.getDisplayColorsOf(ColorId.VERY_BAD)); 42 | } 43 | 44 | @Test 45 | void shouldFormatCoverage() { 46 | String formattedCoverage = INDIRECT_COVERAGE_CHANGES.formatCoverage(COVERAGE_PERCENTAGE, LOCALE); 47 | assertThat(formattedCoverage).isEqualTo("50,00%"); 48 | } 49 | 50 | @Test 51 | void shouldProvideReportUrl() { 52 | assertThat(INDIRECT_COVERAGE_CHANGES.getAnchor()).isEqualTo("#indirectCoverage"); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/main/java/io/jenkins/plugins/coverage/source/SourceFileResolver.java: -------------------------------------------------------------------------------- 1 | package io.jenkins.plugins.coverage.source; 2 | 3 | import java.io.IOException; 4 | import java.util.Map; 5 | import java.util.Set; 6 | 7 | import hudson.ExtensionPoint; 8 | import hudson.FilePath; 9 | import hudson.model.Describable; 10 | import hudson.model.Descriptor; 11 | import hudson.model.Run; 12 | import hudson.model.TaskListener; 13 | import jenkins.model.Jenkins; 14 | 15 | import io.jenkins.plugins.coverage.targets.CoveragePaint; 16 | 17 | public abstract class SourceFileResolver implements ExtensionPoint, Describable { 18 | private DefaultSourceFileResolver.SourceFileResolverLevel level; 19 | private Set possiblePaths; 20 | 21 | public SourceFileResolver(final DefaultSourceFileResolver.SourceFileResolverLevel level) { 22 | this.level = level; 23 | } 24 | 25 | public abstract void resolveSourceFiles(Run run, FilePath workspace, TaskListener listener, Map paints) throws IOException; 26 | 27 | public void setPossiblePaths(final Set possiblePaths) { 28 | this.possiblePaths = possiblePaths; 29 | } 30 | 31 | public Set getPossiblePaths() { 32 | return possiblePaths; 33 | } 34 | 35 | @SuppressWarnings("unchecked") 36 | @Override 37 | public Descriptor getDescriptor() { 38 | return Jenkins.get().getDescriptorOrDie(getClass()); 39 | } 40 | 41 | public DefaultSourceFileResolver.SourceFileResolverLevel getLevel() { 42 | return level; 43 | } 44 | 45 | public void setLevel(final DefaultSourceFileResolver.SourceFileResolverLevel level) { 46 | this.level = level; 47 | } 48 | 49 | public enum SourceFileResolverLevel { 50 | NEVER_STORE(Messages.SourceFileResolver_neverSave()), 51 | STORE_LAST_BUILD(Messages.SourceFileResolver_saveLast()), 52 | STORE_ALL_BUILD(Messages.SourceFileResolver_saveAll()); 53 | 54 | private final String name; 55 | 56 | SourceFileResolverLevel(final String name) { 57 | this.name = name; 58 | } 59 | 60 | public String getName() { 61 | return name; 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/main/java/io/jenkins/plugins/coverage/adapter/converter/DocumentConverter.java: -------------------------------------------------------------------------------- 1 | package io.jenkins.plugins.coverage.adapter.converter; 2 | 3 | import io.jenkins.plugins.coverage.exception.CoverageException; 4 | import org.w3c.dom.Document; 5 | 6 | import javax.xml.XMLConstants; 7 | import javax.xml.parsers.DocumentBuilder; 8 | import javax.xml.parsers.DocumentBuilderFactory; 9 | import javax.xml.parsers.ParserConfigurationException; 10 | 11 | public abstract class DocumentConverter { 12 | 13 | /** 14 | * Convert other format report to standard format Document. 15 | * 16 | * @param report other format report 17 | * @return document converted by other report 18 | */ 19 | public Document convert(T report) throws CoverageException { 20 | DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); 21 | try { 22 | factory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true); 23 | factory.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false); 24 | factory.setFeature("http://xml.org/sax/features/external-parameter-entities", false); 25 | factory.setFeature("http://xml.org/sax/features/external-general-entities", false); 26 | factory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", false); 27 | } catch (ParserConfigurationException e) { 28 | e.printStackTrace(); 29 | } 30 | 31 | factory.setNamespaceAware(true); 32 | DocumentBuilder builder; 33 | try { 34 | builder = factory.newDocumentBuilder(); 35 | } catch (ParserConfigurationException e) { 36 | throw new CoverageException(e); 37 | } 38 | 39 | Document document = builder.newDocument(); 40 | 41 | convert(report, document); 42 | 43 | return document; 44 | } 45 | 46 | /** 47 | * Convert other format report to standard format Document. 48 | * @param report other format report 49 | * @param document document that the report will convert to 50 | * @return document converted by other report 51 | */ 52 | protected abstract Document convert(T report, Document document) throws CoverageException; 53 | 54 | 55 | } 56 | -------------------------------------------------------------------------------- /src/main/java/io/jenkins/plugins/coverage/model/CoverageLeaf.java: -------------------------------------------------------------------------------- 1 | package io.jenkins.plugins.coverage.model; 2 | 3 | import java.io.Serializable; 4 | import java.util.Objects; 5 | 6 | import io.jenkins.plugins.coverage.model.Coverage.CoverageBuilder; 7 | 8 | /** 9 | * A leaf in the coverage hierarchy. A leaf is a non-divisible coverage metric like line or branch coverage. 10 | * 11 | * @author Ullrich Hafner 12 | */ 13 | public final class CoverageLeaf implements Serializable { 14 | private static final long serialVersionUID = -1062406664372222691L; 15 | 16 | private final CoverageMetric metric; 17 | private final Coverage coverage; 18 | 19 | /** 20 | * Creates a new leaf with the given coverage for the specified metric. 21 | * 22 | * @param metric 23 | * the coverage metric 24 | * @param coverage 25 | * the coverage of the element 26 | */ 27 | public CoverageLeaf(final CoverageMetric metric, final Coverage coverage) { 28 | this.metric = metric; 29 | this.coverage = coverage; 30 | } 31 | 32 | public CoverageMetric getMetric() { 33 | return metric; 34 | } 35 | 36 | /** 37 | * Returns the coverage for the specified metric. 38 | * 39 | * @param searchMetric 40 | * the metric to get the coverage for 41 | * 42 | * @return coverage ratio 43 | */ 44 | public Coverage getCoverage(final CoverageMetric searchMetric) { 45 | if (metric.equals(searchMetric)) { 46 | return coverage; 47 | } 48 | return CoverageBuilder.NO_COVERAGE; 49 | } 50 | 51 | @Override 52 | public String toString() { 53 | return String.format("[%s]: %s", metric, coverage); 54 | } 55 | 56 | @Override 57 | public boolean equals(final Object o) { 58 | if (this == o) { 59 | return true; 60 | } 61 | if (o == null || getClass() != o.getClass()) { 62 | return false; 63 | } 64 | CoverageLeaf that = (CoverageLeaf) o; 65 | return Objects.equals(metric, that.metric) && Objects.equals(coverage, that.coverage); 66 | } 67 | 68 | @Override 69 | public int hashCode() { 70 | return Objects.hash(metric, coverage); 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/main/java/io/jenkins/plugins/coverage/model/FilePathValidator.java: -------------------------------------------------------------------------------- 1 | package io.jenkins.plugins.coverage.model; 2 | 3 | import java.util.List; 4 | import java.util.stream.Collectors; 5 | 6 | import edu.hm.hafner.util.FilteredLog; 7 | 8 | import one.util.streamex.StreamEx; 9 | 10 | /** 11 | * Validates the file paths of the processed project files. 12 | * 13 | * @author Florian Orendi 14 | */ 15 | class FilePathValidator { 16 | 17 | static final String AMBIGUOUS_FILES_MESSAGE = 18 | "There are ambiguous file paths which might lead to faulty coverage reports:"; 19 | static final String REMOVED_MESSAGE = 20 | "-> These files have been removed from the report in order to guarantee flawless coverage visualizations"; 21 | static final String PACKAGE_INFO_MESSAGE = 22 | "-> In order to avoid this make sure package names are unique within the whole project"; 23 | 24 | /** 25 | * Private default constructor. 26 | */ 27 | private FilePathValidator() { 28 | // hides the public constructor 29 | } 30 | 31 | /** 32 | * Verifies that the passed coverage tree only contains files with unique paths. Duplicate paths are logged and 33 | * removed from the coverage tree. 34 | * 35 | * @param root 36 | * The {@link CoverageNode root} of the coverage tree 37 | * @param log 38 | * The log 39 | */ 40 | static void verifyPathUniqueness(final CoverageNode root, final FilteredLog log) { 41 | List duplicates = StreamEx.of(root.getAllFileCoverageNodes()) 42 | .map(FileCoverageNode::getPath) 43 | .distinct(2) 44 | .collect(Collectors.toList()); 45 | if (!duplicates.isEmpty()) { 46 | root.getAllFileCoverageNodes().stream() 47 | .filter(node -> duplicates.contains(node.getPath())) 48 | .forEach(CoverageNode::remove); 49 | 50 | String message = AMBIGUOUS_FILES_MESSAGE + System.lineSeparator() 51 | + String.join("," + System.lineSeparator(), duplicates); 52 | log.logError(message); 53 | log.logError(REMOVED_MESSAGE); 54 | log.logError(PACKAGE_INFO_MESSAGE); 55 | } 56 | } 57 | 58 | } 59 | -------------------------------------------------------------------------------- /src/main/java/io/jenkins/plugins/coverage/model/CoverageJobAction.java: -------------------------------------------------------------------------------- 1 | package io.jenkins.plugins.coverage.model; 2 | 3 | import edu.hm.hafner.echarts.BuildResult; 4 | import edu.hm.hafner.echarts.ChartModelConfiguration; 5 | import edu.hm.hafner.echarts.LinesChartModel; 6 | import edu.umd.cs.findbugs.annotations.NonNull; 7 | 8 | import hudson.model.Job; 9 | 10 | import io.jenkins.plugins.coverage.Messages; 11 | import io.jenkins.plugins.coverage.model.visualization.charts.CoverageTrendChart; 12 | import io.jenkins.plugins.echarts.AsyncConfigurableTrendJobAction; 13 | 14 | /** 15 | * Project level action for the coverage results. A job action displays a link on the side panel of a job that refers to 16 | * the last build that contains coverage results (i.e. a {@link CoverageBuildAction} with a {@link CoverageNode} 17 | * instance). This action also is responsible to render the historical trend via its associated 'floatingBox.jelly' 18 | * view. 19 | * 20 | * @author Ullrich Hafner 21 | */ 22 | public class CoverageJobAction extends AsyncConfigurableTrendJobAction { 23 | CoverageJobAction(final Job owner) { 24 | super(owner, CoverageBuildAction.class); 25 | } 26 | 27 | @Override 28 | public String getIconFileName() { 29 | return CoverageBuildAction.ICON; 30 | } 31 | 32 | @Override 33 | public String getDisplayName() { 34 | return Messages.CoverageProjectAction_displayName(); 35 | } 36 | 37 | @Override @NonNull 38 | public String getUrlName() { 39 | return "coverage"; 40 | } 41 | 42 | public Job getProject() { 43 | return getOwner(); 44 | } 45 | 46 | public String getSearchUrl() { 47 | return getUrlName(); 48 | } 49 | 50 | @Override 51 | protected LinesChartModel createChartModel(final String configuration) { 52 | return createChart(createBuildHistory(), configuration); 53 | } 54 | 55 | LinesChartModel createChart(final Iterable> buildHistory, 56 | final String configuration) { 57 | ChartModelConfiguration modelConfiguration = ChartModelConfiguration.fromJson(configuration); 58 | return new CoverageTrendChart().create(buildHistory, modelConfiguration); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/main/java/io/jenkins/plugins/coverage/CompatibleObjectInputStream.java: -------------------------------------------------------------------------------- 1 | package io.jenkins.plugins.coverage; 2 | 3 | import hudson.remoting.ClassFilter; 4 | import hudson.remoting.ObjectInputStreamEx; 5 | 6 | import java.io.*; 7 | 8 | /** 9 | * {@link io.jenkins.plugins.coverage.targets.CoverageElement} didn't have serialVersionUID. In order to keep 10 | * backwards compatibility, use this class to read serialized Object to avoid that serialVersionUID is different. 11 | */ 12 | public class CompatibleObjectInputStream extends ObjectInputStreamEx { 13 | 14 | public CompatibleObjectInputStream(InputStream in) throws IOException { 15 | // TODO Use a classloader in this plugin, or uberClassLoader? 16 | super(in, CoverageProcessor.class.getClassLoader(), ClassFilter.DEFAULT); 17 | } 18 | 19 | 20 | // See https://stackoverflow.com/questions/795470/how-to-deserialize-an-object-persisted-in-a-db-now-when-the-object-has-different?rq=1 21 | @Override 22 | protected ObjectStreamClass readClassDescriptor() throws IOException, ClassNotFoundException { 23 | ObjectStreamClass resultClassDescriptor = super.readClassDescriptor(); // initially streams descriptor 24 | Class localClass = Class.forName(resultClassDescriptor.getName()); // the class in the local JVM that this descriptor represents. 25 | ObjectStreamClass localClassDescriptor = ObjectStreamClass.lookup(localClass); 26 | if (localClassDescriptor != null) { // only if class implements serializable 27 | final long localSUID = localClassDescriptor.getSerialVersionUID(); 28 | final long streamSUID = resultClassDescriptor.getSerialVersionUID(); 29 | if (streamSUID != localSUID) { // check for serialVersionUID mismatch. 30 | String s = String.format("Overriding serialized class %s version mismatch: local serialVersionUID = %d stream serialVersionUID = %d", 31 | resultClassDescriptor.getName(), localSUID, streamSUID); 32 | Exception e = new InvalidClassException(s); 33 | System.out.println("Potentially Fatal Deserialization Operation. " + e); 34 | resultClassDescriptor = localClassDescriptor; // Use local class descriptor for deserialization 35 | } 36 | } 37 | return resultClassDescriptor; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/test/java/io/jenkins/plugins/coverage/model/testutil/JobStubs.java: -------------------------------------------------------------------------------- 1 | package io.jenkins.plugins.coverage.model.testutil; 2 | 3 | import java.util.Arrays; 4 | 5 | import edu.hm.hafner.util.VisibleForTesting; 6 | 7 | import hudson.model.Build; 8 | import hudson.model.Job; 9 | import hudson.model.Run; 10 | 11 | import io.jenkins.plugins.coverage.model.CoverageBuildAction; 12 | 13 | import static org.mockito.Mockito.*; 14 | 15 | /** 16 | * Provides stubs of Jenkins {@link Job} and {@link Build}. 17 | * 18 | * @author Florian Orendi 19 | */ 20 | public final class JobStubs { 21 | 22 | private JobStubs() { 23 | // prevents instantiation 24 | } 25 | 26 | /** 27 | * Creates a stub of {@link Job}. 28 | * 29 | * @return the created stub 30 | */ 31 | @VisibleForTesting 32 | public static Job createJob() { 33 | return mock(Job.class); 34 | } 35 | 36 | /** 37 | * Creates a stub for a {@link Job} that has the specified actions attached. 38 | * 39 | * @param actions 40 | * The actions to attach, might be empty 41 | * 42 | * @return the created stub 43 | */ 44 | @VisibleForTesting 45 | public static Job createJobWithActions(final CoverageBuildAction... actions) { 46 | Job job = createJob(); 47 | Run build = createBuildWithActions(actions); 48 | when(job.getLastCompletedBuild()).thenReturn(build); 49 | return job; 50 | } 51 | 52 | /** 53 | * Creates a stub of {@link Run}. 54 | * 55 | * @return the created stub 56 | */ 57 | @VisibleForTesting 58 | public static Run createBuild() { 59 | return mock(Run.class); 60 | } 61 | 62 | /** 63 | * Creates a stub for a {@link Run} that has the specified actions attached. 64 | * 65 | * @param actions 66 | * the actions to attach, might be empty 67 | * 68 | * @return the created stub 69 | */ 70 | @VisibleForTesting 71 | public static Run createBuildWithActions(final CoverageBuildAction... actions) { 72 | Run build = createBuild(); 73 | when(build.getActions(CoverageBuildAction.class)).thenReturn(Arrays.asList(actions)); 74 | if (actions.length > 0) { 75 | when(build.getAction(CoverageBuildAction.class)).thenReturn(actions[0]); 76 | } 77 | return build; 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/test/java/io/jenkins/plugins/coverage/model/MethodCoverageNodeTest.java: -------------------------------------------------------------------------------- 1 | package io.jenkins.plugins.coverage.model; 2 | 3 | import org.junit.jupiter.api.Test; 4 | 5 | import nl.jqno.equalsverifier.EqualsVerifier; 6 | import nl.jqno.equalsverifier.Warning; 7 | 8 | import io.jenkins.plugins.coverage.model.Coverage.CoverageBuilder; 9 | 10 | import static org.assertj.core.api.Assertions.*; 11 | 12 | /** 13 | * Test class for {@link MethodCoverageNode}. 14 | * 15 | * @author Florian Orendi 16 | */ 17 | class MethodCoverageNodeTest { 18 | 19 | private static final String NAME = "METHOD"; 20 | private static final int LINE = 5; 21 | 22 | @Test 23 | void shouldGetValidLineNumberOfMethod() { 24 | MethodCoverageNode node = createMethodCoverageNode(); 25 | assertThat(node.hasValidLineNumber()).isTrue(); 26 | assertThat(node.getLineNumber()).isEqualTo(5); 27 | } 28 | 29 | @Test 30 | void shouldRecognizeInvalidLineNumberOfMethod() { 31 | MethodCoverageNode node = new MethodCoverageNode(NAME, 0); 32 | assertThat(node.hasValidLineNumber()).isFalse(); 33 | } 34 | 35 | @Test 36 | void shouldCopyTree() { 37 | MethodCoverageNode node = createMethodCoverageNode(); 38 | CoverageNode root = node.getParent().copyTree(); 39 | assertThat(root.getChildren()).containsExactly(node); 40 | } 41 | 42 | @Test 43 | void shouldObeyEqualsContract() { 44 | EqualsVerifier.forClass(MethodCoverageNode.class) 45 | .withPrefabValues(CoverageNode.class, 46 | new MethodCoverageNode("", LINE), 47 | new CoverageNode(CoverageMetric.LINE, "line")) 48 | .suppress(Warning.NONFINAL_FIELDS) 49 | .usingGetClass() 50 | .withIgnoredFields("parent") 51 | .verify(); 52 | } 53 | 54 | /** 55 | * Creates an instance of {@link MethodCoverageNode}. 56 | * 57 | * @return the created instance 58 | */ 59 | private MethodCoverageNode createMethodCoverageNode() { 60 | CoverageNode parent = new CoverageNode(CoverageMetric.LINE, ""); 61 | CoverageLeaf leaf = new CoverageLeaf(CoverageMetric.LINE, CoverageBuilder.NO_COVERAGE); 62 | MethodCoverageNode node = new MethodCoverageNode(NAME, LINE); 63 | node.setParent(parent); 64 | parent.add(node); 65 | node.add(leaf); 66 | return node; 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/main/resources/coverage/configuration.jelly: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Provides the configuration for the coverage recorder and step. 6 | 7 | 8 | 9 |
10 | 11 | 12 |
13 | 14 |
15 |
16 |
17 |
18 |
19 | 20 | 21 |
22 | 23 | 24 |
25 | 26 |
27 |
28 |
29 |
30 |
31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 |
76 | 77 | -------------------------------------------------------------------------------- /src/main/java/io/jenkins/plugins/coverage/adapter/CoberturaReportAdapter.java: -------------------------------------------------------------------------------- 1 | package io.jenkins.plugins.coverage.adapter; 2 | 3 | import java.io.File; 4 | import javax.xml.transform.TransformerException; 5 | 6 | import edu.umd.cs.findbugs.annotations.NonNull; 7 | import org.w3c.dom.Document; 8 | import org.w3c.dom.Element; 9 | 10 | import org.kohsuke.stapler.DataBoundConstructor; 11 | import org.jenkinsci.Symbol; 12 | import hudson.Extension; 13 | 14 | import io.jenkins.plugins.coverage.adapter.util.XMLUtils; 15 | import io.jenkins.plugins.coverage.detector.Detectable; 16 | 17 | /** 18 | * Coverage report adapter for Cobertura. 19 | */ 20 | public final class CoberturaReportAdapter extends JavaXMLCoverageReportAdapter { 21 | 22 | @DataBoundConstructor 23 | public CoberturaReportAdapter(final String path) { 24 | super(path); 25 | } 26 | 27 | /** 28 | * {@inheritDoc} 29 | */ 30 | @Override 31 | public String getXSL() { 32 | return "cobertura-to-standard.xsl"; 33 | } 34 | 35 | /** 36 | * {@inheritDoc} 37 | */ 38 | @Override 39 | public String getXSD() { 40 | return null; 41 | } 42 | 43 | @Symbol({"cobertura", "coberturaReportAdapter"}) 44 | @Extension 45 | public static final class CoberturaReportAdapterDescriptor extends JavaCoverageReportAdapterDescriptor 46 | implements Detectable { 47 | 48 | public CoberturaReportAdapterDescriptor() { 49 | super(CoberturaReportAdapter.class); 50 | } 51 | 52 | /** 53 | * @param file file be detect 54 | * @return true is file is a cobertura report 55 | */ 56 | @Override 57 | public boolean detect(final File file) { 58 | if (!file.exists()) { 59 | return false; 60 | } 61 | 62 | Document d; 63 | try { 64 | d = XMLUtils.getInstance().readXMLtoDocument(file); 65 | } catch (TransformerException ignore) { 66 | return false; 67 | } 68 | 69 | Element e = d.getDocumentElement(); 70 | if (e == null) { 71 | return false; 72 | } 73 | 74 | return "coverage".equals(e.getLocalName()); 75 | } 76 | 77 | @NonNull 78 | @Override 79 | public String getDisplayName() { 80 | return Messages.CoberturaReportAdapter_displayName(); 81 | } 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /src/test/java/io/jenkins/plugins/coverage/model/CoverageMetricTest.java: -------------------------------------------------------------------------------- 1 | package io.jenkins.plugins.coverage.model; 2 | 3 | import java.util.ArrayList; 4 | import java.util.Collections; 5 | import java.util.List; 6 | 7 | import org.junit.jupiter.api.Test; 8 | 9 | import static org.assertj.core.api.Assertions.*; 10 | 11 | /** 12 | * Tests the class {@link CoverageMetric}. 13 | * 14 | * @author Ullrich Hafner 15 | */ 16 | class CoverageMetricTest { 17 | @Test 18 | void shouldSortMetrics() { 19 | List all = new ArrayList<>(); 20 | all.add(CoverageMetric.MODULE); 21 | all.add(CoverageMetric.PACKAGE); 22 | all.add(CoverageMetric.FILE); 23 | all.add(CoverageMetric.CLASS); 24 | all.add(CoverageMetric.METHOD); 25 | all.add(CoverageMetric.LINE); 26 | all.add(CoverageMetric.BRANCH); 27 | all.add(CoverageMetric.INSTRUCTION); 28 | 29 | Collections.sort(all); 30 | verifyOrder(all); 31 | 32 | Collections.reverse(all); 33 | assertThat(all).containsExactly( 34 | CoverageMetric.BRANCH, 35 | CoverageMetric.INSTRUCTION, 36 | CoverageMetric.LINE, 37 | CoverageMetric.METHOD, 38 | CoverageMetric.CLASS, 39 | CoverageMetric.FILE, 40 | CoverageMetric.PACKAGE, 41 | CoverageMetric.MODULE); 42 | 43 | Collections.sort(all); 44 | verifyOrder(all); 45 | } 46 | 47 | @Test 48 | void shouldGetAvailableMetrics() { 49 | assertThat(CoverageMetric.getAvailableCoverageMetrics()).containsExactlyInAnyOrder( 50 | CoverageMetric.BRANCH, 51 | CoverageMetric.INSTRUCTION, 52 | CoverageMetric.LINE, 53 | CoverageMetric.METHOD, 54 | CoverageMetric.CLASS, 55 | CoverageMetric.FILE, 56 | CoverageMetric.PACKAGE, 57 | CoverageMetric.MODULE 58 | ); 59 | } 60 | 61 | private void verifyOrder(final List all) { 62 | assertThat(all).containsExactly( 63 | CoverageMetric.MODULE, 64 | CoverageMetric.PACKAGE, 65 | CoverageMetric.FILE, 66 | CoverageMetric.CLASS, 67 | CoverageMetric.METHOD, 68 | CoverageMetric.LINE, 69 | CoverageMetric.INSTRUCTION, 70 | CoverageMetric.BRANCH 71 | ); 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/main/resources/io/jenkins/plugins/coverage/CoverageAction/summary.jelly: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | ${%Coverage Reports} 7 | 8 | 9 |
10 | 11 | 12 |
13 | 14 | 15 | 16 |

17 | Code coverage increased in comparison with the target 18 | branch build: 19 | +${coverageDiff}% 20 |

21 |
22 | 23 |

24 | Code coverage decreased in comparison with the target 25 | branch build: 26 | ${coverageDiff}% 27 |

28 |
29 | 30 |

Code coverage hasn't changed in comparison with the 31 | target branch build 32 |

33 |
34 |
35 |
36 |
37 | 38 | 39 | ${element.name}: 40 | ${result.getCoverage(element).percentage}% 41 | &nbsp; 42 | 43 | 44 |
45 | 46 | 47 |
48 | ${failMessage} 49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 | -------------------------------------------------------------------------------- /src/main/java/io/jenkins/plugins/coverage/targets/CoverageTreeElement.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2007-2018 Seiji Sogabe, podarsmarty, Michael Barrientos and Jenkins contributors 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to deal 6 | * in the Software without restriction, including without limitation the rights 7 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | * copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | * THE SOFTWARE. 21 | */ 22 | package io.jenkins.plugins.coverage.targets; 23 | 24 | import org.kohsuke.stapler.export.Exported; 25 | import org.kohsuke.stapler.export.ExportedBean; 26 | 27 | import java.io.Serializable; 28 | 29 | // Code adopted from Cobertura Plugin https://github.com/jenkinsci/cobertura-plugin/ 30 | @ExportedBean(defaultVisibility = 2) 31 | public class CoverageTreeElement implements Serializable { 32 | 33 | /** 34 | * Generated 35 | */ 36 | private static final long serialVersionUID = 498666415572813346L; 37 | 38 | private Ratio ratio; 39 | 40 | private CoverageElement element; 41 | 42 | public CoverageTreeElement(CoverageElement element, Ratio ratio) { 43 | this.element = element; 44 | this.ratio = ratio; 45 | } 46 | 47 | @Exported 48 | public String getName() { 49 | return element.getName(); 50 | } 51 | 52 | @Exported 53 | public float getRatio() { 54 | return ratio.getPercentageFloat(); 55 | } 56 | 57 | @Exported 58 | public float getNumerator() { 59 | return ratio.numerator; 60 | } 61 | 62 | @Exported 63 | public float getDenominator() { 64 | return ratio.denominator; 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/test/java/io/jenkins/plugins/coverage/CoverageProcessorTest.java: -------------------------------------------------------------------------------- 1 | package io.jenkins.plugins.coverage; 2 | 3 | import hudson.FilePath; 4 | import hudson.model.Run; 5 | import io.jenkins.plugins.coverage.exception.CoverageException; 6 | import io.jenkins.plugins.coverage.targets.CoverageResult; 7 | import org.junit.Assert; 8 | import org.junit.Before; 9 | import org.junit.Test; 10 | import org.mockito.Mockito; 11 | 12 | import static org.mockito.ArgumentMatchers.any; 13 | import static org.mockito.Mockito.*; 14 | 15 | /** 16 | * Acceptance tests for CoverageProcessor. Verifies if set options in CoverageProcessor are used and lead to accepted 17 | * results. 18 | */ 19 | public class CoverageProcessorTest { 20 | 21 | CoverageProcessor coverageProcessor; 22 | CoverageResult coverageResult; 23 | CoverageAction coverageAction; 24 | 25 | @Before() 26 | public void testInit(){ 27 | coverageProcessor = new CoverageProcessor(mock(Run.class),mock(FilePath.class),null); 28 | coverageResult =mock(CoverageResult.class); 29 | coverageAction =mock(CoverageAction.class); 30 | } 31 | 32 | /** 33 | * Test roundFloat method in CoverageProcessor 34 | */ 35 | @Test 36 | public void testFloatRounding() { 37 | float number=-0.001f; 38 | float scaledNumber= CoverageProcessor.roundFloat(number,2); 39 | Assert.assertEquals(scaledNumber,0.00,0); 40 | } 41 | 42 | @Test() 43 | public void testChangeRequestDecreasedCoverageNotFailed_NoChange() throws CoverageException { 44 | Mockito.when(coverageResult.getCoverageDelta(any())).thenReturn(-0.001f); 45 | //No Exception should be thrown 46 | coverageProcessor.failBuildIfChangeRequestDecreasedCoverage(coverageResult,coverageAction); 47 | verify(coverageAction, times(0)).setFailMessage(any()); 48 | } 49 | 50 | @Test(expected = CoverageException.class) 51 | public void testChangeRequestDecreasedCoverageFailed_DecCoverage() throws CoverageException { 52 | Mockito.when(coverageResult.getCoverageDelta(any())).thenReturn(-1.11f); 53 | coverageProcessor.failBuildIfChangeRequestDecreasedCoverage(coverageResult,coverageAction); 54 | verify(coverageAction, times(1)).setFailMessage(any()); 55 | } 56 | 57 | @Test() 58 | public void testChangeRequestDecreasedCoverageNotFailed_IncCoverage() throws CoverageException { 59 | Mockito.when(coverageResult.getCoverageDelta(any())).thenReturn(0.1f); 60 | coverageProcessor.failBuildIfChangeRequestDecreasedCoverage(coverageResult,coverageAction); 61 | verify(coverageAction, times(0)).setFailMessage(any()); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/test/java/io/jenkins/plugins/coverage/model/PluginArchitectureTest.java: -------------------------------------------------------------------------------- 1 | package io.jenkins.plugins.coverage.model; 2 | 3 | import com.tngtech.archunit.junit.AnalyzeClasses; 4 | import com.tngtech.archunit.junit.ArchTest; 5 | import com.tngtech.archunit.lang.ArchRule; 6 | 7 | import edu.hm.hafner.util.ArchitectureRules; 8 | 9 | import io.jenkins.plugins.util.PluginArchitectureRules; 10 | 11 | /** 12 | * Defines several architecture rules for the static analysis model and parsers. 13 | * 14 | * @author Ullrich Hafner 15 | */ 16 | @SuppressWarnings("hideutilityclassconstructor") 17 | @AnalyzeClasses(packages = "io.jenkins.plugins.coverage.model") 18 | class PluginArchitectureTest { 19 | @ArchTest 20 | static final ArchRule NO_EXCEPTIONS_WITH_NO_ARG_CONSTRUCTOR = ArchitectureRules.NO_EXCEPTIONS_WITH_NO_ARG_CONSTRUCTOR; 21 | 22 | @ArchTest 23 | static final ArchRule NO_PUBLIC_TEST_CLASSES = ArchitectureRules.NO_PUBLIC_TEST_CLASSES; 24 | 25 | @ArchTest 26 | static final ArchRule NO_PUBLIC_TEST_METHODS = ArchitectureRules.ONLY_PACKAGE_PRIVATE_TEST_METHODS; 27 | 28 | @ArchTest 29 | static final ArchRule NO_TEST_API_CALLED = ArchitectureRules.NO_TEST_API_CALLED; 30 | 31 | @ArchTest 32 | static final ArchRule NO_FORBIDDEN_ANNOTATION_USED = ArchitectureRules.NO_FORBIDDEN_ANNOTATION_USED; 33 | 34 | @ArchTest 35 | static final ArchRule NO_FORBIDDEN_CLASSES_CALLED = ArchitectureRules.NO_FORBIDDEN_CLASSES_CALLED; 36 | 37 | @ArchTest 38 | static final ArchRule ONLY_PACKAGE_PRIVATE_ARCHITECTURE_TESTS = ArchitectureRules.ONLY_PACKAGE_PRIVATE_ARCHITECTURE_TESTS; 39 | 40 | @ArchTest 41 | static final ArchRule NO_JENKINS_INSTANCE_CALL = PluginArchitectureRules.NO_JENKINS_INSTANCE_CALL; 42 | 43 | @ArchTest 44 | static final ArchRule NO_FORBIDDEN_PACKAGE_ACCESSED = PluginArchitectureRules.NO_FORBIDDEN_PACKAGE_ACCESSED; 45 | 46 | @ArchTest 47 | static final ArchRule AJAX_PROXY_METHOD_MUST_BE_IN_PUBLIC_CLASS = PluginArchitectureRules.AJAX_PROXY_METHOD_MUST_BE_IN_PUBLIC_CLASS; 48 | 49 | @ArchTest 50 | static final ArchRule DATA_BOUND_CONSTRUCTOR_MUST_BE_IN_PUBLIC_CLASS = PluginArchitectureRules.DATA_BOUND_CONSTRUCTOR_MUST_BE_IN_PUBLIC_CLASS; 51 | 52 | @ArchTest 53 | static final ArchRule DATA_BOUND_SETTER_MUST_BE_IN_PUBLIC_CLASS = PluginArchitectureRules.DATA_BOUND_SETTER_MUST_BE_IN_PUBLIC_CLASS; 54 | 55 | @ArchTest 56 | static final ArchRule USE_POST_FOR_VALIDATION_END_POINTS = PluginArchitectureRules.USE_POST_FOR_VALIDATION_END_POINTS; 57 | 58 | @ArchTest 59 | static final ArchRule USE_POST_FOR_LIST_MODELS_RULE = PluginArchitectureRules.USE_POST_FOR_LIST_AND_COMBOBOX_FILL; 60 | } 61 | -------------------------------------------------------------------------------- /src/main/resources/io/jenkins/plugins/coverage/CoverageProjectAction/floatingBox.jelly: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 48 |
49 | 50 |
51 | -------------------------------------------------------------------------------- /src/test/java/io/jenkins/plugins/coverage/model/visualization/colorization/CoverageChangeLevelTest.java: -------------------------------------------------------------------------------- 1 | package io.jenkins.plugins.coverage.model.visualization.colorization; 2 | 3 | import java.awt.*; 4 | 5 | import org.junit.jupiter.api.Test; 6 | 7 | import io.jenkins.plugins.coverage.model.visualization.colorization.ColorProvider.DisplayColors; 8 | 9 | import static org.assertj.core.api.Assertions.*; 10 | 11 | /** 12 | * Test class for {@link CoverageChangeLevel}. 13 | * 14 | * @author Florian Orendi 15 | */ 16 | class CoverageChangeLevelTest { 17 | 18 | private static final ColorProvider COLOR_PROVIDER = ColorProviderFactory.createDefaultColorProvider(); 19 | 20 | @Test 21 | void shouldHaveWorkingGetters() { 22 | CoverageChangeLevel coverageChangeLevel = CoverageChangeLevel.INCREASE_2; 23 | assertThat(coverageChangeLevel.getChange()).isEqualTo(2.0); 24 | assertThat(coverageChangeLevel.getColorizationId()).isEqualTo(ColorId.VERY_GOOD); 25 | } 26 | 27 | @Test 28 | void shouldGetDisplayColorsOfCoveragePercentage() { 29 | Color blendedLineColor = COLOR_PROVIDER.getDisplayColorsOf(ColorId.BLACK).getFillColor(); 30 | Color blendedColorIncreased = ColorProvider.blendColors( 31 | COLOR_PROVIDER.getDisplayColorsOf(CoverageChangeLevel.INCREASE_2.getColorizationId()).getFillColor(), 32 | COLOR_PROVIDER.getDisplayColorsOf(CoverageChangeLevel.EQUALS.getColorizationId()).getFillColor()); 33 | Color blendedColorDecreased = ColorProvider.blendColors( 34 | COLOR_PROVIDER.getDisplayColorsOf(CoverageChangeLevel.DECREASE_2.getColorizationId()).getFillColor(), 35 | COLOR_PROVIDER.getDisplayColorsOf(CoverageChangeLevel.EQUALS.getColorizationId()).getFillColor()); 36 | 37 | assertThat(CoverageChangeLevel.getDisplayColorsOfCoverageChange(1.0, COLOR_PROVIDER)) 38 | .isEqualTo(new DisplayColors(blendedLineColor, blendedColorIncreased)); 39 | assertThat(CoverageChangeLevel.getDisplayColorsOfCoverageChange(-1.0, COLOR_PROVIDER)) 40 | .isEqualTo(new DisplayColors(blendedLineColor, blendedColorDecreased)); 41 | assertThat(CoverageChangeLevel.getDisplayColorsOfCoverageChange(7.0, COLOR_PROVIDER)) 42 | .isEqualTo(COLOR_PROVIDER.getDisplayColorsOf(ColorId.EXCELLENT)); 43 | assertThat(CoverageChangeLevel.getDisplayColorsOfCoverageChange(-2.0, COLOR_PROVIDER)) 44 | .isEqualTo(COLOR_PROVIDER.getDisplayColorsOf(ColorId.INADEQUATE)); 45 | assertThat(CoverageChangeLevel.getDisplayColorsOfCoverageChange(-110.0, COLOR_PROVIDER)) 46 | .isEqualTo(COLOR_PROVIDER.getDisplayColorsOf(ColorId.WHITE)); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/main/resources/io/jenkins/plugins/coverage/threshold/Threshold/config.jelly: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 31 | 32 | 33 | 34 | 35 | 36 | 0% 38 | 39 | 40 | 42 | 43 | 44 | 0% 46 | 47 | 48 | 50 | 51 | 52 | 54 | 55 | 56 | -------------------------------------------------------------------------------- /src/main/resources/coverage/deprecated-coverage-table.jelly: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Provides a table to render the file coverage nodes without the source code. 6 | 7 | The ID of the table. 8 | 9 | 10 | 11 |
12 | 13 | 14 |
15 | 16 | 17 | 18 |
19 |
20 | 21 | 22 | 23 |
24 |
25 | 27 |
28 | 29 |
30 |
31 |
32 |
33 | 34 |
35 |
36 |
${%Please select a file in the table to open the source code}
37 |
38 |
39 |
40 |
41 | 42 |
43 |
44 |
${%No source code available for this file}
45 |
46 |
47 |
48 |
49 |
50 | 51 |
52 | 53 | 54 | 55 |
56 | 57 |
58 |
59 |
60 | 61 |
62 | -------------------------------------------------------------------------------- /src/main/java/io/jenkins/plugins/coverage/model/visualization/colorization/CoverageChangeLevel.java: -------------------------------------------------------------------------------- 1 | package io.jenkins.plugins.coverage.model.visualization.colorization; 2 | 3 | import edu.umd.cs.findbugs.annotations.NonNull; 4 | 5 | import io.jenkins.plugins.coverage.model.visualization.colorization.ColorProvider.DisplayColors; 6 | 7 | /** 8 | * Provides the colorization for different coverage change levels. 9 | * 10 | * @author Florian Orendi 11 | */ 12 | public enum CoverageChangeLevel { 13 | 14 | INCREASE_5(5.0, ColorId.EXCELLENT), 15 | INCREASE_2(2.0, ColorId.VERY_GOOD), 16 | EQUALS(0.0, ColorId.AVERAGE), 17 | DECREASE_2(-2.0, ColorId.INADEQUATE), 18 | DECREASE_5(-5.0, ColorId.BAD), 19 | DECREASE_10(-10.0, ColorId.VERY_BAD), 20 | DECREASE_20(-20.0, ColorId.INSUFFICIENT), 21 | NA(-100.0, ColorId.WHITE); 22 | 23 | private final double change; 24 | private final ColorId colorizationId; 25 | 26 | CoverageChangeLevel(final double change, final ColorId colorizationId) { 27 | this.change = change; 28 | this.colorizationId = colorizationId; 29 | } 30 | 31 | /** 32 | * Gets the {@link DisplayColors display colors} for representing the passed coverage change. If the change is 33 | * placed between two levels, the fill colors are blended. 34 | * 35 | * @param coverageDifference 36 | * The coverage change 37 | * @param colorProvider 38 | * The {@link ColorProvider color provider} to be used 39 | * 40 | * @return the display colors 41 | */ 42 | public static DisplayColors getDisplayColorsOfCoverageChange(final double coverageDifference, 43 | @NonNull final ColorProvider colorProvider) { 44 | for (int i = 0; i < values().length - 1; i++) { 45 | CoverageChangeLevel level = values()[i]; 46 | if (coverageDifference >= level.change) { 47 | if (i == 0) { 48 | return colorProvider.getDisplayColorsOf(level.colorizationId); 49 | } 50 | double distanceLevel = coverageDifference - level.change; 51 | if (distanceLevel == 0) { 52 | return colorProvider.getDisplayColorsOf(level.colorizationId); 53 | } 54 | CoverageChangeLevel upperLevel = values()[i - 1]; 55 | double distanceUpper = upperLevel.change - coverageDifference; 56 | return colorProvider.getBlendedDisplayColors( 57 | distanceLevel, distanceUpper, 58 | upperLevel.colorizationId, 59 | level.colorizationId); 60 | } 61 | } 62 | return colorProvider.getDisplayColorsOf(NA.colorizationId); 63 | } 64 | 65 | public double getChange() { 66 | return change; 67 | } 68 | 69 | public ColorId getColorizationId() { 70 | return colorizationId; 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/main/java/io/jenkins/plugins/coverage/targets/CoverageElementRegister.java: -------------------------------------------------------------------------------- 1 | package io.jenkins.plugins.coverage.targets; 2 | 3 | 4 | import java.util.*; 5 | import java.util.stream.Collectors; 6 | 7 | public class CoverageElementRegister { 8 | 9 | private static Map> typedRegisteredElements = new HashMap<>(); 10 | 11 | static { 12 | addCoverageElement(CoverageElement.AGGREGATED_REPORT); 13 | addCoverageElement(CoverageElement.REPORT); 14 | addCoverageElement(CoverageElement.LINE); 15 | addCoverageElement(CoverageElement.CONDITIONAL); 16 | } 17 | 18 | public static boolean addCoverageElement(CoverageElement element) { 19 | return addCoverageElement(CoverageElement.COVERAGE_ELEMENT_TYPE_NONE, element); 20 | } 21 | 22 | public static boolean addCoverageElement(String type, CoverageElement element) { 23 | typedRegisteredElements.putIfAbsent(type, new LinkedList<>()); 24 | return typedRegisteredElements.get(type).add(element); 25 | } 26 | 27 | public static boolean addCoverageElements(List elements) { 28 | return addCoverageElements(CoverageElement.COVERAGE_ELEMENT_TYPE_NONE, elements); 29 | } 30 | 31 | public static boolean addCoverageElements(String type, List elements) { 32 | typedRegisteredElements.putIfAbsent(type, new LinkedList<>()); 33 | return typedRegisteredElements.get(type).addAll(elements); 34 | } 35 | 36 | public static CoverageElement get(String type, String name) { 37 | return typedRegisteredElements.get(type).stream().filter(c -> c.is(name)).findAny().orElse(null); 38 | } 39 | 40 | public static CoverageElement getDespiteType(String name) { 41 | return typedRegisteredElements.values().stream() 42 | .flatMap(Collection::stream) 43 | .filter(c -> c.is(name)) 44 | .findAny().orElse(null); 45 | } 46 | 47 | public static CoverageElement[] all() { 48 | return typedRegisteredElements.values().stream() 49 | .flatMap(Collection::stream) 50 | .distinct() 51 | .sorted() 52 | .collect(Collectors.toList()) 53 | .toArray(new CoverageElement[]{}); 54 | } 55 | 56 | public static CoverageElement[] listByType(String type) { 57 | return typedRegisteredElements.get(type).toArray(new CoverageElement[]{}); 58 | } 59 | 60 | 61 | public static CoverageElement[] listCommonsAndSpecificType(String type) { 62 | CoverageElement[] elements = typedRegisteredElements.entrySet().stream() 63 | .filter(e -> e.getKey().equals(CoverageElement.COVERAGE_ELEMENT_TYPE_NONE) || e.getKey().equals(type)) 64 | .flatMap(e -> e.getValue().stream()) 65 | .distinct() 66 | .collect(Collectors.toList()) 67 | .toArray(new CoverageElement[]{}); 68 | 69 | Arrays.sort(elements); 70 | return elements; 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/main/java/io/jenkins/plugins/coverage/CoverageProjectAction.java: -------------------------------------------------------------------------------- 1 | 2 | 3 | package io.jenkins.plugins.coverage; 4 | 5 | import hudson.model.Actionable; 6 | import hudson.model.Job; 7 | import hudson.model.ProminentProjectAction; 8 | import hudson.model.Result; 9 | import hudson.model.Run; 10 | import org.kohsuke.stapler.StaplerRequest; 11 | import org.kohsuke.stapler.StaplerResponse; 12 | 13 | import java.io.IOException; 14 | 15 | /** 16 | * Project level action. 17 | * 18 | * @author Stephen Connolly 19 | */ 20 | public class CoverageProjectAction extends Actionable implements ProminentProjectAction { 21 | 22 | private transient Run run; 23 | 24 | public CoverageProjectAction(Run run) { 25 | this.run = run; 26 | } 27 | 28 | /** 29 | * {@inheritDoc} 30 | */ 31 | public String getIconFileName() { 32 | return "graph.gif"; 33 | } 34 | 35 | /** 36 | * {@inheritDoc} 37 | */ 38 | public String getDisplayName() { 39 | return Messages.CoverageProjectAction_displayName(); 40 | } 41 | 42 | /** 43 | * {@inheritDoc} 44 | */ 45 | public String getUrlName() { 46 | return "coverage"; 47 | } 48 | 49 | /** 50 | * Getter for property 'lastResult'. 51 | * 52 | * @return Value for property 'lastResult'. 53 | */ 54 | public CoverageAction getLastResult() { 55 | for (Run b = run.getParent().getLastSuccessfulBuild(); b != null; b = BuildUtils.getPreviousNotFailedCompletedBuild(b)) { 56 | if (b.getResult() != Result.SUCCESS && b.getResult() != Result.UNSTABLE) 57 | continue; 58 | CoverageAction r = b.getAction(CoverageAction.class); 59 | if (r != null) 60 | return r; 61 | } 62 | return null; 63 | } 64 | 65 | /** 66 | * Getter for property 'lastResult'. 67 | * 68 | * @return Value for property 'lastResult'. 69 | */ 70 | public Integer getLastResultBuild() { 71 | for (Run b = run.getParent().getLastSuccessfulBuild(); b != null; b = BuildUtils.getPreviousNotFailedCompletedBuild(b)) { 72 | if (b.getResult() != Result.SUCCESS && b.getResult() != Result.UNSTABLE) 73 | continue; 74 | CoverageAction r = b.getAction(CoverageAction.class); 75 | if (r != null) 76 | return b.getNumber(); 77 | } 78 | return null; 79 | } 80 | 81 | public void doIndex(StaplerRequest req, StaplerResponse rsp) throws IOException { 82 | Integer buildNumber = getLastResultBuild(); 83 | if (buildNumber == null) { 84 | rsp.sendRedirect2("nodata"); 85 | } else { 86 | rsp.sendRedirect2("../" + buildNumber + "/coverage"); 87 | } 88 | } 89 | 90 | public Job getProject() { 91 | return run.getParent(); 92 | } 93 | 94 | /** 95 | * {@inheritDoc} 96 | */ 97 | public String getSearchUrl() { 98 | return getUrlName(); 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /src/main/java/io/jenkins/plugins/coverage/targets/CoverageElement.java: -------------------------------------------------------------------------------- 1 | package io.jenkins.plugins.coverage.targets; 2 | 3 | import edu.umd.cs.findbugs.annotations.NonNull; 4 | 5 | import java.io.Serializable; 6 | import java.util.Objects; 7 | 8 | public class CoverageElement implements Comparable, Serializable { 9 | private static final long serialVersionUID = 6722992955158201174L; 10 | 11 | public final static CoverageElement AGGREGATED_REPORT = new CoverageElement("Aggregated Report", Integer.MIN_VALUE); 12 | public final static CoverageElement REPORT = new CoverageElement("Report", Integer.MIN_VALUE + 1); 13 | 14 | public final static CoverageElement FILE = new CoverageElement("File", 2); 15 | 16 | public final static CoverageElement LINE = new CoverageElement("Line", Integer.MAX_VALUE - 1, true); 17 | public final static CoverageElement CONDITIONAL = new CoverageElement("Conditional", Integer.MAX_VALUE, true); 18 | 19 | public final static String COVERAGE_ELEMENT_TYPE_NONE = "None"; 20 | public final static String COVERAGE_ELEMENT_TYPE_JAVA = "Java"; 21 | public final static String COVERAGE_ELEMENT_TYPE_JAVASCRIPT = "JavaScript"; 22 | 23 | private final String name; 24 | private final int order; 25 | private final boolean isBasicBlock; 26 | 27 | public CoverageElement(final String name, final int order) { 28 | this(name, order, false); 29 | } 30 | 31 | public CoverageElement(final String name, final int order, final boolean isBasicBlock) { 32 | this.name = name; 33 | this.order = order; 34 | this.isBasicBlock = isBasicBlock; 35 | } 36 | 37 | public String getName() { 38 | return name; 39 | } 40 | 41 | public boolean isBasicBlock() { 42 | return isBasicBlock; 43 | } 44 | 45 | public boolean is(final String name) { 46 | return this.name.equals(name); 47 | } 48 | 49 | @Override 50 | public boolean equals(final Object o) { 51 | if (this == o) return true; 52 | if (o == null || getClass() != o.getClass()) return false; 53 | CoverageElement that = (CoverageElement) o; 54 | return Objects.equals(name, that.name); 55 | } 56 | 57 | @Override 58 | public String toString() { 59 | return name; 60 | } 61 | 62 | @Override 63 | public int hashCode() { 64 | return Objects.hash(name); 65 | } 66 | 67 | @Override 68 | public int compareTo(@NonNull final CoverageElement coverageElement) { 69 | if (this.order == coverageElement.order) { 70 | return 0; 71 | } 72 | 73 | return this.order < coverageElement.order ? -1 : 1; 74 | } 75 | 76 | public static CoverageElement get(final String name) { 77 | // Type element are equal when their names are equal, 78 | // so we can get CoverageElement just by name to keep the code simpler 79 | return CoverageElementRegister.getDespiteType(name); 80 | } 81 | 82 | public static CoverageElement get(final String type, final String name) { 83 | return CoverageElementRegister.get(type, name); 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /src/main/java/io/jenkins/plugins/coverage/model/SafeFraction.java: -------------------------------------------------------------------------------- 1 | package io.jenkins.plugins.coverage.model; 2 | 3 | import org.apache.commons.lang3.math.Fraction; 4 | 5 | /** 6 | * A small wrapper for {@link Fraction} instances that avoids an arithmetic overflow by using double based operations in 7 | * case of an exception. 8 | * 9 | * @author Ullrich Hafner 10 | */ 11 | public class SafeFraction { 12 | private final Fraction fraction; 13 | 14 | /** 15 | * Creates a new fraction instance that wraps the specified fraction with safe operations. 16 | * 17 | * @param fraction 18 | * the fraction to wrap 19 | */ 20 | public SafeFraction(final Fraction fraction) { 21 | this.fraction = fraction; 22 | } 23 | 24 | /** 25 | * Multiplies the value of this fraction by another, returning the result in reduced form. Since there might be an 26 | * arithmetic exception due to an overflow, this method will handle this situation by calculating the multiplication 27 | * based on the double values of the fractions. 28 | * 29 | * @param multiplicator 30 | * the fraction to multiply by 31 | * 32 | * @return a {@code Fraction} instance with the resulting values 33 | */ 34 | public Fraction multiplyBy(final Fraction multiplicator) { 35 | try { 36 | return fraction.multiplyBy(multiplicator); 37 | } 38 | catch (ArithmeticException exception) { 39 | return Fraction.getFraction(fraction.doubleValue() * multiplicator.doubleValue()); 40 | } 41 | } 42 | 43 | /** 44 | * Subtracts the value of another fraction from the value of this one, returning the result in reduced form. Since 45 | * there might be an arithmetic exception due to an overflow, this method will handle this situation by calculating 46 | * the subtraction based on the double values of the fractions. 47 | * 48 | * @param subtrahend 49 | * the fraction to subtract 50 | * 51 | * @return a {@code Fraction} instance with the resulting values 52 | */ 53 | public Fraction subtract(final Fraction subtrahend) { 54 | try { 55 | return fraction.subtract(subtrahend); 56 | } 57 | catch (ArithmeticException exception) { 58 | return Fraction.getFraction(fraction.doubleValue() - subtrahend.doubleValue()); 59 | } 60 | } 61 | 62 | /** 63 | * Adds the value of another fraction to the value of this one, returning the result in reduced form. Since 64 | * there might be an arithmetic exception due to an overflow, this method will handle this situation by calculating 65 | * the addition based on the double values of the fractions. 66 | * 67 | * @param summand 68 | * the fraction to add 69 | * 70 | * @return a {@code Fraction} instance with the resulting values 71 | */ 72 | public Fraction add(final Fraction summand) { 73 | try { 74 | return fraction.add(summand); 75 | } 76 | catch (ArithmeticException exception) { 77 | return Fraction.getFraction(fraction.doubleValue() + summand.doubleValue()); 78 | } 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /src/test/java/io/jenkins/plugins/coverage/CoverageCornerCaseTest.java: -------------------------------------------------------------------------------- 1 | package io.jenkins.plugins.coverage; 2 | 3 | import hudson.FilePath; 4 | import io.jenkins.plugins.coverage.adapter.JacocoReportAdapter; 5 | import org.apache.commons.io.FileUtils; 6 | import org.jenkinsci.plugins.workflow.cps.CpsFlowDefinition; 7 | import org.jenkinsci.plugins.workflow.job.WorkflowJob; 8 | import org.jenkinsci.plugins.workflow.job.WorkflowRun; 9 | import org.junit.Assert; 10 | import org.junit.Rule; 11 | import org.junit.Test; 12 | import org.jvnet.hudson.test.JenkinsRule; 13 | 14 | import java.nio.file.Files; 15 | import java.nio.file.Path; 16 | import java.util.Objects; 17 | 18 | import io.jenkins.plugins.coverage.adapter.util.XMLUtils; 19 | import java.io.File; 20 | import org.w3c.dom.Document; 21 | 22 | public class CoverageCornerCaseTest { 23 | 24 | @Rule 25 | public JenkinsRule j = new JenkinsRule(); 26 | 27 | @Test 28 | public void testIfNoReportFound() throws Exception { 29 | CoverageScriptedPipelineScriptBuilder builder = CoverageScriptedPipelineScriptBuilder.builder(); 30 | 31 | WorkflowJob project = j.createProject(WorkflowJob.class, "coverage-corner-test"); 32 | project.setDefinition(new CpsFlowDefinition(builder.build(), true)); 33 | 34 | WorkflowRun r = Objects.requireNonNull(project.scheduleBuild2(0)).waitForStart(); 35 | Assert.assertNotNull(r); 36 | 37 | j.assertBuildStatusSuccess(j.waitForCompletion(r)); 38 | j.assertLogContains("No reports were found", r); 39 | } 40 | 41 | @Test 42 | public void testIfFoundEmptyReport() throws Exception { 43 | CoverageScriptedPipelineScriptBuilder builder = CoverageScriptedPipelineScriptBuilder.builder() 44 | .addAdapter(new JacocoReportAdapter("jacoco.xml")); 45 | 46 | WorkflowJob project = j.createProject(WorkflowJob.class, "coverage-corner-test"); 47 | FilePath workspace = j.jenkins.getWorkspaceFor(project); 48 | 49 | Path emptyFile = Files.createTempFile("test", "found-empty-report"); 50 | 51 | Objects.requireNonNull(workspace) 52 | .child("jacoco.xml") 53 | .copyFrom(emptyFile.toUri().toURL()); 54 | FileUtils.deleteQuietly(emptyFile.toFile()); 55 | 56 | workspace.child("cobertura-coverage.xml"); 57 | FileUtils.deleteQuietly(emptyFile.toFile()); 58 | 59 | project.setDefinition(new CpsFlowDefinition(builder.build(), true)); 60 | 61 | WorkflowRun r = Objects.requireNonNull(project.scheduleBuild2(0)).waitForStart(); 62 | 63 | 64 | Assert.assertNotNull(r); 65 | 66 | j.assertBuildStatusSuccess(j.waitForCompletion(r)); 67 | j.assertLogContains("No reports were found", r); 68 | } 69 | 70 | 71 | @Test 72 | public void testPreventXXE() throws Exception { 73 | /* 74 | Test for SECURITY-1699: if external entities are executed an exception will be thrown 75 | as an invalid external entity (unknown protocol foobar) is defined in the supplied XML 76 | test file 77 | */ 78 | Document d; 79 | File file = new File(getClass().getResource("sec1699.xml").toURI()); 80 | d = XMLUtils.getInstance().readXMLtoDocument(file); 81 | } 82 | 83 | } 84 | --------------------------------------------------------------------------------