Skip to content

Commit a6046c4

Browse files
authored
Merge pull request #99 from coenm/bugfix/MultipleExcludeFiltersOnlyRespectLastFilterExpression
Bug: Only last filter expression is respected
2 parents 0b42b03 + 9346a42 commit a6046c4

File tree

3 files changed

+122
-8
lines changed

3 files changed

+122
-8
lines changed

README.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,10 +101,16 @@ Coverlet gives the ability to have fine grained control over what gets excluded
101101

102102
Syntax: `/p:Exclude=[Assembly-Filter]Type-Filter`
103103

104+
Wildcards
105+
- `*` => matches zero or more characters
106+
- `?` => the prefixed character is optional
107+
104108
Examples
105109
- `/p:Exclude="[*]*"` => Excludes all types in all assemblies (nothing is instrumented)
106110
- `/p:Exclude="[coverlet.*]Coverlet.Core.Coverage"` => Excludes the Coverage class in the `Coverlet.Core` namespace belonging to any assembly that matches `coverlet.*` (e.g `coverlet.core`)
107111
- `/p:Exclude="[*]Coverlet.Core.Instrumentation.*"` => Excludes all types belonging to `Coverlet.Core.Instrumentation` namespace in any assembly
112+
- `/p:Exclude="[coverlet.*.tests?]*"` => Excludes all types in any assembly starting with `coverlet.` and ending with `.test` or `.tests` (the `?` makes the `s` optional)
113+
- `/p:Exclude="[coverlet.*]*,[*]Coverlet.Core*"` => Excludes assemblies matching `coverlet.*` and excludes all types belonging to the `Coverlet.Core` namespace in any assembly
108114

