Skip to content

Commit

Permalink
Added minimum coverage thresholds to validate coverage goals
Browse files Browse the repository at this point in the history
  • Loading branch information
danielpalme committed Jul 3, 2023
1 parent 5fe2b28 commit 7bf6158
Show file tree
Hide file tree
Showing 12 changed files with 302 additions and 5 deletions.
4 changes: 4 additions & 0 deletions src/Readme.txt
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,10 @@ For further details take a look at LICENSE.txt.

CHANGELOG

5.1.23.0

* New: Added minimum coverage thresholds to validate coverage goals

5.1.22.0

* Fix: #608 Improved performance for classes with many lines
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"profiles": {
"ReportGenerator.Console.NetCore": {
"commandName": "Project",
"commandLineArgs": "-reports:C:\\Users\\danie\\Documents\\Projects\\ReportGenerator\\src\\Testprojects\\CSharp\\Reports\\OpenCover.xml -targetdir:C:\\Users\\danie\\Desktop\\cov -reporttypes:Html;Html_BlueRed settings:createSubdirectoryForAllReportTypes=true"
"commandLineArgs": "-reports:C:\\Users\\danie\\Documents\\Projects\\ReportGenerator\\src\\Testprojects\\CSharp\\Reports\\OpenCover.xml -targetdir:C:\\Users\\danie\\Desktop\\cov -reporttypes:Html;"
}
}
}
78 changes: 76 additions & 2 deletions src/ReportGenerator.Core/Generator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -46,10 +46,18 @@ public bool GenerateReport(IReportConfiguration reportConfiguration)
var settings = new Settings();
configuration.GetSection("settings").Bind(settings);

var minimumCoverageThresholds = new MinimumCoverageThresholds();
configuration.GetSection("minimumCoverageThresholds").Bind(minimumCoverageThresholds);

var riskHotspotsAnalysisThresholds = new RiskHotspotsAnalysisThresholds();
configuration.GetSection("riskHotspotsAnalysisThresholds").Bind(riskHotspotsAnalysisThresholds);

return this.GenerateReport(reportConfiguration, settings, riskHotspotsAnalysisThresholds);
return this.GenerateReport(reportConfiguration, settings, minimumCoverageThresholds, riskHotspotsAnalysisThresholds);
}
catch (LowCoverageException ex)
{
Logger.Error(ex.GetExceptionMessageForDisplay());
return false;
}
catch (Exception ex)
{
Expand Down Expand Up @@ -78,6 +86,27 @@ public bool GenerateReport(IReportConfiguration reportConfiguration)
IReportConfiguration reportConfiguration,
Settings settings,
RiskHotspotsAnalysisThresholds riskHotspotsAnalysisThresholds)
{
return this.GenerateReport(
reportConfiguration,
settings,
new MinimumCoverageThresholds(),
riskHotspotsAnalysisThresholds);
}

