Skip to content

Fix attribute exclusion #884

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

Merged
merged 4 commits into from
Jun 26, 2020
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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Documentation/MSBuildIntegration.md
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ 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 or full name supported):
You can also ignore additional attributes by using the `ExcludeByAttribute` property (short name, i.e. the type name without the namespace, supported only):

```bash
dotnet test /p:CollectCoverage=true /p:ExcludeByAttribute="Obsolete,GeneratedCodeAttribute,CompilerGeneratedAttribute"
Expand Down
28 changes: 12 additions & 16 deletions src/coverlet.core/Instrumentation/Instrumenter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,17 @@ public Instrumenter(
_excludeFilters = excludeFilters;
_includeFilters = includeFilters;
_excludedFilesHelper = new ExcludedFilesHelper(excludedFiles, logger);
_excludedAttributes = excludedAttributes;
_excludedAttributes = (excludedAttributes ?? Array.Empty<string>())
// In case the attribute class ends in "Attribute", but it wasn't specified.
// Both names are included (if it wasn't specified) because the attribute class might not actually end in the prefix.
.SelectMany(a => a.EndsWith("Attribute") ? new[] { a } : new[] { a, $"{a}Attribute" })
// The default custom attributes used to exclude from coverage.
.Union(new List<string>()
{
nameof(ExcludeFromCoverageAttribute),
nameof(ExcludeFromCodeCoverageAttribute)
})
.ToArray();
_singleHit = singleHit;
_isCoreLibrary = Path.GetFileNameWithoutExtension(_module) == "System.Private.CoreLib";
_logger = logger;
Expand Down Expand Up @@ -680,21 +690,7 @@ private static void ReplaceExceptionHandlerBoundary(ExceptionHandler handler, In

private bool IsExcludeAttribute(CustomAttribute customAttribute)
{
// The default custom attributes used to exclude from coverage.
IEnumerable<string> excludeAttributeNames = new List<string>()
{
nameof(ExcludeFromCoverageAttribute),
nameof(ExcludeFromCodeCoverageAttribute)
};

// Include the other attributes to exclude based on incoming parameters.
if (_excludedAttributes != null)
{
excludeAttributeNames = _excludedAttributes.Union(excludeAttributeNames);
}

return excludeAttributeNames.Any(a =>
customAttribute.AttributeType.Name.Equals(a.EndsWith("Attribute") ? a : $"{a}Attribute"));
return Array.IndexOf(_excludedAttributes, customAttribute.AttributeType.Name) != -1;
}

private static MethodBody GetMethodBody(MethodDefinition method)
Expand Down
38 changes: 29 additions & 9 deletions test/coverlet.core.tests/Instrumentation/InstrumenterTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
using Moq;
using Xunit;
using Microsoft.Extensions.DependencyModel;
using Microsoft.VisualStudio.TestPlatform;

namespace Coverlet.Core.Instrumentation.Tests
{
Expand Down Expand Up @@ -136,9 +137,26 @@ public void TestInstrument_ClassesWithExcludeAttributeAreExcluded(Type excludedT
instrumenterTest.Directory.Delete(true);
}

[Theory]
[InlineData(typeof(ClassExcludedByAttrWithoutAttributeNameSuffix), nameof(TestSDKAutoGeneratedCode))]
public void TestInstrument_ClassesWithExcludeAttributeWithoutAttributeNameSuffixAreExcluded(Type excludedType, string excludedAttribute)
{
var instrumenterTest = CreateInstrumentor(attributesToIgnore: new string[] { excludedAttribute });
var result = instrumenterTest.Instrumenter.Instrument();

var doc = result.Documents.Values.FirstOrDefault(d => Path.GetFileName(d.Path) == "Samples.cs");
Assert.NotNull(doc);

var found = doc.Lines.Values.Any(l => l.Class == excludedType.FullName);
Assert.False(found, "Class decorated with with exclude attribute should be excluded");

instrumenterTest.Directory.Delete(true);
}

[Theory]
[InlineData(nameof(ObsoleteAttribute))]
[InlineData("Obsolete")]
[InlineData(nameof(TestSDKAutoGeneratedCode))]
public void TestInstrument_ClassesWithCustomExcludeAttributeAreExcluded(string excludedAttribute)
{
var instrumenterTest = CreateInstrumentor(attributesToIgnore: new string[] { excludedAttribute });
Expand All @@ -155,40 +173,42 @@ public void TestInstrument_ClassesWithCustomExcludeAttributeAreExcluded(string e
}

[Theory]
[InlineData(nameof(ObsoleteAttribute))]
[InlineData("Obsolete")]
public void TestInstrument_ClassesWithMethodWithCustomExcludeAttributeAreExcluded(string excludedAttribute)
[InlineData(nameof(ObsoleteAttribute), "ClassWithMethodExcludedByObsoleteAttr")]
[InlineData("Obsolete", "ClassWithMethodExcludedByObsoleteAttr")]
[InlineData(nameof(TestSDKAutoGeneratedCode), "ClassExcludedByAttrWithoutAttributeNameSuffix")]
public void TestInstrument_ClassesWithMethodWithCustomExcludeAttributeAreExcluded(string excludedAttribute, string testClassName)
{
var instrumenterTest = CreateInstrumentor(attributesToIgnore: new string[] { excludedAttribute });
var result = instrumenterTest.Instrumenter.Instrument();

var doc = result.Documents.Values.FirstOrDefault(d => Path.GetFileName(d.Path) == "Samples.cs");
Assert.NotNull(doc);
#pragma warning disable CS0612 // Type or member is obsolete
var found = doc.Lines.Values.Any(l => l.Method.Equals("System.String Coverlet.Core.Samples.Tests.ClassWithMethodExcludedByObsoleteAttr::Method(System.String)"));
var found = doc.Lines.Values.Any(l => l.Method.Equals($"System.String Coverlet.Core.Samples.Tests.{testClassName}::Method(System.String)"));
#pragma warning restore CS0612 // Type or member is obsolete
Assert.False(found, "Method decorated with with exclude attribute should be excluded");

instrumenterTest.Directory.Delete(true);
}

[Theory]
[InlineData(nameof(ObsoleteAttribute))]
[InlineData("Obsolete")]
public void TestInstrument_ClassesWithPropertyWithCustomExcludeAttributeAreExcluded(string excludedAttribute)
[InlineData(nameof(ObsoleteAttribute), "ClassWithMethodExcludedByObsoleteAttr")]
[InlineData("Obsolete", "ClassWithMethodExcludedByObsoleteAttr")]
[InlineData(nameof(TestSDKAutoGeneratedCode), "ClassExcludedByAttrWithoutAttributeNameSuffix")]
public void TestInstrument_ClassesWithPropertyWithCustomExcludeAttributeAreExcluded(string excludedAttribute, string testClassName)
{
var instrumenterTest = CreateInstrumentor(attributesToIgnore: new string[] { excludedAttribute });
var result = instrumenterTest.Instrumenter.Instrument();

var doc = result.Documents.Values.FirstOrDefault(d => Path.GetFileName(d.Path) == "Samples.cs");
Assert.NotNull(doc);
#pragma warning disable CS0612 // Type or member is obsolete
var getFound = doc.Lines.Values.Any(l => l.Method.Equals("System.String Coverlet.Core.Samples.Tests.ClassWithPropertyExcludedByObsoleteAttr::get_Property()"));
var getFound = doc.Lines.Values.Any(l => l.Method.Equals($"System.String Coverlet.Core.Samples.Tests.{testClassName}::get_Property()"));
#pragma warning restore CS0612 // Type or member is obsolete
Assert.False(getFound, "Property getter decorated with with exclude attribute should be excluded");

#pragma warning disable CS0612 // Type or member is obsolete
var setFound = doc.Lines.Values.Any(l => l.Method.Equals("System.String Coverlet.Core.Samples.Tests.ClassWithPropertyExcludedByObsoleteAttr::set_Property()"));
var setFound = doc.Lines.Values.Any(l => l.Method.Equals($"System.String Coverlet.Core.Samples.Tests.{testClassName}::set_Property()"));
#pragma warning restore CS0612 // Type or member is obsolete
Assert.False(setFound, "Property setter decorated with with exclude attribute should be excluded");

Expand Down
13 changes: 13 additions & 0 deletions test/coverlet.core.tests/Samples/Samples.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
using System.Linq;
using System.Threading.Tasks;
using Coverlet.Core.Attributes;
using Microsoft.VisualStudio.TestPlatform;

namespace Coverlet.Core.Samples.Tests
{
Expand Down Expand Up @@ -213,6 +214,18 @@ public string Method(string input)
}
}

[TestSDKAutoGeneratedCode]
public class ClassExcludedByAttrWithoutAttributeNameSuffix
{
public string Method(string input)
{
if (string.IsNullOrEmpty(input))
throw new ArgumentException("Cannot be empty", nameof(input));

return input;
}
}

[Obsolete]
public class ClassExcludedByObsoleteAttr
{
Expand Down
44 changes: 22 additions & 22 deletions test/coverlet.core.tests/Symbols/CecilSymbolHelperTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,8 @@ public void GetBranchPoints_OneBranch()
Assert.Equal(points[0].Offset, points[1].Offset);
Assert.Equal(0, points[0].Path);
Assert.Equal(1, points[1].Path);
Assert.Equal(21, points[0].StartLine);
Assert.Equal(21, points[1].StartLine);
Assert.Equal(22, points[0].StartLine);
Assert.Equal(22, points[1].StartLine);
Assert.NotNull(points[1].Document);
Assert.Equal(points[0].Document, points[1].Document);
}
Expand Down Expand Up @@ -87,8 +87,8 @@ public void GetBranchPoints_TwoBranch()
Assert.Equal(4, points.Count());
Assert.Equal(points[0].Offset, points[1].Offset);
Assert.Equal(points[2].Offset, points[3].Offset);
Assert.Equal(27, points[0].StartLine);
Assert.Equal(28, points[2].StartLine);
Assert.Equal(28, points[0].StartLine);
Assert.Equal(29, points[2].StartLine);
}

[Fact]
Expand All @@ -105,8 +105,8 @@ public void GetBranchPoints_CompleteIf()
Assert.NotNull(points);
Assert.Equal(2, points.Count());
Assert.Equal(points[0].Offset, points[1].Offset);
Assert.Equal(34, points[0].StartLine);
Assert.Equal(34, points[1].StartLine);
Assert.Equal(35, points[0].StartLine);
Assert.Equal(35, points[1].StartLine);
}

#if !RELEASE // Issue https://github.com/tonerdo/coverlet/issues/389
Expand All @@ -127,10 +127,10 @@ public void GetBranchPoints_Switch()
Assert.Equal(points[0].Offset, points[2].Offset);
Assert.Equal(3, points[3].Path);

Assert.Equal(46, points[0].StartLine);
Assert.Equal(46, points[1].StartLine);
Assert.Equal(46, points[2].StartLine);
Assert.Equal(46, points[3].StartLine);
Assert.Equal(47, points[0].StartLine);
Assert.Equal(47, points[1].StartLine);
Assert.Equal(47, points[2].StartLine);
Assert.Equal(47, points[3].StartLine);
}

[Fact]
Expand All @@ -150,10 +150,10 @@ public void GetBranchPoints_SwitchWithDefault()
Assert.Equal(points[0].Offset, points[2].Offset);
Assert.Equal(3, points[3].Path);

Assert.Equal(60, points[0].StartLine);
Assert.Equal(60, points[1].StartLine);
Assert.Equal(60, points[2].StartLine);
Assert.Equal(60, points[3].StartLine);
Assert.Equal(61, points[0].StartLine);
Assert.Equal(61, points[1].StartLine);
Assert.Equal(61, points[2].StartLine);
Assert.Equal(61, points[3].StartLine);
}

[Fact]
Expand All @@ -173,10 +173,10 @@ public void GetBranchPoints_SwitchWithBreaks()
Assert.Equal(points[0].Offset, points[2].Offset);
Assert.Equal(3, points[3].Path);

Assert.Equal(76, points[0].StartLine);
Assert.Equal(76, points[1].StartLine);
Assert.Equal(76, points[2].StartLine);
Assert.Equal(76, points[3].StartLine);
Assert.Equal(77, points[0].StartLine);
Assert.Equal(77, points[1].StartLine);
Assert.Equal(77, points[2].StartLine);
Assert.Equal(77, points[3].StartLine);
}

[Fact]
Expand All @@ -197,10 +197,10 @@ public void GetBranchPoints_SwitchWithMultipleCases()
Assert.Equal(points[0].Offset, points[3].Offset);
Assert.Equal(3, points[3].Path);

Assert.Equal(94, points[0].StartLine);
Assert.Equal(94, points[1].StartLine);
Assert.Equal(94, points[2].StartLine);
Assert.Equal(94, points[3].StartLine);
Assert.Equal(95, points[0].StartLine);
Assert.Equal(95, points[1].StartLine);
Assert.Equal(95, points[2].StartLine);
Assert.Equal(95, points[3].StartLine);
}
#endif

Expand Down