Skip to content

Commit afe227b

Browse files
Fix exclude by files (#524)
Fix exclude by files
1 parent 75520dd commit afe227b

File tree

6 files changed

+155
-104
lines changed

6 files changed

+155
-104
lines changed

src/coverlet.core/Coverage.cs

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -74,11 +74,9 @@ public Coverage(CoveragePrepareResult prepareResult, ILogger logger)
7474
public CoveragePrepareResult PrepareModules()
7575
{
7676
string[] modules = InstrumentationHelper.GetCoverableModules(_module, _includeDirectories, _includeTestAssembly);
77-
string[] excludes = InstrumentationHelper.GetExcludedFiles(_excludedSourceFiles);
7877

7978
Array.ForEach(_excludeFilters ?? Array.Empty<string>(), filter => _logger.LogVerbose($"Excluded module filter '{filter}'"));
8079
Array.ForEach(_includeFilters ?? Array.Empty<string>(), filter => _logger.LogVerbose($"Included module filter '{filter}'"));
81-
Array.ForEach(excludes ?? Array.Empty<string>(), filter => _logger.LogVerbose($"Excluded source files '{filter}'"));
8280

8381
_excludeFilters = _excludeFilters?.Where(f => InstrumentationHelper.IsValidFilterExpression(f)).ToArray();
8482
_includeFilters = _includeFilters?.Where(f => InstrumentationHelper.IsValidFilterExpression(f)).ToArray();
@@ -92,7 +90,7 @@ public CoveragePrepareResult PrepareModules()
9290
continue;
9391
}
9492

95-
var instrumenter = new Instrumenter(module, _identifier, _excludeFilters, _includeFilters, excludes, _excludeAttributes, _singleHit, _logger);
93+
var instrumenter = new Instrumenter(module, _identifier, _excludeFilters, _includeFilters, _excludedSourceFiles, _excludeAttributes, _singleHit, _logger);
9694
if (instrumenter.CanInstrument())
9795
{
9896
InstrumentationHelper.BackupOriginalModule(module, _identifier);

src/coverlet.core/Helpers/InstrumentationHelper.cs

Lines changed: 0 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -318,46 +318,6 @@ public static bool IsTypeIncluded(string module, string type, string[] includeFi
318318
public static bool IsLocalMethod(string method)
319319
=> new Regex(WildcardToRegex("<*>*__*|*")).IsMatch(method);
320320

321-
public static string[] GetExcludedFiles(string[] excludes)
322-
{
323-
const string RELATIVE_KEY = nameof(RELATIVE_KEY);
324-
string parentDir = Directory.GetCurrentDirectory();
325-
326-
if (excludes == null || !excludes.Any()) return Array.Empty<string>();
327-
328-
var matcherDict = new Dictionary<string, Matcher>() { { RELATIVE_KEY, new Matcher() } };
329-
foreach (var excludeRule in excludes)
330-
{
331-
if (Path.IsPathRooted(excludeRule))
332-
{
333-
var root = Path.GetPathRoot(excludeRule);
334-
if (!matcherDict.ContainsKey(root))
335-
{
336-
matcherDict.Add(root, new Matcher());
337-
}
338-
matcherDict[root].AddInclude(Path.GetFullPath(excludeRule).Substring(root.Length));
339-
}
340-
else
341-
{
342-
matcherDict[RELATIVE_KEY].AddInclude(excludeRule);
343-
}
344-
}
345-
346-
var files = new List<string>();
347-
foreach (var entry in matcherDict)
348-
{
349-
var root = entry.Key;
350-
var matcher = entry.Value;
351-
var directoryInfo = new DirectoryInfo(root.Equals(RELATIVE_KEY) ? parentDir : root);
352-
var fileMatchResult = matcher.Execute(new DirectoryInfoWrapper(directoryInfo));
353-
var currentFiles = fileMatchResult.Files
354-
.Select(f => Path.GetFullPath(Path.Combine(directoryInfo.ToString(), f.Path)));
355-
files.AddRange(currentFiles);
356-
}
357-
358-
return files.Distinct().ToArray();
359-
}
360-
361321
private static bool IsTypeFilterMatch(string module, string type, string[] filters)
362322
{
363323
Debug.Assert(module != null);

src/coverlet.core/Instrumentation/Instrumenter.cs

Lines changed: 50 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
using Coverlet.Core.Helpers;
1010
using Coverlet.Core.Logging;
1111
using Coverlet.Core.Symbols;
12-
12+
using Microsoft.Extensions.FileSystemGlobbing;
1313
using Mono.Cecil;
1414
using Mono.Cecil.Cil;
1515
using Mono.Cecil.Rocks;
@@ -22,7 +22,7 @@ internal class Instrumenter
2222
private readonly string _identifier;
2323
private readonly string[] _excludeFilters;
2424
private readonly string[] _includeFilters;
25-
private readonly string[] _excludedFiles;
25+
private readonly ExcludedFilesHelper _excludedFilesHelper;
2626
private readonly string[] _excludedAttributes;
2727
private readonly bool _singleHit;
2828
private readonly bool _isCoreLibrary;
@@ -36,14 +36,15 @@ internal class Instrumenter
3636
private MethodReference _customTrackerRegisterUnloadEventsMethod;
3737
private MethodReference _customTrackerRecordHitMethod;
3838
private List<string> _asyncMachineStateMethod;
39+
private List<string> _excludedSourceFiles;
3940

4041
public Instrumenter(string module, string identifier, string[] excludeFilters, string[] includeFilters, string[] excludedFiles, string[] excludedAttributes, bool singleHit, ILogger logger)
4142
{
4243
_module = module;
4344
_identifier = identifier;
4445
_excludeFilters = excludeFilters;
4546
_includeFilters = includeFilters;
46-
_excludedFiles = excludedFiles ?? Array.Empty<string>();
47+
_excludedFilesHelper = new ExcludedFilesHelper(excludedFiles, logger);
4748
_excludedAttributes = excludedAttributes;
4849
_singleHit = singleHit;
4950
_isCoreLibrary = Path.GetFileNameWithoutExtension(_module) == "System.Private.CoreLib";
@@ -103,6 +104,14 @@ public InstrumenterResult Instrument()
103104

104105
_result.AsyncMachineStateMethod = _asyncMachineStateMethod == null ? Array.Empty<string>() : _asyncMachineStateMethod.ToArray();
105106

107+
if (_excludedSourceFiles != null)
108+
{
109+
foreach (string sourceFile in _excludedSourceFiles)
110+
{
111+
_logger.LogVerbose($"Excluded source file: '{sourceFile}'");
112+
}
113+
}
114+
106115
return _result;
107116
}
108117

@@ -321,9 +330,12 @@ private void InstrumentType(TypeDefinition type)
321330
private void InstrumentMethod(MethodDefinition method)
322331
{
323332
var sourceFile = method.DebugInformation.SequencePoints.Select(s => s.Document.Url).FirstOrDefault();
324-
if (!string.IsNullOrEmpty(sourceFile) && _excludedFiles.Contains(sourceFile))
333+
if (!string.IsNullOrEmpty(sourceFile) && _excludedFilesHelper.Exclude(sourceFile))
325334
{
326-
_logger.LogVerbose($"Excluded source file: '{sourceFile}'");
335+
if (!(_excludedSourceFiles ??= new List<string>()).Contains(sourceFile))
336+
{
337+
_excludedSourceFiles.Add(sourceFile);
338+
}
327339
return;
328340
}
329341

@@ -562,7 +574,7 @@ private bool IsExcludeAttribute(CustomAttribute customAttribute)
562574
customAttribute.AttributeType.Name.Equals(a.EndsWith("Attribute") ? a : $"{a}Attribute"));
563575
}
564576

565-
private static Mono.Cecil.Cil.MethodBody GetMethodBody(MethodDefinition method)
577+
private static MethodBody GetMethodBody(MethodDefinition method)
566578
{
567579
try
568580
{
@@ -731,4 +743,36 @@ public override AssemblyDefinition Resolve(AssemblyNameReference name)
731743
}
732744
}
733745
}
746+
747+
// Exclude files helper https://docs.microsoft.com/en-us/dotnet/api/microsoft.extensions.filesystemglobbing.matcher?view=aspnetcore-2.2
748+
internal class ExcludedFilesHelper
749+
{
750+
Matcher _matcher;
751+
752+
public ExcludedFilesHelper(string[] excludes, ILogger logger)
753+
{
754+
if (excludes != null && excludes.Length > 0)
755+
{
756+
_matcher = new Matcher();
757+
foreach (var excludeRule in excludes)
758+
{
759+
if (excludeRule is null)
760+
{
761+
continue;
762+
}
763+
logger.LogVerbose($"Excluded source file rule '{excludeRule}'");
764+
_matcher.AddInclude(Path.IsPathRooted(excludeRule) ? excludeRule.Substring(Path.GetPathRoot(excludeRule).Length) : excludeRule);
765+
}
766+
}
767+
}
768+
769+
public bool Exclude(string sourceFile)
770+
{
771+
if (_matcher is null || sourceFile is null)
772+
return false;
773+
774+
// We strip out drive because it doesn't work with globbing
775+
return _matcher.Match(Path.IsPathRooted(sourceFile) ? sourceFile.Substring(Path.GetPathRoot(sourceFile).Length) : sourceFile).HasMatches;
776+
}
777+
}
734778
}

src/coverlet.core/coverlet.core.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
<TargetFramework>netstandard2.0</TargetFramework>
66
<AssemblyVersion>5.1.1</AssemblyVersion>
77
<IsPackable>false</IsPackable>
8+
<LangVersion>preview</LangVersion>
89
</PropertyGroup>
910

1011
<ItemGroup>

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

Lines changed: 0 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -72,61 +72,6 @@ public void TestDeleteHitsFile()
7272
Assert.False(File.Exists(tempFile));
7373
}
7474