109115
```bash
110116
dotnet test /p:CollectCoverage=true /p:Exclude="[coverlet.*]Coverlet.Core.Coverage"

src/coverlet.core/Helpers/InstrumentationHelper.cs

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,7 @@ public static bool IsValidFilterExpression(string filter)
107107
if (filter.EndsWith("]"))
108108
return false;
109109

110-
if (new Regex(@"[^\w*]").IsMatch(filter.Replace(".", "").Replace("[", "").Replace("]", "")))
110+
if (new Regex(@"[^\w*]").IsMatch(filter.Replace(".", "").Replace("?", "").Replace("[", "").Replace("]", "")))
111111
return false;
112112

113113
return true;
@@ -118,30 +118,37 @@ public static bool IsModuleExcluded(string module, string[] filters)
118118
if (filters == null)
119119
return false;
120120

121-
bool isMatch = false;
122121
module = Path.GetFileNameWithoutExtension(module);
122+
if (module == null)
123+
return false;
123124

124125
foreach (var filter in filters)
125126
{
126127
string modulePattern = filter.Substring(1, filter.IndexOf(']') - 1);
127128
string typePattern = filter.Substring(filter.IndexOf(']') + 1);
128129

130+
if (typePattern != "*")
131+
continue;
132+
129133
modulePattern = WildcardToRegex(modulePattern);
130134

131135
var regex = new Regex(modulePattern);
132-
isMatch = regex.IsMatch(module) && typePattern == "*";
136+
137+
if (regex.IsMatch(module))
138+
return true;
133139
}
134140

135-
return isMatch;
141+
return false;
136142
}
137143

138144
public static bool IsTypeExcluded(string module, string type, string[] filters)
139145
{
140146
if (filters == null)
141147
return false;
142148

143-
bool isMatch = false;
144149
module = Path.GetFileNameWithoutExtension(module);
150+
if (module == null)
151+
return false;
145152

146153
foreach (var filter in filters)
147154
{
@@ -151,10 +158,11 @@ public static bool IsTypeExcluded(string module, string type, string[] filters)
151158
typePattern = WildcardToRegex(typePattern);
152159
modulePattern = WildcardToRegex(modulePattern);
153160

154-
isMatch = new Regex(typePattern).IsMatch(type) && new Regex(modulePattern).IsMatch(module);
161+
if (new Regex(typePattern).IsMatch(type) && new Regex(modulePattern).IsMatch(module))
162+
return true;
155163
}
156164

157-
return isMatch;
165+
return false;
158166
}
159167

160168
public static string[] GetExcludedFiles(string[] rules)
@@ -221,7 +229,7 @@ private static string WildcardToRegex(string pattern)
221229
{
222230
return "^" + Regex.Escape(pattern).
223231
Replace("\\*", ".*").
224-
Replace("\\?", ".") + "$";
232+
Replace("\\?", "?") + "$";
225233
}
226234

227235
private static bool IsAssembly(string filePath)

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

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,106 @@ public void TestGetExcludedFilesUsingGlobbing()
141141

142142
Assert.Equal(paths.Length, excludedFiles.Count());
143143
}
144+
145+
[Fact]
146+
public void TestIsModuleExcludedWithoutFilter()
147+
{
148+
var result = InstrumentationHelper.IsModuleExcluded("Module.dll", new string[0]);
149+
150+
Assert.False(result);
151+
}
152+
153+
[Theory]
154+
[InlineData("[Module]mismatch")]
155+
[InlineData("[Mismatch]*")]
156+
public void TestIsModuleExcludedWithSingleMismatchFilter(string filter)
157+
{
158+
var result = InstrumentationHelper.IsModuleExcluded("Module.dll", new[] { filter });
159+
160+
Assert.False(result);
161+
}
162+
163+
[Theory]
164+
[MemberData(nameof(ValidModuleFilterData))]
165+
public void TestIsModuleExcludedWithFilter(string filter)
166+
{
167+
var result = InstrumentationHelper.IsModuleExcluded("Module.dll", new[] { filter });
168+
169+
Assert.True(result);
170+
}
171+
172+
[Theory]
173+
[MemberData(nameof(ValidModuleFilterData))]
174+
public void TestIsModuleExcludedWithMatchingAndMismatchingFilter(string filter)
175+
{
176+
var filters = new[] { "[Mismatch]*", filter, "[Mismatch]*" };
177+
178+
var result = InstrumentationHelper.IsModuleExcluded("Module.dll", filters);
179+
180+
Assert.True(result);
181+
}
182+
183+
[Fact]
184+
public void TestIsTypeExcludedWithoutFilter()
185+
{
186+
var result = InstrumentationHelper.IsTypeExcluded("Module.dll", "a.b.Dto", new string[0]);
187+
188+
Assert.False(result);
189+
}
190+
191+
[Theory]
192+
[InlineData("[Module]mismatch")]
193+
[InlineData("[Mismatch]*")]
194+
[InlineData("[Mismatch]a.b.Dto")]
195+
public void TestIsTypeExcludedWithSingleMismatchFilter(string filter)
196+
{
197+
var result = InstrumentationHelper.IsTypeExcluded("Module.dll", "a.b.Dto", new[] { filter });
198+
199+
Assert.False(result);
200+
}
201+
202+
[Theory]
203+
[MemberData(nameof(ValidModuleAndNamespaceFilterData))]
204+
public void TestIsTypeExcludedWithFilter(string filter)
205+
{
206+
var result = InstrumentationHelper.IsTypeExcluded("Module.dll", "a.b.Dto", new[] { filter });
207+
208+
Assert.True(result);
209+
}
210+
211+
[Theory]
212+
[MemberData(nameof(ValidModuleAndNamespaceFilterData))]
213+
public void TestIsTypeExcludedWithMatchingAndMismatchingFilter(string filter)
214+
{
215+
var filters = new[] { "[Mismatch]*", filter, "[Mismatch]*" };
216+
217+
var result = InstrumentationHelper.IsTypeExcluded("Module.dll", "a.b.Dto", filters);
218+
219+
Assert.True(result);
220+
}
221+
222+
public static IEnumerable<object[]> ValidModuleFilterData =>
223+
new List<object[]>
224+
{
225+
new object[] { "[Module]*" },
226+
new object[] { "[Module*]*" },
227+
new object[] { "[Mod*ule]*" },
228+
new object[] { "[M*e]*" },
229+
new object[] { "[Mod*le*]*" },
230+
new object[] { "[Module?]*" },
231+
new object[] { "[ModuleX?]*" },
232+
};
233+
234+
public static IEnumerable<object[]> ValidModuleAndNamespaceFilterData =>
235+
new List<object[]>
236+
{
237+
new object[] { "[Module]a.b.Dto" },
238+
new object[] { "[Module]a.b.Dtos?" },
239+
new object[] { "[Module]a.*" },
240+
new object[] { "[Module]a*" },
241+
new object[] { "[Module]*b.*" },
242+
}
243+
.Concat(ValidModuleFilterData);
144244
}
145245
}
146246

0 commit comments

Comments
 (0)