diff --git a/README.md b/README.md index 8fcd60e..36ef576 100644 --- a/README.md +++ b/README.md @@ -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) @@ -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 + + + + + + + + opencover + + + + + +``` +[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 | diff --git a/action.yaml b/action.yaml index d755a6f..15262f1 100644 --- a/action.yaml +++ b/action.yaml @@ -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 diff --git a/index.js b/index.js index 4536522..d2f2f3e 100644 --- a/index.js +++ b/index.js @@ -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); @@ -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)}}