75-
public static IEnumerable<object[]> GetExcludedFilesReturnsEmptyArgs =>
76-
new[]
77-
{
78-
new object[]{null},
79-
new object[]{new string[0]},
80-
new object[]{new string[]{ Path.GetRandomFileName() }},
81-
new object[]{new string[]{Path.GetRandomFileName(),
82-
Path.Combine(Directory.GetCurrentDirectory(), Path.GetRandomFileName())}
83-
}
84-
};
85-
86-
[Theory]
87-
[MemberData(nameof(GetExcludedFilesReturnsEmptyArgs))]
88-
public void TestGetExcludedFilesReturnsEmpty(string[] excludedFiles)
89-
{
90-
Assert.False(InstrumentationHelper.GetExcludedFiles(excludedFiles)?.Any());
91-
}
92-
93-
[Fact]
94-
public void TestGetExcludedFilesUsingAbsFile()
95-
{
96-
var file = Path.GetRandomFileName();
97-
File.Create(file).Dispose();
98-
var excludedFiles = InstrumentationHelper.GetExcludedFiles(
99-
new string[] { Path.Combine(Directory.GetCurrentDirectory(), file) }
100-
);
101-
File.Delete(file);
102-
Assert.Single(excludedFiles);
103-
}
104-
105-
[Fact]
106-
public void TestGetExcludedFilesUsingGlobbing()
107-
{
108-
var fileExtension = Path.GetRandomFileName();
109-
var paths = new string[]{
110-
$"{Path.GetRandomFileName()}.{fileExtension}",
111-
$"{Path.GetRandomFileName()}.{fileExtension}"
112-
};
113-
114-
foreach (var path in paths)
115-
{
116-
File.Create(path).Dispose();
117-
}
118-
119-
var excludedFiles = InstrumentationHelper.GetExcludedFiles(
120-
new string[] { $"*.{fileExtension}" });
121-
122-
foreach (var path in paths)
123-
{
124-
File.Delete(path);
125-
}
126-
127-
Assert.Equal(paths.Length, excludedFiles.Count());
128-
}
129-
13075
[Fact]
13176
public void TestIsModuleExcludedWithoutFilter()
13277
{

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

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
using System;
2+
using System.Collections.Generic;
23
using System.IO;
34
using System.Linq;
45
using System.Reflection;
6+
using System.Runtime.InteropServices;
57

68
using Coverlet.Core.Helpers;
79
using Coverlet.Core.Logging;
@@ -238,6 +240,107 @@ public void TestInstrument_NetStandardAwareAssemblyResolver_FromFolder()
238240
Assert.Equal(Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), "netstandard.dll"), Path.GetFullPath(resolved.MainModule.FileName));
239241
}
240242