/// <summary>
/// Generates a report using given configuration.
/// </summary>
/// <param name="reportConfiguration">The report configuration.</param>
/// <param name="settings">The settings.</param>
/// <param name="minimumCoverageThresholds">The minimum coverage thresholds.</param>
/// <param name="riskHotspotsAnalysisThresholds">The risk hotspots analysis thresholds.</param>
/// <returns><c>true</c> if report was generated successfully; otherwise <c>false</c>.</returns>
public bool GenerateReport(
IReportConfiguration reportConfiguration,
Settings settings,
MinimumCoverageThresholds minimumCoverageThresholds,
RiskHotspotsAnalysisThresholds riskHotspotsAnalysisThresholds)
{
if (reportConfiguration == null)
{
Expand All @@ -89,6 +118,11 @@ public bool GenerateReport(IReportConfiguration reportConfiguration)
throw new ArgumentNullException(nameof(settings));
}

if (minimumCoverageThresholds == null)
{
throw new ArgumentNullException(nameof(minimumCoverageThresholds));
}

if (riskHotspotsAnalysisThresholds == null)
{
throw new ArgumentNullException(nameof(riskHotspotsAnalysisThresholds));
Expand Down Expand Up @@ -139,6 +173,7 @@ public bool GenerateReport(IReportConfiguration reportConfiguration)
this.GenerateReport(
reportConfiguration,
settings,
minimumCoverageThresholds,
riskHotspotsAnalysisThresholds,
parserResult);

Expand All @@ -147,6 +182,11 @@ public bool GenerateReport(IReportConfiguration reportConfiguration)

return true;
}
catch (LowCoverageException ex)
{
Logger.Error(ex.GetExceptionMessageForDisplay());
return false;
}
catch (Exception ex)
{
Logger.Error(ex.GetExceptionMessageForDisplay());
Expand Down Expand Up @@ -187,10 +227,13 @@ public bool GenerateReport(IReportConfiguration reportConfiguration)
var settings = new Settings();
configuration.GetSection("settings").Bind(settings);

var minimumCoverageThresholds = new MinimumCoverageThresholds();
configuration.GetSection("minimumCoverageThresholds").Bind(minimumCoverageThresholds);

var riskHotspotsAnalysisThresholds = new RiskHotspotsAnalysisThresholds();
configuration.GetSection("riskHotspotsAnalysisThresholds").Bind(riskHotspotsAnalysisThresholds);

this.GenerateReport(reportConfiguration, settings, riskHotspotsAnalysisThresholds, parserResult);
this.GenerateReport(reportConfiguration, settings, minimumCoverageThresholds, riskHotspotsAnalysisThresholds, parserResult);
}

/// <summary>
Expand All @@ -205,6 +248,29 @@ public bool GenerateReport(IReportConfiguration reportConfiguration)
Settings settings,
RiskHotspotsAnalysisThresholds riskHotspotsAnalysisThresholds,
ParserResult parserResult)
{
this.GenerateReport(
reportConfiguration,
settings,
new MinimumCoverageThresholds(),
riskHotspotsAnalysisThresholds,
parserResult);
}

/// <summary>
/// Executes the report generation.
/// </summary>
/// <param name="reportConfiguration">The report configuration.</param>
/// <param name="settings">The settings.</param>
/// <param name="minimumCoverageThresholds">The minimum coverage thresholds.</param>
/// <param name="riskHotspotsAnalysisThresholds">The risk hotspots analysis thresholds.</param>
/// <param name="parserResult">The parser result generated by <see cref="CoverageReportParser"/>.</param>
public void GenerateReport(
IReportConfiguration reportConfiguration,
Settings settings,
MinimumCoverageThresholds minimumCoverageThresholds,
RiskHotspotsAnalysisThresholds riskHotspotsAnalysisThresholds,
ParserResult parserResult)
{
if (reportConfiguration == null)
{
Expand All @@ -216,6 +282,11 @@ public bool GenerateReport(IReportConfiguration reportConfiguration)
throw new ArgumentNullException(nameof(settings));
}

if (minimumCoverageThresholds == null)
{
throw new ArgumentNullException(nameof(minimumCoverageThresholds));
}

if (riskHotspotsAnalysisThresholds == null)
{
throw new ArgumentNullException(nameof(riskHotspotsAnalysisThresholds));
Expand Down Expand Up @@ -258,6 +329,9 @@ public bool GenerateReport(IReportConfiguration reportConfiguration)
new HistoryReportGenerator(historyStorage)
.CreateReport(parserResult.Assemblies, executionTime, reportConfiguration.Tag);
}

new MinimumCoverageThresholdsValidator(minimumCoverageThresholds)
.Validate(parserResult);
}

/// <summary>
Expand Down
29 changes: 29 additions & 0 deletions src/ReportGenerator.Core/IReportGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,20 @@ public interface IReportGenerator
Settings settings,
RiskHotspotsAnalysisThresholds riskHotspotsAnalysisThresholds);

/// <summary>
/// Generates a report using given configuration.
/// </summary>
/// <param name="reportConfiguration">The report configuration.</param>
/// <param name="settings">The settings.</param>
/// <param name="minimumCoverageThresholds">The minimum coverage thresholds.</param>
/// <param name="riskHotspotsAnalysisThresholds">The risk hotspots analysis thresholds.</param>
/// <returns><c>true</c> if report was generated successfully; otherwise <c>false</c>.</returns>
bool GenerateReport(
IReportConfiguration reportConfiguration,
Settings settings,
MinimumCoverageThresholds minimumCoverageThresholds,
RiskHotspotsAnalysisThresholds riskHotspotsAnalysisThresholds);

/// <summary>
/// Executes the report generation.
/// </summary>
Expand All @@ -49,5 +63,20 @@ public interface IReportGenerator
Settings settings,
RiskHotspotsAnalysisThresholds riskHotspotsAnalysisThresholds,
ParserResult parserResult);

