Skip to content

Commit 19b9611

Browse files
Skip instrumentation for external pdb with no local sources (#538)
Skip instrumentation for external pdb with no local sources
1 parent ce35f26 commit 19b9611

File tree

11 files changed

+111
-18
lines changed

11 files changed

+111
-18
lines changed
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
namespace Coverlet.Core.Abstracts
2+
{
3+
internal interface IFileSystem
4+
{
5+
bool Exists(string path);
6+
}
7+
}

src/coverlet.core/Abstracts/InstrumentationHelper.cs renamed to src/coverlet.core/Abstracts/IInstrumentationHelper.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,8 @@ public interface IInstrumentationHelper
1212
bool IsTypeExcluded(string module, string type, string[] excludeFilters);
1313
bool IsTypeIncluded(string module, string type, string[] includeFilters);
1414
void RestoreOriginalModule(string module, string identifier);
15-
bool EmbeddedPortablePdbHasLocalSource(string module);
15+
bool EmbeddedPortablePdbHasLocalSource(string module, out string firstNotFoundDocument);
16+
bool PortablePdbHasLocalSource(string module, out string firstNotFoundDocument);
1617
bool IsLocalMethod(string method);
1718
}
1819
}

src/coverlet.core/DependencyInjection.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ private static IServiceProvider InitDefaultServices()
2828
IServiceCollection serviceCollection = new ServiceCollection();
2929
serviceCollection.AddTransient<IRetryHelper, RetryHelper>();
3030
serviceCollection.AddTransient<IProcessExitHandler, ProcessExitHandler>();
31+
serviceCollection.AddTransient<IFileSystem, FileSystem>();
3132

3233
// We need to keep singleton/static semantics
3334
serviceCollection.AddSingleton<IInstrumentationHelper, InstrumentationHelper>();
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
using Coverlet.Core.Abstracts;
2+
using System.IO;
3+
4+
namespace Coverlet.Core.Helpers
5+
{
6+
internal class FileSystem : IFileSystem
7+
{
8+
public bool Exists(string path)
9+
{
10+
return File.Exists(path);
11+
}
12+
}
13+
}

src/coverlet.core/Helpers/InstrumentationHelper.cs

Lines changed: 42 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,18 +9,21 @@
99
using System.Reflection.PortableExecutable;
1010
using System.Text.RegularExpressions;
1111
using Coverlet.Core.Abstracts;
12+
using Coverlet.Core.Logging;
1213

1314
namespace Coverlet.Core.Helpers
1415
{
1516
internal class InstrumentationHelper : IInstrumentationHelper
1617
{
1718
private readonly ConcurrentDictionary<string, string> _backupList = new ConcurrentDictionary<string, string>();
1819
private readonly IRetryHelper _retryHelper;
20+
private readonly IFileSystem _fileSystem;
1921

20-
public InstrumentationHelper(IProcessExitHandler processExitHandler, IRetryHelper retryHelper)
22+
public InstrumentationHelper(IProcessExitHandler processExitHandler, IRetryHelper retryHelper, IFileSystem fileSystem)
2123
{
2224
processExitHandler.Add((s, e) => RestoreOriginalModules());
2325
_retryHelper = retryHelper;
26+
_fileSystem = fileSystem;
2427
}
2528

2629
public string[] GetCoverableModules(string module, string[] directories, bool includeTestAssembly)
@@ -93,8 +96,9 @@ public bool HasPdb(string module, out bool embedded)
9396
}
9497
}
9598

96-
public bool EmbeddedPortablePdbHasLocalSource(string module)
99+
public bool EmbeddedPortablePdbHasLocalSource(string module, out string firstNotFoundDocument)
97100
{
101+
firstNotFoundDocument = "";
98102
using (FileStream moduleStream = File.OpenRead(module))
99103
using (var peReader = new PEReader(moduleStream))
100104
{
@@ -115,6 +119,7 @@ public bool EmbeddedPortablePdbHasLocalSource(string module)
115119
// Btw check for all possible extension could be weak approach
116120
if (!File.Exists(docName))
117121
{
122+
firstNotFoundDocument = docName;
118123
return false;
119124
}
120125
}
@@ -128,6 +133,41 @@ public bool EmbeddedPortablePdbHasLocalSource(string module)
128133
return true;
129134
}
130135

