diff --git a/Documentation/DeterministicBuild.md b/Documentation/DeterministicBuild.md index ae39da596..a48865ce9 100644 --- a/Documentation/DeterministicBuild.md +++ b/Documentation/DeterministicBuild.md @@ -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 + + + + + + + + +... +``` +As you can see we have empty `` 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 diff --git a/Documentation/MSBuildIntegration.md b/Documentation/MSBuildIntegration.md index e036c0a51..7aae3fbb7 100644 --- a/Documentation/MSBuildIntegration.md +++ b/Documentation/MSBuildIntegration.md @@ -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 +``` \ No newline at end of file diff --git a/Documentation/VSTestIntegration.md b/Documentation/VSTestIntegration.md index 143904e08..5075897b1 100644 --- a/Documentation/VSTestIntegration.md +++ b/Documentation/VSTestIntegration.md @@ -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? @@ -117,6 +118,7 @@ How to specify these options via runsettings? true true true + false diff --git a/src/coverlet.collector/DataCollection/CoverageManager.cs b/src/coverlet.collector/DataCollection/CoverageManager.cs index 41801ff1f..ce12489f7 100644 --- a/src/coverlet.collector/DataCollection/CoverageManager.cs +++ b/src/coverlet.collector/DataCollection/CoverageManager.cs @@ -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, @@ -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); } @@ -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) { diff --git a/src/coverlet.collector/DataCollection/CoverageWrapper.cs b/src/coverlet.collector/DataCollection/CoverageWrapper.cs index c56d9982e..12f4005e7 100644 --- a/src/coverlet.collector/DataCollection/CoverageWrapper.cs +++ b/src/coverlet.collector/DataCollection/CoverageWrapper.cs @@ -17,7 +17,7 @@ internal class CoverageWrapper : ICoverageWrapper /// Coverage object 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, @@ -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( diff --git a/src/coverlet.collector/DataCollection/CoverletSettings.cs b/src/coverlet.collector/DataCollection/CoverletSettings.cs index 85747612b..347173c76 100644 --- a/src/coverlet.collector/DataCollection/CoverletSettings.cs +++ b/src/coverlet.collector/DataCollection/CoverletSettings.cs @@ -73,6 +73,11 @@ internal class CoverletSettings /// public string[] DoesNotReturnAttributes { get; set; } + /// + /// DeterministicReport flag + /// + public bool DeterministicReport { get; set; } + public override string ToString() { var builder = new StringBuilder(); @@ -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())); + builder.AppendFormat("DeterministicReport: '{0}'", DeterministicReport); return builder.ToString(); } diff --git a/src/coverlet.collector/DataCollection/CoverletSettingsParser.cs b/src/coverlet.collector/DataCollection/CoverletSettingsParser.cs index 31509474a..7fe778173 100644 --- a/src/coverlet.collector/DataCollection/CoverletSettingsParser.cs +++ b/src/coverlet.collector/DataCollection/CoverletSettingsParser.cs @@ -44,6 +44,7 @@ public CoverletSettings Parse(XmlElement configurationElement, IEnumerable + /// Parse ParseDeterministicReport flag + /// + /// Configuration element + /// ParseDeterministicReport flag + private bool ParseDeterministicReport(XmlElement configurationElement) + { + XmlElement deterministicReportElement = configurationElement[CoverletConstants.DeterministicReport]; + bool.TryParse(deterministicReportElement?.InnerText, out bool deterministicReport); + return deterministicReport; + } + /// /// Parse include test assembly flag /// diff --git a/src/coverlet.collector/Utilities/CoverletConstants.cs b/src/coverlet.collector/Utilities/CoverletConstants.cs index 8e4313875..431beafc6 100644 --- a/src/coverlet.collector/Utilities/CoverletConstants.cs +++ b/src/coverlet.collector/Utilities/CoverletConstants.cs @@ -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"; } } diff --git a/src/coverlet.console/Program.cs b/src/coverlet.console/Program.cs index 344ac0c5c..61bf70421 100644 --- a/src/coverlet.console/Program.cs +++ b/src/coverlet.console/Program.cs @@ -37,9 +37,11 @@ static int Main(string[] args) var logger = (ConsoleLogger)serviceProvider.GetService(); var fileSystem = serviceProvider.GetService(); - 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; @@ -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(), @@ -94,16 +96,18 @@ static int Main(string[] args) DoesNotReturnAttributes = doesNotReturnAttributes.Values.ToArray() }; - Coverage coverage = new Coverage(moduleOrAppDirectory.Value, + ISourceRootTranslator sourceRootTranslator = serviceProvider.GetRequiredService(); + + Coverage coverage = new(moduleOrAppDirectory.Value, parameters, logger, serviceProvider.GetRequiredService(), fileSystem, - serviceProvider.GetRequiredService(), + sourceRootTranslator, serviceProvider.GetRequiredService()); 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; @@ -146,7 +150,7 @@ static int Main(string[] args) Directory.CreateDirectory(directory); } - foreach (var format in (formats.HasValue() ? formats.Values : new List(new string[] { "json" }))) + foreach (var format in formats.HasValue() ? formats.Values : new List(new string[] { "json" })) { var reporter = new ReporterFactory(format).CreateReporter(); if (reporter == null) @@ -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 { @@ -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)); } } diff --git a/src/coverlet.core/Reporters/IReporter.cs b/src/coverlet.core/Abstractions/IReporter.cs similarity index 65% rename from src/coverlet.core/Reporters/IReporter.cs rename to src/coverlet.core/Abstractions/IReporter.cs index ab3e8f99d..5a76d1858 100644 --- a/src/coverlet.core/Reporters/IReporter.cs +++ b/src/coverlet.core/Abstractions/IReporter.cs @@ -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 diff --git a/src/coverlet.core/Abstractions/ISourceRootTranslator.cs b/src/coverlet.core/Abstractions/ISourceRootTranslator.cs index 225e550b6..c91077950 100644 --- a/src/coverlet.core/Abstractions/ISourceRootTranslator.cs +++ b/src/coverlet.core/Abstractions/ISourceRootTranslator.cs @@ -6,6 +6,7 @@ namespace Coverlet.Core.Abstractions internal interface ISourceRootTranslator { string ResolveFilePath(string originalFileName); + string ResolveDeterministicPath(string originalFileName); IReadOnlyList ResolvePathRoot(string pathRoot); } } diff --git a/src/coverlet.core/Coverage.cs b/src/coverlet.core/Coverage.cs index cdee8156a..b296eba57 100644 --- a/src/coverlet.core/Coverage.cs +++ b/src/coverlet.core/Coverage.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.IO; using System.Linq; +using System.Runtime.Serialization; using Coverlet.Core.Abstractions; using Coverlet.Core.Helpers; using Coverlet.Core.Instrumentation; @@ -11,43 +12,48 @@ namespace Coverlet.Core { + [DataContract] internal class CoverageParameters { + [DataMember] public string Module { get; set; } + [DataMember] public string[] IncludeFilters { get; set; } + [DataMember] public string[] IncludeDirectories { get; set; } + [DataMember] public string[] ExcludeFilters { get; set; } + [DataMember] public string[] ExcludedSourceFiles { get; set; } + [DataMember] public string[] ExcludeAttributes { get; set; } + [DataMember] public bool IncludeTestAssembly { get; set; } + [DataMember] public bool SingleHit { get; set; } + [DataMember] public string MergeWith { get; set; } + [DataMember] public bool UseSourceLink { get; set; } + [DataMember] public string[] DoesNotReturnAttributes { get; set; } + [DataMember] public bool SkipAutoProps { get; set; } + [DataMember] + public bool DeterministicReport { get; set; } } internal class Coverage { private string _moduleOrAppDirectory; private string _identifier; - private string[] _includeFilters; - private string[] _includeDirectories; - private string[] _excludeFilters; - private string[] _excludedSourceFiles; - private string[] _excludeAttributes; - private bool _includeTestAssembly; - private bool _singleHit; - private string _mergeWith; - private bool _useSourceLink; - private string[] _doesNotReturnAttributes; - private bool _skipAutoProps; private ILogger _logger; private IInstrumentationHelper _instrumentationHelper; private IFileSystem _fileSystem; private ISourceRootTranslator _sourceRootTranslator; private ICecilSymbolHelper _cecilSymbolHelper; private List _results; + private CoverageParameters _parameters; public string Identifier { @@ -63,23 +69,13 @@ public Coverage(string moduleOrDirectory, ICecilSymbolHelper cecilSymbolHelper) { _moduleOrAppDirectory = moduleOrDirectory; - _includeFilters = parameters.IncludeFilters; - _includeDirectories = parameters.IncludeDirectories ?? Array.Empty(); - _excludeFilters = parameters.ExcludeFilters; - _excludedSourceFiles = parameters.ExcludedSourceFiles; - _excludeAttributes = parameters.ExcludeAttributes; - _includeTestAssembly = parameters.IncludeTestAssembly; - _singleHit = parameters.SingleHit; - _mergeWith = parameters.MergeWith; - _useSourceLink = parameters.UseSourceLink; - _doesNotReturnAttributes = parameters.DoesNotReturnAttributes; + parameters.IncludeDirectories ??= Array.Empty(); _logger = logger; _instrumentationHelper = instrumentationHelper; + _parameters = parameters; _fileSystem = fileSystem; _sourceRootTranslator = sourceRootTranslator; _cecilSymbolHelper = cecilSymbolHelper; - _skipAutoProps = parameters.SkipAutoProps; - _identifier = Guid.NewGuid().ToString(); _results = new List(); } @@ -92,8 +88,7 @@ public Coverage(CoveragePrepareResult prepareResult, { _identifier = prepareResult.Identifier; _moduleOrAppDirectory = prepareResult.ModuleOrDirectory; - _mergeWith = prepareResult.MergeWith; - _useSourceLink = prepareResult.UseSourceLink; + _parameters = prepareResult.Parameters; _results = new List(prepareResult.Results); _logger = logger; _instrumentationHelper = instrumentationHelper; @@ -103,19 +98,19 @@ public Coverage(CoveragePrepareResult prepareResult, public CoveragePrepareResult PrepareModules() { - string[] modules = _instrumentationHelper.GetCoverableModules(_moduleOrAppDirectory, _includeDirectories, _includeTestAssembly); + string[] modules = _instrumentationHelper.GetCoverableModules(_moduleOrAppDirectory, _parameters.IncludeDirectories, _parameters.IncludeTestAssembly); - Array.ForEach(_excludeFilters ?? Array.Empty(), filter => _logger.LogVerbose($"Excluded module filter '{filter}'")); - Array.ForEach(_includeFilters ?? Array.Empty(), filter => _logger.LogVerbose($"Included module filter '{filter}'")); - Array.ForEach(_excludedSourceFiles ?? Array.Empty(), filter => _logger.LogVerbose($"Excluded source files filter '{FileSystem.EscapeFileName(filter)}'")); + Array.ForEach(_parameters.ExcludeFilters ?? Array.Empty(), filter => _logger.LogVerbose($"Excluded module filter '{filter}'")); + Array.ForEach(_parameters.IncludeFilters ?? Array.Empty(), filter => _logger.LogVerbose($"Included module filter '{filter}'")); + Array.ForEach(_parameters.ExcludedSourceFiles ?? Array.Empty(), filter => _logger.LogVerbose($"Excluded source files filter '{FileSystem.EscapeFileName(filter)}'")); - _excludeFilters = _excludeFilters?.Where(f => _instrumentationHelper.IsValidFilterExpression(f)).ToArray(); - _includeFilters = _includeFilters?.Where(f => _instrumentationHelper.IsValidFilterExpression(f)).ToArray(); + _parameters.ExcludeFilters = _parameters.ExcludeFilters?.Where(f => _instrumentationHelper.IsValidFilterExpression(f)).ToArray(); + _parameters.IncludeFilters = _parameters.IncludeFilters?.Where(f => _instrumentationHelper.IsValidFilterExpression(f)).ToArray(); foreach (var module in modules) { - if (_instrumentationHelper.IsModuleExcluded(module, _excludeFilters) || - !_instrumentationHelper.IsModuleIncluded(module, _includeFilters)) + if (_instrumentationHelper.IsModuleExcluded(module, _parameters.ExcludeFilters) || + !_instrumentationHelper.IsModuleIncluded(module, _parameters.IncludeFilters)) { _logger.LogVerbose($"Excluded module: '{module}'"); continue; @@ -123,13 +118,7 @@ public CoveragePrepareResult PrepareModules() var instrumenter = new Instrumenter(module, _identifier, - _excludeFilters, - _includeFilters, - _excludedSourceFiles, - _excludeAttributes, - _doesNotReturnAttributes, - _singleHit, - _skipAutoProps, + _parameters, _logger, _instrumentationHelper, _fileSystem, @@ -162,8 +151,7 @@ public CoveragePrepareResult PrepareModules() { Identifier = _identifier, ModuleOrDirectory = _moduleOrAppDirectory, - MergeWith = _mergeWith, - UseSourceLink = _useSourceLink, + Parameters = _parameters, Results = _results.ToArray() }; } @@ -321,11 +309,11 @@ public CoverageResult GetCoverageResult() } } - var coverageResult = new CoverageResult { Identifier = _identifier, Modules = modules, InstrumentedResults = _results, UseSourceLink = _useSourceLink }; + var coverageResult = new CoverageResult { Identifier = _identifier, Modules = modules, InstrumentedResults = _results, Parameters = _parameters }; - if (!string.IsNullOrEmpty(_mergeWith) && !string.IsNullOrWhiteSpace(_mergeWith) && _fileSystem.Exists(_mergeWith)) + if (!string.IsNullOrEmpty(_parameters.MergeWith) && !string.IsNullOrWhiteSpace(_parameters.MergeWith) && _fileSystem.Exists(_parameters.MergeWith)) { - string json = _fileSystem.ReadAllText(_mergeWith); + string json = _fileSystem.ReadAllText(_parameters.MergeWith); coverageResult.Merge(JsonConvert.DeserializeObject(json)); } @@ -382,7 +370,7 @@ private void CalculateCoverage() } List documents = result.Documents.Values.ToList(); - if (_useSourceLink && result.SourceLink != null) + if (_parameters.UseSourceLink && result.SourceLink != null) { var jObject = JObject.Parse(result.SourceLink)["documents"]; var sourceLinkDocuments = JsonConvert.DeserializeObject>(jObject.ToString()); diff --git a/src/coverlet.core/CoveragePrepareResult.cs b/src/coverlet.core/CoveragePrepareResult.cs index 01389aa83..9bf9037f8 100644 --- a/src/coverlet.core/CoveragePrepareResult.cs +++ b/src/coverlet.core/CoveragePrepareResult.cs @@ -20,6 +20,8 @@ internal class CoveragePrepareResult public bool UseSourceLink { get; set; } [DataMember] public InstrumenterResult[] Results { get; set; } + [DataMember] + public CoverageParameters Parameters { get; set; } public static CoveragePrepareResult Deserialize(Stream serializedInstrumentState) { diff --git a/src/coverlet.core/CoverageResult.cs b/src/coverlet.core/CoverageResult.cs index 470bb6c6a..4abd78672 100644 --- a/src/coverlet.core/CoverageResult.cs +++ b/src/coverlet.core/CoverageResult.cs @@ -39,14 +39,14 @@ internal class Modules : Dictionary { } internal class CoverageResult { - public string Identifier; - public Modules Modules; - public bool UseSourceLink; - internal List InstrumentedResults; + public string Identifier { get; set; } + public Modules Modules { get; set; } + public List InstrumentedResults { get; set; } + public CoverageParameters Parameters { get; set; } - internal CoverageResult() { } + public CoverageResult() { } - internal void Merge(Modules modules) + public void Merge(Modules modules) { foreach (var module in modules) { diff --git a/src/coverlet.core/Helpers/SourceRootTranslator.cs b/src/coverlet.core/Helpers/SourceRootTranslator.cs index 5d6c43da0..b75901ada 100644 --- a/src/coverlet.core/Helpers/SourceRootTranslator.cs +++ b/src/coverlet.core/Helpers/SourceRootTranslator.cs @@ -18,6 +18,7 @@ internal class SourceRootTranslator : ISourceRootTranslator private readonly ILogger _logger; private readonly IFileSystem _fileSystem; private readonly Dictionary> _sourceRootMapping; + private readonly Dictionary> _sourceToDeterministicPathMapping; private const string MappingFileName = "CoverletSourceRootsMapping"; private Dictionary _resolutionCacheFiles; @@ -41,6 +42,30 @@ public SourceRootTranslator(string moduleTestPath, ILogger logger, IFileSystem f throw new FileNotFoundException($"Module test path '{moduleTestPath}' not found", moduleTestPath); } _sourceRootMapping = LoadSourceRootMapping(Path.GetDirectoryName(moduleTestPath)); + _sourceToDeterministicPathMapping = LoadSourceToDeterministicPathMapping(_sourceRootMapping); + } + + private Dictionary> LoadSourceToDeterministicPathMapping(Dictionary> sourceRootMapping) + { + if (sourceRootMapping is null) + { + throw new ArgumentNullException(nameof(sourceRootMapping)); + } + + Dictionary> sourceToDeterministicPathMapping = new Dictionary>(); + foreach (KeyValuePair> sourceRootMappingEntry in sourceRootMapping) + { + foreach (SourceRootMapping originalPath in sourceRootMappingEntry.Value) + { + if (!sourceToDeterministicPathMapping.ContainsKey(originalPath.OriginalPath)) + { + sourceToDeterministicPathMapping.Add(originalPath.OriginalPath, new List()); + } + sourceToDeterministicPathMapping[originalPath.OriginalPath].Add(sourceRootMappingEntry.Key); + } + } + + return sourceToDeterministicPathMapping; } private Dictionary> LoadSourceRootMapping(string directory) @@ -70,7 +95,11 @@ private Dictionary> LoadSourceRootMapping(string { mapping.Add(mappedPath, new List()); } - mapping[mappedPath].Add(new SourceRootMapping() { OriginalPath = originalPath, ProjectPath = projectPath }); + + foreach (string path in originalPath.Split(';')) + { + mapping[mappedPath].Add(new SourceRootMapping() { OriginalPath = path, ProjectPath = projectPath }); + } } return mapping; @@ -106,5 +135,21 @@ public string ResolveFilePath(string originalFileName) } return originalFileName; } + + public string ResolveDeterministicPath(string originalFileName) + { + foreach (var originalPath in _sourceToDeterministicPathMapping) + { + if (originalFileName.StartsWith(originalPath.Key)) + { + foreach (string deterministicPath in originalPath.Value) + { + originalFileName = originalFileName.Replace(originalPath.Key, deterministicPath).Replace('\\', '/'); + } + } + } + + return originalFileName; + } } } diff --git a/src/coverlet.core/Instrumentation/Instrumenter.cs b/src/coverlet.core/Instrumentation/Instrumenter.cs index 575be13be..a1f6d88bf 100644 --- a/src/coverlet.core/Instrumentation/Instrumenter.cs +++ b/src/coverlet.core/Instrumentation/Instrumenter.cs @@ -22,12 +22,9 @@ internal class Instrumenter { private readonly string _module; private readonly string _identifier; - private readonly string[] _excludeFilters; - private readonly string[] _includeFilters; private readonly ExcludedFilesHelper _excludedFilesHelper; + private readonly CoverageParameters _parameters; private readonly string[] _excludedAttributes; - private readonly bool _singleHit; - private readonly bool _skipAutoProps; private readonly bool _isCoreLibrary; private readonly ILogger _logger; private readonly IInstrumentationHelper _instrumentationHelper; @@ -55,13 +52,7 @@ internal class Instrumenter public Instrumenter( string module, string identifier, - string[] excludeFilters, - string[] includeFilters, - string[] excludedFiles, - string[] excludedAttributes, - string[] doesNotReturnAttributes, - bool singleHit, - bool skipAutoProps, + CoverageParameters parameters, ILogger logger, IInstrumentationHelper instrumentationHelper, IFileSystem fileSystem, @@ -70,19 +61,16 @@ public Instrumenter( { _module = module; _identifier = identifier; - _excludeFilters = excludeFilters; - _includeFilters = includeFilters; - _excludedFilesHelper = new ExcludedFilesHelper(excludedFiles, logger); - _excludedAttributes = PrepareAttributes(excludedAttributes, nameof(ExcludeFromCoverageAttribute), nameof(ExcludeFromCodeCoverageAttribute)); - _singleHit = singleHit; + _parameters = parameters; + _excludedFilesHelper = new ExcludedFilesHelper(parameters.ExcludedSourceFiles, logger); + _excludedAttributes = PrepareAttributes(parameters.ExcludeAttributes, nameof(ExcludeFromCoverageAttribute), nameof(ExcludeFromCodeCoverageAttribute)); _isCoreLibrary = Path.GetFileNameWithoutExtension(_module) == "System.Private.CoreLib"; _logger = logger; _instrumentationHelper = instrumentationHelper; _fileSystem = fileSystem; _sourceRootTranslator = sourceRootTranslator; _cecilSymbolHelper = cecilSymbolHelper; - _doesNotReturnAttributes = PrepareAttributes(doesNotReturnAttributes); - _skipAutoProps = skipAutoProps; + _doesNotReturnAttributes = PrepareAttributes(parameters.DoesNotReturnAttributes); } private static string[] PrepareAttributes(IEnumerable providedAttrs, params string[] defaultAttrs) @@ -176,7 +164,7 @@ private bool IsTypeExcluded(TypeDefinition type) for (TypeDefinition current = type; current != null; current = current.DeclaringType) { // Check exclude attribute and filters - if (current.CustomAttributes.Any(IsExcludeAttribute) || _instrumentationHelper.IsTypeExcluded(_module, current.FullName, _excludeFilters)) + if (current.CustomAttributes.Any(IsExcludeAttribute) || _instrumentationHelper.IsTypeExcluded(_module, current.FullName, _parameters.ExcludeFilters)) { return true; } @@ -253,7 +241,7 @@ private void InstrumentModule() if ( !Is_System_Threading_Interlocked_CoreLib_Type(type) && !IsTypeExcluded(type) && - _instrumentationHelper.IsTypeIncluded(_module, type.FullName, _includeFilters) + _instrumentationHelper.IsTypeIncluded(_module, type.FullName, _parameters.IncludeFilters) ) { if (IsSynthesizedMemberToBeExcluded(type)) @@ -289,7 +277,7 @@ private void InstrumentModule() _customTrackerClassConstructorIl.InsertBefore(lastInstr, Instruction.Create(OpCodes.Stsfld, _customTrackerHitsArray)); _customTrackerClassConstructorIl.InsertBefore(lastInstr, Instruction.Create(OpCodes.Ldstr, _result.HitsFilePath)); _customTrackerClassConstructorIl.InsertBefore(lastInstr, Instruction.Create(OpCodes.Stsfld, _customTrackerHitsFilePath)); - _customTrackerClassConstructorIl.InsertBefore(lastInstr, Instruction.Create(_singleHit ? OpCodes.Ldc_I4_1 : OpCodes.Ldc_I4_0)); + _customTrackerClassConstructorIl.InsertBefore(lastInstr, Instruction.Create(_parameters.SingleHit ? OpCodes.Ldc_I4_1 : OpCodes.Ldc_I4_0)); _customTrackerClassConstructorIl.InsertBefore(lastInstr, Instruction.Create(OpCodes.Stsfld, _customTrackerSingleHit)); _customTrackerClassConstructorIl.InsertBefore(lastInstr, Instruction.Create(OpCodes.Ldc_I4_1)); _customTrackerClassConstructorIl.InsertBefore(lastInstr, Instruction.Create(OpCodes.Stsfld, _customTrackerFlushHitFile)); @@ -466,7 +454,7 @@ private void InstrumentType(TypeDefinition type) if (actualMethod.IsGetter || actualMethod.IsSetter) { - if (_skipAutoProps && actualMethod.CustomAttributes.Any(ca => ca.AttributeType.FullName == typeof(CompilerGeneratedAttribute).FullName)) + if (_parameters.SkipAutoProps && actualMethod.CustomAttributes.Any(ca => ca.AttributeType.FullName == typeof(CompilerGeneratedAttribute).FullName)) { continue; } @@ -680,7 +668,7 @@ private Instruction AddInstrumentationInstructions(MethodDefinition method, ILPr if (_customTrackerRecordHitMethod == null) { string recordHitMethodName; - if (_singleHit) + if (_parameters.SingleHit) { recordHitMethodName = _isCoreLibrary ? nameof(ModuleTrackerTemplate.RecordSingleHitInCoreLibrary) diff --git a/src/coverlet.core/Reporters/CoberturaReporter.cs b/src/coverlet.core/Reporters/CoberturaReporter.cs index 291462299..e9e13c719 100644 --- a/src/coverlet.core/Reporters/CoberturaReporter.cs +++ b/src/coverlet.core/Reporters/CoberturaReporter.cs @@ -7,6 +7,8 @@ using System.Text; using System.Xml.Linq; +using Coverlet.Core.Abstractions; + namespace Coverlet.Core.Reporters { internal class CoberturaReporter : IReporter @@ -17,7 +19,7 @@ internal class CoberturaReporter : IReporter public string Extension => "cobertura.xml"; - public string Report(CoverageResult result) + public string Report(CoverageResult result, ISourceRootTranslator sourceRootTranslator) { CoverageSummary summary = new CoverageSummary(); @@ -32,8 +34,13 @@ public string Report(CoverageResult result) coverage.Add(new XAttribute("timestamp", (int)(DateTime.UtcNow - new DateTime(1970, 1, 1)).TotalSeconds)); XElement sources = new XElement("sources"); - var absolutePaths = GetBasePaths(result.Modules, result.UseSourceLink).ToList(); - absolutePaths.ForEach(x => sources.Add(new XElement("source", x))); + + List absolutePaths = new List(); + if (!result.Parameters.DeterministicReport) + { + absolutePaths = GetBasePaths(result.Modules, result.Parameters.UseSourceLink).ToList(); + absolutePaths.ForEach(x => sources.Add(new XElement("source", x))); + } XElement packages = new XElement("packages"); foreach (var module in result.Modules) @@ -51,7 +58,16 @@ public string Report(CoverageResult result) { XElement @class = new XElement("class"); @class.Add(new XAttribute("name", cls.Key)); - @class.Add(new XAttribute("filename", GetRelativePathFromBase(absolutePaths, document.Key, result.UseSourceLink))); + string fileName; + if (!result.Parameters.DeterministicReport) + { + fileName = GetRelativePathFromBase(absolutePaths, document.Key, result.Parameters.UseSourceLink); + } + else + { + fileName = sourceRootTranslator.ResolveDeterministicPath(document.Key); + } + @class.Add(new XAttribute("filename", fileName)); @class.Add(new XAttribute("line-rate", (summary.CalculateLineCoverage(cls.Value).Percent / 100).ToString(CultureInfo.InvariantCulture))); @class.Add(new XAttribute("branch-rate", (summary.CalculateBranchCoverage(cls.Value).Percent / 100).ToString(CultureInfo.InvariantCulture))); @class.Add(new XAttribute("complexity", summary.CalculateCyclomaticComplexity(cls.Value))); diff --git a/src/coverlet.core/Reporters/JsonReporter.cs b/src/coverlet.core/Reporters/JsonReporter.cs index 09e23e83f..bbcb4fa31 100644 --- a/src/coverlet.core/Reporters/JsonReporter.cs +++ b/src/coverlet.core/Reporters/JsonReporter.cs @@ -1,5 +1,8 @@ using Newtonsoft.Json; +using Coverlet.Core.Abstractions; +using System; + namespace Coverlet.Core.Reporters { internal class JsonReporter : IReporter @@ -10,7 +13,7 @@ internal class JsonReporter : IReporter public string Extension => "json"; - public string Report(CoverageResult result) + public string Report(CoverageResult result, ISourceRootTranslator _) { return JsonConvert.SerializeObject(result.Modules, Formatting.Indented); } diff --git a/src/coverlet.core/Reporters/LcovReporter.cs b/src/coverlet.core/Reporters/LcovReporter.cs index e8e94b68d..f1ede2437 100644 --- a/src/coverlet.core/Reporters/LcovReporter.cs +++ b/src/coverlet.core/Reporters/LcovReporter.cs @@ -2,6 +2,8 @@ using System.Linq; using System.Collections.Generic; +using Coverlet.Core.Abstractions; + namespace Coverlet.Core.Reporters { internal class LcovReporter : IReporter @@ -12,8 +14,13 @@ internal class LcovReporter : IReporter public string Extension => "info"; - public string Report(CoverageResult result) + public string Report(CoverageResult result, ISourceRootTranslator sourceRootTranslator) { + if (result.Parameters.DeterministicReport) + { + throw new NotSupportedException("Deterministic report not supported by lcov reporter"); + } + CoverageSummary summary = new CoverageSummary(); List lcov = new List(); diff --git a/src/coverlet.core/Reporters/OpenCoverReporter.cs b/src/coverlet.core/Reporters/OpenCoverReporter.cs index 3c4c66b45..ac31f4d0d 100644 --- a/src/coverlet.core/Reporters/OpenCoverReporter.cs +++ b/src/coverlet.core/Reporters/OpenCoverReporter.cs @@ -5,6 +5,8 @@ using System.Text; using System.Xml.Linq; +using Coverlet.Core.Abstractions; + namespace Coverlet.Core.Reporters { internal class OpenCoverReporter : IReporter @@ -15,8 +17,13 @@ internal class OpenCoverReporter : IReporter public string Extension => "opencover.xml"; - public string Report(CoverageResult result) + public string Report(CoverageResult result, ISourceRootTranslator sourceRootTranslator) { + if (result.Parameters.DeterministicReport) + { + throw new NotSupportedException("Deterministic report not supported by openCover reporter"); + } + CoverageSummary summary = new CoverageSummary(); XDocument xml = new XDocument(); XElement coverage = new XElement("CoverageSession"); diff --git a/src/coverlet.core/Reporters/ReporterFactory.cs b/src/coverlet.core/Reporters/ReporterFactory.cs index 7ac72337b..0d13520cb 100644 --- a/src/coverlet.core/Reporters/ReporterFactory.cs +++ b/src/coverlet.core/Reporters/ReporterFactory.cs @@ -1,6 +1,8 @@ using System; using System.Linq; +using Coverlet.Core.Abstractions; + namespace Coverlet.Core.Reporters { internal class ReporterFactory diff --git a/src/coverlet.core/Reporters/TeamCityReporter.cs b/src/coverlet.core/Reporters/TeamCityReporter.cs index a12a8f96c..acb5d4ff1 100644 --- a/src/coverlet.core/Reporters/TeamCityReporter.cs +++ b/src/coverlet.core/Reporters/TeamCityReporter.cs @@ -1,8 +1,9 @@ -using System.Globalization; -using Coverlet.Core; -using Coverlet.Core.Reporters; +using System; +using System.Globalization; using System.Text; +using Coverlet.Core.Abstractions; + namespace Coverlet.Core.Reporters { internal class TeamCityReporter : IReporter @@ -13,8 +14,13 @@ internal class TeamCityReporter : IReporter public string Extension => null; - public string Report(CoverageResult result) + public string Report(CoverageResult result, ISourceRootTranslator sourceRootTranslator) { + if (result.Parameters.DeterministicReport) + { + throw new NotSupportedException("Deterministic report not supported by teamcity reporter"); + } + // Calculate coverage var summary = new CoverageSummary(); var overallLineCoverage = summary.CalculateLineCoverage(result.Modules); diff --git a/src/coverlet.msbuild.tasks/CoverageResultTask.cs b/src/coverlet.msbuild.tasks/CoverageResultTask.cs index fd3cdd9ca..3f7adab44 100644 --- a/src/coverlet.msbuild.tasks/CoverageResultTask.cs +++ b/src/coverlet.msbuild.tasks/CoverageResultTask.cs @@ -93,6 +93,7 @@ public override bool Execute() var formats = OutputFormat.Split(','); var coverageReportPaths = new List(formats.Length); + ISourceRootTranslator sourceRootTranslator = ServiceProvider.GetService(); foreach (var format in formats) { var reporter = new ReporterFactory(format).CreateReporter(); @@ -105,17 +106,18 @@ public override bool Execute() { // Output to console Console.WriteLine(" Outputting results to console"); - Console.WriteLine(reporter.Report(result)); + Console.WriteLine(reporter.Report(result, sourceRootTranslator)); } else { - ReportWriter writer = new ReportWriter(CoverletMultiTargetFrameworksCurrentTFM, + ReportWriter writer = new(CoverletMultiTargetFrameworksCurrentTFM, directory, Output, reporter, fileSystem, ServiceProvider.GetService(), - result); + result, + sourceRootTranslator); var path = writer.WriteReport(); var metadata = new Dictionary { ["Format"] = format }; coverageReportPaths.Add(new TaskItem(path, metadata)); diff --git a/src/coverlet.msbuild.tasks/InstrumentationTask.cs b/src/coverlet.msbuild.tasks/InstrumentationTask.cs index 11bfbc6db..9be72b5c6 100644 --- a/src/coverlet.msbuild.tasks/InstrumentationTask.cs +++ b/src/coverlet.msbuild.tasks/InstrumentationTask.cs @@ -42,6 +42,8 @@ public class InstrumentationTask : BaseTask public string DoesNotReturnAttribute { get; set; } + public bool DeterministicReport { get; set; } + [Output] public ITaskItem InstrumenterState { get; set; } @@ -50,26 +52,18 @@ public InstrumentationTask() _logger = new MSBuildLogger(Log); } - private void WaitForDebuggerIfEnabled() + private void AttachDebugger() { if (int.TryParse(Environment.GetEnvironmentVariable("COVERLET_MSBUILD_INSTRUMENTATIONTASK_DEBUG"), out int result) && result == 1) { - Console.WriteLine("Coverlet msbuild instrumentation task debugging is enabled. Please attach debugger to process to continue"); - Process currentProcess = Process.GetCurrentProcess(); - Console.WriteLine($"Process Id: {currentProcess.Id} Name: {currentProcess.ProcessName}"); - - while (!Debugger.IsAttached) - { - System.Threading.Tasks.Task.Delay(1000).Wait(); - } - + Debugger.Launch(); Debugger.Break(); } } public override bool Execute() { - WaitForDebuggerIfEnabled(); + AttachDebugger(); IServiceCollection serviceCollection = new ServiceCollection(); serviceCollection.AddTransient(); @@ -101,6 +95,7 @@ public override bool Execute() MergeWith = MergeWith, UseSourceLink = UseSourceLink, SkipAutoProps = SkipAutoProps, + DeterministicReport = DeterministicReport, DoesNotReturnAttributes = DoesNotReturnAttribute?.Split(',') }; diff --git a/src/coverlet.msbuild.tasks/ReportWriter.cs b/src/coverlet.msbuild.tasks/ReportWriter.cs index 65fc00f0f..1261ce76c 100644 --- a/src/coverlet.msbuild.tasks/ReportWriter.cs +++ b/src/coverlet.msbuild.tasks/ReportWriter.cs @@ -14,11 +14,13 @@ internal class ReportWriter private readonly IReporter _reporter; private readonly IFileSystem _fileSystem; private readonly IConsole _console; + private readonly ISourceRootTranslator _sourceRootTranslator; private readonly CoverageResult _result; - public ReportWriter(string coverletMultiTargetFrameworksCurrentTFM, string directory, string output, IReporter reporter, IFileSystem fileSystem, IConsole console, CoverageResult result) - => (_coverletMultiTargetFrameworksCurrentTFM, _directory, _output, _reporter, _fileSystem, _console, _result) = - (coverletMultiTargetFrameworksCurrentTFM, directory, output, reporter, fileSystem, console, result); + public ReportWriter(string coverletMultiTargetFrameworksCurrentTFM, string directory, string output, + IReporter reporter, IFileSystem fileSystem, IConsole console, CoverageResult result, ISourceRootTranslator sourceRootTranslator) + => (_coverletMultiTargetFrameworksCurrentTFM, _directory, _output, _reporter, _fileSystem, _console, _result, _sourceRootTranslator) = + (coverletMultiTargetFrameworksCurrentTFM, directory, output, reporter, fileSystem, console, result, sourceRootTranslator); public string WriteReport() { @@ -47,7 +49,7 @@ public string WriteReport() string report = Path.Combine(_directory, filename); _console.WriteLine($" Generating report '{report}'"); - _fileSystem.WriteAllText(report, _reporter.Report(_result)); + _fileSystem.WriteAllText(report, _reporter.Report(_result, _sourceRootTranslator)); return report; } } diff --git a/src/coverlet.msbuild.tasks/coverlet.msbuild.props b/src/coverlet.msbuild.tasks/coverlet.msbuild.props index e6906ccc3..3821845ec 100644 --- a/src/coverlet.msbuild.tasks/coverlet.msbuild.props +++ b/src/coverlet.msbuild.tasks/coverlet.msbuild.props @@ -10,6 +10,7 @@ false false + false json $([MSBuild]::EnsureTrailingSlash('$(MSBuildProjectDirectory)')) 0 diff --git a/src/coverlet.msbuild.tasks/coverlet.msbuild.targets b/src/coverlet.msbuild.tasks/coverlet.msbuild.targets index a980283cb..c271ec2d1 100644 --- a/src/coverlet.msbuild.tasks/coverlet.msbuild.targets +++ b/src/coverlet.msbuild.tasks/coverlet.msbuild.targets @@ -48,6 +48,7 @@ MergeWith="$(MergeWith)" UseSourceLink="$(UseSourceLink)" SkipAutoProps="$(SkipAutoProps)" + DeterministicReport="$(DeterministicReport)" DoesNotReturnAttribute="$(DoesNotReturnAttribute)"> diff --git a/test/coverlet.collector.tests/CoverletSettingsParserTests.cs b/test/coverlet.collector.tests/CoverletSettingsParserTests.cs index a54abe9cc..ebcba0b3f 100644 --- a/test/coverlet.collector.tests/CoverletSettingsParserTests.cs +++ b/test/coverlet.collector.tests/CoverletSettingsParserTests.cs @@ -75,6 +75,7 @@ public void ParseShouldCorrectlyParseConfigurationElement(string includeFilters, this.CreateCoverletNodes(doc, configElement, CoverletConstants.SingleHitElementName, "true"); this.CreateCoverletNodes(doc, configElement, CoverletConstants.IncludeTestAssemblyElementName, "true"); this.CreateCoverletNodes(doc, configElement, CoverletConstants.SkipAutoProps, "true"); + this.CreateCoverletNodes(doc, configElement, CoverletConstants.DeterministicReport, "true"); this.CreateCoverletNodes(doc, configElement, CoverletConstants.DoesNotReturnAttributesElementName, doesNotReturnAttributes); CoverletSettings coverletSettings = _coverletSettingsParser.Parse(configElement, testModules); @@ -100,6 +101,7 @@ public void ParseShouldCorrectlyParseConfigurationElement(string includeFilters, Assert.True(coverletSettings.SingleHit); Assert.True(coverletSettings.IncludeTestAssembly); Assert.True(coverletSettings.SkipAutoProps); + Assert.True(coverletSettings.DeterministicReport); } [Fact] diff --git a/test/coverlet.core.tests/Coverage/InstrumenterHelper.cs b/test/coverlet.core.tests/Coverage/InstrumenterHelper.cs index 9b8f913b9..65365163a 100644 --- a/test/coverlet.core.tests/Coverage/InstrumenterHelper.cs +++ b/test/coverlet.core.tests/Coverage/InstrumenterHelper.cs @@ -34,9 +34,9 @@ public static void GenerateHtmlReport(CoverageResult coverageResult, IReporter r dir.Delete(true); dir.Create(); string reportFile = Path.Combine(dir.FullName, Path.ChangeExtension("report", defaultReporter.Extension)); - File.WriteAllText(reportFile, defaultReporter.Report(coverageResult)); + File.WriteAllText(reportFile, defaultReporter.Report(coverageResult, new Mock().Object)); reportFile = Path.Combine(dir.FullName, Path.ChangeExtension("report", reporter.Extension)); - File.WriteAllText(reportFile, reporter.Report(coverageResult)); + File.WriteAllText(reportFile, reporter.Report(coverageResult, new Mock().Object)); // i.e. reportgenerator -reports:"C:\git\coverlet\test\coverlet.core.tests\bin\Debug\netcoreapp2.0\Condition_If\report.cobertura.xml" -targetdir:"C:\git\coverlet\test\coverlet.core.tests\bin\Debug\netcoreapp2.0\Condition_If" -filefilters:+**\Samples\Instrumentation.cs Assert.True(new Generator().GenerateReport(new ReportConfiguration( new[] { reportFile }, diff --git a/test/coverlet.core.tests/Instrumentation/InstrumenterTests.cs b/test/coverlet.core.tests/Instrumentation/InstrumenterTests.cs index 005c995fb..53219b293 100644 --- a/test/coverlet.core.tests/Instrumentation/InstrumenterTests.cs +++ b/test/coverlet.core.tests/Instrumentation/InstrumenterTests.cs @@ -84,10 +84,10 @@ public void TestCoreLibInstrumentation() } }); var sourceRootTranslator = new SourceRootTranslator(_mockLogger.Object, new FileSystem()); + CoverageParameters parameters = new CoverageParameters(); InstrumentationHelper instrumentationHelper = new InstrumentationHelper(new ProcessExitHandler(), new RetryHelper(), partialMockFileSystem.Object, _mockLogger.Object, sourceRootTranslator); - Instrumenter instrumenter = new Instrumenter(Path.Combine(directory.FullName, files[0]), "_coverlet_instrumented", Array.Empty(), Array.Empty(), Array.Empty(), - Array.Empty(), Array.Empty(), false, false, _mockLogger.Object, instrumentationHelper, partialMockFileSystem.Object, sourceRootTranslator, new CecilSymbolHelper()); + Instrumenter instrumenter = new Instrumenter(Path.Combine(directory.FullName, files[0]), "_coverlet_instrumented", parameters, _mockLogger.Object, instrumentationHelper, partialMockFileSystem.Object, sourceRootTranslator, new CecilSymbolHelper()); Assert.True(instrumenter.CanInstrument()); InstrumenterResult result = instrumenter.Instrument(); @@ -252,8 +252,12 @@ private InstrumenterTest CreateInstrumentor(bool fakeCoreLibModule = false, stri new InstrumentationHelper(new ProcessExitHandler(), new RetryHelper(), new FileSystem(), new Mock().Object, new SourceRootTranslator(new Mock().Object, new FileSystem())); module = Path.Combine(directory.FullName, destModule); - Instrumenter instrumenter = new Instrumenter(module, identifier, Array.Empty(), Array.Empty(), Array.Empty(), attributesToIgnore, new string[] { "DoesNotReturnAttribute" }, false, false, - _mockLogger.Object, instrumentationHelper, new FileSystem(), new SourceRootTranslator(_mockLogger.Object, new FileSystem()), new CecilSymbolHelper()); + CoverageParameters parameters = new() + { + ExcludeAttributes = attributesToIgnore, + DoesNotReturnAttributes = new string[] { "DoesNotReturnAttribute" } + }; + Instrumenter instrumenter = new Instrumenter(module, identifier, parameters, _mockLogger.Object, instrumentationHelper, new FileSystem(), new SourceRootTranslator(_mockLogger.Object, new FileSystem()), new CecilSymbolHelper()); return new InstrumenterTest { Instrumenter = instrumenter, @@ -429,8 +433,7 @@ public void SkipEmbeddedPpdbWithoutLocalSource() new InstrumentationHelper(new ProcessExitHandler(), new RetryHelper(), new FileSystem(), new Mock().Object, new SourceRootTranslator(xunitDll, new Mock().Object, new FileSystem())); - Instrumenter instrumenter = new Instrumenter(xunitDll, "_xunit_instrumented", Array.Empty(), Array.Empty(), Array.Empty(), - Array.Empty(), Array.Empty(), false, false, loggerMock.Object, instrumentationHelper, new FileSystem(), new SourceRootTranslator(xunitDll, loggerMock.Object, new FileSystem()), new CecilSymbolHelper()); + Instrumenter instrumenter = new Instrumenter(xunitDll, "_xunit_instrumented", new CoverageParameters(), loggerMock.Object, instrumentationHelper, new FileSystem(), new SourceRootTranslator(xunitDll, loggerMock.Object, new FileSystem()), new CecilSymbolHelper()); Assert.True(instrumentationHelper.HasPdb(xunitDll, out bool embedded)); Assert.True(embedded); Assert.False(instrumenter.CanInstrument()); @@ -442,8 +445,7 @@ public void SkipEmbeddedPpdbWithoutLocalSource() new InstrumentationHelper(new ProcessExitHandler(), new RetryHelper(), new FileSystem(), new Mock().Object, new SourceRootTranslator(sample, new Mock().Object, new FileSystem())); - instrumenter = new Instrumenter(sample, "_coverlet_tests_projectsample_empty", Array.Empty(), Array.Empty(), Array.Empty(), - Array.Empty(), Array.Empty(), false, false, loggerMock.Object, instrumentationHelper, new FileSystem(), new SourceRootTranslator(sample, loggerMock.Object, new FileSystem()), new CecilSymbolHelper()); + instrumenter = new Instrumenter(sample, "_coverlet_tests_projectsample_empty", new CoverageParameters(), loggerMock.Object, instrumentationHelper, new FileSystem(), new SourceRootTranslator(sample, loggerMock.Object, new FileSystem()), new CecilSymbolHelper()); Assert.True(instrumentationHelper.HasPdb(sample, out embedded)); Assert.False(embedded); @@ -488,8 +490,7 @@ public void SkipPpdbWithoutLocalSource() new InstrumentationHelper(new ProcessExitHandler(), new RetryHelper(), partialMockFileSystem.Object, _mockLogger.Object, new SourceRootTranslator(_mockLogger.Object, new FileSystem())); string sample = Directory.GetFiles(Path.Combine(Directory.GetCurrentDirectory(), "TestAssets"), dllFileName).First(); var loggerMock = new Mock(); - Instrumenter instrumenter = new Instrumenter(sample, "_75d9f96508d74def860a568f426ea4a4_instrumented", Array.Empty(), Array.Empty(), Array.Empty(), - Array.Empty(), Array.Empty(), false, false, loggerMock.Object, instrumentationHelper, partialMockFileSystem.Object, new SourceRootTranslator(loggerMock.Object, new FileSystem()), new CecilSymbolHelper()); + Instrumenter instrumenter = new Instrumenter(sample, "_75d9f96508d74def860a568f426ea4a4_instrumented", new CoverageParameters(), loggerMock.Object, instrumentationHelper, partialMockFileSystem.Object, new SourceRootTranslator(loggerMock.Object, new FileSystem()), new CecilSymbolHelper()); Assert.True(instrumentationHelper.HasPdb(sample, out bool embedded)); Assert.False(embedded); @@ -506,8 +507,7 @@ public void TestInstrument_MissingModule() new InstrumentationHelper(new ProcessExitHandler(), new RetryHelper(), new FileSystem(), new Mock().Object, new SourceRootTranslator(new Mock().Object, new FileSystem())); - var instrumenter = new Instrumenter("test", "_test_instrumented", Array.Empty(), Array.Empty(), Array.Empty(), - Array.Empty(), Array.Empty(), false, false, loggerMock.Object, instrumentationHelper, new FileSystem(), new SourceRootTranslator(loggerMock.Object, new FileSystem()), new CecilSymbolHelper()); + var instrumenter = new Instrumenter("test", "_test_instrumented", new CoverageParameters(), loggerMock.Object, instrumentationHelper, new FileSystem(), new SourceRootTranslator(loggerMock.Object, new FileSystem()), new CecilSymbolHelper()); Assert.False(instrumenter.CanInstrument()); loggerMock.Verify(l => l.LogWarning(It.IsAny())); @@ -585,9 +585,9 @@ public int SampleMethod() InstrumentationHelper instrumentationHelper = new InstrumentationHelper(new ProcessExitHandler(), new RetryHelper(), new FileSystem(), new Mock().Object, new SourceRootTranslator(new Mock().Object, new FileSystem())); - - Instrumenter instrumenter = new Instrumenter(excludedbyattributeDll, "_xunit_excludedbyattribute", Array.Empty(), Array.Empty(), Array.Empty(), - excludedAttributes, Array.Empty(), false, false, loggerMock.Object, instrumentationHelper, partialMockFileSystem.Object, new SourceRootTranslator(loggerMock.Object, new FileSystem()), new CecilSymbolHelper()); + CoverageParameters parametes = new(); + parametes.ExcludeAttributes = excludedAttributes; + Instrumenter instrumenter = new Instrumenter(excludedbyattributeDll, "_xunit_excludedbyattribute", parametes, loggerMock.Object, instrumentationHelper, partialMockFileSystem.Object, new SourceRootTranslator(loggerMock.Object, new FileSystem()), new CecilSymbolHelper()); InstrumenterResult result = instrumenter.Instrument(); if (expectedExcludes) diff --git a/test/coverlet.core.tests/Reporters/CoberturaReporterTests.cs b/test/coverlet.core.tests/Reporters/CoberturaReporterTests.cs index db70e16bd..f92c922d2 100644 --- a/test/coverlet.core.tests/Reporters/CoberturaReporterTests.cs +++ b/test/coverlet.core.tests/Reporters/CoberturaReporterTests.cs @@ -7,6 +7,9 @@ using System.Text; using System.Threading; using System.Xml.Linq; + +using Coverlet.Core.Abstractions; +using Moq; using Xunit; namespace Coverlet.Core.Reporters.Tests @@ -49,6 +52,7 @@ public void TestReport() result.Modules = new Modules(); result.Modules.Add("module", documents); + result.Parameters = new CoverageParameters(); CultureInfo currentCulture = Thread.CurrentThread.CurrentCulture; Thread.CurrentThread.CurrentCulture = new CultureInfo("it-IT"); @@ -59,7 +63,7 @@ public void TestReport() Assert.Equal("1,5", (1.5).ToString()); CoberturaReporter reporter = new CoberturaReporter(); - string report = reporter.Report(result); + string report = reporter.Report(result, new Mock().Object); Assert.NotEmpty(report); @@ -110,6 +114,7 @@ public void TestEnsureParseMethodStringCorrectly( string expectedSignature) { CoverageResult result = new CoverageResult(); + result.Parameters = new CoverageParameters(); result.Identifier = Guid.NewGuid().ToString(); Lines lines = new Lines(); @@ -140,7 +145,7 @@ public void TestEnsureParseMethodStringCorrectly( result.Modules.Add("module", documents); CoberturaReporter reporter = new CoberturaReporter(); - string report = reporter.Report(result); + string report = reporter.Report(result, new Mock().Object); Assert.NotEmpty(report); @@ -157,6 +162,7 @@ public void TestEnsureParseMethodStringCorrectly( public void TestReportWithDifferentDirectories() { CoverageResult result = new CoverageResult(); + result.Parameters = new CoverageParameters(); result.Identifier = Guid.NewGuid().ToString(); string absolutePath1; @@ -210,7 +216,7 @@ public void TestReportWithDifferentDirectories() result.Modules = new Modules { { "Module", documents } }; CoberturaReporter reporter = new CoberturaReporter(); - string report = reporter.Report(result); + string report = reporter.Report(result, new Mock().Object); var doc = XDocument.Load(new MemoryStream(Encoding.UTF8.GetBytes(report))); @@ -241,7 +247,7 @@ public void TestReportWithDifferentDirectories() [Fact] public void TestReportWithSourcelinkPaths() { - CoverageResult result = new CoverageResult { UseSourceLink = true, Identifier = Guid.NewGuid().ToString() }; + CoverageResult result = new CoverageResult { Parameters = new CoverageParameters() { UseSourceLink = true }, Identifier = Guid.NewGuid().ToString() }; var absolutePath = @"https://raw.githubusercontent.com/johndoe/Coverlet/02c09baa8bfdee3b6cdf4be89bd98c8157b0bc08/Demo.cs"; @@ -252,7 +258,7 @@ public void TestReportWithSourcelinkPaths() result.Modules = new Modules { { "Module", documents } }; CoberturaReporter reporter = new CoberturaReporter(); - string report = reporter.Report(result); + string report = reporter.Report(result, new Mock().Object); var doc = XDocument.Load(new MemoryStream(Encoding.UTF8.GetBytes(report))); var fileName = doc.Element("coverage").Element("packages").Element("package").Element("classes").Elements() diff --git a/test/coverlet.core.tests/Reporters/JsonReporterTests.cs b/test/coverlet.core.tests/Reporters/JsonReporterTests.cs index 2bfc2ca43..910b891a0 100644 --- a/test/coverlet.core.tests/Reporters/JsonReporterTests.cs +++ b/test/coverlet.core.tests/Reporters/JsonReporterTests.cs @@ -1,3 +1,5 @@ +using Coverlet.Core.Abstractions; +using Moq; using System; using Xunit; @@ -30,8 +32,8 @@ public void TestReport() result.Modules.Add("module", documents); JsonReporter reporter = new JsonReporter(); - Assert.NotEqual("{\n}", reporter.Report(result)); - Assert.NotEqual(string.Empty, reporter.Report(result)); + Assert.NotEqual("{\n}", reporter.Report(result, new Mock().Object)); + Assert.NotEqual(string.Empty, reporter.Report(result, new Mock().Object)); } } } \ No newline at end of file diff --git a/test/coverlet.core.tests/Reporters/LcovReporterTests.cs b/test/coverlet.core.tests/Reporters/LcovReporterTests.cs index f5c888bf1..9df9f33ca 100644 --- a/test/coverlet.core.tests/Reporters/LcovReporterTests.cs +++ b/test/coverlet.core.tests/Reporters/LcovReporterTests.cs @@ -1,3 +1,5 @@ +using Coverlet.Core.Abstractions; +using Moq; using System; using System.Collections.Generic; using Xunit; @@ -10,6 +12,7 @@ public class LcovReporterTests public void TestReport() { CoverageResult result = new CoverageResult(); + result.Parameters = new CoverageParameters(); result.Identifier = Guid.NewGuid().ToString(); Lines lines = new Lines(); @@ -35,7 +38,7 @@ public void TestReport() result.Modules.Add("module", documents); LcovReporter reporter = new LcovReporter(); - string report = reporter.Report(result); + string report = reporter.Report(result, new Mock().Object); Assert.NotEmpty(report); Assert.Equal("SF:doc.cs", report.Split(Environment.NewLine)[0]); diff --git a/test/coverlet.core.tests/Reporters/OpenCoverReporterTests.cs b/test/coverlet.core.tests/Reporters/OpenCoverReporterTests.cs index f614d72b4..75698b63b 100644 --- a/test/coverlet.core.tests/Reporters/OpenCoverReporterTests.cs +++ b/test/coverlet.core.tests/Reporters/OpenCoverReporterTests.cs @@ -1,3 +1,5 @@ +using Coverlet.Core.Abstractions; +using Moq; using System; using System.IO; using System.Linq; @@ -13,13 +15,14 @@ public class OpenCoverReporterTests public void TestReport() { CoverageResult result = new CoverageResult(); + result.Parameters = new CoverageParameters(); result.Identifier = Guid.NewGuid().ToString(); result.Modules = new Modules(); result.Modules.Add("Coverlet.Core.Reporters.Tests", CreateFirstDocuments()); OpenCoverReporter reporter = new OpenCoverReporter(); - string report = reporter.Report(result); + string report = reporter.Report(result, new Mock().Object); Assert.NotEmpty(report); XDocument doc = XDocument.Load(new MemoryStream(Encoding.UTF8.GetBytes(report))); Assert.Empty(doc.Descendants().Attributes("sequenceCoverage").Where(v => v.Value != "33.33")); @@ -30,6 +33,7 @@ public void TestReport() public void TestFilesHaveUniqueIdsOverMultipleModules() { CoverageResult result = new CoverageResult(); + result.Parameters = new CoverageParameters(); result.Identifier = Guid.NewGuid().ToString(); result.Modules = new Modules(); @@ -37,7 +41,7 @@ public void TestFilesHaveUniqueIdsOverMultipleModules() result.Modules.Add("Some.Other.Module", CreateSecondDocuments()); OpenCoverReporter reporter = new OpenCoverReporter(); - var xml = reporter.Report(result); + var xml = reporter.Report(result, new Mock().Object); Assert.NotEqual(string.Empty, xml); Assert.Contains(@"", xml); @@ -50,20 +54,21 @@ public void TestLineBranchCoverage() var result = new CoverageResult { Identifier = Guid.NewGuid().ToString(), - Modules = new Modules { {"Coverlet.Core.Reporters.Tests", CreateBranchCoverageDocuments()} } + Modules = new Modules { { "Coverlet.Core.Reporters.Tests", CreateBranchCoverageDocuments() } }, + Parameters = new CoverageParameters() }; - - var xml = new OpenCoverReporter().Report(result); - + + var xml = new OpenCoverReporter().Report(result, new Mock().Object); + // Line 1: Two branches, no coverage (bec = 2, bev = 0) Assert.Contains(@"", xml); - + // Line 2: Two branches, one covered (bec = 2, bev = 1) Assert.Contains(@"", xml); - + // Line 3: Two branches, all covered (bec = 2, bev = 2) Assert.Contains(@"", xml); - + // Line 4: Three branches, two covered (bec = 3, bev = 2) Assert.Contains(@"", xml); } @@ -120,8 +125,8 @@ private static Documents CreateBranchCoverageDocuments() { var lines = new Lines { - {1, 1}, - {2, 1}, + {1, 1}, + {2, 1}, {3, 1}, {4, 1}, }; @@ -145,7 +150,7 @@ private static Documents CreateBranchCoverageDocuments() new BranchInfo {Line = 4, Hits = 2, Offset = 40, EndOffset = 44, Path = 1, Ordinal = 4}, new BranchInfo {Line = 4, Hits = 0, Offset = 40, EndOffset = 44, Path = 1, Ordinal = 4} }; - + const string methodString = "System.Void Coverlet.Core.Reporters.Tests.OpenCoverReporterTests.TestReport()"; var methods = new Methods { diff --git a/test/coverlet.core.tests/Reporters/Reporters.cs b/test/coverlet.core.tests/Reporters/Reporters.cs index 1c516aeb0..5c65f0770 100644 --- a/test/coverlet.core.tests/Reporters/Reporters.cs +++ b/test/coverlet.core.tests/Reporters/Reporters.cs @@ -40,7 +40,7 @@ public void Msbuild_ReportWriter(string coverletMultiTargetFrameworksCurrentTFM, new ReporterFactory(reportFormat).CreateReporter(), fileSystem.Object, console.Object, - new CoverageResult() { Modules = new Modules() }); + new CoverageResult() { Modules = new Modules(), Parameters = new CoverageParameters() }, null); var path = reportWriter.WriteReport(); // Path.Combine depends on OS so we can change only win side to avoid duplication diff --git a/test/coverlet.core.tests/Reporters/TeamCityReporter.cs b/test/coverlet.core.tests/Reporters/TeamCityReporter.cs index 8328dd90e..7ff12a0b1 100644 --- a/test/coverlet.core.tests/Reporters/TeamCityReporter.cs +++ b/test/coverlet.core.tests/Reporters/TeamCityReporter.cs @@ -1,5 +1,7 @@ using System; -using Coverlet.Core.Reporters; + +using Coverlet.Core.Abstractions; +using Moq; using Xunit; namespace Coverlet.Core.Reporters.Tests @@ -13,6 +15,7 @@ public TestCreateReporterTests() { _reporter = new TeamCityReporter(); _result = new CoverageResult(); + _result.Parameters = new CoverageParameters(); _result.Identifier = Guid.NewGuid().ToString(); var lines = new Lines { { 1, 1 }, { 2, 0 } }; @@ -86,7 +89,7 @@ public void Format_IsNull() public void Report_ReturnsNonNullString() { // Act - var output = _reporter.Report(_result); + var output = _reporter.Report(_result, new Mock().Object); // Assert Assert.False(string.IsNullOrWhiteSpace(output), "Output is not null or whitespace"); @@ -96,7 +99,7 @@ public void Report_ReturnsNonNullString() public void Report_ReportsLineCoverage() { // Act - var output = _reporter.Report(_result); + var output = _reporter.Report(_result, new Mock().Object); // Assert Assert.Contains("##teamcity[buildStatisticValue key='CodeCoverageAbsLCovered' value='1']", output); @@ -107,7 +110,7 @@ public void Report_ReportsLineCoverage() public void Report_ReportsBranchCoverage() { // Act - var output = _reporter.Report(_result); + var output = _reporter.Report(_result, new Mock().Object); // Assert Assert.Contains("##teamcity[buildStatisticValue key='CodeCoverageAbsBCovered' value='1']", output); @@ -118,7 +121,7 @@ public void Report_ReportsBranchCoverage() public void Report_ReportsMethodCoverage() { // Act - var output = _reporter.Report(_result); + var output = _reporter.Report(_result, new Mock().Object); // Assert Assert.Contains("##teamcity[buildStatisticValue key='CodeCoverageAbsMCovered' value='1']", output); diff --git a/test/coverlet.integration.tests/BaseTest.cs b/test/coverlet.integration.tests/BaseTest.cs index a4acd8bc2..be15680dd 100644 --- a/test/coverlet.integration.tests/BaseTest.cs +++ b/test/coverlet.integration.tests/BaseTest.cs @@ -80,7 +80,7 @@ private protected ClonedTemplateProject CloneTemplateProject(bool cleanupOnDispo private protected bool RunCommand(string command, string arguments, out string standardOutput, out string standardError, string workingDirectory = "") { - Debug.WriteLine($"BaseTest.RunCommand: {command} {arguments}"); + Debug.WriteLine($"BaseTest.RunCommand: {command} {arguments}\nWorkingDirectory: {workingDirectory}"); ProcessStartInfo psi = new ProcessStartInfo(command, arguments); psi.WorkingDirectory = workingDirectory; psi.RedirectStandardError = true; @@ -200,7 +200,7 @@ private protected void AddCoverletCollectosRef(string projectPath) xml.Save(csproj); } - private protected string AddCollectorRunsettingsFile(string projectPath, string includeFilter = "[coverletsamplelib.integration.template]*DeepThought", bool sourceLink = false) + private protected string AddCollectorRunsettingsFile(string projectPath, string includeFilter = "[coverletsamplelib.integration.template]*DeepThought", bool sourceLink = false, bool deterministicReport = false) { string runSettings = $@" @@ -210,6 +210,7 @@ private protected string AddCollectorRunsettingsFile(string projectPath, string json,cobertura + {deterministicReport} {includeFilter} {(sourceLink ? "true" : "false")} diff --git a/test/coverlet.integration.tests/DeterministicBuild.cs b/test/coverlet.integration.tests/DeterministicBuild.cs index 66a6b498a..1013e2303 100644 --- a/test/coverlet.integration.tests/DeterministicBuild.cs +++ b/test/coverlet.integration.tests/DeterministicBuild.cs @@ -35,7 +35,7 @@ private void CreateDeterministicTestPropsFile() deterministicTestProps.Save(Path.Combine(_testProjectPath, PropsFileName)); } - private protected void AssertCoverage(string standardOutput = "") + private protected void AssertCoverage(string standardOutput = "", bool checkDeterministicReport = true) { if (_buildConfiguration == "Debug") { @@ -54,6 +54,16 @@ private protected void AssertCoverage(string standardOutput = "") Assert.True(coverageChecked, $"Coverage check fail\n{standardOutput}"); File.Delete(reportFilePath); Assert.False(File.Exists(reportFilePath)); + + if (checkDeterministicReport) + { + // Verify deterministic report + foreach (string coverageFile in Directory.GetFiles(_testProjectPath, "coverage.cobertura.xml", SearchOption.AllDirectories)) + { + Assert.Contains("/_/test/coverlet.integration.determisticbuild/DeepThought.cs", File.ReadAllText(coverageFile)); + File.Delete(coverageFile); + } + } } } @@ -68,7 +78,7 @@ public void Msbuild() Assert.True(!string.IsNullOrEmpty(File.ReadAllText(sourceRootMappingFilePath)), "Empty CoverletSourceRootsMapping file"); Assert.Contains("=/_/", File.ReadAllText(sourceRootMappingFilePath)); - DotnetCli($"test -c {_buildConfiguration} --no-build /p:CollectCoverage=true /p:Include=\"[coverletsample.integration.determisticbuild]*DeepThought\" /p:IncludeTestAssembly=true", out standardOutput, out _, _testProjectPath); + DotnetCli($"test -c {_buildConfiguration} --no-build /p:CollectCoverage=true /p:DeterministicReport=true /p:CoverletOutputFormat=\"cobertura%2cjson\" /p:Include=\"[coverletsample.integration.determisticbuild]*DeepThought\" /p:IncludeTestAssembly=true", out standardOutput, out _, _testProjectPath); Assert.Contains("Passed!", standardOutput); Assert.Contains("| coverletsample.integration.determisticbuild | 100% | 100% | 100% |", standardOutput); Assert.True(File.Exists(Path.Combine(_testProjectPath, "coverage.json"))); @@ -91,12 +101,12 @@ public void Msbuild_SourceLink() Assert.True(!string.IsNullOrEmpty(File.ReadAllText(sourceRootMappingFilePath)), "Empty CoverletSourceRootsMapping file"); Assert.Contains("=/_/", File.ReadAllText(sourceRootMappingFilePath)); - DotnetCli($"test -c {_buildConfiguration} --no-build /p:CollectCoverage=true /p:UseSourceLink=true /p:Include=\"[coverletsample.integration.determisticbuild]*DeepThought\" /p:IncludeTestAssembly=true", out standardOutput, out _, _testProjectPath); + DotnetCli($"test -c {_buildConfiguration} --no-build /p:CollectCoverage=true /p:CoverletOutputFormat=\"cobertura%2cjson\" /p:UseSourceLink=true /p:Include=\"[coverletsample.integration.determisticbuild]*DeepThought\" /p:IncludeTestAssembly=true", out standardOutput, out _, _testProjectPath); Assert.Contains("Passed!", standardOutput); Assert.Contains("| coverletsample.integration.determisticbuild | 100% | 100% | 100% |", standardOutput); Assert.True(File.Exists(Path.Combine(_testProjectPath, "coverage.json"))); Assert.Contains("raw.githubusercontent.com", File.ReadAllText(Path.Combine(_testProjectPath, "coverage.json"))); - AssertCoverage(standardOutput); + AssertCoverage(standardOutput, checkDeterministicReport: false); // Process exits hang on clean seem that process doesn't close, maybe some mbuild node reuse? btw manually tested // DotnetCli("clean", out standardOutput, out standardError, _fixture.TestProjectPath); @@ -115,7 +125,7 @@ public void Collectors() Assert.NotEmpty(File.ReadAllText(sourceRootMappingFilePath)); Assert.Contains("=/_/", File.ReadAllText(sourceRootMappingFilePath)); - string runSettingsPath = AddCollectorRunsettingsFile(_testProjectPath, "[coverletsample.integration.determisticbuild]*DeepThought"); + string runSettingsPath = AddCollectorRunsettingsFile(_testProjectPath, "[coverletsample.integration.determisticbuild]*DeepThought", deterministicReport: true); Assert.True(DotnetCli($"test -c {_buildConfiguration} --no-build \"{_testProjectPath}\" --collect:\"XPlat Code Coverage\" --settings \"{runSettingsPath}\" --diag:{Path.Combine(_testProjectPath, "log.txt")}", out standardOutput, out _), standardOutput); Assert.Contains("Passed!", standardOutput); AssertCoverage(standardOutput); @@ -146,7 +156,7 @@ public void Collectors_SourceLink() string runSettingsPath = AddCollectorRunsettingsFile(_testProjectPath, "[coverletsample.integration.determisticbuild]*DeepThought", sourceLink: true); Assert.True(DotnetCli($"test -c {_buildConfiguration} --no-build \"{_testProjectPath}\" --collect:\"XPlat Code Coverage\" --settings \"{runSettingsPath}\" --diag:{Path.Combine(_testProjectPath, "log.txt")}", out standardOutput, out _), standardOutput); Assert.Contains("Passed!", standardOutput); - AssertCoverage(standardOutput); + AssertCoverage(standardOutput, checkDeterministicReport: false); Assert.Contains("raw.githubusercontent.com", File.ReadAllText(Directory.GetFiles(_testProjectPath, "coverage.cobertura.xml", SearchOption.AllDirectories).Single())); // Check out/in process collectors injection