From 52ddde6e8dbf92e56f0f79af4782d0849324f166 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20M=C3=BCller?= Date: Thu, 15 Feb 2024 13:10:57 +0100 Subject: [PATCH] Adaptions to exclude by attribute feature (#1603) --- Documentation/Changelog.md | 1 + Documentation/GlobalTool.md | 5 +++- Documentation/MSBuildIntegration.md | 5 +++- Documentation/VSTestIntegration.md | 1 + .../Instrumentation/Instrumenter.cs | 3 ++- .../CoverletSettingsParserTests.cs | 24 +++++++++---------- .../Instrumentation/InstrumenterTests.cs | 14 ++++++----- 7 files changed, 32 insertions(+), 21 deletions(-) diff --git a/Documentation/Changelog.md b/Documentation/Changelog.md index 33cb124d4..22edce12a 100644 --- a/Documentation/Changelog.md +++ b/Documentation/Changelog.md @@ -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 diff --git a/Documentation/GlobalTool.md b/Documentation/GlobalTool.md index 4dc47e652..4b3d2ab33 100644 --- a/Documentation/GlobalTool.md +++ b/Documentation/GlobalTool.md @@ -182,7 +182,10 @@ coverlet --target --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 --target --targetargs --exclude-by-attribute 'Obsolete' --exclude-by-attribute 'GeneratedCode' --exclude-by-attribute 'CompilerGenerated' diff --git a/Documentation/MSBuildIntegration.md b/Documentation/MSBuildIntegration.md index f3bd95251..d1b64af16 100644 --- a/Documentation/MSBuildIntegration.md +++ b/Documentation/MSBuildIntegration.md @@ -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" diff --git a/Documentation/VSTestIntegration.md b/Documentation/VSTestIntegration.md index bbf1bef84..d85715c05 100644 --- a/Documentation/VSTestIntegration.md +++ b/Documentation/VSTestIntegration.md @@ -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. | diff --git a/src/coverlet.core/Instrumentation/Instrumenter.cs b/src/coverlet.core/Instrumentation/Instrumenter.cs index 509a992b0..8ef5e2349 100644 --- a/src/coverlet.core/Instrumentation/Instrumenter.cs +++ b/src/coverlet.core/Instrumentation/Instrumenter.cs @@ -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) diff --git a/test/coverlet.collector.tests/CoverletSettingsParserTests.cs b/test/coverlet.collector.tests/CoverletSettingsParserTests.cs index 9578a5407..006c83813 100644 --- a/test/coverlet.collector.tests/CoverletSettingsParserTests.cs +++ b/test/coverlet.collector.tests/CoverletSettingsParserTests.cs @@ -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, diff --git a/test/coverlet.core.tests/Instrumentation/InstrumenterTests.cs b/test/coverlet.core.tests/Instrumentation/InstrumenterTests.cs index 22d373ec1..ed704aa16 100644 --- a/test/coverlet.core.tests/Instrumentation/InstrumenterTests.cs +++ b/test/coverlet.core.tests/Instrumentation/InstrumenterTests.cs @@ -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 }); @@ -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 }); @@ -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"); @@ -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 }); @@ -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 });