136+
public bool PortablePdbHasLocalSource(string module, out string firstNotFoundDocument)
137+
{
138+
firstNotFoundDocument = "";
139+
using (var moduleStream = File.OpenRead(module))
140+
using (var peReader = new PEReader(moduleStream))
141+
{
142+
foreach (var entry in peReader.ReadDebugDirectory())
143+
{
144+
if (entry.Type == DebugDirectoryEntryType.CodeView)
145+
{
146+
var codeViewData = peReader.ReadCodeViewDebugDirectoryData(entry);
147+
using FileStream pdbStream = new FileStream(codeViewData.Path, FileMode.Open);
148+
using MetadataReaderProvider metadataReaderProvider = MetadataReaderProvider.FromPortablePdbStream(pdbStream);
149+
MetadataReader metadataReader = metadataReaderProvider.GetMetadataReader();
150+
foreach (DocumentHandle docHandle in metadataReader.Documents)
151+
{
152+
Document document = metadataReader.GetDocument(docHandle);
153+
string docName = metadataReader.GetString(document.Name);
154+
155+
// We verify all docs and return false if not all are present in local
156+
// We could have false negative if doc is not a source
157+
// Btw check for all possible extension could be weak approach
158+
if (!_fileSystem.Exists(docName))
159+
{
160+
firstNotFoundDocument = docName;
161+
return false;
162+
}
163+
}
164+
}
165+
}
166+
}
167+
168+
return true;
169+
}
170+
131171
public void BackupOriginalModule(string module, string identifier)
132172
{
133173
var backupPath = GetBackupPath(module, identifier);

src/coverlet.core/Instrumentation/Instrumenter.cs

Lines changed: 18 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -40,13 +40,13 @@ internal class Instrumenter
4040
private List<string> _excludedSourceFiles;
4141

4242
public Instrumenter(
43-
string module,
44-
string identifier,
45-
string[] excludeFilters,
46-
string[] includeFilters,
47-
string[] excludedFiles,
48-
string[] excludedAttributes,
49-
bool singleHit,
43+
string module,
44+
string identifier,
45+
string[] excludeFilters,
46+
string[] includeFilters,
47+
string[] excludedFiles,
48+
string[] excludedAttributes,
49+
bool singleHit,
5050
ILogger logger,
5151
IInstrumentationHelper instrumentationHelper)
5252
{
@@ -70,19 +70,27 @@ public bool CanInstrument()
7070
{
7171
if (embeddedPdb)
7272
{
73-
if (_instrumentationHelper.EmbeddedPortablePdbHasLocalSource(_module))
73+
if (_instrumentationHelper.EmbeddedPortablePdbHasLocalSource(_module, out string firstNotFoundDocument))
7474
{
7575
return true;
7676
}
7777
else
7878
{
79-
_logger.LogWarning($"Unable to instrument module: {_module}, embedded pdb without local source files");
79+
_logger.LogWarning($"Unable to instrument module: {_module}, embedded pdb without local source files, [{firstNotFoundDocument}]");
8080
return false;
8181
}
8282
}
8383
else
8484
{
85-
return true;
85+
if (_instrumentationHelper.PortablePdbHasLocalSource(_module, out string firstNotFoundDocument))
86+
{
87+
return true;
88+
}
89+
else
90+
{
91+
_logger.LogWarning($"Unable to instrument module: {_module}, pdb without local source files, [{firstNotFoundDocument}]");
92+
return false;
93+
}
8694
}
8795
}
8896
else

src/coverlet.core/Properties/AssemblyInfo.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,6 @@
44
"2e5bb58a827a3c46c2f959318327ad30d1b52e918321ffbd847fb21565b8576d2a3a24562a93e8" +
55
"6c77a298b564a0f1b98f63d7a1441a3a8bcc206da3ed09d5dacc76e122a109a9d3ac608e21a054" +
66
"d667a2bae98510a1f0f653c0e6f58f42b4b3934f6012f5ec4a09b3dfd3e14d437ede1424bdb722" +
7-
"aead64ad")]
7+
"aead64ad")]
8+
// Needed to mock internal type https://github.com/Moq/moq4/wiki/Quickstart#advanced-features
9+
[assembly: System.Runtime.CompilerServices.InternalsVisibleTo("DynamicProxyGenAssembly2, PublicKey=0024000004800000940000000602000000240000525341310004000001000100c547cac37abd99c8db225ef2f6c8a3602f3b3606cc9891605d02baa56104f4cfc0734aa39b93bf7852f7d9266654753cc297e7d2edfe0bac1cdcf9f717241550e0a7b191195b7667bb4f64bcb8e2121380fd1d9d46ad2d92d2d15605093924cceaf74c4861eff62abf69b9291ed0a340e113be11e6a7d3113e92484cf7045cc7")]

