Skip to content

Commit

Permalink
Azure DevOps task: Added new setting "publishCodeCoverageResults" Thi…
Browse files Browse the repository at this point in the history
…s allows to publish report in 'Code Coverage' tab directly.

Makes the 'PublishCodeCoverageResults' task obsolete.
  • Loading branch information
danielpalme committed May 9, 2024
1 parent 750c48f commit 01271ab
Show file tree
Hide file tree
Showing 20 changed files with 5,192 additions and 12 deletions.
6 changes: 3 additions & 3 deletions src/.gitignore
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/.vs
/AzureDevopsTask/ReportGenerator/tools
/AzureDevopsTask/ReportGenerator/reportgenerator.js
/AzureDevopsTask/*.vsix
/AzureDevopsTask*/ReportGenerator/tools
/AzureDevopsTask*/ReportGenerator/reportgenerator.js
/AzureDevopsTask*/*.vsix
/TestResults
/Testprojects/CoverageTools
/Testprojects/TestResults
Expand Down
17 changes: 14 additions & 3 deletions src/AzureDevopsTask/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,12 +28,13 @@ To learn how to use *ReportGenerator* have a look at the:
verbosity: 'Info' # The verbosity level of the log messages. Values: Verbose, Info, Warning, Error, Off
title: '' # Optional title.
tag: '$(build.buildnumber)_#$(build.buildid)' # Optional tag or build version.
publishCodeCoverageResults: false # Directly publish report in 'Code Coverage' tab. Makes the 'PublishCodeCoverageResults' task obsolete.
license: '' # Optional license for PRO version. Get your license here: https://reportgenerator.io/pro
customSettings: '' # Optional custom settings (separated by semicolon). See: https://github.com/danielpalme/ReportGenerator/wiki/Settings.
```

### Attention
The [PublishCodeCoverageResultsV1](https://learn.microsoft.com/de-de/azure/devops/pipelines/tasks/reference/publish-code-coverage-results-v1?view=azure-pipelines) task from Microsoft regenerates the report with different settings and based on the supplied _Coberatura_ file (see [announcement](https://docs.microsoft.com/en-us/azure/devops/release-notes/2019/sprint-150-update#cobertura-code-coverage-report-updates)). Moreover it does not necessarily use the latest version of _ReportGenerator_.
The [PublishCodeCoverageResults@1](https://learn.microsoft.com/de-de/azure/devops/pipelines/tasks/reference/publish-code-coverage-results-v1?view=azure-pipelines) task from Microsoft regenerates the report with different settings and based on the supplied _Coberatura_ file (see [announcement](https://docs.microsoft.com/en-us/azure/devops/release-notes/2019/sprint-150-update#cobertura-code-coverage-report-updates)). Moreover it does not necessarily use the latest version of _ReportGenerator_.
To disable the regeneration of the report, you can use the following environment variable in your build:
```yaml
disable.coverage.autogenerate: 'true' # Global environment variable
Expand All @@ -48,14 +49,24 @@ disable.coverage.autogenerate: 'true' # Global environment variable
DISABLE_COVERAGE_AUTOGENERATE: 'true' # Local environment variable
```

The [PublishCodeCoverageResultsV1](https://learn.microsoft.com/de-de/azure/devops/pipelines/tasks/reference/publish-code-coverage-results-v1?view=azure-pipelines) will get [deprecated](https://devblogs.microsoft.com/devops/new-pccr-task/).
The [PublishCodeCoverageResults@1](https://learn.microsoft.com/de-de/azure/devops/pipelines/tasks/reference/publish-code-coverage-results-v1?view=azure-pipelines) will get [deprecated](https://devblogs.microsoft.com/devops/new-pccr-task/).
Microsoft recommends to use the [PublishCodeCoverageResults@2](https://learn.microsoft.com/de-de/azure/devops/pipelines/tasks/reference/publish-code-coverage-results-v2?view=azure-pipelines) instead.
The new version has several disadvantages regarding the report in the `Code Coverage` tab within Azure DevOps
- No branch and method coverage
- No details page for each class

**Recommendation**:
Additionally create an artifact containing the full coverage report generated by *ReportGenerator*. You can download the artifact and get full report with all features:
Use the setting `publishCodeCoverageResults: true` of the *ReportGenerator* task. This way you don't have to use the *PublishCodeCoverageResults@1* or *PublishCodeCoverageResults@2* task at all.
```yaml
- task: reportgenerator@5
displayName: ReportGenerator
inputs:
reports: 'coverage.xml'
targetdir: 'coveragereport'
publishCodeCoverageResults: true
```

If you want to use the *PublishCodeCoverageResults@2* task you could additionally create an artifact containing the full coverage report generated by *ReportGenerator*. You can download the artifact and get full report with all features:
```yaml
- publish: $(Build.SourcesDirectory)/coveragereport
artifact: CoverageReports
Expand Down
78 changes: 74 additions & 4 deletions src/AzureDevopsTask/ReportGenerator/reportgenerator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,24 +26,94 @@ async function executeReportGenerator(): Promise<number> {
});
}

return await tool.exec();
return await tool.execAsync();
}

function publishCodeCoverageReport() {
if (((tl.getInput('publishCodeCoverageResults') || 'false') + '').toLowerCase() !== 'true') {
return;
}

const targetdir = trimPathEnd((tl.getInput('targetdir') || ''));
const reporttypes = (tl.getInput('reporttypes') || '').toLowerCase().split(';');
const createSubdirectoryForAllReportTypes = (tl.getInput('customSettings') || '').toLowerCase().indexOf('createsubdirectoryforallreporttypes=true') > -1;

if (!reporttypes.find(r => r === 'cobertura')) {
tl.setResult(tl.TaskResult.Failed, tl.loc('PublishCodeCoverageResultsRequiresCobertura'));
return;
}

const supportedReportTypes = ['HtmlInline_AzurePipelines', 'HtmlInline_AzurePipelines_Light', 'HtmlInline_AzurePipelines_Dark',
'Html', 'Html_Light', 'Html_Dark', 'Html_BlueRed', 'HtmlInline', 'HtmlSummary', 'Html_BlueRed_Summary',
'HtmlChart'];
let htmlReportType = '';

for (let i = 0; i < supportedReportTypes.length; i++) {
if (reporttypes.find(r => r === supportedReportTypes[i].toLowerCase())) {
htmlReportType = supportedReportTypes[i];
break;
}
}

if (htmlReportType === '') {
tl.setResult(tl.TaskResult.Failed, tl.loc('PublishCodeCoverageResultsRequiresHtmlFormat'));
return;
}

//See: https://github.com/microsoft/azure-pipelines-tasks/blob/master/Tasks/PublishCodeCoverageResultsV1/publishcodecoverageresults.ts
const ccPublisher = new tl.CodeCoveragePublisher();
ccPublisher.publish(
'Cobertura',
targetdir + (createSubdirectoryForAllReportTypes ? '/Cobertura' : '') + '/Cobertura.xml',
targetdir + (createSubdirectoryForAllReportTypes ? '/' + htmlReportType : ''),
undefined);

tl.setResult(tl.TaskResult.Succeeded, tl.loc('SucceedMsg'));
}

async function run() {
const publishCodeCoverageResults = ((tl.getInput('publishCodeCoverageResults') || 'false') + '').toLowerCase() === 'true';
try {
tl.setResourcePath(path.join( __dirname, 'task.json'));

let code = await executeReportGenerator();

if (code != 0) {
tl.setResult(tl.TaskResult.Failed, tl.loc('FailedMsg'));
return;
}

tl.setResult(tl.TaskResult.Succeeded, tl.loc('SucceedMsg'));
} catch (e) {
if (!publishCodeCoverageResults) {
tl.setResult(tl.TaskResult.Succeeded, tl.loc('SucceedMsg'));
return;
}
}
catch (e) {
tl.debug(e.message);
tl.setResult(tl.TaskResult.Failed, e.message);
}

try {
publishCodeCoverageReport();
}
catch (e) {
tl.debug(e.message);
tl.setResult(tl.TaskResult.Failed, tl.loc('FailedToPublishReportMsg') + ': ' + e.message);
return;
}
}

function trimPathEnd(input: string) {
if (!input || input.length === 0) {
return input;
}

let result = input;

while (result.length > 0 && (result.endsWith('/') || result.endsWith('\\'))) {
result = result.substring(0, result.length - 1);
}

return result;
}

run();
10 changes: 9 additions & 1 deletion src/AzureDevopsTask/ReportGenerator/task.json
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,14 @@
"required": false,
"helpMarkDown": "Optional tag or build version."
},
{
"name": "publishCodeCoverageResults",
"type": "boolean",
"label": "Publish code coverage results",
"defaultValue": false,
"required": false,
"helpMarkDown": "Directly publish report in 'Code Coverage' tab. Makes the 'PublishCodeCoverageResults' task obsolete."
},
{
"name": "license",
"type": "string",
Expand All @@ -140,7 +148,7 @@
}
],
"execution": {
"Node10": {
"Node": {
"target": "reportgenerator.js",
"argumentFormat": ""
}
Expand Down
2 changes: 1 addition & 1 deletion src/AzureDevopsTask/vss-extension.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
],
"description": "ReportGenerator converts coverage reports generated by coverlet, OpenCover, dotCover, Visual Studio, NCover, Cobertura, JaCoCo, Clover, gcov, or lcov into human readable reports in various formats.",
"categories": [
"Build and release"
"Azure Pipelines"
],
"tags": ["Code Coverage", "Reporting", "Testing", "TDD"],
"screenshots": [
Expand Down
78 changes: 78 additions & 0 deletions src/AzureDevopsTaskTest/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
# ReportGenerator
*ReportGenerator* converts coverage reports generated by coverlet, OpenCover, dotCover, Visual Studio, NCover, Cobertura, JaCoCo, Clover, gcov, or lcov into human readable reports in various formats.

Author: Daniel Palme
Blog: [www.palmmedia.de](https://www.palmmedia.de)
Twitter: [@danielpalme](https://twitter.com/danielpalme)

## Usage
To learn how to use *ReportGenerator* have a look at the:
* [Usage Guide](https://reportgenerator.io/usage)
* [Command line parameters](https://github.com/danielpalme/ReportGenerator/#usage--command-line-parameters)

### YAML example
```yaml

- task: reportgenerator@5
displayName: ReportGenerator
inputs:
reports: 'coverage.xml' # REQUIRED # The coverage reports that should be parsed (separated by semicolon). Globbing is supported.
targetdir: 'coveragereport' # REQUIRED # The directory where the generated report should be saved.
reporttypes: 'HtmlInline_AzurePipelines;Cobertura' # The output formats and scope (separated by semicolon) Values: Badges, Clover, Cobertura, OpenCover, CsvSummary, Html, Html_Dark, Html_Light, Html_BlueRed, HtmlChart, HtmlInline, HtmlInline_AzurePipelines, HtmlInline_AzurePipelines_Dark, HtmlInline_AzurePipelines_Light, HtmlSummary, Html_BlueRed_Summary, JsonSummary, CodeClimate, Latex, LatexSummary, lcov, MarkdownSummary, MarkdownSummaryGithub, MarkdownDeltaSummary, MHtml, SvgChart, SonarQube, TeamCitySummary, TextSummary, TextDeltaSummary, Xml, XmlSummary
sourcedirs: '' # Optional directories which contain the corresponding source code (separated by semicolon). The source directories are used if coverage report contains classes without path information.
historydir: '' # Optional directory for storing persistent coverage information. Can be used in future reports to show coverage evolution.
plugins: '' # Optional plugin files for custom reports or custom history storage (separated by semicolon).
assemblyfilters: '+*' # Optional list of assemblies that should be included or excluded in the report. Exclusion filters take precedence over inclusion filters. Wildcards are allowed.
classfilters: '+*' # Optional list of classes that should be included or excluded in the report. Exclusion filters take precedence over inclusion filters. Wildcards are allowed.
filefilters: '+*' # Optional list of files that should be included or excluded in the report. Exclusion filters take precedence over inclusion filters. Wildcards are allowed.
verbosity: 'Info' # The verbosity level of the log messages. Values: Verbose, Info, Warning, Error, Off
title: '' # Optional title.
tag: '$(build.buildnumber)_#$(build.buildid)' # Optional tag or build version.
publishCodeCoverageResults: false # Directly publish report in 'Code Coverage' tab. Makes the 'PublishCodeCoverageResults' task obsolete.
license: '' # Optional license for PRO version. Get your license here: https://reportgenerator.io/pro
customSettings: '' # Optional custom settings (separated by semicolon). See: https://github.com/danielpalme/ReportGenerator/wiki/Settings.
```

### Attention
The [PublishCodeCoverageResults@1](https://learn.microsoft.com/de-de/azure/devops/pipelines/tasks/reference/publish-code-coverage-results-v1?view=azure-pipelines) task from Microsoft regenerates the report with different settings and based on the supplied _Coberatura_ file (see [announcement](https://docs.microsoft.com/en-us/azure/devops/release-notes/2019/sprint-150-update#cobertura-code-coverage-report-updates)). Moreover it does not necessarily use the latest version of _ReportGenerator_.
To disable the regeneration of the report, you can use the following environment variable in your build:
```yaml
disable.coverage.autogenerate: 'true' # Global environment variable

- task: PublishCodeCoverageResults@1
displayName: 'Publish code coverage results'
inputs:
codeCoverageTool: Cobertura
summaryFileLocation: '$(Build.SourcesDirectory)/coveragereport/Cobertura.xml'
reportDirectory: '$(Build.SourcesDirectory)/coveragereport'
env:
DISABLE_COVERAGE_AUTOGENERATE: 'true' # Local environment variable
```

The [PublishCodeCoverageResults@1](https://learn.microsoft.com/de-de/azure/devops/pipelines/tasks/reference/publish-code-coverage-results-v1?view=azure-pipelines) will get [deprecated](https://devblogs.microsoft.com/devops/new-pccr-task/).
Microsoft recommends to use the [PublishCodeCoverageResults@2](https://learn.microsoft.com/de-de/azure/devops/pipelines/tasks/reference/publish-code-coverage-results-v2?view=azure-pipelines) instead.
The new version has several disadvantages regarding the report in the `Code Coverage` tab within Azure DevOps
- No branch and method coverage
- No details page for each class

**Recommendation**:
Use the setting `publishCodeCoverageResults: true` of the *ReportGenerator* task. This way you don't have to use the *PublishCodeCoverageResults@1* or *PublishCodeCoverageResults@2* task at all.
```yaml
- task: reportgenerator@5
displayName: ReportGenerator
inputs:
reports: 'coverage.xml'
targetdir: 'coveragereport'
publishCodeCoverageResults: true
```

If you want to use the *PublishCodeCoverageResults@2* task you could additionally create an artifact containing the full coverage report generated by *ReportGenerator*. You can download the artifact and get full report with all features:
```yaml
- publish: $(Build.SourcesDirectory)/coveragereport
artifact: CoverageReports
```

## Screenshots
The screenshots show two snippets of the generated reports:
![Screenshot 1](img/screenshot1.png)
![Screenshot 2](img/screenshot2.png)
Binary file added src/AzureDevopsTaskTest/ReportGenerator/icon.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading

0 comments on commit 01271ab

Please sign in to comment.