Skip to content

Commit 7a76884

Browse files
authored
merge PR #18 Health indicators, change thresholds + fail workflow
2 parents 711b5c9 + 2389c98 commit 7a76884

File tree

5 files changed

+136
-52
lines changed

5 files changed

+136
-52
lines changed

action.yml

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,25 +9,43 @@ inputs:
99
description: 'Code coverage file to analyse.'
1010
required: true
1111
badge:
12-
description: 'Include a badge in the output - true / false (default).'
12+
description: 'Include a Line Rate coverage badge in the output using shields.io - true / false (default).'
13+
required: false
14+
default: 'false'
15+
fail_below_min:
16+
description: 'Fail if overall Line Rate below lower threshold - true / false (default).'
1317
required: false
1418
default: 'false'
1519
format:
1620
description: 'Output Format - markdown or text (default).'
1721
required: false
1822
default: 'text'
23+
indicators:
24+
description: 'Include health indicators in the output - true (default) / false.'
25+
required: false
26+
default: 'true'
1927
output:
2028
description: 'Output Type - console (default), file or both.'
2129
required: false
2230
default: 'console'
31+
thresholds:
32+
description: 'Threshold percentages for badge and health indicators, lower threshold can also be used to fail the action.'
33+
required: false
34+
default: '50 75'
2335
runs:
2436
using: 'docker'
2537
image: 'docker://ghcr.io/irongut/codecoveragesummary:v1.0.5'
2638
args:
2739
- ${{ inputs.filename }}
2840
- '--badge'
2941
- ${{ inputs.badge }}
42+
- '--fail'
43+
- ${{ inputs.fail_below_min }}
3044
- '--format'
3145
- ${{ inputs.format }}
46+
- '--indicators'
47+
- ${{ inputs.indicators }}
3248
- '--output'
3349
- ${{ inputs.output }}
50+
- '--thresholds'
51+
- ${{ inputs.thresholds }}
Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using CommandLine;
2+
using System;
23

34
namespace CodeCoverageSummary
45
{
@@ -7,13 +8,28 @@ public class CommandLineOptions
78
[Value(index: 0, Required = true, HelpText = "Code coverage file to analyse.")]
89
public string Filename { get; set; }
910

10-
[Option(longName: "badge", Required = false, HelpText = "Include a badge reporting the Line Rate coverage in the output using shields.io - true or false.", Default = false)]
11-
public bool Badge { get; set; }
11+
[Option(longName: "badge", Required = false, HelpText = "Include a Line Rate coverage badge in the output using shields.io - true or false.", Default = "false")]
12+
public string BadgeString { get; set; }
13+
14+
public bool Badge => BadgeString.Equals("true", StringComparison.OrdinalIgnoreCase);
15+
16+
[Option(longName: "fail", Required = false, HelpText = "Fail if overall Line Rate below lower threshold - true or false.", Default = "false")]
17+
public string FailString { get; set; }
18+
19+
public bool FailBelowThreshold => FailString.Equals("true", StringComparison.OrdinalIgnoreCase);
1220

1321
[Option(longName: "format", Required = false, HelpText = "Output Format - markdown or text.", Default = "text")]
1422
public string Format { get; set; }
1523

24+
[Option(longName: "indicators", Required = false, HelpText = "Include health indicators in the output - true or false.", Default = "true")]
25+
public string IndicatorsString { get; set; }
26+
27+
public bool Indicators => IndicatorsString.Equals("true", StringComparison.OrdinalIgnoreCase);
28+
1629
[Option(longName: "output", Required = false, HelpText = "Output Type - console, file or both.", Default = "console")]
1730
public string Output { get; set; }
31+
32+
[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")]
33+
public string Thresholds { get; set; }
1834
}
1935
}

src/CodeCoverageSummary/GlobalSuppressions.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,4 @@
66
using System.Diagnostics.CodeAnalysis;
77

