Skip to content

Commit f760787

Browse files
author
Christopher-Marcel Böddecker
authored
Fix attribute exclusion
1 parent f7c3fd4 commit f760787

File tree

5 files changed

+77
-48
lines changed

5 files changed

+77
-48
lines changed

Documentation/MSBuildIntegration.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,7 @@ dotnet test /p:CollectCoverage=true /p:Threshold=80 /p:ThresholdType=line /p:Thr
111111

112112
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.
113113

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

116116
```bash
117117
dotnet test /p:CollectCoverage=true /p:ExcludeByAttribute="Obsolete,GeneratedCodeAttribute,CompilerGeneratedAttribute"

src/coverlet.core/Instrumentation/Instrumenter.cs

Lines changed: 12 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,17 @@ public Instrumenter(
6666
_excludeFilters = excludeFilters;
6767
_includeFilters = includeFilters;
6868
_excludedFilesHelper = new ExcludedFilesHelper(excludedFiles, logger);
69-
_excludedAttributes = excludedAttributes;
69+
_excludedAttributes = (excludedAttributes ?? Array.Empty<string>())
70+
// In case the attribute class ends in "Attribute", but it wasn't specified.
71+
// Both names are included (if it wasn't specified) because the attribute class might not actually end in the prefix.
72+
.SelectMany(a => a.EndsWith("Attribute") ? new[] { a } : new[] { a, $"{a}Attribute" })
73+
// The default custom attributes used to exclude from coverage.
74+
.Union(new List<string>()
75+
{
76+
nameof(ExcludeFromCoverageAttribute),
77+
nameof(ExcludeFromCodeCoverageAttribute)
78+
})
79+
.ToArray();
7080
_singleHit = singleHit;
7181
_isCoreLibrary = Path.GetFileNameWithoutExtension(_module) == "System.Private.CoreLib";
7282
_logger = logger;
@@ -680,21 +690,7 @@ private static void ReplaceExceptionHandlerBoundary(ExceptionHandler handler, In
680690

681691
private bool IsExcludeAttribute(CustomAttribute customAttribute)
682692
{
683-
// The default custom attributes used to exclude from coverage.
684-
IEnumerable<string> excludeAttributeNames = new List<string>()
685-
{
686-
nameof(ExcludeFromCoverageAttribute),
687-
nameof(ExcludeFromCodeCoverageAttribute)
688-
};
689-
690-
// Include the other attributes to exclude based on incoming parameters.
691-
if (_excludedAttributes != null)
692-
{
693-
excludeAttributeNames = _excludedAttributes.Union(excludeAttributeNames);
694-
}
695-
696-
return excludeAttributeNames.Any(a =>
697-
customAttribute.AttributeType.Name.Equals(a.EndsWith("Attribute") ? a : $"{a}Attribute"));
693+
return Array.IndexOf(_excludedAttributes, customAttribute.AttributeType.Name) != -1;
698694
}
699695

700696
private static MethodBody GetMethodBody(MethodDefinition method)

test/coverlet.core.tests/Instrumentation/InstrumenterTests.cs

Lines changed: 29 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
using Moq;
1818
using Xunit;
1919
using Microsoft.Extensions.DependencyModel;
20+
using Microsoft.VisualStudio.TestPlatform;
2021

2122
namespace Coverlet.Core.Instrumentation.Tests
2223
{
@@ -136,9 +137,26 @@ public void TestInstrument_ClassesWithExcludeAttributeAreExcluded(Type excludedT
136137
instrumenterTest.Directory.Delete(true);
137138
}
138139

140+
[Theory]
141+
[InlineData(typeof(ClassExcludedByAttrWithoutAttributeNameSuffix), nameof(TestSDKAutoGeneratedCode))]
142+
public void TestInstrument_ClassesWithExcludeAttributeWithoutAttributeNameSuffixAreExcluded(Type excludedType, string excludedAttribute)
143+
{
144+
var instrumenterTest = CreateInstrumentor(attributesToIgnore: new string[] { excludedAttribute });
145+
var result = instrumenterTest.Instrumenter.Instrument();
146+
147+
var doc = result.Documents.Values.FirstOrDefault(d => Path.GetFileName(d.Path) == "Samples.cs");
148+
Assert.NotNull(doc);
149+
150+
var found = doc.Lines.Values.Any(l => l.Class == excludedType.FullName);
151+
Assert.False(found, "Class decorated with with exclude attribute should be excluded");
152+
153+
instrumenterTest.Directory.Delete(true);
154+
}
155+
139156
[Theory]
140157
[InlineData(nameof(ObsoleteAttribute))]
141158
[InlineData("Obsolete")]
159+
[InlineData(nameof(TestSDKAutoGeneratedCode))]
142160
public void TestInstrument_ClassesWithCustomExcludeAttributeAreExcluded(string excludedAttribute)
143161
{
144162
var instrumenterTest = CreateInstrumentor(attributesToIgnore: new string[] { excludedAttribute });
@@ -155,40 +173,42 @@ public void TestInstrument_ClassesWithCustomExcludeAttributeAreExcluded(string e
155173
}
156174

157175
[Theory]
158-
[InlineData(nameof(ObsoleteAttribute))]
159-
[InlineData("Obsolete")]
160-
public void TestInstrument_ClassesWithMethodWithCustomExcludeAttributeAreExcluded(string excludedAttribute)
176+
[InlineData(nameof(ObsoleteAttribute), "ClassWithMethodExcludedByObsoleteAttr")]
177+
[InlineData("Obsolete", "ClassWithMethodExcludedByObsoleteAttr")]
178+
[InlineData(nameof(TestSDKAutoGeneratedCode), "ClassExcludedByAttrWithoutAttributeNameSuffix")]
179+
public void TestInstrument_ClassesWithMethodWithCustomExcludeAttributeAreExcluded(string excludedAttribute, string testClassName)
161180
{
162181
var instrumenterTest = CreateInstrumentor(attributesToIgnore: new string[] { excludedAttribute });
163182
var result = instrumenterTest.Instrumenter.Instrument();
164183

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

172191
instrumenterTest.Directory.Delete(true);
173192
}
174193

175194
[Theory]
176-
[InlineData(nameof(ObsoleteAttribute))]
177-
[InlineData("Obsolete")]
178-
public void TestInstrument_ClassesWithPropertyWithCustomExcludeAttributeAreExcluded(string excludedAttribute)
195+
[InlineData(nameof(ObsoleteAttribute), "ClassWithMethodExcludedByObsoleteAttr")]
196+
[InlineData("Obsolete", "ClassWithMethodExcludedByObsoleteAttr")]
197+
[InlineData(nameof(TestSDKAutoGeneratedCode), "ClassExcludedByAttrWithoutAttributeNameSuffix")]
198+
public void TestInstrument_ClassesWithPropertyWithCustomExcludeAttributeAreExcluded(string excludedAttribute, string testClassName)
179199
{
180200
var instrumenterTest = CreateInstrumentor(attributesToIgnore: new string[] { excludedAttribute });
181201
var result = instrumenterTest.Instrumenter.Instrument();
182202

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

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

test/coverlet.core.tests/Samples/Samples.cs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
using System.Linq;
77
using System.Threading.Tasks;
88
using Coverlet.Core.Attributes;
9+
using Microsoft.VisualStudio.TestPlatform;
910

1011
namespace Coverlet.Core.Samples.Tests
1112
{
@@ -213,6 +214,18 @@ public string Method(string input)
213214
}
214215
}
215216

217+
[TestSDKAutoGeneratedCode]
218+
public class ClassExcludedByAttrWithoutAttributeNameSuffix
219+
{
220+
public string Method(string input)
221+
{
222+
if (string.IsNullOrEmpty(input))
223+
throw new ArgumentException("Cannot be empty", nameof(input));
224+
225+
return input;
226+
}
227+
}
228+
216229
[Obsolete]
217230
public class ClassExcludedByObsoleteAttr
218231
{

test/coverlet.core.tests/Symbols/CecilSymbolHelperTests.cs

Lines changed: 22 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -40,8 +40,8 @@ public void GetBranchPoints_OneBranch()
4040
Assert.Equal(points[0].Offset, points[1].Offset);
4141
Assert.Equal(0, points[0].Path);
4242
Assert.Equal(1, points[1].Path);
43-
Assert.Equal(21, points[0].StartLine);
44-
Assert.Equal(21, points[1].StartLine);
43+
Assert.Equal(22, points[0].StartLine);
44+
Assert.Equal(22, points[1].StartLine);
4545
Assert.NotNull(points[1].Document);
4646
Assert.Equal(points[0].Document, points[1].Document);
4747
}
@@ -87,8 +87,8 @@ public void GetBranchPoints_TwoBranch()
8787
Assert.Equal(4, points.Count());
8888
Assert.Equal(points[0].Offset, points[1].Offset);
8989
Assert.Equal(points[2].Offset, points[3].Offset);
90-
Assert.Equal(27, points[0].StartLine);
91-
Assert.Equal(28, points[2].StartLine);
90+
Assert.Equal(28, points[0].StartLine);
91+
Assert.Equal(29, points[2].StartLine);
9292
}
9393

9494
[Fact]
@@ -105,8 +105,8 @@ public void GetBranchPoints_CompleteIf()
105105
Assert.NotNull(points);
106106
Assert.Equal(2, points.Count());
107107
Assert.Equal(points[0].Offset, points[1].Offset);
108-
Assert.Equal(34, points[0].StartLine);
109-
Assert.Equal(34, points[1].StartLine);
108+
Assert.Equal(35, points[0].StartLine);
109+
Assert.Equal(35, points[1].StartLine);
110110
}
111111

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

130-
Assert.Equal(46, points[0].StartLine);
131-
Assert.Equal(46, points[1].StartLine);
132-
Assert.Equal(46, points[2].StartLine);
133-
Assert.Equal(46, points[3].StartLine);
130+
Assert.Equal(47, points[0].StartLine);
131+
Assert.Equal(47, points[1].StartLine);
132+
Assert.Equal(47, points[2].StartLine);
133+
Assert.Equal(47, points[3].StartLine);
134134
}
135135

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

153-
Assert.Equal(60, points[0].StartLine);
154-
Assert.Equal(60, points[1].StartLine);
155-
Assert.Equal(60, points[2].StartLine);
156-
Assert.Equal(60, points[3].StartLine);
153+
Assert.Equal(61, points[0].StartLine);
154+
Assert.Equal(61, points[1].StartLine);
155+
Assert.Equal(61, points[2].StartLine);
156+
Assert.Equal(61, points[3].StartLine);
157157
}
158158

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

176-
Assert.Equal(76, points[0].StartLine);
177-
Assert.Equal(76, points[1].StartLine);
178-
Assert.Equal(76, points[2].StartLine);
179-
Assert.Equal(76, points[3].StartLine);
176+
Assert.Equal(77, points[0].StartLine);
177+
Assert.Equal(77, points[1].StartLine);
178+
Assert.Equal(77, points[2].StartLine);
179+
Assert.Equal(77, points[3].StartLine);
180180
}
181181

182182
[Fact]
@@ -197,10 +197,10 @@ public void GetBranchPoints_SwitchWithMultipleCases()
197197
Assert.Equal(points[0].Offset, points[3].Offset);
198198
Assert.Equal(3, points[3].Path);
199199

200-
Assert.Equal(94, points[0].StartLine);
201-
Assert.Equal(94, points[1].StartLine);
202-
Assert.Equal(94, points[2].StartLine);
203-
Assert.Equal(94, points[3].StartLine);
200+
Assert.Equal(95, points[0].StartLine);
201+
Assert.Equal(95, points[1].StartLine);
202+
Assert.Equal(95, points[2].StartLine);
203+
Assert.Equal(95, points[3].StartLine);
204204
}
205205
#endif
206206

0 commit comments

Comments
 (0)