test/coverlet.core.tests/CoverageTests.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ namespace Coverlet.Core.Tests
1414
{
1515
public class CoverageTests
1616
{
17-
private readonly InstrumentationHelper _instrumentationHelper = new InstrumentationHelper(new ProcessExitHandler(), new RetryHelper());
17+
private readonly InstrumentationHelper _instrumentationHelper = new InstrumentationHelper(new ProcessExitHandler(), new RetryHelper(), new FileSystem());
1818
private readonly Mock<ILogger> _mockLogger = new Mock<ILogger>();
1919

2020
[Fact]

test/coverlet.core.tests/Helpers/InstrumentationHelperTests.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ namespace Coverlet.Core.Helpers.Tests
88
{
99
public class InstrumentationHelperTests
1010
{
11-
private readonly InstrumentationHelper _instrumentationHelper = new InstrumentationHelper(new ProcessExitHandler(), new RetryHelper());
11+
private readonly InstrumentationHelper _instrumentationHelper = new InstrumentationHelper(new ProcessExitHandler(), new RetryHelper(), new FileSystem());
1212

1313
[Fact]
1414
public void TestGetDependencies()

test/coverlet.core.tests/Instrumentation/InstrumenterTests.cs

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
using System.Reflection;
66
using System.Runtime.InteropServices;
77

8+
using Coverlet.Core.Abstracts;
89
using Coverlet.Core.Helpers;
910
using Coverlet.Core.Logging;
1011
using Coverlet.Core.Samples.Tests;
@@ -20,7 +21,7 @@ namespace Coverlet.Core.Instrumentation.Tests
2021
{
2122
public class InstrumenterTests
2223
{
23-
private readonly InstrumentationHelper _instrumentationHelper = new InstrumentationHelper(new ProcessExitHandler(), new RetryHelper());
24+
private readonly InstrumentationHelper _instrumentationHelper = new InstrumentationHelper(new ProcessExitHandler(), new RetryHelper(), new FileSystem());
2425
private readonly Mock<ILogger> _mockLogger = new Mock<ILogger>();
2526

2627
[Fact(Skip = "To be used only validating System.Private.CoreLib instrumentation")]
@@ -363,6 +364,25 @@ public void SkipEmbeddedPpdbWithoutLocalSource()
363364
loggerMock.VerifyNoOtherCalls();
364365
}
365366

367+
[Fact]
368+
public void SkipPpdbWithoutLocalSource()
369+
{
370+
Mock<IFileSystem> mockFileSystem = new Mock<IFileSystem>();
371+
mockFileSystem.Setup(fs => fs.Exists(It.IsAny<string>())).Returns((string path) =>
372+
{
373+
return Path.GetExtension(path) != ".cs";
374+
});
375+
InstrumentationHelper instrumentationHelper = new InstrumentationHelper(new ProcessExitHandler(), new RetryHelper(), mockFileSystem.Object);
376+
377+
string coverletLib = Directory.GetFiles(Directory.GetCurrentDirectory(), "coverlet.core.dll").First();
378+
var loggerMock = new Mock<ILogger>();
379+
Instrumenter instrumenter = new Instrumenter(coverletLib, "_corelib_instrumented", Array.Empty<string>(), Array.Empty<string>(), Array.Empty<string>(), Array.Empty<string>(), false, loggerMock.Object, instrumentationHelper);
380+
Assert.True(_instrumentationHelper.HasPdb(coverletLib, out bool embedded));
381+
Assert.False(embedded);
382+
Assert.False(instrumenter.CanInstrument());
383+
loggerMock.Verify(l => l.LogWarning(It.IsAny<string>()));
384+
}
385+
366386
[Fact]
367387
public void TestInstrument_MissingModule()
368388
{

0 commit comments

Comments
 (0)