Skip to content

Commit 0e6520f

Browse files
Reworked modules filtering process (#1645)
* Reworked modules filtering process * Added case insitivity * refactoring + coverage * nit * update change log --------- Co-authored-by: David Müller <muellerdavid4@gmail.com>
1 parent 723d341 commit 0e6520f

File tree

5 files changed

+78
-76
lines changed

5 files changed

+78
-76
lines changed

Documentation/Changelog.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77
## Unreleased
88

99
### Fixed
10+
- Fix slow modules filtering process [#1646](https://github.com/coverlet-coverage/coverlet/issues/1646) by https://github.com/BlackGad
1011
- Fix incorrect coverage await using in generic method [#1490](https://github.com/coverlet-coverage/coverlet/issues/1490)
1112

1213
## Release date 2024-03-13

src/coverlet.core/Abstractions/IInstrumentationHelper.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
// Copyright (c) Toni Solarin-Sodara
22
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
33

4+
using System.Collections.Generic;
45
using Coverlet.Core.Enums;
56

67
namespace Coverlet.Core.Abstractions
@@ -11,8 +12,7 @@ internal interface IInstrumentationHelper
1112
void DeleteHitsFile(string path);
1213
string[] GetCoverableModules(string module, string[] directories, bool includeTestAssembly);
1314
bool HasPdb(string module, out bool embedded);
14-
bool IsModuleExcluded(string module, string[] excludeFilters);
15-
bool IsModuleIncluded(string module, string[] includeFilters);
15+
IEnumerable<string> SelectModules(IEnumerable<string> modules, string[] includeFilters, string[] excludeFilters);
1616
bool IsValidFilterExpression(string filter);
1717
bool IsTypeExcluded(string module, string type, string[] excludeFilters);
1818
bool IsTypeIncluded(string module, string type, string[] includeFilters);

src/coverlet.core/Coverage.cs

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -107,15 +107,14 @@ public CoveragePrepareResult PrepareModules()
107107
_parameters.ExcludeFilters = _parameters.ExcludeFilters?.Where(f => _instrumentationHelper.IsValidFilterExpression(f)).ToArray();
108108
_parameters.IncludeFilters = _parameters.IncludeFilters?.Where(f => _instrumentationHelper.IsValidFilterExpression(f)).ToArray();
109109

110-
foreach (string module in modules)
110+
IReadOnlyList<string> validModules = _instrumentationHelper.SelectModules(modules, _parameters.IncludeFilters, _parameters.ExcludeFilters).ToList();
111+
foreach (var excludedModule in modules.Except(validModules))
111112
{
112-
if (_instrumentationHelper.IsModuleExcluded(module, _parameters.ExcludeFilters) ||
113-
!_instrumentationHelper.IsModuleIncluded(module, _parameters.IncludeFilters))
114-
{
115-
_logger.LogVerbose($"Excluded module: '{module}'");
116-
continue;
117-
}
113+
_logger.LogVerbose($"Excluded module: '{excludedModule}'");
114+
}
118115

116+
foreach (string module in validModules)
117+
{
119118
var instrumenter = new Instrumenter(module,
120119
Identifier,
121120
_parameters,

src/coverlet.core/Helpers/InstrumentationHelper.cs

Lines changed: 40 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -339,63 +339,60 @@ public bool IsValidFilterExpression(string filter)
339339
return true;
340340
}
341341

342-
public bool IsModuleExcluded(string module, string[] excludeFilters)
342+
public IEnumerable<string> SelectModules(IEnumerable<string> modules, string[] includeFilters, string[] excludeFilters)
343343
{
344-
if (excludeFilters == null || excludeFilters.Length == 0)
345-
return false;
344+
const char escapeSymbol = '!';
345+
ILookup<string, string> modulesLookup = modules.Where(x => x != null)
346+
.ToLookup(x => $"{escapeSymbol}{Path.GetFileNameWithoutExtension(x)}{escapeSymbol}");
346347

347-
module = Path.GetFileNameWithoutExtension(module);
348-
if (module == null)
349-
return false;
348+
string moduleKeys = string.Join(Environment.NewLine, modulesLookup.Select(x => x.Key));
349+
string includedModuleKeys = GetModuleKeysForIncludeFilters(includeFilters, escapeSymbol, moduleKeys);
350+
string excludedModuleKeys = GetModuleKeysForExcludeFilters(excludeFilters, escapeSymbol, includedModuleKeys);
350351

351-
foreach (string filter in excludeFilters)
352-
{
353-
#pragma warning disable IDE0057 // Use range operator
354-
string typePattern = filter.Substring(filter.IndexOf(']') + 1);
355-
356-
if (typePattern != "*")
357-
continue;
352+
IEnumerable<string> moduleKeysToInclude = includedModuleKeys
353+
.Split(new[] { Environment.NewLine }, StringSplitOptions.RemoveEmptyEntries)
354+
.Except(excludedModuleKeys.Split(new[] { Environment.NewLine }, StringSplitOptions.RemoveEmptyEntries));
358355

359-
string modulePattern = filter.Substring(1, filter.IndexOf(']') - 1);
360-
#pragma warning restore IDE0057 // Use range operator
361-
modulePattern = WildcardToRegex(modulePattern);
362-
363-
var regex = new Regex(modulePattern, s_regexOptions, TimeSpan.FromSeconds(10));
364-
365-
if (regex.IsMatch(module))
366-
return true;
367-
}
368-
369-
return false;
356+
return moduleKeysToInclude.SelectMany(x => modulesLookup[x]);
370357
}
371358

372-
public bool IsModuleIncluded(string module, string[] includeFilters)
359+
private string GetModuleKeysForIncludeFilters(IEnumerable<string> filters, char escapeSymbol, string moduleKeys)
373360
{
374-
if (includeFilters == null || includeFilters.Length == 0)
375-
return true;
361+
string[] validFilters = GetValidFilters(filters);
376362

377-
module = Path.GetFileNameWithoutExtension(module);
378-
if (module == null)
379-
return false;
363+
return !validFilters.Any() ? moduleKeys : GetModuleKeysForValidFilters(escapeSymbol, moduleKeys, validFilters);
364+
}
380365

381-
foreach (string filter in includeFilters)
382-
{
383-
#pragma warning disable IDE0057 // Use range operator
384-
string modulePattern = filter.Substring(1, filter.IndexOf(']') - 1);
385-
#pragma warning restore IDE0057 // Use range operator
366+
private string GetModuleKeysForExcludeFilters(IEnumerable<string> filters, char escapeSymbol, string moduleKeys)
367+
{
368+
string[] validFilters = GetValidFilters(filters);
386369

387-
if (modulePattern == "*")
388-
return true;
370+
return !validFilters.Any() ? string.Empty : GetModuleKeysForValidFilters(escapeSymbol, moduleKeys, validFilters);
371+
}
389372

390-
modulePattern = WildcardToRegex(modulePattern);
373+
private static string GetModuleKeysForValidFilters(char escapeSymbol, string moduleKeys, string[] validFilters)
374+
{
375+
string pattern = CreateRegexPattern(validFilters, escapeSymbol);
376+
IEnumerable<Match> matches = Regex.Matches(moduleKeys, pattern, RegexOptions.IgnoreCase).Cast<Match>();
391377

392-
var regex = new Regex(modulePattern, s_regexOptions, TimeSpan.FromSeconds(10));
378+
return string.Join(
379+
Environment.NewLine,
380+
matches.Where(x => x.Success).Select(x => x.Groups[0].Value));
381+
}
393382

394-
if (regex.IsMatch(module))
395-
return true;
396-
}
383+
private string[] GetValidFilters(IEnumerable<string> filters)
384+
{
385+
return (filters ?? Array.Empty<string>())
386+
.Where(IsValidFilterExpression)
387+
.Where(x => x.EndsWith("*"))
388+
.ToArray();
389+
}
397390

398-
return false;
391+
private static string CreateRegexPattern(IEnumerable<string> filters, char escapeSymbol)
392+
{
393+
IEnumerable<string> regexPatterns = filters.Select(x =>
394+
$"{escapeSymbol}{WildcardToRegex(x.Substring(1, x.IndexOf(']') - 1)).Trim('^', '$')}{escapeSymbol}");
395+
return string.Join("|", regexPatterns);
399396
}
400397

401398
public bool IsTypeExcluded(string module, string type, string[] excludeFilters)

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

Lines changed: 29 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,10 @@ public void TestIsValidFilterExpression()
124124
Assert.False(_instrumentationHelper.IsValidFilterExpression("[-]*"));
125125
Assert.False(_instrumentationHelper.IsValidFilterExpression("*"));
126126
Assert.False(_instrumentationHelper.IsValidFilterExpression("]["));
127+
Assert.False(_instrumentationHelper.IsValidFilterExpression("["));
128+
Assert.False(_instrumentationHelper.IsValidFilterExpression("[assembly][*"));
129+
Assert.False(_instrumentationHelper.IsValidFilterExpression("[assembly]*]"));
130+
Assert.False(_instrumentationHelper.IsValidFilterExpression("[]"));
127131
Assert.False(_instrumentationHelper.IsValidFilterExpression(null));
128132
}
129133

@@ -138,61 +142,54 @@ public void TestDeleteHitsFile()
138142
}
139143

140144
[Fact]
141-
public void TestIsModuleExcludedWithoutFilter()
145+
public void TestSelectModulesWithoutIncludeAndExcludedFilters()
142146
{
143-
bool result = _instrumentationHelper.IsModuleExcluded("Module.dll", new string[0]);
147+
string[] modules = new [] {"Module.dll"};
148+
IEnumerable<string> result = _instrumentationHelper.SelectModules(modules, new string[0], new string[0]);
144149

145-
Assert.False(result);
146-
}
147-
148-
[Fact]
149-
public void TestIsModuleIncludedWithoutFilter()
150-
{
151-
bool result = _instrumentationHelper.IsModuleIncluded("Module.dll", new string[0]);
152-
153-
Assert.True(result);
150+
Assert.Equal(modules, result);
154151
}
155152

156153
[Theory]
157154
[InlineData("[Module]mismatch")]
158155
[InlineData("[Mismatch]*")]
159156
public void TestIsModuleExcludedWithSingleMismatchFilter(string filter)
160157
{
161-
bool result = _instrumentationHelper.IsModuleExcluded("Module.dll", new[] { filter });
158+
string[] modules = new [] {"Module.dll"};
159+
IEnumerable<string> result = _instrumentationHelper.SelectModules(modules, new string[0], new[] {filter});
162160

163-
Assert.False(result);
161+
Assert.Equal(modules, result);
164162
}
165163

166164
[Fact]
167165
public void TestIsModuleIncludedWithSingleMismatchFilter()
168166
{
169-
bool result = _instrumentationHelper.IsModuleIncluded("Module.dll", new[] { "[Mismatch]*" });
167+
string[] modules = new [] {"Module.dll"};
168+
IEnumerable<string> result = _instrumentationHelper.SelectModules(modules, new[] { "[Mismatch]*" }, new string[0]);
170169

171-
Assert.False(result);
170+
Assert.Empty(result);
172171
}
173172

174173
[Theory]
175174
[MemberData(nameof(ValidModuleFilterData))]
176175
public void TestIsModuleExcludedAndIncludedWithFilter(string filter)
177176
{
178-
bool result = _instrumentationHelper.IsModuleExcluded("Module.dll", new[] { filter });
179-
Assert.True(result);
177+
string[] modules = new [] {"Module.dll"};
178+
IEnumerable<string> result = _instrumentationHelper.SelectModules(modules, new[] { filter }, new[] { filter });
180179

181-
result = _instrumentationHelper.IsModuleIncluded("Module.dll", new[] { filter });
182-
Assert.True(result);
180+
Assert.Empty(result);
183181
}
184182

185183
[Theory]
186184
[MemberData(nameof(ValidModuleFilterData))]
187185
public void TestIsModuleExcludedAndIncludedWithMatchingAndMismatchingFilter(string filter)
188186
{
189-
string[] filters = new[] { "[Mismatch]*", filter, "[Mismatch]*" };
187+
string[] modules = new[] {"Module.dll"};
188+
string[] filters = new[] {"[Mismatch]*", filter, "[Mismatch]*"};
190189

191-
bool result = _instrumentationHelper.IsModuleExcluded("Module.dll", filters);
192-
Assert.True(result);
190+
IEnumerable<string> result = _instrumentationHelper.SelectModules(modules, filters, filters);
193191

194-
result = _instrumentationHelper.IsModuleIncluded("Module.dll", filters);
195-
Assert.True(result);
192+
Assert.Empty(result);
196193
}
197194

198195
[Fact]
@@ -305,6 +302,14 @@ public void TestIncludeDirectories()
305302
newDir2.Delete(true);
306303
}
307304

305+
[Theory]
306+
[InlineData("<TestMethod>g__LocalFunction|0_0", true)]
307+
[InlineData("TestMethod", false)]
308+
public void InstrumentationHelper_IsLocalMethod_ReturnsExpectedResult(string method, bool result)
309+
{
310+
Assert.Equal(_instrumentationHelper.IsLocalMethod(method), result);
311+
}
312+
308313
public static IEnumerable<object[]> ValidModuleFilterData =>
309314
new List<object[]>
310315
{

0 commit comments

Comments
 (0)