diff --git a/.gitignore b/.gitignore
index 56a4db2e5..7a005aa76 100644
--- a/.gitignore
+++ b/.gitignore
@@ -298,3 +298,6 @@ __pycache__/
# OSX
.DS_Store
+
+# DeterministicSourcePaths workaround generated file DeterministicBuild.targets
+.AssemblyAttributes
diff --git a/DeterministicBuild.targets b/DeterministicBuild.targets
new file mode 100644
index 000000000..78052937d
--- /dev/null
+++ b/DeterministicBuild.targets
@@ -0,0 +1,23 @@
+
+
+
+
+ $([System.IO.Path]::Combine('$(IntermediateOutputPath)','$(TargetFrameworkMoniker).AssemblyAttributes$(DefaultLanguageSourceExtension)'))
+
+
+
+
+
+
+
+
+
+
+ <_LocalTopLevelSourceRoot Include="@(SourceRoot)" Condition="'%(SourceRoot.NestedRoot)' == ''"/>
+
+
+
diff --git a/Directory.Build.props b/Directory.Build.props
index 686a97357..606e016b0 100644
--- a/Directory.Build.props
+++ b/Directory.Build.props
@@ -15,6 +15,11 @@
+
+ true
+ true
+
+
diff --git a/Directory.Build.targets b/Directory.Build.targets
index 4c3816608..94f23698a 100644
--- a/Directory.Build.targets
+++ b/Directory.Build.targets
@@ -26,4 +26,7 @@
+
+
+
diff --git a/coverlet.sln b/coverlet.sln
index 57c594c15..4ec7ed78e 100644
--- a/coverlet.sln
+++ b/coverlet.sln
@@ -31,6 +31,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
eng\azure-pipelines-nightly.yml = eng\azure-pipelines-nightly.yml
eng\azure-pipelines.yml = eng\azure-pipelines.yml
eng\build.yml = eng\build.yml
+ DeterministicBuild.targets = DeterministicBuild.targets
Directory.Build.props = Directory.Build.props
Directory.Build.targets = Directory.Build.targets
global.json = global.json
@@ -47,6 +48,12 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "coverlet.core.tests.samples
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "coverlet.tests.xunit.extensions", "test\coverlet.tests.xunit.extensions\coverlet.tests.xunit.extensions.csproj", "{F8199E19-FA9A-4559-9101-CAD7028121B4}"
EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Build", "Build", "{9A8B19D4-4A24-4217-AEFE-159B68F029A1}"
+ ProjectSection(SolutionItems) = preProject
+ test\Directory.Build.props = test\Directory.Build.props
+ test\Directory.Build.targets = test\Directory.Build.targets
+ EndProjectSection
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -128,6 +135,7 @@ Global
{F6FE7678-C662-43D3-AC6A-64F6AC5A5935} = {2FEBDE1B-83E3-445B-B9F8-5644B0E0E134}
{5FF404AD-7C0B-465A-A1E9-558CDC642B0C} = {2FEBDE1B-83E3-445B-B9F8-5644B0E0E134}
{F8199E19-FA9A-4559-9101-CAD7028121B4} = {2FEBDE1B-83E3-445B-B9F8-5644B0E0E134}
+ {9A8B19D4-4A24-4217-AEFE-159B68F029A1} = {2FEBDE1B-83E3-445B-B9F8-5644B0E0E134}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {9CA57C02-97B0-4C38-A027-EA61E8741F10}
diff --git a/eng/build.yml b/eng/build.yml
index 5aff4fdb3..077400f31 100644
--- a/eng/build.yml
+++ b/eng/build.yml
@@ -6,7 +6,7 @@ steps:
- task: UseDotNet@2
inputs:
- version: 3.1.200
+ version: 3.1.201
displayName: Install .NET Core SDK
- script: dotnet restore
diff --git a/global.json b/global.json
index a90a85c6d..05d0ae3d2 100644
--- a/global.json
+++ b/global.json
@@ -1,5 +1,5 @@
{
"sdk": {
- "version": "3.1.*"
+ "version": "3.1.201"
}
}
diff --git a/src/coverlet.collector/DataCollection/CoverageManager.cs b/src/coverlet.collector/DataCollection/CoverageManager.cs
index 5dd59e2f6..d5330ddba 100644
--- a/src/coverlet.collector/DataCollection/CoverageManager.cs
+++ b/src/coverlet.collector/DataCollection/CoverageManager.cs
@@ -22,7 +22,8 @@ internal class CoverageManager
public IReporter[] Reporters { get; }
- public CoverageManager(CoverletSettings settings, TestPlatformEqtTrace eqtTrace, TestPlatformLogger logger, ICoverageWrapper coverageWrapper)
+ public CoverageManager(CoverletSettings settings, TestPlatformEqtTrace eqtTrace, TestPlatformLogger logger, ICoverageWrapper coverageWrapper,
+ IInstrumentationHelper instrumentationHelper, IFileSystem fileSystem, ISourceRootTranslator sourceRootTranslator)
: this(settings,
settings.ReportFormats.Select(format =>
{
@@ -38,18 +39,19 @@ public CoverageManager(CoverletSettings settings, TestPlatformEqtTrace eqtTrace,
}
}).Where(r => r != null).ToArray(),
new CoverletLogger(eqtTrace, logger),
- coverageWrapper)
+ coverageWrapper, instrumentationHelper, fileSystem, sourceRootTranslator)
{
}
- public CoverageManager(CoverletSettings settings, IReporter[] reporters, ILogger logger, ICoverageWrapper coverageWrapper)
+ public CoverageManager(CoverletSettings settings, IReporter[] reporters, ILogger logger, ICoverageWrapper coverageWrapper,
+ IInstrumentationHelper instrumentationHelper, IFileSystem fileSystem, ISourceRootTranslator sourceRootTranslator)
{
// Store input vars
Reporters = reporters;
_coverageWrapper = coverageWrapper;
// Coverage object
- _coverage = _coverageWrapper.CreateCoverage(settings, logger);
+ _coverage = _coverageWrapper.CreateCoverage(settings, logger, instrumentationHelper, fileSystem, sourceRootTranslator);
}
///
diff --git a/src/coverlet.collector/DataCollection/CoverageWrapper.cs b/src/coverlet.collector/DataCollection/CoverageWrapper.cs
index 990274af3..27a0fb44a 100644
--- a/src/coverlet.collector/DataCollection/CoverageWrapper.cs
+++ b/src/coverlet.collector/DataCollection/CoverageWrapper.cs
@@ -16,7 +16,7 @@ internal class CoverageWrapper : ICoverageWrapper
/// Coverlet settings
/// Coverlet logger
/// Coverage object
- public Coverage CreateCoverage(CoverletSettings settings, ILogger coverletLogger)
+ public Coverage CreateCoverage(CoverletSettings settings, ILogger coverletLogger, IInstrumentationHelper instrumentationHelper, IFileSystem fileSystem, ISourceRootTranslator sourceRootTranslator)
{
return new Coverage(
settings.TestModule,
@@ -30,8 +30,9 @@ public Coverage CreateCoverage(CoverletSettings settings, ILogger coverletLogger
settings.MergeWith,
settings.UseSourceLink,
coverletLogger,
- DependencyInjection.Current.GetService(),
- DependencyInjection.Current.GetService());
+ instrumentationHelper,
+ fileSystem,
+ sourceRootTranslator);
}
///
diff --git a/src/coverlet.collector/DataCollection/CoverletCoverageCollector.cs b/src/coverlet.collector/DataCollection/CoverletCoverageCollector.cs
index 8d5e27638..75bea49ff 100644
--- a/src/coverlet.collector/DataCollection/CoverletCoverageCollector.cs
+++ b/src/coverlet.collector/DataCollection/CoverletCoverageCollector.cs
@@ -21,24 +21,28 @@ namespace Coverlet.Collector.DataCollection
public class CoverletCoverageCollector : DataCollector
{
private readonly TestPlatformEqtTrace _eqtTrace;
+ private readonly ICoverageWrapper _coverageWrapper;
+ private readonly ICountDownEventFactory _countDownEventFactory;
+ private readonly Func _serviceCollectionFactory;
+
private DataCollectionEvents _events;
private TestPlatformLogger _logger;
private XmlElement _configurationElement;
private DataCollectionSink _dataSink;
private DataCollectionContext _dataCollectionContext;
private CoverageManager _coverageManager;
- private ICoverageWrapper _coverageWrapper;
- private ICountDownEventFactory _countDownEventFactory;
+ private IServiceProvider _serviceProvider;
- public CoverletCoverageCollector() : this(new TestPlatformEqtTrace(), new CoverageWrapper(), new CollectorCountdownEventFactory())
+ public CoverletCoverageCollector() : this(new TestPlatformEqtTrace(), new CoverageWrapper(), new CollectorCountdownEventFactory(), GetDefaultServiceCollection)
{
}
- internal CoverletCoverageCollector(TestPlatformEqtTrace eqtTrace, ICoverageWrapper coverageWrapper, ICountDownEventFactory countDownEventFactory) : base()
+ internal CoverletCoverageCollector(TestPlatformEqtTrace eqtTrace, ICoverageWrapper coverageWrapper, ICountDownEventFactory countDownEventFactory, Func serviceCollectionFactory) : base()
{
_eqtTrace = eqtTrace;
_coverageWrapper = coverageWrapper;
_countDownEventFactory = countDownEventFactory;
+ _serviceCollectionFactory = serviceCollectionFactory;
}
private void AttachDebugger()
@@ -79,7 +83,6 @@ public override void Initialize(
_dataSink = dataSink;
_dataCollectionContext = environmentContext.SessionDataCollectionContext;
_logger = new TestPlatformLogger(logger, _dataCollectionContext);
- DependencyInjection.Set(GetServiceProvider(_eqtTrace, _logger));
// Register events
_events.SessionStart += OnSessionStart;
@@ -125,8 +128,13 @@ private void OnSessionStart(object sender, SessionStartEventArgs sessionStartEve
var coverletSettingsParser = new CoverletSettingsParser(_eqtTrace);
CoverletSettings coverletSettings = coverletSettingsParser.Parse(_configurationElement, testModules);
+ // Build services container
+ _serviceProvider = _serviceCollectionFactory(_eqtTrace, _logger, coverletSettings.TestModule).BuildServiceProvider();
+
// Get coverage and attachment managers
- _coverageManager = new CoverageManager(coverletSettings, _eqtTrace, _logger, _coverageWrapper);
+ _coverageManager = new CoverageManager(coverletSettings, _eqtTrace, _logger, _coverageWrapper,
+ _serviceProvider.GetRequiredService(), _serviceProvider.GetRequiredService(),
+ _serviceProvider.GetRequiredService());
// Instrument modules
_coverageManager.InstrumentModules();
@@ -209,18 +217,18 @@ private static IEnumerable GetPropertyValueWrapper(SessionStartEventArgs
return sessionStartEventArgs.GetPropertyValue>(CoverletConstants.TestSourcesPropertyName);
}
- private static IServiceProvider GetServiceProvider(TestPlatformEqtTrace eqtTrace, TestPlatformLogger logger)
+ private static IServiceCollection GetDefaultServiceCollection(TestPlatformEqtTrace eqtTrace, TestPlatformLogger logger, string testModule)
{
IServiceCollection serviceCollection = new ServiceCollection();
serviceCollection.AddTransient();
serviceCollection.AddTransient();
serviceCollection.AddTransient();
serviceCollection.AddTransient(_ => new CoverletLogger(eqtTrace, logger));
-
// We need to keep singleton/static semantics
serviceCollection.AddSingleton();
-
- return serviceCollection.BuildServiceProvider();
+ // We cache resolutions
+ serviceCollection.AddSingleton(serviceProvider => new SourceRootTranslator(testModule, serviceProvider.GetRequiredService(), serviceProvider.GetRequiredService()));
+ return serviceCollection;
}
}
}
diff --git a/src/coverlet.collector/Utilities/Interfaces/ICoverageWrapper.cs b/src/coverlet.collector/Utilities/Interfaces/ICoverageWrapper.cs
index 59cab7c04..cb3340fe0 100644
--- a/src/coverlet.collector/Utilities/Interfaces/ICoverageWrapper.cs
+++ b/src/coverlet.collector/Utilities/Interfaces/ICoverageWrapper.cs
@@ -15,8 +15,11 @@ internal interface ICoverageWrapper
///
/// Coverlet settings
/// Coverlet logger
+ /// Coverlet instrumentationHelper
+ /// Coverlet fileSystem
+ /// Coverlet sourceRootTranslator
/// Coverage object
- Coverage CreateCoverage(CoverletSettings settings, ILogger logger);
+ Coverage CreateCoverage(CoverletSettings settings, ILogger logger, IInstrumentationHelper instrumentationHelper, IFileSystem fileSystem, ISourceRootTranslator sourceRootTranslator);
///
/// Gets the coverage result from provided coverage object
diff --git a/src/coverlet.console/Program.cs b/src/coverlet.console/Program.cs
index 5670f2900..3855b06aa 100644
--- a/src/coverlet.console/Program.cs
+++ b/src/coverlet.console/Program.cs
@@ -26,14 +26,15 @@ static int Main(string[] args)
serviceCollection.AddTransient();
serviceCollection.AddTransient();
serviceCollection.AddTransient();
-
// We need to keep singleton/static semantics
serviceCollection.AddSingleton();
+ serviceCollection.AddSingleton(serviceProvider => new SourceRootTranslator(serviceProvider.GetRequiredService(), serviceProvider.GetRequiredService()));
DependencyInjection.Set(serviceCollection.BuildServiceProvider());
- var logger = (ConsoleLogger) DependencyInjection.Current.GetService();
+ var logger = (ConsoleLogger)DependencyInjection.Current.GetService();
var fileSystem = DependencyInjection.Current.GetService();
+ var sourceTranslator = DependencyInjection.Current.GetService();
var app = new CommandLineApplication();
app.Name = "coverlet";
@@ -86,7 +87,8 @@ static int Main(string[] args)
useSourceLink.HasValue(),
logger,
DependencyInjection.Current.GetService(),
- fileSystem);
+ fileSystem,
+ sourceTranslator);
coverage.PrepareModules();
Process process = new Process();
diff --git a/src/coverlet.core/Abstracts/IFileSystem.cs b/src/coverlet.core/Abstracts/IFileSystem.cs
index c58b4fc79..6499e44ce 100644
--- a/src/coverlet.core/Abstracts/IFileSystem.cs
+++ b/src/coverlet.core/Abstracts/IFileSystem.cs
@@ -19,5 +19,7 @@ internal interface IFileSystem
Stream NewFileStream(string path, FileMode mode);
Stream NewFileStream(string path, FileMode mode, FileAccess access);
+
+ string[] ReadAllLines(string path);
}
}
diff --git a/src/coverlet.core/Abstracts/ISourceRootTranslator.cs b/src/coverlet.core/Abstracts/ISourceRootTranslator.cs
new file mode 100644
index 000000000..b440acea1
--- /dev/null
+++ b/src/coverlet.core/Abstracts/ISourceRootTranslator.cs
@@ -0,0 +1,7 @@
+namespace Coverlet.Core.Abstracts
+{
+ internal interface ISourceRootTranslator
+ {
+ string ResolveFilePath(string originalFileName);
+ }
+}
diff --git a/src/coverlet.core/Coverage.cs b/src/coverlet.core/Coverage.cs
index 1a2050824..2149b3321 100644
--- a/src/coverlet.core/Coverage.cs
+++ b/src/coverlet.core/Coverage.cs
@@ -26,6 +26,7 @@ internal class Coverage
private ILogger _logger;
private IInstrumentationHelper _instrumentationHelper;
private IFileSystem _fileSystem;
+ private ISourceRootTranslator _sourceRootTranslator;
private List _results;
public string Identifier
@@ -45,7 +46,8 @@ public Coverage(string module,
bool useSourceLink,
ILogger logger,
IInstrumentationHelper instrumentationHelper,
- IFileSystem fileSystem)
+ IFileSystem fileSystem,
+ ISourceRootTranslator sourceRootTranslator)
{
_module = module;
_includeFilters = includeFilters;
@@ -60,6 +62,7 @@ public Coverage(string module,
_logger = logger;
_instrumentationHelper = instrumentationHelper;
_fileSystem = fileSystem;
+ _sourceRootTranslator = sourceRootTranslator;
_identifier = Guid.NewGuid().ToString();
_results = new List();
@@ -97,7 +100,8 @@ public CoveragePrepareResult PrepareModules()
continue;
}
- var instrumenter = new Instrumenter(module, _identifier, _excludeFilters, _includeFilters, _excludedSourceFiles, _excludeAttributes, _singleHit, _logger, _instrumentationHelper, _fileSystem);
+ var instrumenter = new Instrumenter(module, _identifier, _excludeFilters, _includeFilters, _excludedSourceFiles, _excludeAttributes, _singleHit, _logger, _instrumentationHelper, _fileSystem, _sourceRootTranslator);
+
if (instrumenter.CanInstrument())
{
_instrumentationHelper.BackupOriginalModule(module, _identifier);
diff --git a/src/coverlet.core/Helpers/FileSystem.cs b/src/coverlet.core/Helpers/FileSystem.cs
index 7037214a6..45c8ce68a 100644
--- a/src/coverlet.core/Helpers/FileSystem.cs
+++ b/src/coverlet.core/Helpers/FileSystem.cs
@@ -48,5 +48,10 @@ public virtual Stream NewFileStream(string path, FileMode mode, FileAccess acces
{
return new FileStream(path, mode, access);
}
+
+ public string[] ReadAllLines(string path)
+ {
+ return File.ReadAllLines(path);
+ }
}
}
diff --git a/src/coverlet.core/Helpers/InstrumentationHelper.cs b/src/coverlet.core/Helpers/InstrumentationHelper.cs
index 7a586ad6a..20ff8073b 100644
--- a/src/coverlet.core/Helpers/InstrumentationHelper.cs
+++ b/src/coverlet.core/Helpers/InstrumentationHelper.cs
@@ -18,14 +18,16 @@ internal class InstrumentationHelper : IInstrumentationHelper
private readonly ConcurrentDictionary _backupList = new ConcurrentDictionary();
private readonly IRetryHelper _retryHelper;
private readonly IFileSystem _fileSystem;
+ private readonly ISourceRootTranslator _sourceRootTranslator;
private ILogger _logger;
- public InstrumentationHelper(IProcessExitHandler processExitHandler, IRetryHelper retryHelper, IFileSystem fileSystem, ILogger logger)
+ public InstrumentationHelper(IProcessExitHandler processExitHandler, IRetryHelper retryHelper, IFileSystem fileSystem, ILogger logger, ISourceRootTranslator sourceRootTranslator)
{
processExitHandler.Add((s, e) => RestoreOriginalModules());
_retryHelper = retryHelper;
_fileSystem = fileSystem;
_logger = logger;
+ _sourceRootTranslator = sourceRootTranslator;
}
public string[] GetCoverableModules(string module, string[] directories, bool includeTestAssembly)
@@ -83,14 +85,14 @@ public bool HasPdb(string module, out bool embedded)
if (entry.Type == DebugDirectoryEntryType.CodeView)
{
var codeViewData = peReader.ReadCodeViewDebugDirectoryData(entry);
- if (codeViewData.Path == $"{Path.GetFileNameWithoutExtension(module)}.pdb")
+ if (_sourceRootTranslator.ResolveFilePath(codeViewData.Path) == $"{Path.GetFileNameWithoutExtension(module)}.pdb")
{
// PDB is embedded
embedded = true;
return true;
}
- return _fileSystem.Exists(codeViewData.Path);
+ return _fileSystem.Exists(_sourceRootTranslator.ResolveFilePath(codeViewData.Path));
}
}
@@ -114,7 +116,7 @@ public bool EmbeddedPortablePdbHasLocalSource(string module, out string firstNot
foreach (DocumentHandle docHandle in metadataReader.Documents)
{
Document document = metadataReader.GetDocument(docHandle);
- string docName = metadataReader.GetString(document.Name);
+ string docName = _sourceRootTranslator.ResolveFilePath(metadataReader.GetString(document.Name));
// We verify all docs and return false if not all are present in local
// We could have false negative if doc is not a source
@@ -146,7 +148,7 @@ public bool PortablePdbHasLocalSource(string module, out string firstNotFoundDoc
if (entry.Type == DebugDirectoryEntryType.CodeView)
{
var codeViewData = peReader.ReadCodeViewDebugDirectoryData(entry);
- using Stream pdbStream = _fileSystem.OpenRead(codeViewData.Path);
+ using Stream pdbStream = _fileSystem.OpenRead(_sourceRootTranslator.ResolveFilePath(codeViewData.Path));
using MetadataReaderProvider metadataReaderProvider = MetadataReaderProvider.FromPortablePdbStream(pdbStream);
MetadataReader metadataReader = null;
try
@@ -161,7 +163,7 @@ public bool PortablePdbHasLocalSource(string module, out string firstNotFoundDoc
foreach (DocumentHandle docHandle in metadataReader.Documents)
{
Document document = metadataReader.GetDocument(docHandle);
- string docName = metadataReader.GetString(document.Name);
+ string docName = _sourceRootTranslator.ResolveFilePath(metadataReader.GetString(document.Name));
// We verify all docs and return false if not all are present in local
// We could have false negative if doc is not a source
diff --git a/src/coverlet.core/Helpers/SourceRootTranslator.cs b/src/coverlet.core/Helpers/SourceRootTranslator.cs
new file mode 100644
index 000000000..be7604bce
--- /dev/null
+++ b/src/coverlet.core/Helpers/SourceRootTranslator.cs
@@ -0,0 +1,105 @@
+using Coverlet.Core.Abstracts;
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.IO;
+
+namespace Coverlet.Core.Helpers
+{
+ [DebuggerDisplay("ProjectPath = {ProjectPath} OriginalPath = {OriginalPath}")]
+ internal class SourceRootMapping
+ {
+ public string ProjectPath { get; set; }
+ public string OriginalPath { get; set; }
+ }
+
+ internal class SourceRootTranslator : ISourceRootTranslator
+ {
+ private readonly ILogger _logger;
+ private readonly IFileSystem _fileSystem;
+ private readonly Dictionary> _sourceRootMapping;
+ private const string MappingFileName = "CoverletSourceRootsMapping";
+ private Dictionary _resolutionCache;
+
+ public SourceRootTranslator(ILogger logger, IFileSystem fileSystem)
+ {
+ _logger = logger ?? throw new ArgumentNullException(nameof(logger));
+ _fileSystem = fileSystem ?? throw new ArgumentNullException(nameof(fileSystem));
+ _sourceRootMapping = new Dictionary>();
+ }
+
+ public SourceRootTranslator(string moduleTestPath, ILogger logger, IFileSystem fileSystem)
+ {
+ _logger = logger ?? throw new ArgumentNullException(nameof(logger));
+ _fileSystem = fileSystem ?? throw new ArgumentNullException(nameof(fileSystem));
+ if (moduleTestPath is null)
+ {
+ throw new ArgumentNullException(nameof(moduleTestPath));
+ }
+ if (!_fileSystem.Exists(moduleTestPath))
+ {
+ throw new FileNotFoundException("Module test path not found", moduleTestPath);
+ }
+ _sourceRootMapping = LoadSourceRootMapping(Path.GetDirectoryName(moduleTestPath)) ?? new Dictionary>();
+ }
+
+ private Dictionary> LoadSourceRootMapping(string directory)
+ {
+ Dictionary> mapping = new Dictionary>();
+
+ string mappingFilePath = Path.Combine(directory, MappingFileName);
+ if (!_fileSystem.Exists(mappingFilePath))
+ {
+ return mapping;
+ }
+
+ foreach (string mappingRecord in _fileSystem.ReadAllLines(mappingFilePath))
+ {
+ int projecFileSeparatorIndex = mappingRecord.IndexOf('|');
+ int pathMappingSeparatorIndex = mappingRecord.IndexOf('=');
+ if (projecFileSeparatorIndex == -1 || pathMappingSeparatorIndex == -1)
+ {
+ _logger.LogWarning($"Malformed mapping '{mappingRecord}'");
+ continue;
+ }
+ string projectPath = mappingRecord.Substring(0, projecFileSeparatorIndex);
+ string originalPath = mappingRecord.Substring(projecFileSeparatorIndex + 1, pathMappingSeparatorIndex - projecFileSeparatorIndex - 1);
+ string mappedPath = mappingRecord.Substring(pathMappingSeparatorIndex + 1);
+
+ if (!mapping.ContainsKey(mappedPath))
+ {
+ mapping.Add(mappedPath, new List());
+ }
+ mapping[mappedPath].Add(new SourceRootMapping() { OriginalPath = originalPath, ProjectPath = projectPath });
+ }
+
+ return mapping;
+ }
+
+ public string ResolveFilePath(string originalFileName)
+ {
+ if (_resolutionCache != null && _resolutionCache.ContainsKey(originalFileName))
+ {
+ return _resolutionCache[originalFileName];
+ }
+
+ foreach (KeyValuePair> mapping in _sourceRootMapping)
+ {
+ if (originalFileName.StartsWith(mapping.Key))
+ {
+ foreach (SourceRootMapping srm in mapping.Value)
+ {
+ string pathToCheck;
+ if (_fileSystem.Exists(pathToCheck = Path.GetFullPath(originalFileName.Replace(mapping.Key, srm.OriginalPath))))
+ {
+ (_resolutionCache ??= new Dictionary()).Add(originalFileName, pathToCheck);
+ _logger.LogVerbose($"Mapping resolved: '{originalFileName}' -> '{pathToCheck}'");
+ return pathToCheck;
+ }
+ }
+ }
+ }
+ return originalFileName;
+ }
+ }
+}
diff --git a/src/coverlet.core/Instrumentation/Instrumenter.cs b/src/coverlet.core/Instrumentation/Instrumenter.cs
index 4b84edd8b..fa26c75fc 100644
--- a/src/coverlet.core/Instrumentation/Instrumenter.cs
+++ b/src/coverlet.core/Instrumentation/Instrumenter.cs
@@ -29,6 +29,7 @@ internal class Instrumenter
private readonly ILogger _logger;
private readonly IInstrumentationHelper _instrumentationHelper;
private readonly IFileSystem _fileSystem;
+ private readonly ISourceRootTranslator _sourceRootTranslator;
private InstrumenterResult _result;
private FieldDefinition _customTrackerHitsArray;
private FieldDefinition _customTrackerHitsFilePath;
@@ -54,7 +55,8 @@ public Instrumenter(
bool singleHit,
ILogger logger,
IInstrumentationHelper instrumentationHelper,
- IFileSystem fileSystem)
+ IFileSystem fileSystem,
+ ISourceRootTranslator sourceRootTranslator)
{
_module = module;
_identifier = identifier;
@@ -67,6 +69,7 @@ public Instrumenter(
_logger = logger;
_instrumentationHelper = instrumentationHelper;
_fileSystem = fileSystem;
+ _sourceRootTranslator = sourceRootTranslator;
}
public bool CanInstrument()
@@ -411,7 +414,7 @@ private void InstrumentType(TypeDefinition type)
private void InstrumentMethod(MethodDefinition method)
{
- var sourceFile = method.DebugInformation.SequencePoints.Select(s => s.Document.Url).FirstOrDefault();
+ var sourceFile = method.DebugInformation.SequencePoints.Select(s => _sourceRootTranslator.ResolveFilePath(s.Document.Url)).FirstOrDefault();
if (!string.IsNullOrEmpty(sourceFile) && _excludedFilesHelper.Exclude(sourceFile))
{
if (!(_excludedSourceFiles ??= new List()).Contains(sourceFile))
@@ -495,9 +498,9 @@ private void InstrumentIL(MethodDefinition method)
private Instruction AddInstrumentationCode(MethodDefinition method, ILProcessor processor, Instruction instruction, SequencePoint sequencePoint)
{
- if (!_result.Documents.TryGetValue(sequencePoint.Document.Url, out var document))
+ if (!_result.Documents.TryGetValue(_sourceRootTranslator.ResolveFilePath(sequencePoint.Document.Url), out var document))
{
- document = new Document { Path = sequencePoint.Document.Url };
+ document = new Document { Path = _sourceRootTranslator.ResolveFilePath(sequencePoint.Document.Url) };
document.Index = _result.Documents.Count;
_result.Documents.Add(document.Path, document);
}
@@ -515,9 +518,9 @@ private Instruction AddInstrumentationCode(MethodDefinition method, ILProcessor
private Instruction AddInstrumentationCode(MethodDefinition method, ILProcessor processor, Instruction instruction, BranchPoint branchPoint)
{
- if (!_result.Documents.TryGetValue(branchPoint.Document, out var document))
+ if (!_result.Documents.TryGetValue(_sourceRootTranslator.ResolveFilePath(branchPoint.Document), out var document))
{
- document = new Document { Path = branchPoint.Document };
+ document = new Document { Path = _sourceRootTranslator.ResolveFilePath(branchPoint.Document) };
document.Index = _result.Documents.Count;
_result.Documents.Add(document.Path, document);
}
diff --git a/src/coverlet.msbuild.tasks/InstrumentationTask.cs b/src/coverlet.msbuild.tasks/InstrumentationTask.cs
index 3dbfc1b8d..4c6e15a57 100644
--- a/src/coverlet.msbuild.tasks/InstrumentationTask.cs
+++ b/src/coverlet.msbuild.tasks/InstrumentationTask.cs
@@ -122,12 +122,13 @@ public override bool Execute()
WaitForDebuggerIfEnabled();
IServiceCollection serviceCollection = new ServiceCollection();
- serviceCollection.AddSingleton();
serviceCollection.AddTransient();
serviceCollection.AddTransient();
serviceCollection.AddTransient();
- serviceCollection.AddTransient(x => _logger);
-
+ serviceCollection.AddTransient(_ => _logger);
+ serviceCollection.AddTransient();
+ // We cache resolutions
+ serviceCollection.AddSingleton(serviceProvider => new SourceRootTranslator(_path, serviceProvider.GetRequiredService(), serviceProvider.GetRequiredService()));
// We need to keep singleton/static semantics
serviceCollection.AddSingleton();
@@ -154,7 +155,8 @@ public override bool Execute()
_useSourceLink,
_logger,
DependencyInjection.Current.GetService(),
- fileSystem);
+ fileSystem,
+ DependencyInjection.Current.GetService());
CoveragePrepareResult prepareResult = coverage.PrepareModules();
InstrumenterState = new TaskItem(System.IO.Path.GetTempFileName());
diff --git a/src/coverlet.msbuild.tasks/coverlet.msbuild.targets b/src/coverlet.msbuild.tasks/coverlet.msbuild.targets
index eecec4ee4..99d0eebab 100644
--- a/src/coverlet.msbuild.tasks/coverlet.msbuild.targets
+++ b/src/coverlet.msbuild.tasks/coverlet.msbuild.targets
@@ -3,6 +3,28 @@
+
+
+
+
+
+ <_byProject Include="@(_LocalTopLevelSourceRoot->'%(MSBuildSourceProjectFile)')" OriginalPath="%(Identity)" />
+ <_mapping Include="@(_byProject->'%(Identity)|%(OriginalPath)=%(MappedPath)')" />
+
+
+ <_sourceRootMappingFilePath>$(OutputPath)CoverletSourceRootsMapping
+
+
+
+
+
+
+
();
_mockCountDownEventFactory = new Mock();
+ _mockCountDownEventFactory.Setup(def => def.Create(It.IsAny(), It.IsAny())).Returns(new Mock().Object);
}
[Fact]
public void OnSessionStartShouldInitializeCoverageWithCorrectCoverletSettings()
{
- _coverletCoverageDataCollector = new CoverletCoverageCollector(new TestPlatformEqtTrace(), _mockCoverageWrapper.Object, _mockCountDownEventFactory.Object);
+ Func serviceCollectionFactory = (TestPlatformEqtTrace eqtTrace, TestPlatformLogger logger, string testModule) =>
+ {
+ IServiceCollection serviceCollection = new ServiceCollection();
+ Mock fileSystem = new Mock();
+ fileSystem.Setup(f => f.Exists(It.IsAny())).Returns((string testLib) => testLib == "abc.dll");
+ serviceCollection.AddTransient(_ => fileSystem.Object);
+
+ serviceCollection.AddTransient();
+ serviceCollection.AddTransient();
+ serviceCollection.AddTransient(_ => new CoverletLogger(eqtTrace, logger));
+ serviceCollection.AddSingleton();
+ serviceCollection.AddSingleton(serviceProvider => new SourceRootTranslator(testModule, serviceProvider.GetRequiredService(), serviceProvider.GetRequiredService()));
+ return serviceCollection;
+ };
+ _coverletCoverageDataCollector = new CoverletCoverageCollector(new TestPlatformEqtTrace(), _mockCoverageWrapper.Object, _mockCountDownEventFactory.Object, serviceCollectionFactory);
_coverletCoverageDataCollector.Initialize(
_configurationElement,
_mockDataColectionEvents.Object,
@@ -60,13 +76,27 @@ public void OnSessionStartShouldInitializeCoverageWithCorrectCoverletSettings()
_mockDataColectionEvents.Raise(x => x.SessionStart += null, new SessionStartEventArgs(sessionStartProperties));
- _mockCoverageWrapper.Verify(x => x.CreateCoverage(It.Is(y => string.Equals(y.TestModule, "abc.dll")), It.IsAny()), Times.Once);
+ _mockCoverageWrapper.Verify(x => x.CreateCoverage(It.Is(y => string.Equals(y.TestModule, "abc.dll")), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Once);
}
[Fact]
public void OnSessionStartShouldPrepareModulesForCoverage()
{
- _coverletCoverageDataCollector = new CoverletCoverageCollector(new TestPlatformEqtTrace(), _mockCoverageWrapper.Object, _mockCountDownEventFactory.Object);
+ Func serviceCollectionFactory = (TestPlatformEqtTrace eqtTrace, TestPlatformLogger logger, string testModule) =>
+ {
+ IServiceCollection serviceCollection = new ServiceCollection();
+ Mock fileSystem = new Mock();
+ fileSystem.Setup(f => f.Exists(It.IsAny())).Returns((string testLib) => testLib == "abc.dll");
+ serviceCollection.AddTransient(_ => fileSystem.Object);
+
+ serviceCollection.AddTransient();
+ serviceCollection.AddTransient();
+ serviceCollection.AddTransient(_ => new CoverletLogger(eqtTrace, logger));
+ serviceCollection.AddSingleton();
+ serviceCollection.AddSingleton(serviceProvider => new SourceRootTranslator(testModule, serviceProvider.GetRequiredService(), serviceProvider.GetRequiredService()));
+ return serviceCollection;
+ };
+ _coverletCoverageDataCollector = new CoverletCoverageCollector(new TestPlatformEqtTrace(), _mockCoverageWrapper.Object, _mockCountDownEventFactory.Object, serviceCollectionFactory);
_coverletCoverageDataCollector.Initialize(
_configurationElement,
_mockDataColectionEvents.Object,
@@ -74,21 +104,39 @@ public void OnSessionStartShouldPrepareModulesForCoverage()
null,
_context);
IDictionary sessionStartProperties = new Dictionary();
- Coverage coverage = new Coverage("abc.dll", null, null, null, null, null, true, true, "abc.json", true, It.IsAny(), DependencyInjection.Current.GetService(), DependencyInjection.Current.GetService());
+ IInstrumentationHelper instrumentationHelper =
+ new InstrumentationHelper(new Mock().Object,
+ new Mock().Object,
+ new Mock().Object,
+ new Mock().Object,
+ new Mock().Object);
+
+ Coverage coverage = new Coverage("abc.dll", null, null, null, null, null, true, true, "abc.json", true, It.IsAny(), instrumentationHelper, new Mock().Object, new Mock().Object);
sessionStartProperties.Add("TestSources", new List { "abc.dll" });
- _mockCoverageWrapper.Setup(x => x.CreateCoverage(It.IsAny(), It.IsAny())).Returns(coverage);
+ _mockCoverageWrapper.Setup(x => x.CreateCoverage(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())).Returns(coverage);
_mockDataColectionEvents.Raise(x => x.SessionStart += null, new SessionStartEventArgs(sessionStartProperties));
- _mockCoverageWrapper.Verify(x => x.CreateCoverage(It.Is(y => y.TestModule.Contains("abc.dll")), It.IsAny()), Times.Once);
+ _mockCoverageWrapper.Verify(x => x.CreateCoverage(It.Is(y => y.TestModule.Contains("abc.dll")), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Once);
_mockCoverageWrapper.Verify(x => x.PrepareModules(It.IsAny()), Times.Once);
}
[Fact]
public void OnSessionEndShouldSendGetCoverageReportToTestPlatform()
{
- _coverletCoverageDataCollector = new CoverletCoverageCollector(new TestPlatformEqtTrace(), new CoverageWrapper(), _mockCountDownEventFactory.Object);
+ Func serviceCollectionFactory = (TestPlatformEqtTrace eqtTrace, TestPlatformLogger logger, string testModule) =>
+ {
+ IServiceCollection serviceCollection = new ServiceCollection();
+ serviceCollection.AddTransient();
+ serviceCollection.AddTransient();
+ serviceCollection.AddTransient();
+ serviceCollection.AddTransient(_ => new CoverletLogger(eqtTrace, logger));
+ serviceCollection.AddSingleton();
+ serviceCollection.AddSingleton(serviceProvider => new SourceRootTranslator(testModule, serviceProvider.GetRequiredService(), serviceProvider.GetRequiredService()));
+ return serviceCollection;
+ };
+ _coverletCoverageDataCollector = new CoverletCoverageCollector(new TestPlatformEqtTrace(), new CoverageWrapper(), _mockCountDownEventFactory.Object, serviceCollectionFactory);
_coverletCoverageDataCollector.Initialize(
_configurationElement,
_mockDataColectionEvents.Object,
@@ -121,7 +169,21 @@ public void OnSessionEndShouldSendGetCoverageReportToTestPlatform()
[InlineData("json,cobertura,lcov", 3)]
public void OnSessionEndShouldSendCoverageReportsForMultipleFormatsToTestPlatform(string formats, int sendReportsCount)
{
- _coverletCoverageDataCollector = new CoverletCoverageCollector(new TestPlatformEqtTrace(), new CoverageWrapper(), _mockCountDownEventFactory.Object);
+ Func serviceCollectionFactory = (TestPlatformEqtTrace eqtTrace, TestPlatformLogger logger, string testModule) =>
+ {
+ IServiceCollection serviceCollection = new ServiceCollection();
+ Mock fileSystem = new Mock();
+ fileSystem.Setup(f => f.Exists(It.IsAny())).Returns((string testLib) => testLib == "Test");
+ serviceCollection.AddTransient(_ => fileSystem.Object);
+
+ serviceCollection.AddTransient();
+ serviceCollection.AddTransient();
+ serviceCollection.AddTransient(_ => new CoverletLogger(eqtTrace, logger));
+ serviceCollection.AddSingleton();
+ serviceCollection.AddSingleton(serviceProvider => new SourceRootTranslator(testModule, serviceProvider.GetRequiredService(), serviceProvider.GetRequiredService()));
+ return serviceCollection;
+ };
+ _coverletCoverageDataCollector = new CoverletCoverageCollector(new TestPlatformEqtTrace(), new CoverageWrapper(), _mockCountDownEventFactory.Object, serviceCollectionFactory);
IList reporters = formats.Split(',').Select(f => new ReporterFactory(f).CreateReporter()).Where(x => x != null).ToList();
Mock mockDataCollectionSink = new Mock();
@@ -159,7 +221,21 @@ public void OnSessionEndShouldSendCoverageReportsForMultipleFormatsToTestPlatfor
[Fact]
public void OnSessionStartShouldLogWarningIfInstrumentationFailed()
{
- _coverletCoverageDataCollector = new CoverletCoverageCollector(new TestPlatformEqtTrace(), _mockCoverageWrapper.Object, _mockCountDownEventFactory.Object);
+ Func serviceCollectionFactory = (TestPlatformEqtTrace eqtTrace, TestPlatformLogger logger, string testModule) =>
+ {
+ IServiceCollection serviceCollection = new ServiceCollection();
+ Mock fileSystem = new Mock();
+ fileSystem.Setup(f => f.Exists(It.IsAny())).Returns((string testLib) => testLib == "abc.dll");
+ serviceCollection.AddTransient(_ => fileSystem.Object);
+
+ serviceCollection.AddTransient();
+ serviceCollection.AddTransient();
+ serviceCollection.AddTransient(_ => new CoverletLogger(eqtTrace, logger));
+ serviceCollection.AddSingleton();
+ serviceCollection.AddSingleton(serviceProvider => new SourceRootTranslator(testModule, serviceProvider.GetRequiredService(), serviceProvider.GetRequiredService()));
+ return serviceCollection;
+ };
+ _coverletCoverageDataCollector = new CoverletCoverageCollector(new TestPlatformEqtTrace(), _mockCoverageWrapper.Object, _mockCountDownEventFactory.Object, serviceCollectionFactory);
_coverletCoverageDataCollector.Initialize(
_configurationElement,
_mockDataColectionEvents.Object,
diff --git a/test/coverlet.core.tests/Coverage/CoverageTests.ExcludeFromCoverageAttribute.cs b/test/coverlet.core.tests/Coverage/CoverageTests.ExcludeFromCoverageAttribute.cs
index 19e1d0ca4..c4c48876f 100644
--- a/test/coverlet.core.tests/Coverage/CoverageTests.ExcludeFromCoverageAttribute.cs
+++ b/test/coverlet.core.tests/Coverage/CoverageTests.ExcludeFromCoverageAttribute.cs
@@ -27,8 +27,15 @@ public void TestCoverageSkipModule__AssemblyMarkedAsExcludeFromCodeCoverage()
var loggerMock = new Mock();
string excludedbyattributeDll = Directory.GetFiles(Path.Combine(Directory.GetCurrentDirectory(), "TestAssets"), "coverlet.tests.projectsample.excludedbyattribute.dll").First();
+
+ InstrumentationHelper instrumentationHelper =
+ new InstrumentationHelper(new ProcessExitHandler(), new RetryHelper(), new FileSystem(), new Mock().Object,
+ new SourceRootTranslator(excludedbyattributeDll, new Mock().Object, new FileSystem()));
+
// test skip module include test assembly feature
- var coverage = new Coverage(excludedbyattributeDll, new string[] { "[coverlet.tests.projectsample.excludedbyattribute*]*" }, Array.Empty(), Array.Empty(), Array.Empty(), Array.Empty(), true, false, string.Empty, false, loggerMock.Object, _instrumentationHelper, partialMockFileSystem.Object);
+ var coverage = new Coverage(excludedbyattributeDll, new string[] { "[coverlet.tests.projectsample.excludedbyattribute*]*" }, Array.Empty(), Array.Empty(),
+ Array.Empty(), Array.Empty(), true, false, string.Empty, false, loggerMock.Object, instrumentationHelper, partialMockFileSystem.Object,
+ new SourceRootTranslator(loggerMock.Object, new FileSystem()));
CoveragePrepareResult result = coverage.PrepareModules();
Assert.Empty(result.Results);
loggerMock.Verify(l => l.LogVerbose(It.IsAny()));
diff --git a/test/coverlet.core.tests/Coverage/CoverageTests.cs b/test/coverlet.core.tests/Coverage/CoverageTests.cs
index 63b765a8b..05ab418bd 100644
--- a/test/coverlet.core.tests/Coverage/CoverageTests.cs
+++ b/test/coverlet.core.tests/Coverage/CoverageTests.cs
@@ -10,7 +10,6 @@ namespace Coverlet.Core.Tests
{
public partial class CoverageTests
{
- private readonly InstrumentationHelper _instrumentationHelper = new InstrumentationHelper(new ProcessExitHandler(), new RetryHelper(), new FileSystem(), new Mock().Object);
private readonly Mock _mockLogger = new Mock();
[Fact]
@@ -25,8 +24,12 @@ public void TestCoverage()
File.Copy(pdb, Path.Combine(directory.FullName, Path.GetFileName(pdb)), true);
// TODO: Find a way to mimick hits
+ InstrumentationHelper instrumentationHelper =
+ new InstrumentationHelper(new ProcessExitHandler(), new RetryHelper(), new FileSystem(), new Mock().Object,
+ new SourceRootTranslator(module, new Mock().Object, new FileSystem()));
- var coverage = new Coverage(Path.Combine(directory.FullName, Path.GetFileName(module)), Array.Empty(), Array.Empty(), Array.Empty(), Array.Empty(), Array.Empty(), false, false, string.Empty, false, _mockLogger.Object, _instrumentationHelper, new FileSystem());
+ var coverage = new Coverage(Path.Combine(directory.FullName, Path.GetFileName(module)), Array.Empty(), Array.Empty(), Array.Empty(), Array.Empty(),
+ Array.Empty(), false, false, string.Empty, false, _mockLogger.Object, instrumentationHelper, new FileSystem(), new SourceRootTranslator(_mockLogger.Object, new FileSystem()));
coverage.PrepareModules();
var result = coverage.GetCoverageResult();
@@ -47,7 +50,13 @@ public void TestCoverageWithTestAssembly()
File.Copy(module, Path.Combine(directory.FullName, Path.GetFileName(module)), true);
File.Copy(pdb, Path.Combine(directory.FullName, Path.GetFileName(pdb)), true);
- var coverage = new Coverage(Path.Combine(directory.FullName, Path.GetFileName(module)), Array.Empty(), Array.Empty(), Array.Empty(), Array.Empty(), Array.Empty(), true, false, string.Empty, false, _mockLogger.Object, _instrumentationHelper, new FileSystem());
+ InstrumentationHelper instrumentationHelper =
+ new InstrumentationHelper(new ProcessExitHandler(), new RetryHelper(), new FileSystem(), new Mock().Object,
+ new SourceRootTranslator(module, new Mock().Object, new FileSystem()));
+
+ var coverage = new Coverage(Path.Combine(directory.FullName, Path.GetFileName(module)), Array.Empty(), Array.Empty(), Array.Empty(), Array.Empty(),
+ Array.Empty(), true, false, string.Empty, false, _mockLogger.Object, instrumentationHelper, new FileSystem(),
+ new SourceRootTranslator(module, _mockLogger.Object, new FileSystem()));
coverage.PrepareModules();
var result = coverage.GetCoverageResult();
diff --git a/test/coverlet.core.tests/Coverage/InstrumenterHelper.cs b/test/coverlet.core.tests/Coverage/InstrumenterHelper.cs
index 579e6caf3..d29b4bf17 100644
--- a/test/coverlet.core.tests/Coverage/InstrumenterHelper.cs
+++ b/test/coverlet.core.tests/Coverage/InstrumenterHelper.cs
@@ -74,8 +74,6 @@ async public static Task Run(Func callM
throw new ArgumentNullException(nameof(persistPrepareResultToFile));
}
- SetTestContainer(disableRestoreModules);
-
// Rename test file to avoid locks
string location = typeof(T).Assembly.Location;
string fileName = Path.ChangeExtension($"testgen_{Path.GetFileNameWithoutExtension(Path.GetRandomFileName())}", ".dll");
@@ -85,6 +83,8 @@ async public static Task Run(Func callM
File.Copy(location, newPath);
File.Copy(Path.ChangeExtension(location, ".pdb"), Path.ChangeExtension(newPath, ".pdb"));
+ SetTestContainer(newPath, disableRestoreModules);
+
static string[] defaultFilters(string _) => Array.Empty();
// Instrument module
Coverage coverage = new Coverage(newPath,
@@ -98,7 +98,8 @@ async public static Task Run(Func callM
{
"[xunit.*]*",
"[coverlet.*]*"
- }).ToArray(), Array.Empty(), Array.Empty(), true, false, "", false, new Logger(logFile), _processWideContainer.GetService(), _processWideContainer.GetService());
+ }).ToArray(), Array.Empty(), Array.Empty(), true, false, "", false, new Logger(logFile),
+ _processWideContainer.GetService(), _processWideContainer.GetService(), _processWideContainer.GetService());
CoveragePrepareResult prepareResult = coverage.PrepareModules();
Assert.Single(prepareResult.Results);
@@ -128,7 +129,7 @@ async public static Task Run(Func callM
return prepareResult;
}
- private static void SetTestContainer(bool disableRestoreModules = false)
+ private static void SetTestContainer(string testModule = null, bool disableRestoreModules = false)
{
LazyInitializer.EnsureInitialized(ref _processWideContainer, () =>
{
@@ -147,6 +148,10 @@ private static void SetTestContainer(bool disableRestoreModules = false)
{
serviceCollection.AddSingleton();
}
+ serviceCollection.AddSingleton(serviceProvider =>
+ string.IsNullOrEmpty(testModule) ?
+ new SourceRootTranslator(serviceProvider.GetRequiredService(), serviceProvider.GetRequiredService()) :
+ new SourceRootTranslator(testModule, serviceProvider.GetRequiredService(), serviceProvider.GetRequiredService()));
return serviceCollection.BuildServiceProvider();
});
@@ -244,8 +249,8 @@ public void LogWarning(string message)
class InstrumentationHelperForDebugging : InstrumentationHelper
{
- public InstrumentationHelperForDebugging(IProcessExitHandler processExitHandler, IRetryHelper retryHelper, IFileSystem fileSystem, ILogger logger)
- : base(processExitHandler, retryHelper, fileSystem, logger)
+ public InstrumentationHelperForDebugging(IProcessExitHandler processExitHandler, IRetryHelper retryHelper, IFileSystem fileSystem, ILogger logger, ISourceRootTranslator sourceTranslator)
+ : base(processExitHandler, retryHelper, fileSystem, logger, sourceTranslator)
{
}
diff --git a/test/coverlet.core.tests/Helpers/InstrumentationHelperTests.cs b/test/coverlet.core.tests/Helpers/InstrumentationHelperTests.cs
index 54bbea1b8..0de6e03a1 100644
--- a/test/coverlet.core.tests/Helpers/InstrumentationHelperTests.cs
+++ b/test/coverlet.core.tests/Helpers/InstrumentationHelperTests.cs
@@ -10,7 +10,8 @@ namespace Coverlet.Core.Helpers.Tests
{
public class InstrumentationHelperTests
{
- private readonly InstrumentationHelper _instrumentationHelper = new InstrumentationHelper(new ProcessExitHandler(), new RetryHelper(), new FileSystem(), new Mock().Object);
+ private readonly InstrumentationHelper _instrumentationHelper =
+ new InstrumentationHelper(new ProcessExitHandler(), new RetryHelper(), new FileSystem(), new Mock().Object, new SourceRootTranslator(typeof(InstrumentationHelperTests).Assembly.Location, new Mock().Object, new FileSystem()));
[Fact]
public void TestGetDependencies()
diff --git a/test/coverlet.core.tests/Helpers/RetryHelperTests.cs b/test/coverlet.core.tests/Helpers/RetryHelperTests.cs
index 2b547623f..93ce3359c 100644
--- a/test/coverlet.core.tests/Helpers/RetryHelperTests.cs
+++ b/test/coverlet.core.tests/Helpers/RetryHelperTests.cs
@@ -1,8 +1,6 @@
using System;
-using System.IO;
using Xunit;
-using Coverlet.Core.Helpers;
namespace Coverlet.Core.Helpers.Tests
{
diff --git a/test/coverlet.core.tests/Helpers/SourceRootTranslatorTests.cs b/test/coverlet.core.tests/Helpers/SourceRootTranslatorTests.cs
new file mode 100644
index 000000000..08e103701
--- /dev/null
+++ b/test/coverlet.core.tests/Helpers/SourceRootTranslatorTests.cs
@@ -0,0 +1,64 @@
+using System.IO;
+
+using Coverlet.Core.Abstracts;
+using Coverlet.Tests.Xunit.Extensions;
+using Moq;
+using Xunit;
+
+namespace Coverlet.Core.Helpers.Tests
+{
+ public class SourceRootTranslatorTests
+ {
+ [ConditionalFact]
+ [SkipOnOS(OS.Linux, "Windows path format only")]
+ [SkipOnOS(OS.MacOS, "Windows path format only")]
+ public void Translate_Success()
+ {
+ string fileToTranslate = "/_/src/coverlet.core/obj/Debug/netstandard2.0/coverlet.core.pdb";
+ Mock logger = new Mock();
+ Mock fileSystem = new Mock();
+ fileSystem.Setup(f => f.Exists(It.IsAny())).Returns((string p) =>
+ {
+ if (p == "testLib.dll" || p == @"C:\git\coverlet\src\coverlet.core\obj\Debug\netstandard2.0\coverlet.core.pdb" || p == "CoverletSourceRootsMapping") return true;
+ return false;
+ });
+ fileSystem.Setup(f => f.ReadAllLines(It.IsAny())).Returns(File.ReadAllLines(@"TestAssets/CoverletSourceRootsMappingTest"));
+ SourceRootTranslator translator = new SourceRootTranslator("testLib.dll", logger.Object, fileSystem.Object);
+ Assert.Equal(@"C:\git\coverlet\src\coverlet.core\obj\Debug\netstandard2.0\coverlet.core.pdb", translator.ResolveFilePath(fileToTranslate));
+ Assert.Equal(@"C:\git\coverlet\src\coverlet.core\obj\Debug\netstandard2.0\coverlet.core.pdb", translator.ResolveFilePath(fileToTranslate));
+ }
+
+ [Fact]
+ public void Translate_EmptyFile()
+ {
+ string fileToTranslate = "/_/src/coverlet.core/obj/Debug/netstandard2.0/coverlet.core.pdb";
+ Mock logger = new Mock();
+ Mock fileSystem = new Mock();
+ fileSystem.Setup(f => f.Exists(It.IsAny())).Returns((string p) =>
+ {
+ if (p == "testLib.dll" || p == "CoverletSourceRootsMapping") return true;
+ return false;
+ });
+ fileSystem.Setup(f => f.ReadAllLines(It.IsAny())).Returns(new string[0]);
+ SourceRootTranslator translator = new SourceRootTranslator("testLib.dll", logger.Object, fileSystem.Object);
+ Assert.Equal(fileToTranslate, translator.ResolveFilePath(fileToTranslate));
+ }
+
+ [Fact]
+ public void Translate_MalformedFile()
+ {
+ string fileToTranslate = "/_/src/coverlet.core/obj/Debug/netstandard2.0/coverlet.core.pdb";
+ Mock logger = new Mock();
+ Mock fileSystem = new Mock();
+ fileSystem.Setup(f => f.Exists(It.IsAny())).Returns((string p) =>
+ {
+ if (p == "testLib.dll" || p == "CoverletSourceRootsMapping") return true;
+ return false;
+ });
+ fileSystem.Setup(f => f.ReadAllLines(It.IsAny())).Returns(new string[1] { "malformedRow" });
+ SourceRootTranslator translator = new SourceRootTranslator("testLib.dll", logger.Object, fileSystem.Object);
+ Assert.Equal(fileToTranslate, translator.ResolveFilePath(fileToTranslate));
+ logger.Verify(l => l.LogWarning(It.IsAny()), Times.Once);
+ }
+ }
+}
diff --git a/test/coverlet.core.tests/Instrumentation/InstrumenterTests.cs b/test/coverlet.core.tests/Instrumentation/InstrumenterTests.cs
index 217fa0651..5e51569b1 100644
--- a/test/coverlet.core.tests/Instrumentation/InstrumenterTests.cs
+++ b/test/coverlet.core.tests/Instrumentation/InstrumenterTests.cs
@@ -21,7 +21,6 @@ namespace Coverlet.Core.Instrumentation.Tests
{
public class InstrumenterTests
{
- private readonly InstrumentationHelper _instrumentationHelper = new InstrumentationHelper(new ProcessExitHandler(), new RetryHelper(), new FileSystem(), new Mock().Object);
private readonly Mock _mockLogger = new Mock();
[ConditionalFact]
@@ -72,9 +71,11 @@ public void TestCoreLibInstrumentation()
}
}
});
-
- InstrumentationHelper instrumentationHelper = new InstrumentationHelper(new ProcessExitHandler(), new RetryHelper(), partialMockFileSystem.Object, _mockLogger.Object);
- Instrumenter instrumenter = new Instrumenter(Path.Combine(directory.FullName, files[0]), "_coverlet_instrumented", Array.Empty(), Array.Empty(), Array.Empty(), Array.Empty(), false, _mockLogger.Object, instrumentationHelper, partialMockFileSystem.Object);
+ var sourceRootTranslator = new SourceRootTranslator(_mockLogger.Object, new FileSystem());
+ 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(), false, _mockLogger.Object, instrumentationHelper, partialMockFileSystem.Object, sourceRootTranslator);
Assert.True(instrumenter.CanInstrument());
InstrumenterResult result = instrumenter.Instrument();
@@ -216,8 +217,12 @@ private InstrumenterTest CreateInstrumentor(bool fakeCoreLibModule = false, stri
File.Copy(module, Path.Combine(directory.FullName, destModule), true);
File.Copy(pdb, Path.Combine(directory.FullName, destPdb), true);
+ InstrumentationHelper instrumentationHelper =
+ 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, false, _mockLogger.Object, _instrumentationHelper, new FileSystem());
+ Instrumenter instrumenter = new Instrumenter(module, identifier, Array.Empty(), Array.Empty(), Array.Empty(), attributesToIgnore, false,
+ _mockLogger.Object, instrumentationHelper, new FileSystem(), new SourceRootTranslator(_mockLogger.Object, new FileSystem()));
return new InstrumenterTest
{
Instrumenter = instrumenter,
@@ -388,16 +393,28 @@ public void SkipEmbeddedPpdbWithoutLocalSource()
{
string xunitDll = Directory.GetFiles(Directory.GetCurrentDirectory(), "xunit.core.dll").First();
var loggerMock = new Mock();
- Instrumenter instrumenter = new Instrumenter(xunitDll, "_xunit_instrumented", Array.Empty(), Array.Empty(), Array.Empty(), Array.Empty