88
[assembly: SuppressMessage("Performance", "RCS1197:Optimize StringBuilder.Append/AppendLine call.", Scope = "type", Target = "~T:CodeCoverageSummary.Program")]
9+
[assembly: SuppressMessage("Style", "IDE0057:Use range operator", Scope = "member", Target = "~M:CodeCoverageSummary.Program.SetThresholds(System.String)")]

src/CodeCoverageSummary/Program.cs

Lines changed: 97 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,9 @@ namespace CodeCoverageSummary
99
{
1010
internal static class Program
1111
{
12+
private static double lowerThreshold = 0.5;
13+
private static double upperThreshold = 0.75;
14+
1215
private static int Main(string[] args)
1316
{
1417
return Parser.Default.ParseArguments<CommandLineOptions>(args)
@@ -32,6 +35,10 @@ private static int Main(string[] args)
3235
}
3336
else
3437
{
38+
// set health badge thresholds
39+
if (!string.IsNullOrWhiteSpace(o.Thresholds))
40+
SetThresholds(o.Thresholds);
41+
3542
// generate badge
3643
string badgeUrl = o.Badge ? GenerateBadge(summary) : null;
3744

@@ -41,12 +48,16 @@ private static int Main(string[] args)
4148
if (o.Format.Equals("text", StringComparison.OrdinalIgnoreCase))
4249
{
4350
fileExt = "txt";
44-
output = GenerateTextOutput(summary, badgeUrl);
51+
output = GenerateTextOutput(summary, badgeUrl, o.Indicators);
52+
if (o.FailBelowThreshold)
53+
output += $"Minimum allowed line rate is {lowerThreshold * 100:N0}%{Environment.NewLine}";
4554
}
4655
else if (o.Format.Equals("md", StringComparison.OrdinalIgnoreCase) || o.Format.Equals("markdown", StringComparison.OrdinalIgnoreCase))
4756
{
4857
fileExt = "md";
49-
output = GenerateMarkdownOutput(summary, badgeUrl);
58+
output = GenerateMarkdownOutput(summary, badgeUrl, o.Indicators);
59+
if (o.FailBelowThreshold)
60+
output += $"{Environment.NewLine}_Minimum allowed line rate is `{lowerThreshold * 100:N0}%`_{Environment.NewLine}";
5061
}
5162
else
5263
{
@@ -74,6 +85,12 @@ private static int Main(string[] args)
7485
return -2; // error
7586
}
7687

88+
if (o.FailBelowThreshold && summary.LineRate < lowerThreshold)
89+
{
90+
Console.WriteLine($"FAIL: Overall line rate below minimum threshold of {lowerThreshold * 100:N0}%.");
91+
return -2;
92+
}
93+
7794
return 0; // success
7895
}
7996
}
@@ -158,14 +175,49 @@ private static CodeSummary ParseTestResults(string filename)
158175
}
159176
}
160177

178+
private static void SetThresholds(string thresholds)
179+
{
180+
int lowerPercentage;
181+
int upperPercentage = (int)(upperThreshold * 100);
182+
int s = thresholds.IndexOf(" ");
183+
if (s == 0)
184+
{
185+
throw new ArgumentException("Threshold parameter set incorrectly.");
186+
}
187+
else if (s < 0)
188+
{
189+
if (!int.TryParse(thresholds, out lowerPercentage))
190+
throw new ArgumentException("Threshold parameter set incorrectly.");
191+
}
192+
else
193+
{
194+
if (!int.TryParse(thresholds.Substring(0, s), out lowerPercentage))
195+
throw new ArgumentException("Threshold parameter set incorrectly.");
196+
197+
if (!int.TryParse(thresholds.Substring(s + 1), out upperPercentage))
198+
throw new ArgumentException("Threshold parameter set incorrectly.");
199+
}
200+
lowerThreshold = lowerPercentage / 100.0;
201+
upperThreshold = upperPercentage / 100.0;
202+
203+
if (lowerThreshold > 1.0)
204+
lowerThreshold = 1.0;
205+
206+
if (lowerThreshold > upperThreshold)
207+
upperThreshold = lowerThreshold + 0.1;
208+
209+
if (upperThreshold > 1.0)
210+
upperThreshold = 1.0;
211+
}
212+
161213
private static string GenerateBadge(CodeSummary summary)
162214
{
163215
string colour;
164-
if (summary.LineRate < 0.5)
216+
if (summary.LineRate < lowerThreshold)
165217
{
166218
colour = "critical";
167219
}
168-
else if (summary.LineRate < 0.75)
220+
else if (summary.LineRate < upperThreshold)
169221
{
170222
colour = "yellow";
171223
}
@@ -176,78 +228,75 @@ private static string GenerateBadge(CodeSummary summary)
176228
return $"https://img.shields.io/badge/Code%20Coverage-{summary.LineRate * 100:N0}%25-{colour}?style=flat";
177229
}
178230

