Skip to content

Commit c74b9c4

Browse files
authored
Add multiple threshold (#1123)
Add multiple threshold
1 parent ba72070 commit c74b9c4

File tree

5 files changed

+266
-54
lines changed

5 files changed

+266
-54
lines changed

Documentation/MSBuildIntegration.md

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,12 @@ The above command will automatically fail the build if the line, branch or metho
9898
dotnet test /p:CollectCoverage=true /p:Threshold=80 /p:ThresholdType=line
9999
```
100100

101-
You can specify multiple values for `ThresholdType` by separating them with commas. Valid values include `line`, `branch` and `method`.
101+
You can specify multiple values for `ThresholdType` by separating them with commas. Valid values include `line`, `branch` and `method`.
102+
You can do the same for `Threshold` as well.
103+
104+
```bash
105+
dotnet test /p:CollectCoverage=true /p:Threshold="80,100,70", /p:ThresholdType="line,branch,method"
106+
```
102107

103108
By default, Coverlet will validate the threshold value against the coverage result of each module. The `/p:ThresholdStat` option allows you to change this behaviour and can have any of the following values:
104109

src/coverlet.console/Program.cs

Lines changed: 43 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
using System.ComponentModel;
44
using System.Diagnostics;
55
using System.IO;
6+
using System.Linq;
67
using System.Text;
78

89
using ConsoleTables;
@@ -133,13 +134,13 @@ static int Main(string[] args)
133134
process.WaitForExit();
134135

135136
var dOutput = output.HasValue() ? output.Value() : Directory.GetCurrentDirectory() + Path.DirectorySeparatorChar.ToString();
136-
var dThreshold = threshold.HasValue() ? double.Parse(threshold.Value()) : 0;
137137
var dThresholdTypes = thresholdTypes.HasValue() ? thresholdTypes.Values : new List<string>(new string[] { "line", "branch", "method" });
138138
var dThresholdStat = thresholdStat.HasValue() ? Enum.Parse<ThresholdStatistic>(thresholdStat.Value(), true) : Enum.Parse<ThresholdStatistic>("minimum", true);
139139

140140
logger.LogInformation("\nCalculating coverage result...");
141141

142142
var result = coverage.GetCoverageResult();
143+
143144
var directory = Path.GetDirectoryName(dOutput);
144145
if (directory == string.Empty)
145146
{
@@ -177,27 +178,57 @@ static int Main(string[] args)
177178
}
178179
}
179180

180-
var thresholdTypeFlags = ThresholdTypeFlags.None;
181-
181+
var thresholdTypeFlagQueue = new Queue<ThresholdTypeFlags>();
182+
182183
foreach (var thresholdType in dThresholdTypes)
183184
{
184185
if (thresholdType.Equals("line", StringComparison.OrdinalIgnoreCase))
185186
{
186-
thresholdTypeFlags |= ThresholdTypeFlags.Line;
187+
thresholdTypeFlagQueue.Enqueue(ThresholdTypeFlags.Line);
187188
}
188189
else if (thresholdType.Equals("branch", StringComparison.OrdinalIgnoreCase))
189190
{
190-
thresholdTypeFlags |= ThresholdTypeFlags.Branch;
191+
thresholdTypeFlagQueue.Enqueue(ThresholdTypeFlags.Branch);
191192
}
192193
else if (thresholdType.Equals("method", StringComparison.OrdinalIgnoreCase))
193194
{
194-
thresholdTypeFlags |= ThresholdTypeFlags.Method;
195+
thresholdTypeFlagQueue.Enqueue(ThresholdTypeFlags.Method);
196+
}
197+
}
198+
199+
Dictionary<ThresholdTypeFlags, double> thresholdTypeFlagValues = new Dictionary<ThresholdTypeFlags, double>();
200+
if (threshold.HasValue() && threshold.Value().Contains(','))
201+
{
202+
var thresholdValues = threshold.Value().Split(',', StringSplitOptions.RemoveEmptyEntries).Select(t => t.Trim());
203+
if (thresholdValues.Count() != thresholdTypeFlagQueue.Count())
204+
{
205+
throw new Exception($"Threshold type flag count ({thresholdTypeFlagQueue.Count()}) and values count ({thresholdValues.Count()}) doesn't match");
206+
}
207+
208+
foreach (var thresholdValue in thresholdValues)
209+
{
210+
if (double.TryParse(thresholdValue, out var value))
211+
{
212+
thresholdTypeFlagValues[thresholdTypeFlagQueue.Dequeue()] = value;
213+
}
214+
else
215+
{
216+
throw new Exception($"Invalid threshold value must be numeric");
217+
}
218+
}
219+
}
220+
else
221+
{
222+
double thresholdValue = threshold.HasValue() ? double.Parse(threshold.Value()) : 0;
223+
224+
while (thresholdTypeFlagQueue.Any())
225+
{
226+
thresholdTypeFlagValues[thresholdTypeFlagQueue.Dequeue()] = thresholdValue;
195227
}
196228
}
197229

198230
var coverageTable = new ConsoleTable("Module", "Line", "Branch", "Method");
199231
var summary = new CoverageSummary();
200-
int numModules = result.Modules.Count;
201232

202233
var linePercentCalculation = summary.CalculateLineCoverage(result.Modules);
203234
var branchPercentCalculation = summary.CalculateBranchCoverage(result.Modules);
@@ -234,26 +265,26 @@ static int Main(string[] args)
234265
{
235266
exitCode += (int)CommandExitCodes.TestFailed;
236267
}
237-
thresholdTypeFlags = result.GetThresholdTypesBelowThreshold(summary, dThreshold, thresholdTypeFlags, dThresholdStat);
268+
269+
var thresholdTypeFlags = result.GetThresholdTypesBelowThreshold(summary, thresholdTypeFlagValues, dThresholdStat);
238270
if (thresholdTypeFlags != ThresholdTypeFlags.None)
239271
{
240272
exitCode += (int)CommandExitCodes.CoverageBelowThreshold;
241273
var exceptionMessageBuilder = new StringBuilder();
242274
if ((thresholdTypeFlags & ThresholdTypeFlags.Line) != ThresholdTypeFlags.None)
243275
{
244-
exceptionMessageBuilder.AppendLine($"The {dThresholdStat.ToString().ToLower()} line coverage is below the specified {dThreshold}");
276+
exceptionMessageBuilder.AppendLine($"The {dThresholdStat.ToString().ToLower()} line coverage is below the specified {thresholdTypeFlagValues[ThresholdTypeFlags.Line]}");
245277
}
246278

247279
if ((thresholdTypeFlags & ThresholdTypeFlags.Branch) != ThresholdTypeFlags.None)
248280
{
249-
exceptionMessageBuilder.AppendLine($"The {dThresholdStat.ToString().ToLower()} branch coverage is below the specified {dThreshold}");
281+
exceptionMessageBuilder.AppendLine($"The {dThresholdStat.ToString().ToLower()} branch coverage is below the specified {thresholdTypeFlagValues[ThresholdTypeFlags.Branch]}");
250282
}
251283

252284
if ((thresholdTypeFlags & ThresholdTypeFlags.Method) != ThresholdTypeFlags.None)
253285
{
254-
exceptionMessageBuilder.AppendLine($"The {dThresholdStat.ToString().ToLower()} method coverage is below the specified {dThreshold}");
286+
exceptionMessageBuilder.AppendLine($"The {dThresholdStat.ToString().ToLower()} method coverage is below the specified {thresholdTypeFlagValues[ThresholdTypeFlags.Method]}");
255287
}
256-
257288
throw new Exception(exceptionMessageBuilder.ToString());
258289
}
259290

src/coverlet.core/CoverageResult.cs

Lines changed: 21 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,7 @@ public void Merge(Modules modules)
111111
}
112112
}
113113

114-
public ThresholdTypeFlags GetThresholdTypesBelowThreshold(CoverageSummary summary, double threshold, ThresholdTypeFlags thresholdTypes, ThresholdStatistic thresholdStat)
114+
public ThresholdTypeFlags GetThresholdTypesBelowThreshold(CoverageSummary summary, Dictionary<ThresholdTypeFlags, double> thresholdTypeFlagValues, ThresholdStatistic thresholdStat)
115115
{
116116
var thresholdTypeFlags = ThresholdTypeFlags.None;
117117
switch (thresholdStat)
@@ -123,23 +123,20 @@ public ThresholdTypeFlags GetThresholdTypesBelowThreshold(CoverageSummary summar
123123
double line = summary.CalculateLineCoverage(module.Value).Percent;
124124
double branch = summary.CalculateBranchCoverage(module.Value).Percent;
125125
double method = summary.CalculateMethodCoverage(module.Value).Percent;
126-
127-
if ((thresholdTypes & ThresholdTypeFlags.Line) != ThresholdTypeFlags.None)
126+
127+
if (thresholdTypeFlagValues.TryGetValue(ThresholdTypeFlags.Line, out var lineThresholdValue) && lineThresholdValue > line)
128128
{
129-
if (line < threshold)
130-
thresholdTypeFlags |= ThresholdTypeFlags.Line;
129+
thresholdTypeFlags |= ThresholdTypeFlags.Line;
131130
}
132131

133-
if ((thresholdTypes & ThresholdTypeFlags.Branch) != ThresholdTypeFlags.None)
132+
if (thresholdTypeFlagValues.TryGetValue(ThresholdTypeFlags.Branch, out var branchThresholdValue) && branchThresholdValue > branch)
134133
{
135-
if (branch < threshold)
136-
thresholdTypeFlags |= ThresholdTypeFlags.Branch;
134+
thresholdTypeFlags |= ThresholdTypeFlags.Branch;
137135
}
138136

139-
if ((thresholdTypes & ThresholdTypeFlags.Method) != ThresholdTypeFlags.None)
137+
if (thresholdTypeFlagValues.TryGetValue(ThresholdTypeFlags.Method, out var methodThresholdValue) && methodThresholdValue > method)
140138
{
141-
if (method < threshold)
142-
thresholdTypeFlags |= ThresholdTypeFlags.Method;
139+
thresholdTypeFlags |= ThresholdTypeFlags.Method;
143140
}
144141
}
145142
}
@@ -149,23 +146,20 @@ public ThresholdTypeFlags GetThresholdTypesBelowThreshold(CoverageSummary summar
149146
double line = summary.CalculateLineCoverage(Modules).AverageModulePercent;
150147
double branch = summary.CalculateBranchCoverage(Modules).AverageModulePercent;
151148
double method = summary.CalculateMethodCoverage(Modules).AverageModulePercent;
152-
153-
if ((thresholdTypes & ThresholdTypeFlags.Line) != ThresholdTypeFlags.None)
149+
150+
if (thresholdTypeFlagValues.TryGetValue(ThresholdTypeFlags.Line, out var lineThresholdValue) && lineThresholdValue > line)
154151
{
155-
if (line < threshold)
156-
thresholdTypeFlags |= ThresholdTypeFlags.Line;
152+
thresholdTypeFlags |= ThresholdTypeFlags.Line;
157153
}
158154

159-
if ((thresholdTypes & ThresholdTypeFlags.Branch) != ThresholdTypeFlags.None)
155+
if (thresholdTypeFlagValues.TryGetValue(ThresholdTypeFlags.Branch, out var branchThresholdValue) && branchThresholdValue > branch)
160156
{
161-
if (branch < threshold)
162-
thresholdTypeFlags |= ThresholdTypeFlags.Branch;
157+
thresholdTypeFlags |= ThresholdTypeFlags.Branch;
163158
}
164159

165-
if ((thresholdTypes & ThresholdTypeFlags.Method) != ThresholdTypeFlags.None)
160+
if (thresholdTypeFlagValues.TryGetValue(ThresholdTypeFlags.Method, out var methodThresholdValue) && methodThresholdValue > method)
166161
{
167-
if (method < threshold)
168-
thresholdTypeFlags |= ThresholdTypeFlags.Method;
162+
thresholdTypeFlags |= ThresholdTypeFlags.Method;
169163
}
170164
}
171165
break;
@@ -175,22 +169,19 @@ public ThresholdTypeFlags GetThresholdTypesBelowThreshold(CoverageSummary summar
175169
double branch = summary.CalculateBranchCoverage(Modules).Percent;
176170
double method = summary.CalculateMethodCoverage(Modules).Percent;
177171

178-
if ((thresholdTypes & ThresholdTypeFlags.Line) != ThresholdTypeFlags.None)
172+
if (thresholdTypeFlagValues.TryGetValue(ThresholdTypeFlags.Line, out var lineThresholdValue) && lineThresholdValue > line)
179173
{
180-
if (line < threshold)
181-
thresholdTypeFlags |= ThresholdTypeFlags.Line;
174+
thresholdTypeFlags |= ThresholdTypeFlags.Line;
182175
}
183176

184-
if ((thresholdTypes & ThresholdTypeFlags.Branch) != ThresholdTypeFlags.None)
177+
if (thresholdTypeFlagValues.TryGetValue(ThresholdTypeFlags.Branch, out var branchThresholdValue) && branchThresholdValue > branch)
185178
{
186-
if (branch < threshold)
187-
thresholdTypeFlags |= ThresholdTypeFlags.Branch;
179+
thresholdTypeFlags |= ThresholdTypeFlags.Branch;
188180
}
189181

190-
if ((thresholdTypes & ThresholdTypeFlags.Method) != ThresholdTypeFlags.None)
182+
if (thresholdTypeFlagValues.TryGetValue(ThresholdTypeFlags.Method, out var methodThresholdValue) && methodThresholdValue > method)
191183
{
192-
if (method < threshold)
193-
thresholdTypeFlags |= ThresholdTypeFlags.Method;
184+
thresholdTypeFlags |= ThresholdTypeFlags.Method;
194185
}
195186
}
196187
break;

src/coverlet.msbuild.tasks/CoverageResultTask.cs

Lines changed: 44 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ public class CoverageResultTask : BaseTask
2525
public string OutputFormat { get; set; }
2626

2727
[Required]
28-
public double Threshold { get; set; }
28+
public string Threshold { get; set; }
2929

3030
[Required]
3131
public string ThresholdType { get; set; }
@@ -126,25 +126,56 @@ public override bool Execute()
126126

127127
ReportItems = coverageReportPaths.ToArray();
128128

129-
var thresholdTypeFlags = ThresholdTypeFlags.None;
130-
var thresholdStat = ThresholdStatistic.Minimum;
129+
var thresholdTypeFlagQueue = new Queue<ThresholdTypeFlags>();
131130

132131
foreach (var thresholdType in ThresholdType.Split(',').Select(t => t.Trim()))
133132
{
134133
if (thresholdType.Equals("line", StringComparison.OrdinalIgnoreCase))
135134
{
136-
thresholdTypeFlags |= ThresholdTypeFlags.Line;
135+
thresholdTypeFlagQueue.Enqueue(ThresholdTypeFlags.Line);
137136
}
138137
else if (thresholdType.Equals("branch", StringComparison.OrdinalIgnoreCase))
139138
{
140-
thresholdTypeFlags |= ThresholdTypeFlags.Branch;
139+
thresholdTypeFlagQueue.Enqueue(ThresholdTypeFlags.Branch);
141140
}
142141
else if (thresholdType.Equals("method", StringComparison.OrdinalIgnoreCase))
143142
{
144-
thresholdTypeFlags |= ThresholdTypeFlags.Method;
143+
thresholdTypeFlagQueue.Enqueue(ThresholdTypeFlags.Method);
145144
}
146145
}
146+
147+
Dictionary<ThresholdTypeFlags, double> thresholdTypeFlagValues = new Dictionary<ThresholdTypeFlags, double>();
148+
if (Threshold.Contains(','))
149+
{
150+
var thresholdValues = Threshold.Split(new char[] {','}, StringSplitOptions.RemoveEmptyEntries).Select(t => t.Trim());
151+
if(thresholdValues.Count() != thresholdTypeFlagQueue.Count())
152+
{
153+
throw new Exception($"Threshold type flag count ({thresholdTypeFlagQueue.Count()}) and values count ({thresholdValues.Count()}) doesn't match");
154+
}
147155

156+
foreach (var threshold in thresholdValues)
157+
{
158+
if (double.TryParse(threshold, out var value))
159+
{
160+
thresholdTypeFlagValues[thresholdTypeFlagQueue.Dequeue()] = value;
161+
}
162+
else
163+
{
164+
throw new Exception($"Invalid threshold value must be numeric");
165+
}
166+
}
167+
}
168+
else
169+
{
170+
double thresholdValue = double.Parse(Threshold);
171+
172+
while (thresholdTypeFlagQueue.Any())
173+
{
174+
thresholdTypeFlagValues[thresholdTypeFlagQueue.Dequeue()] = thresholdValue;
175+
}
176+
}
177+
178+
var thresholdStat = ThresholdStatistic.Minimum;
148179
if (ThresholdStat.Equals("average", StringComparison.OrdinalIgnoreCase))
149180
{
150181
thresholdStat = ThresholdStatistic.Average;
@@ -156,7 +187,6 @@ public override bool Execute()
156187

157188
var coverageTable = new ConsoleTable("Module", "Line", "Branch", "Method");
158189
var summary = new CoverageSummary();
159-
int numModules = result.Modules.Count;
160190

161191
var linePercentCalculation = summary.CalculateLineCoverage(result.Modules);
162192
var branchPercentCalculation = summary.CalculateBranchCoverage(result.Modules);
@@ -191,23 +221,26 @@ public override bool Execute()
191221

192222
Console.WriteLine(coverageTable.ToStringAlternative());
193223

194-
thresholdTypeFlags = result.GetThresholdTypesBelowThreshold(summary, Threshold, thresholdTypeFlags, thresholdStat);
224+
var thresholdTypeFlags = result.GetThresholdTypesBelowThreshold(summary, thresholdTypeFlagValues, thresholdStat);
195225
if (thresholdTypeFlags != ThresholdTypeFlags.None)
196226
{
197227
var exceptionMessageBuilder = new StringBuilder();
198228
if ((thresholdTypeFlags & ThresholdTypeFlags.Line) != ThresholdTypeFlags.None)
199229
{
200-
exceptionMessageBuilder.AppendLine($"The {thresholdStat.ToString().ToLower()} line coverage is below the specified {Threshold}");
230+
exceptionMessageBuilder.AppendLine(
231+
$"The {thresholdStat.ToString().ToLower()} line coverage is below the specified {thresholdTypeFlagValues[ThresholdTypeFlags.Line]}");
201232
}
202233

203234
if ((thresholdTypeFlags & ThresholdTypeFlags.Branch) != ThresholdTypeFlags.None)
204235
{
205-
exceptionMessageBuilder.AppendLine($"The {thresholdStat.ToString().ToLower()} branch coverage is below the specified {Threshold}");
236+
exceptionMessageBuilder.AppendLine(
237+
$"The {thresholdStat.ToString().ToLower()} branch coverage is below the specified {thresholdTypeFlagValues[ThresholdTypeFlags.Branch]}");
206238
}
207239

208240
if ((thresholdTypeFlags & ThresholdTypeFlags.Method) != ThresholdTypeFlags.None)
209241
{
210-
exceptionMessageBuilder.AppendLine($"The {thresholdStat.ToString().ToLower()} method coverage is below the specified {Threshold}");
242+
exceptionMessageBuilder.AppendLine(
243+
$"The {thresholdStat.ToString().ToLower()} method coverage is below the specified {thresholdTypeFlagValues[ThresholdTypeFlags.Method]}");
211244
}
212245

213246
throw new Exception(exceptionMessageBuilder.ToString());

0 commit comments

Comments
 (0)