Skip to content

Commit

Permalink
Add deterministic report support (#1113)
Browse files Browse the repository at this point in the history
Add deterministic report support
  • Loading branch information
MarcoRossignoli authored Mar 13, 2021
1 parent 9716c45 commit a2082b3
Show file tree
Hide file tree
Showing 38 changed files with 336 additions and 187 deletions.
17 changes: 17 additions & 0 deletions Documentation/DeterministicBuild.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,23 @@ As explained above, to improve the level of security of generated artifacts (for

Finally, thanks to deterministic CI builds (with the `ContinuousIntegrationBuild` property set to `true`) plus signature we can validate artifacts and be sure that the binary was built from specific sources (because there is no hard-coded variable metadata, like paths from different build machines).

# Deterministic report

Coverlet supports also deterministic reports(for now only for cobertura coverage format).
If you include `DeterministicReport` parameters for `msbuild` and `collectors` integrations resulting report will be like:
```xml
<?xml version="1.0" encoding="utf-8"?>
<coverage line-rate="0.8571" branch-rate="0.5" version="1.9" timestamp="1612702997" lines-covered="6" lines-valid="7" branches-covered="1" branches-valid="2">
<sources />
<packages>
<package name="MyLibrary" line-rate="0.8571" branch-rate="0.5" complexity="3">
<classes>
<class name="MyLibrary.Hello" filename="/_/MyLibrary/Hello.cs" line-rate="0.8571" branch-rate="0.5" complexity="3">
<methods>
...
```
As you can see we have empty `<sources />` element and the `filename` start with well known deterministic fragment `/_/...`

**Deterministic build is supported without any workaround since version 3.1.100 of .NET Core SDK**

## Workaround only for .NET Core SDK < 3.1.100
Expand Down
8 changes: 8 additions & 0 deletions Documentation/MSBuildIntegration.md
Original file line number Diff line number Diff line change
Expand Up @@ -206,3 +206,11 @@ The workaround is to use the .NET Core `dotnet msbuild` command instead of using
## SourceLink

Coverlet supports [SourceLink](https://github.com/dotnet/sourcelink) custom debug information contained in PDBs. When you specify the `/p:UseSourceLink=true` property, Coverlet will generate results that contain the URL to the source files in your source control instead of local file paths.

## Deterministic build

Take a look at [documentation](Documentation/DeterministicBuild.md) for further informations.
To generate deterministc report the parameter is:
```
/p:DeterministicReport=true
```
26 changes: 14 additions & 12 deletions Documentation/VSTestIntegration.md
Original file line number Diff line number Diff line change
Expand Up @@ -85,18 +85,19 @@ We're working to fill the gaps.

These are a list of options that are supported by coverlet. These can be specified as datacollector configurations in the runsettings.

| Option | Summary |
|:-------------------------|:----------------------------------------------------------------------------------------------------------------------------------|
| Format | Coverage output format. These are either cobertura, json, lcov, opencover or teamcity as well as combinations of these formats. |
| Exclude | Exclude from code coverage analysing using filter expressions. |
| ExcludeByFile | Ignore specific source files from code coverage. |
| Include | Explicitly set what to include in code coverage analysis using filter expressions. |
| IncludeDirectory | Explicitly set which directories to include in code coverage analysis. |
| SingleHit | Specifies whether to limit code coverage hit reporting to a single hit for each location. |
| UseSourceLink | Specifies whether to use SourceLink URIs in place of file system paths. |
| IncludeTestAssembly | Include coverage of the test assembly. |
| SkipAutoProps | Neither track nor record auto-implemented properties. |
| DoesNotReturnAttribute | Methods marked with these attributes are known not to return, statements following them will be excluded from coverage |
| Option | Summary |
|:-------------------------|:----------------------------------------------------------------------------------------------------------------------------------------------------------------|
| Format | Coverage output format. These are either cobertura, json, lcov, opencover or teamcity as well as combinations of these formats. |
| Exclude | Exclude from code coverage analysing using filter expressions. |
| ExcludeByFile | Ignore specific source files from code coverage. |
| Include | Explicitly set what to include in code coverage analysis using filter expressions. |
| IncludeDirectory | Explicitly set which directories to include in code coverage analysis. |
| SingleHit | Specifies whether to limit code coverage hit reporting to a single hit for each location. |
| UseSourceLink | Specifies whether to use SourceLink URIs in place of file system paths. |
| IncludeTestAssembly | Include coverage of the test assembly. |
| SkipAutoProps | Neither track nor record auto-implemented properties. |
| DoesNotReturnAttribute | Methods marked with these attributes are known not to return, statements following them will be excluded from coverage |
| DeterministicReport | Generates deterministic report in context of deterministic build. Take a look at [documentation](Documentation/DeterministicBuild.md) for further informations. |

How to specify these options via runsettings?

Expand All @@ -117,6 +118,7 @@ How to specify these options via runsettings?
<UseSourceLink>true</UseSourceLink>
<IncludeTestAssembly>true</IncludeTestAssembly>
<SkipAutoProps>true</SkipAutoProps>
<DeterministicReport>false</DeterministicReport>
</Configuration>
</DataCollector>
</DataCollectors>
Expand Down
9 changes: 4 additions & 5 deletions src/coverlet.collector/DataCollection/CoverageManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,8 @@ namespace Coverlet.Collector.DataCollection
internal class CoverageManager
{
private readonly Coverage _coverage;

private ICoverageWrapper _coverageWrapper;

private readonly ICoverageWrapper _coverageWrapper;
private readonly ISourceRootTranslator _sourceRootTranslator;
public IReporter[] Reporters { get; }

public CoverageManager(CoverletSettings settings, TestPlatformEqtTrace eqtTrace, TestPlatformLogger logger, ICoverageWrapper coverageWrapper,
Expand Down Expand Up @@ -49,7 +48,7 @@ public CoverageManager(CoverletSettings settings, IReporter[] reporters, ILogger
// Store input vars
Reporters = reporters;
_coverageWrapper = coverageWrapper;

_sourceRootTranslator = sourceRootTranslator;
// Coverage object
_coverage = _coverageWrapper.CreateCoverage(settings, logger, instrumentationHelper, fileSystem, sourceRootTranslator, cecilSymbolHelper);
}
Expand Down Expand Up @@ -108,7 +107,7 @@ private CoverageResult GetCoverageResult()
{
try
{
return Reporters.Select(reporter => (reporter.Report(coverageResult), Path.ChangeExtension(CoverletConstants.DefaultFileName, reporter.Extension)));
return Reporters.Select(reporter => (reporter.Report(coverageResult, _sourceRootTranslator), Path.ChangeExtension(CoverletConstants.DefaultFileName, reporter.Extension)));
}
catch (Exception ex)
{
Expand Down
5 changes: 3 additions & 2 deletions src/coverlet.collector/DataCollection/CoverageWrapper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ internal class CoverageWrapper : ICoverageWrapper
/// <returns>Coverage object</returns>
public Coverage CreateCoverage(CoverletSettings settings, ILogger coverletLogger, IInstrumentationHelper instrumentationHelper, IFileSystem fileSystem, ISourceRootTranslator sourceRootTranslator, ICecilSymbolHelper cecilSymbolHelper)
{
CoverageParameters parameters = new CoverageParameters
CoverageParameters parameters = new()
{
IncludeFilters = settings.IncludeFilters,
IncludeDirectories = settings.IncludeDirectories,
Expand All @@ -29,7 +29,8 @@ public Coverage CreateCoverage(CoverletSettings settings, ILogger coverletLogger
MergeWith = settings.MergeWith,
UseSourceLink = settings.UseSourceLink,
SkipAutoProps = settings.SkipAutoProps,
DoesNotReturnAttributes = settings.DoesNotReturnAttributes
DoesNotReturnAttributes = settings.DoesNotReturnAttributes,
DeterministicReport = settings.DeterministicReport
};

return new Coverage(
Expand Down
6 changes: 6 additions & 0 deletions src/coverlet.collector/DataCollection/CoverletSettings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,11 @@ internal class CoverletSettings
/// </summary>
public string[] DoesNotReturnAttributes { get; set; }

/// <summary>
/// DeterministicReport flag
/// </summary>
public bool DeterministicReport { get; set; }

public override string ToString()
{
var builder = new StringBuilder();
Expand All @@ -89,6 +94,7 @@ public override string ToString()
builder.AppendFormat("IncludeTestAssembly: '{0}'", IncludeTestAssembly);
builder.AppendFormat("SkipAutoProps: '{0}'", SkipAutoProps);
builder.AppendFormat("DoesNotReturnAttributes: '{0}'", string.Join(",", DoesNotReturnAttributes ?? Enumerable.Empty<string>()));
builder.AppendFormat("DeterministicReport: '{0}'", DeterministicReport);

return builder.ToString();
}
Expand Down
13 changes: 13 additions & 0 deletions src/coverlet.collector/DataCollection/CoverletSettingsParser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ public CoverletSettings Parse(XmlElement configurationElement, IEnumerable<strin
coverletSettings.IncludeTestAssembly = ParseIncludeTestAssembly(configurationElement);
coverletSettings.SkipAutoProps = ParseSkipAutoProps(configurationElement);
coverletSettings.DoesNotReturnAttributes = ParseDoesNotReturnAttributes(configurationElement);
coverletSettings.DeterministicReport = ParseDeterministicReport(configurationElement);
}

coverletSettings.ReportFormats = ParseReportFormats(configurationElement);
Expand Down Expand Up @@ -195,6 +196,18 @@ private bool ParseSingleHit(XmlElement configurationElement)
return singleHit;
}

/// <summary>
/// Parse ParseDeterministicReport flag
/// </summary>
/// <param name="configurationElement">Configuration element</param>
/// <returns>ParseDeterministicReport flag</returns>
private bool ParseDeterministicReport(XmlElement configurationElement)
{
XmlElement deterministicReportElement = configurationElement[CoverletConstants.DeterministicReport];
bool.TryParse(deterministicReportElement?.InnerText, out bool deterministicReport);
return deterministicReport;
}

/// <summary>
/// Parse include test assembly flag
/// </summary>
Expand Down
1 change: 1 addition & 0 deletions src/coverlet.collector/Utilities/CoverletConstants.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,5 +22,6 @@ internal static class CoverletConstants
public const string InProcDataCollectorName = "CoverletInProcDataCollector";
public const string SkipAutoProps = "SkipAutoProps";
public const string DoesNotReturnAttributesElementName = "DoesNotReturnAttribute";
public const string DeterministicReport = "DeterministicReport";
}
}
24 changes: 14 additions & 10 deletions src/coverlet.console/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,11 @@ static int Main(string[] args)
var logger = (ConsoleLogger)serviceProvider.GetService<ILogger>();
var fileSystem = serviceProvider.GetService<IFileSystem>();

var app = new CommandLineApplication();
app.Name = "coverlet";
app.FullName = "Cross platform .NET Core code coverage tool";
var app = new CommandLineApplication
{
Name = "coverlet",
FullName = "Cross platform .NET Core code coverage tool"
};
app.HelpOption("-h|--help");
app.VersionOption("-v|--version", GetAssemblyVersion());
int exitCode = (int)CommandExitCodes.Success;
Expand Down Expand Up @@ -79,7 +81,7 @@ static int Main(string[] args)
logger.Level = verbosity.ParsedValue;
}
CoverageParameters parameters = new CoverageParameters
CoverageParameters parameters = new()
{
IncludeFilters = includeFilters.Values.ToArray(),
IncludeDirectories = includeDirectories.Values.ToArray(),
Expand All @@ -94,16 +96,18 @@ static int Main(string[] args)
DoesNotReturnAttributes = doesNotReturnAttributes.Values.ToArray()
};
Coverage coverage = new Coverage(moduleOrAppDirectory.Value,
ISourceRootTranslator sourceRootTranslator = serviceProvider.GetRequiredService<ISourceRootTranslator>();
Coverage coverage = new(moduleOrAppDirectory.Value,
parameters,
logger,
serviceProvider.GetRequiredService<IInstrumentationHelper>(),
fileSystem,
serviceProvider.GetRequiredService<ISourceRootTranslator>(),
sourceRootTranslator,
serviceProvider.GetRequiredService<ICecilSymbolHelper>());
coverage.PrepareModules();
Process process = new Process();
Process process = new();
process.StartInfo.FileName = target.Value();
process.StartInfo.Arguments = targs.HasValue() ? targs.Value() : string.Empty;
process.StartInfo.CreateNoWindow = true;
Expand Down Expand Up @@ -146,7 +150,7 @@ static int Main(string[] args)
Directory.CreateDirectory(directory);
}
foreach (var format in (formats.HasValue() ? formats.Values : new List<string>(new string[] { "json" })))
foreach (var format in formats.HasValue() ? formats.Values : new List<string>(new string[] { "json" }))
{
var reporter = new ReporterFactory(format).CreateReporter();
if (reporter == null)
Expand All @@ -158,7 +162,7 @@ static int Main(string[] args)
{
// Output to console
logger.LogInformation(" Outputting results to console", important: true);
logger.LogInformation(reporter.Report(result), important: true);
logger.LogInformation(reporter.Report(result, sourceRootTranslator), important: true);
}
else
{
Expand All @@ -169,7 +173,7 @@ static int Main(string[] args)
var report = Path.Combine(directory, filename);
logger.LogInformation($" Generating report '{report}'", important: true);
fileSystem.WriteAllText(report, reporter.Report(result));
fileSystem.WriteAllText(report, reporter.Report(result, sourceRootTranslator));
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
namespace Coverlet.Core.Reporters
namespace Coverlet.Core.Abstractions
{
internal interface IReporter
{
ReporterOutputType OutputType { get; }
string Format { get; }
string Extension { get; }
string Report(CoverageResult result);
string Report(CoverageResult result, ISourceRootTranslator sourceRootTranslator);
}

internal enum ReporterOutputType
Expand Down
1 change: 1 addition & 0 deletions src/coverlet.core/Abstractions/ISourceRootTranslator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ namespace Coverlet.Core.Abstractions
internal interface ISourceRootTranslator
{
string ResolveFilePath(string originalFileName);
string ResolveDeterministicPath(string originalFileName);
IReadOnlyList<SourceRootMapping> ResolvePathRoot(string pathRoot);
}
}
Loading

0 comments on commit a2082b3

Please sign in to comment.