243+
public static IEnumerable<object[]> TestInstrument_ExcludedFilesHelper_Data()
244+
{
245+
yield return new object[] { new string[]{ @"one.txt" }, new ValueTuple<string, bool, bool>[]
246+
{
247+
(@"one.txt", true, false),
248+
(@"c:\dir\one.txt", false, true),
249+
(@"dir/one.txt", false, false)
250+
}};
251+
yield return new object[] { new string[]{ @"*one.txt" }, new ValueTuple<string, bool, bool>[]
252+
{
253+
(@"one.txt", true , false),
254+
(@"c:\dir\one.txt", false, true),
255+
(@"dir/one.txt", false, false)
256+
}};
257+
yield return new object[] { new string[]{ @"*.txt" }, new ValueTuple<string, bool, bool>[]
258+
{
259+
(@"one.txt", true, false),
260+
(@"c:\dir\one.txt", false, true),
261+
(@"dir/one.txt", false, false)
262+
}};
263+
yield return new object[] { new string[]{ @"*.*" }, new ValueTuple<string, bool, bool>[]
264+
{
265+
(@"one.txt", true, false),
266+
(@"c:\dir\one.txt", false, true),
267+
(@"dir/one.txt", false, false)
268+
}};
269+
yield return new object[] { new string[]{ @"one.*" }, new ValueTuple<string, bool, bool>[]
270+
{
271+
(@"one.txt", true, false),
272+
(@"c:\dir\one.txt", false, true),
273+
(@"dir/one.txt", false, false)
274+
}};
275+
yield return new object[] { new string[]{ @"dir/*.txt" }, new ValueTuple<string, bool, bool>[]
276+
{
277+
(@"one.txt", false, false),
278+
(@"c:\dir\one.txt", true, true),
279+
(@"dir/one.txt", true, false)
280+
}};
281+
yield return new object[] { new string[]{ @"dir\*.txt" }, new ValueTuple<string, bool, bool>[]
282+
{
283+
(@"one.txt", false, false),
284+
(@"c:\dir\one.txt", true, true),
285+
(@"dir/one.txt", true, false)
286+
}};
287+
yield return new object[] { new string[]{ @"**/*" }, new ValueTuple<string, bool, bool>[]
288+
{
289+
(@"one.txt", true, false),
290+
(@"c:\dir\one.txt", true, true),
291+
(@"dir/one.txt", true, false)
292+
}};
293+
yield return new object[] { new string[]{ @"dir/**/*" }, new ValueTuple<string, bool, bool>[]
294+
{
295+
(@"one.txt", false, false),
296+
(@"c:\dir\one.txt", true, true),
297+
(@"dir/one.txt", true, false),
298+
(@"c:\dir\dir2\one.txt", true, true),
299+
(@"dir/dir2/one.txt", true, false)
300+
}};
301+
yield return new object[] { new string[]{ @"one.txt", @"dir\*two.txt" }, new ValueTuple<string, bool, bool>[]
302+
{
303+
(@"one.txt", true, false),
304+
(@"c:\dir\imtwo.txt", true, true),
305+
(@"dir/one.txt", false, false)
306+
}};
307+
308+
// This is a special case test different drive same path
309+
// We strip out drive from path to check for globbing
310+
// BTW I don't know if makes sense add a filter with full path maybe we should forbid
311+
yield return new object[] { new string[]{ @"c:\dir\one.txt" }, new ValueTuple<string, bool, bool>[]
312+
{
313+
(@"c:\dir\one.txt", true, true),
314+
(@"d:\dir\one.txt", true, true) // maybe should be false?
315+
}};
316+
317+
yield return new object[] { new string[]{ null }, new ValueTuple<string, bool, bool>[]
318+
{
319+
(null, false, false),
320+
}};
321+
}
322+
323+
[Theory]
324+
[MemberData(nameof(TestInstrument_ExcludedFilesHelper_Data))]
325+
public void TestInstrument_ExcludedFilesHelper(string[] excludeFilterHelper, ValueTuple<string, bool, bool>[] result)
326+
{
327+
var exludeFilterHelper = new ExcludedFilesHelper(excludeFilterHelper, new Mock<ILogger>().Object);
328+
foreach (ValueTuple<string, bool, bool> checkFile in result)
329+
{
330+
if (checkFile.Item3) // run test only on windows platform
331+
{
332+
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
333+
{
334+
Assert.Equal(checkFile.Item2, exludeFilterHelper.Exclude(checkFile.Item1));
335+
}
336+
}
337+
else
338+
{
339+
Assert.Equal(checkFile.Item2, exludeFilterHelper.Exclude(checkFile.Item1));
340+
}
341+
}
342+
}
343+
241344
[Fact]
242345
public void SkipEmbeddedPpdbWithoutLocalSource()
243346
{

0 commit comments

Comments
 (0)