Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 50 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ This action allows you to create badges for your README.md, with shields.io, whi
## Table of Content
* [How it Works](#how-it-works)
* [Requirements](#requirements)
* [VSTest](#vstest)
* [MSBuild](#msbuild)
* [Inputs](#inputs)
* [Outputs](#outputs)
* [Example Usage](#example-usage)
Expand All @@ -32,25 +34,63 @@ a gist secret and filename is give, then the shields.io
## Requirements
For this action to work there must be an opencover.xml file available in the workflow and a path to it must be specified as an input parameter.

Making this opencover.xml in .NET is really simple. All you need to do is to install the nuget package ```coverlet.msbuild``` and it's dependency ```coverlet.collector``` in your test project. Then you can generate the test coverage file during your test execution with this command:
For .NET, [Coverlet](https://github.com/coverlet-coverage/coverlet) makes this really simple and flexible. However, you should be aware that there is a
[known issue](https://github.com/coverlet-coverage/coverlet/blob/master/Documentation/KnownIssues.md#1-vstest-stops-process-execution-earlydotnet-test) in
the msbuild version of the project, despite it being the easiest option!

### VSTest

If you are using VSTest to run your tests, you can install the `coverlet.collector` nuget package to create opencover formatted coverage results. To setup your application
for `coverlet.collector`, you must first create a file with a `.runsettings` extension in the solution directory. You will use that `.runsettings` file to configure `coverlet.collector`
like so:

```xml
<!-- coverlet.runsettings -->
<?xml version="1.0" encoding="utf-8" ?>
<RunSettings>
<DataCollectionRunSettings>
<DataCollectors>
<DataCollector friendlyName="XPlat code coverage">
<Configuration>
<Format>opencover</Format>
</Configuration>
</DataCollector>
</DataCollectors>
</DataCollectionRunSettings>
</RunSettings>
```
[Check here for more configuration options](https://github.com/coverlet-coverage/coverlet/blob/master/Documentation/VSTestIntegration.md)

Once you have your `.runsettings` all wrapped up, you can use the following command to generate the coverage report:

```
dotnet test -p:CollectCoverage=true -p:CoverletOutput=TestResults/ -p:CoverletOutputFormat=opencover
dotnet test --collect:"XPlat Code Coverage" --settings coverlet.runsettings
```

### MSTest

Despite it's known issues, if it's usable in your environment, `coverlet.msbuild` is by far the easiest option. All you need to do is install the `coverlet.msbuild` nuget
package in your test project, and then you can run the following command to generate test coverage:

```
dotnet test -p:CollectCoverage=true -p:CoverletOutput=TestResults/ -p:CoverletOutputFormat=opencover
```
The above command will generate an opencover report in ```TestResults/coverage.opencover.xml```.


You don't necessarily have to use the above example to generate the opencover report. If you have other means of doing this, then that should not cause any problems. You actually don't even need a .NET solution. As long as you can provide a path for the coverage file.

## Inputs
| Name | Required | Description |
| --------------- |:---------:| ------------|
| label | Optional | The badge label. For example "Unit Test Coverage". Default value is "Test Coverage" |
| color | Optional | The color of the badge. See https://shields.io/ for options. Default value is brightgreen |
| path | Required | The path to the opencover.xml file |
| gist-filename | Optional | Filename of the Gist used for storing the badge data |
| gist-id | Optional | ID if the Gist used for storing the badge data |
| gist-auth-token | Optional | Auth token that alows to write to the given Gist |
| Name | Required | Description |
| ------------------- |:---------:| ----------------------------------------------------------------------------------------- |
| label | Optional | The badge label. For example "Unit Test Coverage". Default value is "Test Coverage" |
| color | Optional | The color of the badge. See https://shields.io/ for options. Default value is brightgreen |
| path | Required | The path to the opencover.xml file or test results directory |
| filename | Optional | The name of opencover.xml file. Optional if full path is provided in path |
| discover-directory | Optional | Ff true, attempts to locate the most recent test run directory |
| gist-filename | Optional | Filename of the Gist used for storing the badge data |
| gist-id | Optional | ID if the Gist used for storing the badge data |
| gist-auth-token | Optional | Auth token that alows to write to the given Gist |

## Outputs
| Name | Description |
Expand Down
8 changes: 7 additions & 1 deletion action.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,14 @@ inputs:
required: false
default: 'brightgreen'
path:
description: 'the path to the opencover.xml file'
description: 'The path to the opencover.xml file or the test results directory'
required: true
filename:
description: 'The name of opencover.xml file. Optional if full path is provided in path'
required: false
discover-directory:
description: 'If true, attempts to locate the most recent test run directory'
required: false
gist-filename:
description: 'Filename of the Gist used for storing the badge data'
required: false
Expand Down
25 changes: 22 additions & 3 deletions index.js
Original file line number Diff line number Diff line change
@@ -1,16 +1,23 @@
const core = require('@actions/core');
const http = require('https');
const fs = require('fs')
const fs = require('fs');
const path = require('path');

try {
const label = core.getInput('label');
const path = core.getInput('path');
const filepath = core.getInput('path');
const filename = core.getInput('filename');
const discoverDirectory = core.getInput('discover-directory');
const color = core.getInput('color');
const gistFilename = core.getInput('gist-filename');
const gistId = core.getInput('gist-id');
const gistAuthToken = core.getInput('gist-auth-token');

let testReport = readFile(path);
const dir = !!discoverDirectory ?
path.join(filepath, getMostRecentDirectory(filepath).file, filename) :
path.join(filepath, filename);

let testReport = readFile(dir);
let coveragePercentage = extractSummaryFromOpencover(testReport);
let badgeData = createBadgeData(label, coveragePercentage, color);
publishBadge(badgeData, gistFilename, gistId, gistAuthToken);
Expand All @@ -31,6 +38,18 @@ function publishBadge(badgeData, gistFilename, gistId, gistAuthToken) {
}
}

function getMostRecentDirectory(dir) {
const files = orderMostRecentDirectories(dir);
return files.length ? files[0] : undefined;
}

function orderMostRecentDirectories(dir) {
return fs.readdirSync(dir)
.filter(file => fs.lstatSync(path.join(dir, file)).isDirectory())
.map(file => ({ file, mtime: fs.lstatSync(path.join(dir, file)).mtime }))
.sort((a, b) => b.mtime.getTime() - a.mtime.getTime());
}

function postGist(badgeData, gistFilename, gistId, gistAuthToken) {
const request = JSON.stringify({
files: {[gistFilename]: {content: JSON.stringify(badgeData)}}
Expand Down