179-
private static string GenerateTextOutput(CodeSummary summary, string badgeUrl)
231+
private static string GenerateHealthIndicator(double rate)
180232
{
181-
StringBuilder textOutput = new();
182-
183-
if (!string.IsNullOrWhiteSpace(badgeUrl))
233+
if (rate < lowerThreshold)
184234
{
185-
textOutput.AppendLine(badgeUrl);
235+
return "❌";
186236
}
187-
188-
textOutput.AppendLine($"Line Rate = {summary.LineRate * 100:N0}%, Lines Covered = {summary.LinesCovered} / {summary.LinesValid}")
189-
.AppendLine($"Branch Rate = {summary.BranchRate * 100:N0}%, Branches Covered = {summary.BranchesCovered} / {summary.BranchesValid}");
190-
191-
if (summary.Complexity % 1 == 0)
237+
else if (rate < upperThreshold)
192238
{
193-
textOutput.AppendLine($"Complexity = {summary.Complexity}");
239+
return "➖";
194240
}
195241
else
196242
{
197-
textOutput.AppendLine($"Complexity = {summary.Complexity:N4}");
243+
return "✔";
244+
}
245+
}
246+
247+
private static string GenerateTextOutput(CodeSummary summary, string badgeUrl, bool indicators)
248+
{
249+
StringBuilder textOutput = new();
250+
251+
if (!string.IsNullOrWhiteSpace(badgeUrl))
252+
{
253+
textOutput.AppendLine(badgeUrl)
254+
.AppendLine();
198255
}
199256

200257
foreach (CodeCoverage package in summary.Packages)
201258
{
202-
if (package.Complexity % 1 == 0)
203-
{
204-
textOutput.AppendLine($"{package.Name}: Line Rate = {package.LineRate * 100:N0}%, Branch Rate = {package.BranchRate * 100:N0}%, Complexity = {package.Complexity}");
205-
}
206-
else
207-
{
208-
textOutput.AppendLine($"{package.Name}: Line Rate = {package.LineRate * 100:N0}%, Branch Rate = {package.BranchRate * 100:N0}%, Complexity = {package.Complexity:N4}");
209-
}
259+
textOutput.Append($"{package.Name}: Line Rate = {package.LineRate * 100:N0}%")
260+
.Append($", Branch Rate = {package.BranchRate * 100:N0}%")
261+
.Append((package.Complexity % 1 == 0) ? $", Complexity = {package.Complexity}" : $", Complexity = {package.Complexity:N4}")
262+
.AppendLine(indicators ? $", {GenerateHealthIndicator(package.LineRate)}" : string.Empty);
210263
}
211264

265+
textOutput.Append($"Summary: Line Rate = {summary.LineRate * 100:N0}% ({summary.LinesCovered} / {summary.LinesValid})")
266+
.Append($", Branch Rate = {summary.BranchRate * 100:N0}% ({summary.BranchesCovered} / {summary.BranchesValid})")
267+
.Append((summary.Complexity % 1 == 0) ? $", Complexity = {summary.Complexity}" : $", Complexity = {summary.Complexity:N4}")
268+
.AppendLine(indicators ? $", {GenerateHealthIndicator(summary.LineRate)}" : string.Empty);
269+
212270
return textOutput.ToString();
213271
}
214272

