Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Bug: Only last filter expression is respected #99

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
6 changes: 6 additions & 0 deletions README.md
Expand Up @@ -101,10 +101,16 @@ Coverlet gives the ability to have fine grained control over what gets excluded

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

Wildcards
- `*` => matches zero or more characters
- `?` => the prefixed character is optional

Examples
- `/p:Exclude="[*]*"` => Excludes all types in all assemblies (nothing is instrumented)
- `/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`)
- `/p:Exclude="[*]Coverlet.Core.Instrumentation.*"` => Excludes all types belonging to `Coverlet.Core.Instrumentation` namespace in any assembly
- `/p:Exclude="[coverlet.*.tests?]*"` => Excludes all types in any assembly starting with `coverlet.` and ending with `.test` or `.tests` (the `?` makes the `s` optional)
- `/p:Exclude="[coverlet.*]*,[*]Coverlet.Core*"` => Excludes assemblies matching `coverlet.*` and excludes all types belonging to the `Coverlet.Core` namespace in any assembly

```bash
dotnet test /p:CollectCoverage=true /p:Exclude="[coverlet.*]Coverlet.Core.Coverage"
Expand Down
24 changes: 16 additions & 8 deletions src/coverlet.core/Helpers/InstrumentationHelper.cs
Expand Up @@ -107,7 +107,7 @@ public static bool IsValidFilterExpression(string filter)
if (filter.EndsWith("]"))
return false;

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

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

bool isMatch = false;
module = Path.GetFileNameWithoutExtension(module);
if (module == null)
return false;

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

if (typePattern != "*")
continue;

modulePattern = WildcardToRegex(modulePattern);

var regex = new Regex(modulePattern);
isMatch = regex.IsMatch(module) && typePattern == "*";

if (regex.IsMatch(module))
return true;
}

return isMatch;
return false;
}

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

bool isMatch = false;
module = Path.GetFileNameWithoutExtension(module);
if (module == null)
return false;

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

isMatch = new Regex(typePattern).IsMatch(type) && new Regex(modulePattern).IsMatch(module);
if (new Regex(typePattern).IsMatch(type) && new Regex(modulePattern).IsMatch(module))
return true;
}

return isMatch;
return false;
}

public static string[] GetExcludedFiles(string[] rules)
Expand Down Expand Up @@ -221,7 +229,7 @@ private static string WildcardToRegex(string pattern)
{
return "^" + Regex.Escape(pattern).
Replace("\\*", ".*").
Replace("\\?", ".") + "$";
Replace("\\?", "?") + "$";
}

private static bool IsAssembly(string filePath)
Expand Down
100 changes: 100 additions & 0 deletions test/coverlet.core.tests/Helpers/InstrumentationHelperTests.cs
Expand Up @@ -141,6 +141,106 @@ public void TestGetExcludedFilesUsingGlobbing()

Assert.Equal(paths.Length, excludedFiles.Count());
}

[Fact]
public void TestIsModuleExcludedWithoutFilter()
{
var result = InstrumentationHelper.IsModuleExcluded("Module.dll", new string[0]);

Assert.False(result);
}

[Theory]
[InlineData("[Module]mismatch")]
[InlineData("[Mismatch]*")]
public void TestIsModuleExcludedWithSingleMismatchFilter(string filter)
{
var result = InstrumentationHelper.IsModuleExcluded("Module.dll", new[] { filter });

Assert.False(result);
}

[Theory]
[MemberData(nameof(ValidModuleFilterData))]
public void TestIsModuleExcludedWithFilter(string filter)
{
var result = InstrumentationHelper.IsModuleExcluded("Module.dll", new[] { filter });

Assert.True(result);
}

[Theory]
[MemberData(nameof(ValidModuleFilterData))]
public void TestIsModuleExcludedWithMatchingAndMismatchingFilter(string filter)
{
var filters = new[] { "[Mismatch]*", filter, "[Mismatch]*" };

var result = InstrumentationHelper.IsModuleExcluded("Module.dll", filters);

Assert.True(result);
}

[Fact]
public void TestIsTypeExcludedWithoutFilter()
{
var result = InstrumentationHelper.IsTypeExcluded("Module.dll", "a.b.Dto", new string[0]);

Assert.False(result);
}

[Theory]
[InlineData("[Module]mismatch")]
[InlineData("[Mismatch]*")]
[InlineData("[Mismatch]a.b.Dto")]
public void TestIsTypeExcludedWithSingleMismatchFilter(string filter)
{
var result = InstrumentationHelper.IsTypeExcluded("Module.dll", "a.b.Dto", new[] { filter });

Assert.False(result);
}

[Theory]
[MemberData(nameof(ValidModuleAndNamespaceFilterData))]
public void TestIsTypeExcludedWithFilter(string filter)
{
var result = InstrumentationHelper.IsTypeExcluded("Module.dll", "a.b.Dto", new[] { filter });

Assert.True(result);
}

[Theory]
[MemberData(nameof(ValidModuleAndNamespaceFilterData))]
public void TestIsTypeExcludedWithMatchingAndMismatchingFilter(string filter)
{
var filters = new[] { "[Mismatch]*", filter, "[Mismatch]*" };

var result = InstrumentationHelper.IsTypeExcluded("Module.dll", "a.b.Dto", filters);

Assert.True(result);
}

public static IEnumerable<object[]> ValidModuleFilterData =>
new List<object[]>
{
new object[] { "[Module]*" },
new object[] { "[Module*]*" },
new object[] { "[Mod*ule]*" },
new object[] { "[M*e]*" },
new object[] { "[Mod*le*]*" },
new object[] { "[Module?]*" },
new object[] { "[ModuleX?]*" },
};

public static IEnumerable<object[]> ValidModuleAndNamespaceFilterData =>
new List<object[]>
{
new object[] { "[Module]a.b.Dto" },
new object[] { "[Module]a.b.Dtos?" },
new object[] { "[Module]a.*" },
new object[] { "[Module]a*" },
new object[] { "[Module]*b.*" },
}
.Concat(ValidModuleFilterData);
}
}

Expand Down