Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add deterministic report support: Cobertura #1113

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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