mirror of
https://github.com/irongut/CodeCoverageSummary.git
synced 2026-05-14 06:00:13 +02:00
Compare commits
31 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| ac37b8e9b7 | |||
| 7a76884d03 | |||
| 2389c9883a | |||
| 02bb824606 | |||
| b97bc1147a | |||
| d8b1bc5ef2 | |||
| 7c070aea38 | |||
| 6ed1f6c50d | |||
| ecc89a90a8 | |||
| 4a0dc323d3 | |||
| 711b5c996a | |||
| 9bbe00ce6b | |||
| f9c0e2020c | |||
| 1b1147a0ce | |||
| 3766e5780e | |||
| 473d18e3ad | |||
| 61d10948c8 | |||
| 7cd8fe49e9 | |||
| 174c97ac3d | |||
| 9b11daeca8 | |||
| 6db46e287e | |||
| c5d12d4a89 | |||
| cae1cbcdad | |||
| ed258b1479 | |||
| 0723132cf6 | |||
| 758c38f591 | |||
| 24062a1b2a | |||
| dcef09e405 | |||
| 9cce3f5374 | |||
| 20025e7e93 | |||
| 0bcadffdb9 |
@@ -0,0 +1,3 @@
|
||||
# These are supported funding model platforms
|
||||
|
||||
github: [irongut]
|
||||
@@ -11,8 +11,10 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
name: CI Build
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Setup .NET
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Setup .Net
|
||||
uses: actions/setup-dotnet@v1
|
||||
with:
|
||||
dotnet-version: 5.0.x
|
||||
@@ -23,11 +25,5 @@ jobs:
|
||||
- name: Build CodeCoverageSummary
|
||||
run: dotnet build src/CodeCoverageSummary.sln --configuration Release --no-restore
|
||||
|
||||
- name: Run CodeCoverageSummary with sample file
|
||||
- name: Test with sample file
|
||||
run: dotnet src/CodeCoverageSummary/bin/Release/net5.0/CodeCoverageSummary.dll src/coverage.cobertura.xml --badge true
|
||||
|
||||
- name: Test Action
|
||||
uses: irongut/CodeCoverageSummary@master
|
||||
with:
|
||||
filename: '/app/sample.coverage.xml'
|
||||
badge: 'true'
|
||||
|
||||
@@ -15,6 +15,8 @@ jobs:
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Setup .Net
|
||||
uses: actions/setup-dotnet@v1
|
||||
@@ -30,6 +32,28 @@ jobs:
|
||||
- name: Test with sample file
|
||||
run: dotnet src/CodeCoverageSummary/bin/Release/net5.0/CodeCoverageSummary.dll src/coverage.cobertura.xml --badge true
|
||||
|
||||
- name: Get Previous Tag
|
||||
id: get_previous_tag
|
||||
run: |
|
||||
PREV_TAG=$(git describe --abbrev=0 --tags "${{ github.ref }}^")
|
||||
echo "::set-output name=baseRef::${PREV_TAG}"
|
||||
|
||||
- name: Generate Changelog
|
||||
id: generate_changelog
|
||||
uses: nblagoev/pull-release-notes-action@v1.0.2
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
base-ref: ${{ steps.get_previous_tag.outputs.baseRef }}
|
||||
head-ref: ${{ github.ref }}
|
||||
|
||||
- name: Add Changelog to Release
|
||||
uses: irongut/EditRelease@v1.0.0
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
id: ${{ github.event.release.id }}
|
||||
body: ${{ steps.generate_changelog.outputs.result }}
|
||||
|
||||
deploy:
|
||||
name: Deploy to GHCR
|
||||
needs: [build]
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
name: Test Linux Runner
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
branches: [master]
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
name: CI Build
|
||||
steps:
|
||||
- name: Test Action
|
||||
uses: irongut/CodeCoverageSummary@master
|
||||
with:
|
||||
filename: '/app/sample.coverage.xml'
|
||||
badge: 'true'
|
||||
@@ -0,0 +1,18 @@
|
||||
name: Test MacOS Runner
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
branches: [master]
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
test:
|
||||
runs-on: macos-latest
|
||||
name: CI Build
|
||||
steps:
|
||||
- name: Test Action
|
||||
uses: irongut/CodeCoverageSummary@master
|
||||
with:
|
||||
filename: '/app/sample.coverage.xml'
|
||||
badge: 'true'
|
||||
@@ -0,0 +1,18 @@
|
||||
name: Test Windows Runner
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
branches: [master]
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
test:
|
||||
runs-on: windows-latest
|
||||
name: CI Build
|
||||
steps:
|
||||
- name: Test Action
|
||||
uses: irongut/CodeCoverageSummary@master
|
||||
with:
|
||||
filename: '/app/sample.coverage.xml'
|
||||
badge: 'true'
|
||||
@@ -1,8 +1,10 @@
|
||||
# Code Coverage Summary
|
||||
|
||||
A GitHub Action that reads Cobertura format code coverage files from your test suite and outputs a text or markdown summary. This summary can then be posted as a Pull Request comment, included in Release Notes, etc by another action to give you an immediate insight into the health of your code without using a third-party site.
|
||||
A GitHub Action that reads Cobertura format code coverage files from your test suite and outputs a text or markdown summary. This summary can be posted as a Pull Request comment or included in Release Notes by other actions to give you an immediate insight into the health of your code without using a third-party site.
|
||||
|
||||
Code Coverage Summary was designed for use with [Coverlet](https://github.com/coverlet-coverage/coverlet) and .Net but it should work with any test framework that outputs coverage in Cobertura format.
|
||||
Code Coverage Summary is designed for use with [Coverlet](https://github.com/coverlet-coverage/coverlet) and [gcovr](https://github.com/gcovr/gcovr) but it should work with any test framework that outputs coverage in Cobertura format.
|
||||
|
||||
As a Docker based action Code Coverage Summary requires a Linux runner, see [Types of Action](https://docs.github.com/en/actions/creating-actions/about-custom-actions#types-of-actions). If you need to build with a Windows or MacOS runner a workaround would be to upload the coverage file as an artifact and use a separate job with a Linux runner to generate the summary.
|
||||
|
||||
## Inputs
|
||||
|
||||
@@ -17,12 +19,30 @@ Note: Coverlet creates the coverage file in a random named directory (guid) so y
|
||||
|
||||
Include a badge reporting the Line Rate coverage in the output using [shields.io](https://shields.io/) - `true` or `false` (default).
|
||||
|
||||
If the overall Line Rate is less than 50% the badge will be red, if it is 50% - 74% it will be yellow and if it is 75% or over it will be green.
|
||||
If the overall Line Rate is less than the lower threshold (50%) the badge will be red, if it is between thresholds it will be yellow and if it greater than or equal to the higher threshold (75%) it will be green. See [`thresholds`](#thresholds) to change these values.
|
||||
|
||||
#### `fail_below_min`
|
||||
**v1.1.0-beta only**
|
||||
|
||||
Fail the workflow if the overall Line Rate is below lower threshold - `true` or `false` (default). The default lower threshold is 50%, see [`thresholds`](#thresholds).
|
||||
|
||||
#### `format`
|
||||
|
||||
Output Format - `markdown` or `text` (default).
|
||||
|
||||
#### `indicators`
|
||||
**v1.1.0-beta only**
|
||||
|
||||
Include health indicators in the output - `true` (default) or `false`.'
|
||||
|
||||
Line Rate | Indicator
|
||||
--------- | ---------
|
||||
less than lower threshold (50%) | ❌
|
||||
between thresholds (50% - 74%) | ➖
|
||||
equal or greater than upper threshold (75%) | ✔
|
||||
|
||||
See [`thresholds`](#thresholds) to change these values.
|
||||
|
||||
#### `output`
|
||||
|
||||
Output Type - `console` (default), `file` or `both`.
|
||||
@@ -33,16 +53,21 @@ Output Type - `console` (default), `file` or `both`.
|
||||
|
||||
`both` will output the coverage summary to the Action log and a file as above.
|
||||
|
||||
#### `thresholds`
|
||||
**v1.1.0-beta only**
|
||||
|
||||
Lower and upper threshold percentages for badge and health indicators, lower threshold can also be used to fail the action. Separate the values with a space and enclose them in quotes; default `'50 75'`.
|
||||
|
||||
## Outputs
|
||||
|
||||
#### Text Example
|
||||
```
|
||||
https://img.shields.io/badge/Code%20Coverage-77%25-success?style=flat
|
||||
Line Rate = 77%, Lines Covered = 1107 / 1433
|
||||
Branch Rate = 60%, Branches Covered = 321 / 532
|
||||
Complexity = 917
|
||||
Company.Example: Line Rate = 78%, Branch Rate = 60%, Complexity = 906
|
||||
Company.Example.Library: Line Rate = 27%, Branch Rate = 100%, Complexity = 11
|
||||
https://img.shields.io/badge/Code%20Coverage-83%25-success?style=flat
|
||||
|
||||
Company.Example: Line Rate = 83%, Branch Rate = 69%, Complexity = 671, ✔
|
||||
Company.Example.Library: Line Rate = 27%, Branch Rate = 100%, Complexity = 11, ❌
|
||||
Summary: Line Rate = 83% (1212 / 1460), Branch Rate = 69% (262 / 378), Complexity = 682, ✔
|
||||
Minimum allowed line rate is 50%
|
||||
```
|
||||
|
||||
#### Markdown Example
|
||||
@@ -52,7 +77,7 @@ Company.Example.Library: Line Rate = 27%, Branch Rate = 100%, Complexity = 11
|
||||
|
||||
```yaml
|
||||
name: Code Coverage Summary Report
|
||||
uses: irongut/CodeCoverageSummary@v1.0.2
|
||||
uses: irongut/CodeCoverageSummary@v1.0.5
|
||||
with:
|
||||
filename: coverage/coverage.cobertura.xml
|
||||
```
|
||||
@@ -94,7 +119,7 @@ jobs:
|
||||
run: cp coverage/**/coverage.cobertura.xml coverage/coverage.cobertura.xml
|
||||
|
||||
- name: Code Coverage Summary Report
|
||||
uses: irongut/CodeCoverageSummary@v1.0.2
|
||||
uses: irongut/CodeCoverageSummary@v1.0.5
|
||||
with:
|
||||
filename: coverage/coverage.cobertura.xml
|
||||
badge: true
|
||||
|
||||
+20
-2
@@ -9,25 +9,43 @@ inputs:
|
||||
description: 'Code coverage file to analyse.'
|
||||
required: true
|
||||
badge:
|
||||
description: 'Include a badge in the output - true / false (default).'
|
||||
description: 'Include a Line Rate coverage badge in the output using shields.io - true / false (default).'
|
||||
required: false
|
||||
default: 'false'
|
||||
fail_below_min:
|
||||
description: 'Fail if overall Line Rate below lower threshold - true / false (default).'
|
||||
required: false
|
||||
default: 'false'
|
||||
format:
|
||||
description: 'Output Format - markdown or text (default).'
|
||||
required: false
|
||||
default: 'text'
|
||||
indicators:
|
||||
description: 'Include health indicators in the output - true (default) / false.'
|
||||
required: false
|
||||
default: 'true'
|
||||
output:
|
||||
description: 'Output Type - console (default), file or both.'
|
||||
required: false
|
||||
default: 'console'
|
||||
thresholds:
|
||||
description: 'Threshold percentages for badge and health indicators, lower threshold can also be used to fail the action.'
|
||||
required: false
|
||||
default: '50 75'
|
||||
runs:
|
||||
using: 'docker'
|
||||
image: 'docker://ghcr.io/irongut/codecoveragesummary:v1.0.2'
|
||||
image: 'docker://ghcr.io/irongut/codecoveragesummary:v1.1.0-beta'
|
||||
args:
|
||||
- ${{ inputs.filename }}
|
||||
- '--badge'
|
||||
- ${{ inputs.badge }}
|
||||
- '--fail'
|
||||
- ${{ inputs.fail_below_min }}
|
||||
- '--format'
|
||||
- ${{ inputs.format }}
|
||||
- '--indicators'
|
||||
- ${{ inputs.indicators }}
|
||||
- '--output'
|
||||
- ${{ inputs.output }}
|
||||
- '--thresholds'
|
||||
- ${{ inputs.thresholds }}
|
||||
|
||||
@@ -6,14 +6,15 @@
|
||||
<DockerDefaultTargetOS>Linux</DockerDefaultTargetOS>
|
||||
<Company>Taranis Software</Company>
|
||||
<Authors>Irongut</Authors>
|
||||
<Description>A GitHub Action that reads Cobertura format code coverage files and outputs a summary.</Description>
|
||||
<Description>A GitHub Action that reads Cobertura format code coverage files and outputs a text or markdown summary.</Description>
|
||||
<Copyright>Copyright © 2021 Taranis Software</Copyright>
|
||||
<PackageId>Taranis.CodeCoverageSummary</PackageId>
|
||||
<PackageLicenseExpression>MIT</PackageLicenseExpression>
|
||||
<PackageProjectUrl>https://github.com/irongut/CodeCoverageSummary</PackageProjectUrl>
|
||||
<RepositoryUrl>https://github.com/irongut/CodeCoverageSummary</RepositoryUrl>
|
||||
<RepositoryType>git</RepositoryType>
|
||||
<PackageTags>code-coverage cobertura coverlet</PackageTags>
|
||||
<PackageTags>coverage test-coverage cobertura action code-coverage coverlet github-actions</PackageTags>
|
||||
<Version>1.1.0</Version>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -10,7 +10,7 @@ namespace CodeCoverageSummary
|
||||
|
||||
public double BranchRate { get; set; }
|
||||
|
||||
public int Complexity { get; set; }
|
||||
public double Complexity { get; set; }
|
||||
}
|
||||
|
||||
public class CodeSummary
|
||||
@@ -27,7 +27,7 @@ namespace CodeCoverageSummary
|
||||
|
||||
public int BranchesValid { get; set; }
|
||||
|
||||
public int Complexity { get; set; }
|
||||
public double Complexity { get; set; }
|
||||
|
||||
public List<CodeCoverage> Packages { get; set; }
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using CommandLine;
|
||||
using System;
|
||||
|
||||
namespace CodeCoverageSummary
|
||||
{
|
||||
@@ -7,13 +8,28 @@ namespace CodeCoverageSummary
|
||||
[Value(index: 0, Required = true, HelpText = "Code coverage file to analyse.")]
|
||||
public string Filename { get; set; }
|
||||
|
||||
[Option(shortName: 'b', longName: "badge", Required = false, HelpText = "Include a badge in the output - true / false.", Default = false)]
|
||||
public bool Badge { get; set; }
|
||||
[Option(longName: "badge", Required = false, HelpText = "Include a Line Rate coverage badge in the output using shields.io - true or false.", Default = "false")]
|
||||
public string BadgeString { get; set; }
|
||||
|
||||
[Option(shortName: 'f', longName: "format", Required = false, HelpText = "Output Format - markdown or text.", Default = "text")]
|
||||
public bool Badge => BadgeString.Equals("true", StringComparison.OrdinalIgnoreCase);
|
||||
|
||||
[Option(longName: "fail", Required = false, HelpText = "Fail if overall Line Rate below lower threshold - true or false.", Default = "false")]
|
||||
public string FailString { get; set; }
|
||||
|
||||
public bool FailBelowThreshold => FailString.Equals("true", StringComparison.OrdinalIgnoreCase);
|
||||
|
||||
[Option(longName: "format", Required = false, HelpText = "Output Format - markdown or text.", Default = "text")]
|
||||
public string Format { get; set; }
|
||||
|
||||
[Option(shortName: 'o', longName: "output", Required = false, HelpText = "Output Type - console, file or both.", Default = "console")]
|
||||
[Option(longName: "indicators", Required = false, HelpText = "Include health indicators in the output - true or false.", Default = "true")]
|
||||
public string IndicatorsString { get; set; }
|
||||
|
||||
public bool Indicators => IndicatorsString.Equals("true", StringComparison.OrdinalIgnoreCase);
|
||||
|
||||
[Option(longName: "output", Required = false, HelpText = "Output Type - console, file or both.", Default = "console")]
|
||||
public string Output { get; set; }
|
||||
|
||||
[Option(longName: "thresholds", Required = false, HelpText = "Threshold percentages for badge and health indicators, lower threshold can also be used to fail the action.", Default = "50 75")]
|
||||
public string Thresholds { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
// This file is used by Code Analysis to maintain SuppressMessage
|
||||
// attributes that are applied to this project.
|
||||
// Project-level suppressions either have no target or are given
|
||||
// a specific target and scoped to a namespace, type, member, etc.
|
||||
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
|
||||
[assembly: SuppressMessage("Performance", "RCS1197:Optimize StringBuilder.Append/AppendLine call.", Scope = "type", Target = "~T:CodeCoverageSummary.Program")]
|
||||
[assembly: SuppressMessage("Style", "IDE0057:Use range operator", Scope = "member", Target = "~M:CodeCoverageSummary.Program.SetThresholds(System.String)")]
|
||||
@@ -9,83 +9,98 @@ namespace CodeCoverageSummary
|
||||
{
|
||||
internal static class Program
|
||||
{
|
||||
// test file: /Dev/Csharp/CodeCoverageSummary/coverage.cobertura.xml
|
||||
private static double lowerThreshold = 0.5;
|
||||
private static double upperThreshold = 0.75;
|
||||
|
||||
private static int Main(string[] args)
|
||||
{
|
||||
return Parser.Default.ParseArguments<CommandLineOptions>(args)
|
||||
.MapResult(o =>
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!File.Exists(o.Filename))
|
||||
{
|
||||
Console.WriteLine("Error: Code coverage file not found.");
|
||||
return -2; // error
|
||||
}
|
||||
.MapResult(o =>
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!File.Exists(o.Filename))
|
||||
{
|
||||
Console.WriteLine("Error: Code coverage file not found.");
|
||||
return -2; // error
|
||||
}
|
||||
|
||||
// parse code coverage file
|
||||
Console.WriteLine($"Code Coverage File: {o.Filename}");
|
||||
CodeSummary summary = ParseTestResults(o.Filename);
|
||||
if (summary == null)
|
||||
{
|
||||
Console.WriteLine("Error: Parsing code coverage file.");
|
||||
return -2; // error
|
||||
}
|
||||
else
|
||||
{
|
||||
// generate badge
|
||||
string badgeUrl = o.Badge ? GenerateBadge(summary) : null;
|
||||
// parse code coverage file
|
||||
Console.WriteLine($"Code Coverage File: {o.Filename}");
|
||||
CodeSummary summary = ParseTestResults(o.Filename);
|
||||
if (summary == null)
|
||||
{
|
||||
Console.WriteLine("Error: Parsing code coverage file.");
|
||||
return -2; // error
|
||||
}
|
||||
else
|
||||
{
|
||||
// set health badge thresholds
|
||||
if (!string.IsNullOrWhiteSpace(o.Thresholds))
|
||||
SetThresholds(o.Thresholds);
|
||||
|
||||
// generate output
|
||||
string output;
|
||||
string fileExt;
|
||||
if (o.Format.Equals("text", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
fileExt = "txt";
|
||||
output = GenerateTextOutput(summary, badgeUrl);
|
||||
}
|
||||
else if (o.Format.Equals("md", StringComparison.OrdinalIgnoreCase) || o.Format.Equals("markdown", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
fileExt = "md";
|
||||
output = GenerateMarkdownOutput(summary, badgeUrl);
|
||||
}
|
||||
else
|
||||
{
|
||||
Console.WriteLine("Error: Unknown output format.");
|
||||
return -2; // error
|
||||
}
|
||||
// generate badge
|
||||
string badgeUrl = o.Badge ? GenerateBadge(summary) : null;
|
||||
|
||||
// output
|
||||
if (o.Output.Equals("console", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
Console.WriteLine(output);
|
||||
}
|
||||
else if (o.Output.Equals("file", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
File.WriteAllText($"code-coverage-results.{fileExt}", output);
|
||||
}
|
||||
else if (o.Output.Equals("both", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
Console.WriteLine(output);
|
||||
File.WriteAllText($"code-coverage-results.{fileExt}", output);
|
||||
}
|
||||
else
|
||||
{
|
||||
Console.WriteLine("Error: Unknown output type.");
|
||||
return -2; // error
|
||||
}
|
||||
// generate output
|
||||
string output;
|
||||
string fileExt;
|
||||
if (o.Format.Equals("text", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
fileExt = "txt";
|
||||
output = GenerateTextOutput(summary, badgeUrl, o.Indicators);
|
||||
if (o.FailBelowThreshold)
|
||||
output += $"Minimum allowed line rate is {lowerThreshold * 100:N0}%{Environment.NewLine}";
|
||||
}
|
||||
else if (o.Format.Equals("md", StringComparison.OrdinalIgnoreCase) || o.Format.Equals("markdown", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
fileExt = "md";
|
||||
output = GenerateMarkdownOutput(summary, badgeUrl, o.Indicators);
|
||||
if (o.FailBelowThreshold)
|
||||
output += $"{Environment.NewLine}_Minimum allowed line rate is `{lowerThreshold * 100:N0}%`_{Environment.NewLine}";
|
||||
}
|
||||
else
|
||||
{
|
||||
Console.WriteLine("Error: Unknown output format.");
|
||||
return -2; // error
|
||||
}
|
||||
|
||||
return 0; // success
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"Error: {ex.GetType()} - {ex.Message}");
|
||||
return -3; // unhandled error
|
||||
}
|
||||
},
|
||||
errs => -1); // invalid arguments
|
||||
// output
|
||||
if (o.Output.Equals("console", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
Console.WriteLine(output);
|
||||
}
|
||||
else if (o.Output.Equals("file", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
File.WriteAllText($"code-coverage-results.{fileExt}", output);
|
||||
}
|
||||
else if (o.Output.Equals("both", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
Console.WriteLine(output);
|
||||
File.WriteAllText($"code-coverage-results.{fileExt}", output);
|
||||
}
|
||||
else
|
||||
{
|
||||
Console.WriteLine("Error: Unknown output type.");
|
||||
return -2; // error
|
||||
}
|
||||
|
||||
if (o.FailBelowThreshold && summary.LineRate < lowerThreshold)
|
||||
{
|
||||
Console.WriteLine($"FAIL: Overall line rate below minimum threshold of {lowerThreshold * 100:N0}%.");
|
||||
return -2;
|
||||
}
|
||||
|
||||
return 0; // success
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"Error: {ex.GetType()} - {ex.Message}");
|
||||
return -3; // unhandled error
|
||||
}
|
||||
},
|
||||
_ => -1); // invalid arguments
|
||||
}
|
||||
|
||||
private static CodeSummary ParseTestResults(string filename)
|
||||
@@ -136,17 +151,19 @@ namespace CodeCoverageSummary
|
||||
var packages = from item in coverage.Descendants("package")
|
||||
select item;
|
||||
|
||||
int i = 1;
|
||||
foreach (var item in packages)
|
||||
{
|
||||
CodeCoverage packageCoverage = new()
|
||||
{
|
||||
Name = item.Attribute("name").Value,
|
||||
LineRate = double.Parse(item.Attribute("line-rate").Value),
|
||||
BranchRate = double.Parse(item.Attribute("branch-rate").Value),
|
||||
Complexity = int.Parse(item.Attribute("complexity").Value)
|
||||
Name = string.IsNullOrWhiteSpace(item.Attribute("name").Value) ? $"Package {i}" : item.Attribute("name").Value,
|
||||
LineRate = double.Parse(item.Attribute("line-rate")?.Value ?? "0"),
|
||||
BranchRate = double.Parse(item.Attribute("branch-rate")?.Value ?? "0"),
|
||||
Complexity = double.Parse(item.Attribute("complexity")?.Value ?? "0")
|
||||
};
|
||||
summary.Packages.Add(packageCoverage);
|
||||
summary.Complexity += packageCoverage.Complexity;
|
||||
i++;
|
||||
}
|
||||
|
||||
return summary;
|
||||
@@ -158,14 +175,49 @@ namespace CodeCoverageSummary
|
||||
}
|
||||
}
|
||||
|
||||
private static void SetThresholds(string thresholds)
|
||||
{
|
||||
int lowerPercentage;
|
||||
int upperPercentage = (int)(upperThreshold * 100);
|
||||
int s = thresholds.IndexOf(" ");
|
||||
if (s == 0)
|
||||
{
|
||||
throw new ArgumentException("Threshold parameter set incorrectly.");
|
||||
}
|
||||
else if (s < 0)
|
||||
{
|
||||
if (!int.TryParse(thresholds, out lowerPercentage))
|
||||
throw new ArgumentException("Threshold parameter set incorrectly.");
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!int.TryParse(thresholds.Substring(0, s), out lowerPercentage))
|
||||
throw new ArgumentException("Threshold parameter set incorrectly.");
|
||||
|
||||
if (!int.TryParse(thresholds.Substring(s + 1), out upperPercentage))
|
||||
throw new ArgumentException("Threshold parameter set incorrectly.");
|
||||
}
|
||||
lowerThreshold = lowerPercentage / 100.0;
|
||||
upperThreshold = upperPercentage / 100.0;
|
||||
|
||||
if (lowerThreshold > 1.0)
|
||||
lowerThreshold = 1.0;
|
||||
|
||||
if (lowerThreshold > upperThreshold)
|
||||
upperThreshold = lowerThreshold + 0.1;
|
||||
|
||||
if (upperThreshold > 1.0)
|
||||
upperThreshold = 1.0;
|
||||
}
|
||||
|
||||
private static string GenerateBadge(CodeSummary summary)
|
||||
{
|
||||
string colour;
|
||||
if (summary.LineRate < 0.5)
|
||||
if (summary.LineRate < lowerThreshold)
|
||||
{
|
||||
colour = "critical";
|
||||
}
|
||||
else if (summary.LineRate < 0.75)
|
||||
else if (summary.LineRate < upperThreshold)
|
||||
{
|
||||
colour = "yellow";
|
||||
}
|
||||
@@ -176,47 +228,75 @@ namespace CodeCoverageSummary
|
||||
return $"https://img.shields.io/badge/Code%20Coverage-{summary.LineRate * 100:N0}%25-{colour}?style=flat";
|
||||
}
|
||||
|
||||
private static string GenerateTextOutput(CodeSummary summary, string badgeUrl)
|
||||
private static string GenerateHealthIndicator(double rate)
|
||||
{
|
||||
if (rate < lowerThreshold)
|
||||
{
|
||||
return "❌";
|
||||
}
|
||||
else if (rate < upperThreshold)
|
||||
{
|
||||
return "➖";
|
||||
}
|
||||
else
|
||||
{
|
||||
return "✔";
|
||||
}
|
||||
}
|
||||
|
||||
private static string GenerateTextOutput(CodeSummary summary, string badgeUrl, bool indicators)
|
||||
{
|
||||
StringBuilder textOutput = new();
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(badgeUrl))
|
||||
{
|
||||
textOutput.AppendLine(badgeUrl);
|
||||
textOutput.AppendLine(badgeUrl)
|
||||
.AppendLine();
|
||||
}
|
||||
|
||||
textOutput.AppendLine($"Line Rate = {summary.LineRate * 100:N0}%, Lines Covered = {summary.LinesCovered} / {summary.LinesValid}")
|
||||
.AppendLine($"Branch Rate = {summary.BranchRate * 100:N0}%, Branches Covered = {summary.BranchesCovered} / {summary.BranchesValid}")
|
||||
.AppendLine($"Complexity = {summary.Complexity}");
|
||||
|
||||
foreach (CodeCoverage package in summary.Packages)
|
||||
{
|
||||
textOutput.AppendLine($"{package.Name}: Line Rate = {package.LineRate * 100:N0}%, Branch Rate = {package.BranchRate * 100:N0}%, Complexity = {package.Complexity}");
|
||||
textOutput.Append($"{package.Name}: Line Rate = {package.LineRate * 100:N0}%")
|
||||
.Append($", Branch Rate = {package.BranchRate * 100:N0}%")
|
||||
.Append((package.Complexity % 1 == 0) ? $", Complexity = {package.Complexity}" : $", Complexity = {package.Complexity:N4}")
|
||||
.AppendLine(indicators ? $", {GenerateHealthIndicator(package.LineRate)}" : string.Empty);
|
||||
}
|
||||
|
||||
textOutput.Append($"Summary: Line Rate = {summary.LineRate * 100:N0}% ({summary.LinesCovered} / {summary.LinesValid})")
|
||||
.Append($", Branch Rate = {summary.BranchRate * 100:N0}% ({summary.BranchesCovered} / {summary.BranchesValid})")
|
||||
.Append((summary.Complexity % 1 == 0) ? $", Complexity = {summary.Complexity}" : $", Complexity = {summary.Complexity:N4}")
|
||||
.AppendLine(indicators ? $", {GenerateHealthIndicator(summary.LineRate)}" : string.Empty);
|
||||
|
||||
return textOutput.ToString();
|
||||
}
|
||||
|
||||
private static string GenerateMarkdownOutput(CodeSummary summary, string badgeUrl)
|
||||
private static string GenerateMarkdownOutput(CodeSummary summary, string badgeUrl, bool indicators)
|
||||
{
|
||||
StringBuilder markdownOutput = new();
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(badgeUrl))
|
||||
{
|
||||
markdownOutput.AppendLine($"");
|
||||
markdownOutput.AppendLine("");
|
||||
markdownOutput.AppendLine($"")
|
||||
.AppendLine();
|
||||
}
|
||||
|
||||
markdownOutput.AppendLine("Package | Line Rate | Branch Rate | Complexity")
|
||||
.AppendLine("-------- | --------- | ----------- | ----------");
|
||||
markdownOutput.Append("Package | Line Rate | Branch Rate | Complexity")
|
||||
.AppendLine(indicators ? " | Health" : string.Empty)
|
||||
.Append("-------- | --------- | ----------- | ----------")
|
||||
.AppendLine(indicators ? " | ------" : string.Empty);
|
||||
|
||||
foreach (CodeCoverage package in summary.Packages)
|
||||
{
|
||||
markdownOutput.AppendLine($"{package.Name} | {package.LineRate * 100:N0}% | {package.BranchRate * 100:N0}% | {package.Complexity}");
|
||||
markdownOutput.Append($"{package.Name} | {package.LineRate * 100:N0}%")
|
||||
.Append($" | {package.BranchRate * 100:N0}%")
|
||||
.Append((package.Complexity % 1 == 0) ? $" | {package.Complexity}" : $" | {package.Complexity:N4}" )
|
||||
.AppendLine(indicators ? $" | {GenerateHealthIndicator(package.LineRate)}" : string.Empty);
|
||||
}
|
||||
|
||||
markdownOutput.Append($"**Summary** | **{summary.LineRate * 100:N0}%** ({summary.LinesCovered} / {summary.LinesValid}) | ")
|
||||
.AppendLine($"**{summary.BranchRate * 100:N0}%** ({summary.BranchesCovered} / {summary.BranchesValid}) | {summary.Complexity}");
|
||||
markdownOutput.Append($"**Summary** | **{summary.LineRate * 100:N0}%** ({summary.LinesCovered} / {summary.LinesValid})")
|
||||
.Append($" | **{summary.BranchRate * 100:N0}%** ({summary.BranchesCovered} / {summary.BranchesValid})")
|
||||
.Append((summary.Complexity % 1 == 0) ? $" | {summary.Complexity}" : $" | {summary.Complexity:N4}")
|
||||
.AppendLine(indicators ? $" | {GenerateHealthIndicator(summary.LineRate)}" : string.Empty);
|
||||
|
||||
return markdownOutput.ToString();
|
||||
}
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
{
|
||||
"profiles": {
|
||||
"CodeCoverageSummary": {
|
||||
"commandName": "Project"
|
||||
"commandName": "Project",
|
||||
"commandLineArgs": "../../../../coverage.cobertura.xml --format=md --badge true --thresholds=\"85 90\" --fail true"
|
||||
},
|
||||
"Docker": {
|
||||
"commandName": "Docker",
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
<!-- Portions (C) International Organization for Standardization 1986:
|
||||
Permission to copy in any form is granted for use with
|
||||
conforming SGML systems and applications as defined in
|
||||
ISO 8879, provided this notice is included in all copies.
|
||||
-->
|
||||
|
||||
<!ELEMENT coverage (sources?,packages)>
|
||||
<!ATTLIST coverage line-rate CDATA #REQUIRED>
|
||||
<!ATTLIST coverage branch-rate CDATA #REQUIRED>
|
||||
<!ATTLIST coverage lines-covered CDATA #REQUIRED>
|
||||
<!ATTLIST coverage lines-valid CDATA #REQUIRED>
|
||||
<!ATTLIST coverage branches-covered CDATA #REQUIRED>
|
||||
<!ATTLIST coverage branches-valid CDATA #REQUIRED>
|
||||
<!ATTLIST coverage complexity CDATA #REQUIRED>
|
||||
<!ATTLIST coverage version CDATA #REQUIRED>
|
||||
<!ATTLIST coverage timestamp CDATA #REQUIRED>
|
||||
|
||||
<!ELEMENT sources (source*)>
|
||||
|
||||
<!ELEMENT source (#PCDATA)>
|
||||
|
||||
<!ELEMENT packages (package*)>
|
||||
|
||||
<!ELEMENT package (classes)>
|
||||
<!ATTLIST package name CDATA #REQUIRED>
|
||||
<!ATTLIST package line-rate CDATA #REQUIRED>
|
||||
<!ATTLIST package branch-rate CDATA #REQUIRED>
|
||||
<!ATTLIST package complexity CDATA #REQUIRED>
|
||||
|
||||
<!ELEMENT classes (class*)>
|
||||
|
||||
<!ELEMENT class (methods,lines)>
|
||||
<!ATTLIST class name CDATA #REQUIRED>
|
||||
<!ATTLIST class filename CDATA #REQUIRED>
|
||||
<!ATTLIST class line-rate CDATA #REQUIRED>
|
||||
<!ATTLIST class branch-rate CDATA #REQUIRED>
|
||||
<!ATTLIST class complexity CDATA #REQUIRED>
|
||||
|
||||
<!ELEMENT methods (method*)>
|
||||
|
||||
<!ELEMENT method (lines)>
|
||||
<!ATTLIST method name CDATA #REQUIRED>
|
||||
<!ATTLIST method signature CDATA #REQUIRED>
|
||||
<!ATTLIST method line-rate CDATA #REQUIRED>
|
||||
<!ATTLIST method branch-rate CDATA #REQUIRED>
|
||||
|
||||
<!ELEMENT lines (line*)>
|
||||
|
||||
<!ELEMENT line (conditions*)>
|
||||
<!ATTLIST line number CDATA #REQUIRED>
|
||||
<!ATTLIST line hits CDATA #REQUIRED>
|
||||
<!ATTLIST line branch CDATA "false">
|
||||
<!ATTLIST line condition-coverage CDATA "100%">
|
||||
|
||||
<!ELEMENT conditions (condition*)>
|
||||
|
||||
<!ELEMENT condition EMPTY>
|
||||
<!ATTLIST condition number CDATA #REQUIRED>
|
||||
<!ATTLIST condition type CDATA #REQUIRED>
|
||||
<!ATTLIST condition coverage CDATA #REQUIRED>
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user