/// <summary>
/// Executes the report generation.
/// </summary>
/// <param name="reportConfiguration">The report configuration.</param>
/// <param name="settings">The settings.</param>
/// <param name="minimumCoverageThresholds">The minimum coverage thresholds.</param>
/// <param name="riskHotspotsAnalysisThresholds">The risk hotspots analysis thresholds.</param>
/// <param name="parserResult">The parser result generated by <see cref="CoverageReportParser"/>.</param>
void GenerateReport(
IReportConfiguration reportConfiguration,
Settings settings,
MinimumCoverageThresholds minimumCoverageThresholds,
RiskHotspotsAnalysisThresholds riskHotspotsAnalysisThresholds,
ParserResult parserResult);
}
}
7 changes: 6 additions & 1 deletion src/ReportGenerator.Core/Licensing/License.cs
Original file line number Diff line number Diff line change
Expand Up @@ -37,13 +37,18 @@ internal class License
/// </summary>
public DateTime IssuedAt { get; set; }

/// <summary>
/// Gets or sets the issue at date.
/// </summary>
public DateTime? ExpiresAt { get; set; }

/// <summary>
/// Gets a string containing the relevant properties for the signature.
/// </summary>
/// <returns>The string containing the relevant properties for the signature.</returns>
public string GetSignatureInput()
{
return $"{this.Id:N}{this.Login}{this.Name}{this.Email}{this.LicenseType}{this.IssuedAt:yyyyMMddHH:mm:ss}";
return $"{this.Id:N}{this.Login}{this.Name}{this.Email}{this.LicenseType}{this.IssuedAt:yyyyMMddHH:mm:ss}{this.ExpiresAt:yyyyMMddHH:mm:ss}";
}
}
}
12 changes: 11 additions & 1 deletion src/ReportGenerator.Core/Licensing/LicenseValidator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ internal static class LicenseValidator
{ Guid.Parse("860a488f-b3ce-4294-ae75-e331546ee830"), DateTime.MinValue },
{ Guid.Parse("8f66e7a8-e6c0-4a31-9747-67ed27b5f721"), DateTime.MinValue },
{ Guid.Parse("9717392c-fc55-415f-a1bf-1a407c9ec705"), new DateTime(2023, 12, 1) },
{ Guid.Parse("265472d9-799d-44db-b7f2-b8da433812f9"), new DateTime(2023, 7, 28) },
{ Guid.Parse("70dcfc78-6ca3-4a0b-bb43-a3840e39ff4f"), new DateTime(2024, 1, 1) },
{ Guid.Parse("6e4d43dd-84a5-40fd-beb2-34f3c4930994"), new DateTime(2024, 4, 1) },
};
Expand Down Expand Up @@ -110,7 +111,16 @@ public static bool IsValid(this string license)
}
}

return true;
if (cachedLicense.License.LicenseType == "Pro")
{
return true;
}
else if (cachedLicense.License.LicenseType == "Trial" && cachedLicense.License.ExpiresAt.HasValue)
{
return cachedLicense.License.ExpiresAt.Value < DateTime.UtcNow;
}

