Skip to content

Commit

Permalink
Adaptions to exclude by attribute feature (#1603)
Browse files Browse the repository at this point in the history
  • Loading branch information
daveMueller authored Feb 15, 2024
1 parent 16eda38 commit 52ddde6
Show file tree
Hide file tree
Showing 7 changed files with 32 additions and 21 deletions.
1 change: 1 addition & 0 deletions Documentation/Changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Fix unable to instrument module for Microsoft.AspNetCore.Mvc.Razor [#1459](https://github.com/coverlet-coverage/coverlet/issues/1459) by https://github.com/lg2de

### Improvements
- Extended exclude by attribute feature to work with fully qualified name [#1589](https://github.com/coverlet-coverage/coverlet/issues/1589)
- Use System.CommandLine instead of McMaster.Extensions.CommandLineUtils [#1474](https://github.com/coverlet-coverage/coverlet/issues/1474) by https://github.com/Bertk
- Fix deadlog in Coverlet.Integration.Tests.BaseTest [#1541](https://github.com/coverlet-coverage/coverlet/pull/1541) by https://github.com/Bertk
- Add coverlet.msbuild.tasks unit tests [#1534](https://github.com/coverlet-coverage/coverlet/pull/1534) by https://github.com/Bertk
Expand Down
5 changes: 4 additions & 1 deletion Documentation/GlobalTool.md
Original file line number Diff line number Diff line change
Expand Up @@ -182,7 +182,10 @@ coverlet <ASSEMBLY> --target <TARGET> --targetargs <TARGETARGS> --threshold 80 -

You can ignore a method or an entire class from code coverage by creating and applying the `ExcludeFromCodeCoverage` attribute present in the `System.Diagnostics.CodeAnalysis` namespace.

You can also ignore additional attributes by using the `ExcludeByAttribute` property (short name or full name supported):
You can also ignore additional attributes by using the `ExcludeByAttribute` property

* Can be specified multiple times
* Use attribute name, attribute full name or fully qualified name of the attribute type (`Obsolete`, `ObsoleteAttribute`, `System.ObsoleteAttribute`)

```bash
coverlet <ASSEMBLY> --target <TARGET> --targetargs <TARGETARGS> --exclude-by-attribute 'Obsolete' --exclude-by-attribute 'GeneratedCode' --exclude-by-attribute 'CompilerGenerated'
Expand Down
5 changes: 4 additions & 1 deletion Documentation/MSBuildIntegration.md
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,10 @@ dotnet test /p:CollectCoverage=true /p:Threshold=80 /p:ThresholdType=line /p:Thr

You can ignore a method, an entire class or assembly from code coverage by creating and applying the `ExcludeFromCodeCoverage` attribute present in the `System.Diagnostics.CodeAnalysis` namespace.

You can also ignore additional attributes by using the `ExcludeByAttribute` property (short name, i.e. the type name without the namespace, supported only):
You can also ignore additional attributes by using the `ExcludeByAttribute` property

* Use single or multiple attributes (separate by comma)
* Use attribute name, attribute full name or fully qualified name of the attribute type (`Obsolete`, `ObsoleteAttribute`, `System.ObsoleteAttribute`)

```bash
dotnet test /p:CollectCoverage=true /p:ExcludeByAttribute="Obsolete,GeneratedCodeAttribute,CompilerGeneratedAttribute"
Expand Down
1 change: 1 addition & 0 deletions Documentation/VSTestIntegration.md
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@ These are a list of options that are supported by coverlet. These can be specifi
|:-------------------------|:----------------------------------------------------------------------------------------------------------------------------------------------------------------|
| Format | Coverage output format. These are either cobertura, json, lcov, opencover or teamcity as well as combinations of these formats. |
| Exclude | Exclude from code coverage analysing using filter expressions. |
| ExcludeByAttribute | Exclude a method, an entire class or assembly from code coverage decorated by an attribute. |
| ExcludeByFile | Ignore specific source files from code coverage. |
| Include | Explicitly set what to include in code coverage analysis using filter expressions. |
| IncludeDirectory | Explicitly set which directories to include in code coverage analysis. |
Expand Down
3 changes: 2 additions & 1 deletion src/coverlet.core/Instrumentation/Instrumenter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -774,7 +774,8 @@ private static void ReplaceExceptionHandlerBoundary(ExceptionHandler handler, ID

private bool IsExcludeAttribute(CustomAttribute customAttribute)
{
return Array.IndexOf(_excludedAttributes, customAttribute.AttributeType.Name) != -1;
return Array.IndexOf(_excludedAttributes, customAttribute.AttributeType.Name) != -1 ||
Array.IndexOf(_excludedAttributes, customAttribute.AttributeType.FullName) != -1;
}

private static MethodBody GetMethodBody(MethodDefinition method)
Expand Down
24 changes: 12 additions & 12 deletions test/coverlet.collector.tests/CoverletSettingsParserTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -46,18 +46,18 @@ public void ParseShouldSelectFirstTestModuleFromTestModulesList()
}

[Theory]
[InlineData("[*]*,[coverlet]*", "[coverlet.*.tests?]*,[coverlet.*.tests.*]*", @"E:\temp,/var/tmp", "module1.cs,module2.cs", "Obsolete,GeneratedCodeAttribute,CompilerGeneratedAttribute", "DoesNotReturnAttribute,ThrowsAttribute")]
[InlineData("[*]*,,[coverlet]*", "[coverlet.*.tests?]*,,[coverlet.*.tests.*]*", @"E:\temp,,/var/tmp", "module1.cs,,module2.cs", "Obsolete,,GeneratedCodeAttribute,,CompilerGeneratedAttribute", "DoesNotReturnAttribute,,ThrowsAttribute")]
[InlineData("[*]*, ,[coverlet]*", "[coverlet.*.tests?]*, ,[coverlet.*.tests.*]*", @"E:\temp, ,/var/tmp", "module1.cs, ,module2.cs", "Obsolete, ,GeneratedCodeAttribute, ,CompilerGeneratedAttribute", "DoesNotReturnAttribute, ,ThrowsAttribute")]
[InlineData("[*]*,\t,[coverlet]*", "[coverlet.*.tests?]*,\t,[coverlet.*.tests.*]*", "E:\\temp,\t,/var/tmp", "module1.cs,\t,module2.cs", "Obsolete,\t,GeneratedCodeAttribute,\t,CompilerGeneratedAttribute", "DoesNotReturnAttribute,\t,ThrowsAttribute")]
[InlineData("[*]*, [coverlet]*", "[coverlet.*.tests?]*, [coverlet.*.tests.*]*", @"E:\temp, /var/tmp", "module1.cs, module2.cs", "Obsolete, GeneratedCodeAttribute, CompilerGeneratedAttribute", "DoesNotReturnAttribute, ThrowsAttribute")]
[InlineData("[*]*,\t[coverlet]*", "[coverlet.*.tests?]*,\t[coverlet.*.tests.*]*", "E:\\temp,\t/var/tmp", "module1.cs,\tmodule2.cs", "Obsolete,\tGeneratedCodeAttribute,\tCompilerGeneratedAttribute", "DoesNotReturnAttribute,\tThrowsAttribute")]
[InlineData("[*]*, \t[coverlet]*", "[coverlet.*.tests?]*, \t[coverlet.*.tests.*]*", "E:\\temp, \t/var/tmp", "module1.cs, \tmodule2.cs", "Obsolete, \tGeneratedCodeAttribute, \tCompilerGeneratedAttribute", "DoesNotReturnAttribute, \tThrowsAttribute")]
[InlineData("[*]*,\r\n[coverlet]*", "[coverlet.*.tests?]*,\r\n[coverlet.*.tests.*]*", "E:\\temp,\r\n/var/tmp", "module1.cs,\r\nmodule2.cs", "Obsolete,\r\nGeneratedCodeAttribute,\r\nCompilerGeneratedAttribute", "DoesNotReturnAttribute,\r\nThrowsAttribute")]
[InlineData("[*]*, \r\n [coverlet]*", "[coverlet.*.tests?]*, \r\n [coverlet.*.tests.*]*", "E:\\temp, \r\n /var/tmp", "module1.cs, \r\n module2.cs", "Obsolete, \r\n GeneratedCodeAttribute, \r\n CompilerGeneratedAttribute", "DoesNotReturnAttribute, \r\n ThrowsAttribute")]
[InlineData("[*]*,\t\r\n\t[coverlet]*", "[coverlet.*.tests?]*,\t\r\n\t[coverlet.*.tests.*]*", "E:\\temp,\t\r\n\t/var/tmp", "module1.cs,\t\r\n\tmodule2.cs", "Obsolete,\t\r\n\tGeneratedCodeAttribute,\t\r\n\tCompilerGeneratedAttribute", "DoesNotReturnAttribute,\t\r\n\tThrowsAttribute")]
[InlineData("[*]*, \t \r\n \t [coverlet]*", "[coverlet.*.tests?]*, \t \r\n \t [coverlet.*.tests.*]*", "E:\\temp, \t \r\n \t /var/tmp", "module1.cs, \t \r\n \t module2.cs", "Obsolete, \t \r\n \t GeneratedCodeAttribute, \t \r\n \t CompilerGeneratedAttribute", "DoesNotReturnAttribute, \t \r\n \t ThrowsAttribute")]
[InlineData(" [*]* , [coverlet]* ", " [coverlet.*.tests?]* , [coverlet.*.tests.*]* ", " E:\\temp , /var/tmp ", " module1.cs , module2.cs ", " Obsolete , GeneratedCodeAttribute , CompilerGeneratedAttribute ", "DoesNotReturnAttribute , ThrowsAttribute")]
[InlineData("[*]*,[coverlet]*", "[coverlet.*.tests?]*,[coverlet.*.tests.*]*", @"E:\temp,/var/tmp", "module1.cs,module2.cs", "Obsolete,GeneratedCodeAttribute,CompilerGeneratedAttribute,System.ObsoleteAttribute", "DoesNotReturnAttribute,ThrowsAttribute")]
[InlineData("[*]*,,[coverlet]*", "[coverlet.*.tests?]*,,[coverlet.*.tests.*]*", @"E:\temp,,/var/tmp", "module1.cs,,module2.cs", "Obsolete,,GeneratedCodeAttribute,,CompilerGeneratedAttribute,,System.ObsoleteAttribute", "DoesNotReturnAttribute,,ThrowsAttribute")]
[InlineData("[*]*, ,[coverlet]*", "[coverlet.*.tests?]*, ,[coverlet.*.tests.*]*", @"E:\temp, ,/var/tmp", "module1.cs, ,module2.cs", "Obsolete, ,GeneratedCodeAttribute, ,CompilerGeneratedAttribute, ,System.ObsoleteAttribute", "DoesNotReturnAttribute, ,ThrowsAttribute")]
[InlineData("[*]*,\t,[coverlet]*", "[coverlet.*.tests?]*,\t,[coverlet.*.tests.*]*", "E:\\temp,\t,/var/tmp", "module1.cs,\t,module2.cs", "Obsolete,\t,GeneratedCodeAttribute,\t,CompilerGeneratedAttribute,\t,System.ObsoleteAttribute", "DoesNotReturnAttribute,\t,ThrowsAttribute")]
[InlineData("[*]*, [coverlet]*", "[coverlet.*.tests?]*, [coverlet.*.tests.*]*", @"E:\temp, /var/tmp", "module1.cs, module2.cs", "Obsolete, GeneratedCodeAttribute, CompilerGeneratedAttribute, System.ObsoleteAttribute", "DoesNotReturnAttribute, ThrowsAttribute")]
[InlineData("[*]*,\t[coverlet]*", "[coverlet.*.tests?]*,\t[coverlet.*.tests.*]*", "E:\\temp,\t/var/tmp", "module1.cs,\tmodule2.cs", "Obsolete,\tGeneratedCodeAttribute,\tCompilerGeneratedAttribute,\tSystem.ObsoleteAttribute", "DoesNotReturnAttribute,\tThrowsAttribute")]
[InlineData("[*]*, \t[coverlet]*", "[coverlet.*.tests?]*, \t[coverlet.*.tests.*]*", "E:\\temp, \t/var/tmp", "module1.cs, \tmodule2.cs", "Obsolete, \tGeneratedCodeAttribute, \tCompilerGeneratedAttribute, \tSystem.ObsoleteAttribute", "DoesNotReturnAttribute, \tThrowsAttribute")]
[InlineData("[*]*,\r\n[coverlet]*", "[coverlet.*.tests?]*,\r\n[coverlet.*.tests.*]*", "E:\\temp,\r\n/var/tmp", "module1.cs,\r\nmodule2.cs", "Obsolete,\r\nGeneratedCodeAttribute,\r\nCompilerGeneratedAttribute,\r\nSystem.ObsoleteAttribute", "DoesNotReturnAttribute,\r\nThrowsAttribute")]
[InlineData("[*]*, \r\n [coverlet]*", "[coverlet.*.tests?]*, \r\n [coverlet.*.tests.*]*", "E:\\temp, \r\n /var/tmp", "module1.cs, \r\n module2.cs", "Obsolete, \r\n GeneratedCodeAttribute, \r\n CompilerGeneratedAttribute, \r\n System.ObsoleteAttribute", "DoesNotReturnAttribute, \r\n ThrowsAttribute")]
[InlineData("[*]*,\t\r\n\t[coverlet]*", "[coverlet.*.tests?]*,\t\r\n\t[coverlet.*.tests.*]*", "E:\\temp,\t\r\n\t/var/tmp", "module1.cs,\t\r\n\tmodule2.cs", "Obsolete,\t\r\n\tGeneratedCodeAttribute,\t\r\n\tCompilerGeneratedAttribute,\t\r\n\tSystem.ObsoleteAttribute", "DoesNotReturnAttribute,\t\r\n\tThrowsAttribute")]
[InlineData("[*]*, \t \r\n \t [coverlet]*", "[coverlet.*.tests?]*, \t \r\n \t [coverlet.*.tests.*]*", "E:\\temp, \t \r\n \t /var/tmp", "module1.cs, \t \r\n \t module2.cs", "Obsolete, \t \r\n \t GeneratedCodeAttribute, \t \r\n \t CompilerGeneratedAttribute, \t \r\n \t System.ObsoleteAttribute", "DoesNotReturnAttribute, \t \r\n \t ThrowsAttribute")]
[InlineData(" [*]* , [coverlet]* ", " [coverlet.*.tests?]* , [coverlet.*.tests.*]* ", " E:\\temp , /var/tmp ", " module1.cs , module2.cs ", " Obsolete , GeneratedCodeAttribute , CompilerGeneratedAttribute , System.ObsoleteAttribute ", "DoesNotReturnAttribute , ThrowsAttribute")]
public void ParseShouldCorrectlyParseConfigurationElement(string includeFilters,
string excludeFilters,
string includeDirectories,
Expand Down
14 changes: 8 additions & 6 deletions test/coverlet.core.tests/Instrumentation/InstrumenterTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,7 @@ public void TestInstrument_ClassesWithExcludeAttributeAreExcluded(Type excludedT

[Theory]
[InlineData(typeof(ClassExcludedByAttrWithoutAttributeNameSuffix), nameof(TestSDKAutoGeneratedCode))]
[InlineData(typeof(ClassExcludedByAttrWithoutAttributeNameSuffix), "Microsoft.VisualStudio.TestPlatform.TestSDKAutoGeneratedCode")]
public void TestInstrument_ClassesWithExcludeAttributeWithoutAttributeNameSuffixAreExcluded(Type excludedType, string excludedAttribute)
{
InstrumenterTest instrumenterTest = CreateInstrumentor(attributesToIgnore: new string[] { excludedAttribute });
Expand All @@ -162,7 +163,7 @@ public void TestInstrument_ClassesWithExcludeAttributeWithoutAttributeNameSuffix
[Theory]
[InlineData(nameof(ObsoleteAttribute))]
[InlineData("Obsolete")]
[InlineData(nameof(TestSDKAutoGeneratedCode))]
[InlineData("System.ObsoleteAttribute")]
public void TestInstrument_ClassesWithCustomExcludeAttributeAreExcluded(string excludedAttribute)
{
InstrumenterTest instrumenterTest = CreateInstrumentor(attributesToIgnore: new string[] { excludedAttribute });
Expand All @@ -171,7 +172,7 @@ public void TestInstrument_ClassesWithCustomExcludeAttributeAreExcluded(string e
Document doc = result.Documents.Values.FirstOrDefault(d => Path.GetFileName(d.Path) == "Samples.cs");
Assert.NotNull(doc);
#pragma warning disable CS0612 // Type or member is obsolete
bool found = doc.Lines.Values.Any(l => l.Class.Equals(nameof(ClassExcludedByObsoleteAttr)));
bool found = doc.Lines.Values.Any(l => l.Class.Equals(typeof(ClassExcludedByObsoleteAttr).FullName));
#pragma warning restore CS0612 // Type or member is obsolete
Assert.False(found, "Class decorated with with exclude attribute should be excluded");

Expand All @@ -181,7 +182,8 @@ public void TestInstrument_ClassesWithCustomExcludeAttributeAreExcluded(string e
[Theory]
[InlineData(nameof(ObsoleteAttribute), "ClassWithMethodExcludedByObsoleteAttr")]
[InlineData("Obsolete", "ClassWithMethodExcludedByObsoleteAttr")]
[InlineData(nameof(TestSDKAutoGeneratedCode), "ClassExcludedByAttrWithoutAttributeNameSuffix")]
[InlineData("System.ObsoleteAttribute", "ClassWithMethodExcludedByObsoleteAttr")]
[InlineData(nameof(TestSDKAutoGeneratedCode), "ClassExcludedByAttrWithoutAttributeNameSuffix")] //extend this with full name
public void TestInstrument_ClassesWithMethodWithCustomExcludeAttributeAreExcluded(string excludedAttribute, string testClassName)
{
InstrumenterTest instrumenterTest = CreateInstrumentor(attributesToIgnore: new string[] { excludedAttribute });
Expand All @@ -196,9 +198,9 @@ public void TestInstrument_ClassesWithMethodWithCustomExcludeAttributeAreExclude
}

[Theory]
[InlineData(nameof(ObsoleteAttribute), "ClassWithMethodExcludedByObsoleteAttr")]
[InlineData("Obsolete", "ClassWithMethodExcludedByObsoleteAttr")]
[InlineData(nameof(TestSDKAutoGeneratedCode), "ClassExcludedByAttrWithoutAttributeNameSuffix")]
[InlineData(nameof(ObsoleteAttribute), "ClassWithPropertyExcludedByObsoleteAttr")]
[InlineData("Obsolete", "ClassWithPropertyExcludedByObsoleteAttr")]
[InlineData("System.ObsoleteAttribute", "ClassWithPropertyExcludedByObsoleteAttr")]
public void TestInstrument_ClassesWithPropertyWithCustomExcludeAttributeAreExcluded(string excludedAttribute, string testClassName)
{
InstrumenterTest instrumenterTest = CreateInstrumentor(attributesToIgnore: new string[] { excludedAttribute });
Expand Down

0 comments on commit 52ddde6

Please sign in to comment.