215-
private static string GenerateMarkdownOutput(CodeSummary summary, string badgeUrl)
273+
private static string GenerateMarkdownOutput(CodeSummary summary, string badgeUrl, bool indicators)
216274
{
217275
StringBuilder markdownOutput = new();
218276

219277
if (!string.IsNullOrWhiteSpace(badgeUrl))
220278
{
221-
markdownOutput.AppendLine($"![Code Coverage]({badgeUrl})");
222-
markdownOutput.AppendLine("");
279+
markdownOutput.AppendLine($"![Code Coverage]({badgeUrl})")
280+
.AppendLine();
223281
}
224282

225-
markdownOutput.AppendLine("Package | Line Rate | Branch Rate | Complexity")
226-
.AppendLine("-------- | --------- | ----------- | ----------");
283+
markdownOutput.Append("Package | Line Rate | Branch Rate | Complexity")
284+
.AppendLine(indicators ? " | Health" : string.Empty)
285+
.Append("-------- | --------- | ----------- | ----------")
286+
.AppendLine(indicators ? " | ------" : string.Empty);
227287

228288
foreach (CodeCoverage package in summary.Packages)
229289
{
230-
if (package.Complexity % 1 == 0)
231-
{
232-
markdownOutput.AppendLine($"{package.Name} | {package.LineRate * 100:N0}% | {package.BranchRate * 100:N0}% | {package.Complexity}");
233-
}
234-
else
235-
{
236-
markdownOutput.AppendLine($"{package.Name} | {package.LineRate * 100:N0}% | {package.BranchRate * 100:N0}% | {package.Complexity:N4}");
237-
}
290+
markdownOutput.Append($"{package.Name} | {package.LineRate * 100:N0}%")
291+
.Append($" | {package.BranchRate * 100:N0}%")
292+
.Append((package.Complexity % 1 == 0) ? $" | {package.Complexity}" : $" | {package.Complexity:N4}" )
293+
.AppendLine(indicators ? $" | {GenerateHealthIndicator(package.LineRate)}" : string.Empty);
238294
}
239295

240-
markdownOutput.Append($"**Summary** | **{summary.LineRate * 100:N0}%** ({summary.LinesCovered} / {summary.LinesValid}) | ")
241-
.Append($"**{summary.BranchRate * 100:N0}%** ({summary.BranchesCovered} / {summary.BranchesValid}) | ");
242-
243-
if (summary.Complexity % 1 == 0)
244-
{
245-
markdownOutput.AppendLine(summary.Complexity.ToString());
246-
}
247-
else
248-
{
249-
markdownOutput.AppendLine(summary.Complexity.ToString("N4"));
250-
}
296+
markdownOutput.Append($"**Summary** | **{summary.LineRate * 100:N0}%** ({summary.LinesCovered} / {summary.LinesValid})")
297+
.Append($" | **{summary.BranchRate * 100:N0}%** ({summary.BranchesCovered} / {summary.BranchesValid})")
298+
.Append((summary.Complexity % 1 == 0) ? $" | {summary.Complexity}" : $" | {summary.Complexity:N4}")
299+
.AppendLine(indicators ? $" | {GenerateHealthIndicator(summary.LineRate)}" : string.Empty);
251300

252301
return markdownOutput.ToString();
253302
}

src/CodeCoverageSummary/Properties/launchSettings.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
"profiles": {
33
"CodeCoverageSummary": {
44
"commandName": "Project",
5-
"commandLineArgs": "../../../../coverage.gcovr.xml --format=md --badge=true"
5+
"commandLineArgs": "../../../../coverage.cobertura.xml --format=md --badge true --thresholds=\"85 90\" --fail true"
66
},
77
"Docker": {
88
"commandName": "Docker",

0 commit comments

Comments
 (0)