return false;
}
finally
{
Expand Down
37 changes: 37 additions & 0 deletions src/ReportGenerator.Core/LowCoverageException.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
using System;

namespace Palmmedia.ReportGenerator.Core
{
/// <summary>
/// Exception indicating that mimimum coverage goals are not satisfied.
/// </summary>
[Serializable]
internal class LowCoverageException : Exception
{
/// <summary>
/// Initializes a new instance of the <see cref="LowCoverageException"/> class.
/// </summary>
public LowCoverageException()
{
}

/// <summary>
/// Initializes a new instance of the <see cref="LowCoverageException"/> class.
/// </summary>
/// <param name="message">The message.</param>
public LowCoverageException(string message)
: base(message)
{
}

/// <summary>
/// Initializes a new instance of the <see cref="LowCoverageException"/> class.
/// </summary>
/// <param name="message">The message.</param>
/// <param name="inner">The inner exception.</param>
public LowCoverageException(string message, Exception inner)
: base(message, inner)
{
}
}
}
23 changes: 23 additions & 0 deletions src/ReportGenerator.Core/MinimumCoverageThresholds.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
namespace Palmmedia.ReportGenerator.Core
{
/// <summary>
/// Minimum coverage thresholds.
/// </summary>
public class MinimumCoverageThresholds
{
/// <summary>
/// Gets or sets minimum line coverage. If line coverage falls below this treshold, ReportGenerator will exit unsuccessfully.
/// </summary>
public int? LineCoverage { get; set; }

/// <summary>
/// Gets or sets minimum branch coverage. If branch coverage falls below this treshold, ReportGenerator will exit unsuccessfully.
/// </summary>
public int? BranchCoverage { get; set; }

/// <summary>
/// Gets or sets minimum method coverage. If method coverage falls below this treshold, ReportGenerator will exit unsuccessfully.
/// </summary>
public int? MethodCoverage { get; set; }
}
}
74 changes: 74 additions & 0 deletions src/ReportGenerator.Core/MinimumCoverageThresholdsValidator.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
using System;
using System.Collections.Generic;
using Palmmedia.ReportGenerator.Core.Parser;
using Palmmedia.ReportGenerator.Core.Parser.Analysis;
using Palmmedia.ReportGenerator.Core.Properties;

namespace Palmmedia.ReportGenerator.Core
{
public class MinimumCoverageThresholdsValidator

Check warning on line 9 in src/ReportGenerator.Core/MinimumCoverageThresholdsValidator.cs

View workflow job for this annotation

GitHub Actions / build

Missing XML comment for publicly visible type or member 'MinimumCoverageThresholdsValidator'

Check warning on line 9 in src/ReportGenerator.Core/MinimumCoverageThresholdsValidator.cs

View workflow job for this annotation

GitHub Actions / build

Elements should be documented
{
/// <summary>
/// The minimum coverage thresholds.
/// </summary>
private readonly MinimumCoverageThresholds minimumCoverageThresholds;

/// <summary>
/// Initializes a new instance of the <see cref="MinimumCoverageThresholdsValidator" /> class.
/// </summary>
/// <param name="minimumCoverageThresholds">The minimum coverage thresholds.</param>
public MinimumCoverageThresholdsValidator(MinimumCoverageThresholds minimumCoverageThresholds)
{
if (minimumCoverageThresholds == null)
{
throw new ArgumentNullException(nameof(minimumCoverageThresholds));
}

this.minimumCoverageThresholds = minimumCoverageThresholds;
}

/// <summary>
/// Validates the coverage thresholds.
/// </summary>
/// <param name="parserResult">The parser result.</param>
public void Validate(ParserResult parserResult)
{
if (!this.minimumCoverageThresholds.LineCoverage.HasValue
&& !this.minimumCoverageThresholds.BranchCoverage.HasValue
&& !this.minimumCoverageThresholds.MethodCoverage.HasValue)
{
return;
}

var errors = new List<string>();

var summaryResult = new SummaryResult(parserResult);

if (this.minimumCoverageThresholds.LineCoverage.HasValue
&& summaryResult.CoverageQuota.HasValue
&& summaryResult.CoverageQuota.Value < this.minimumCoverageThresholds.LineCoverage.Value)
{
errors.Add(string.Format(Resources.ErrorLowLineCoverage, summaryResult.CoverageQuota.Value, this.minimumCoverageThresholds.LineCoverage.Value));
}

if (this.minimumCoverageThresholds.BranchCoverage.HasValue
&& summaryResult.BranchCoverageQuota.HasValue
&& summaryResult.BranchCoverageQuota.Value < this.minimumCoverageThresholds.BranchCoverage.Value)
{
errors.Add(string.Format(Resources.ErrorLowBranchCoverage, summaryResult.BranchCoverageQuota.Value, this.minimumCoverageThresholds.BranchCoverage.Value));
}

if (this.minimumCoverageThresholds.MethodCoverage.HasValue
&& summaryResult.CodeElementCoverageQuota.HasValue
&& summaryResult.CodeElementCoverageQuota < this.minimumCoverageThresholds.MethodCoverage.Value)
{
errors.Add(string.Format(Resources.ErrorLowMethodCoverage, summaryResult.CodeElementCoverageQuota.Value, this.minimumCoverageThresholds.MethodCoverage.Value));
}

if (errors.Count > 0)
{
throw new LowCoverageException(string.Join("\r\n", errors));
}
}
}
}
Loading

0 comments on commit 7bf6158

Please sign in to comment.