Skip to content

Commit d4450ac

Browse files
authored
Merge branch 'master' into ProbePath
2 parents 89f5be5 + 1a28fcd commit d4450ac

File tree

10 files changed

+258
-192
lines changed

10 files changed

+258
-192
lines changed

README.md

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,12 @@ coverlet <ASSEMBLY> --target <TARGET> --targetargs <TARGETARGS> --threshold 80 -
177177
178178
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.
179179
180+
You can also ignore additional attributes by using the `ExcludeByAttribute` property (short name or full name supported):
181+
182+
```bash
183+
coverlet <ASSEMBLY> --target <TARGET> --targetargs <TARGETARGS> --exclude-by-attribute "Obsolete,GeneratedCodeAttribute,CompilerGeneratedAttribute"
184+
```
185+
180186
##### Source Files
181187
182188
You can also ignore specific source files from code coverage using the `--exclude-by-file` option
@@ -209,7 +215,7 @@ Examples
209215
coverlet <ASSEMBLY> --target <TARGET> --targetargs <TARGETARGS> --exclude "[coverlet.*]Coverlet.Core.Coverage"
210216
```
211217
212-
Coverlet goes a step in the other direction by also letting you explicitly set what can be included using the `--include` option.
218+
Coverlet goes a step in the other direction by also letting you explicitly set what can be included using the `--include` option.
213219
214220
Examples
215221
- `--include "[*]*"` => Includes all types in all assemblies (everything is instrumented)
@@ -225,7 +231,7 @@ In this mode, Coverlet doesn't require any additional setup other than including
225231
If a property takes multiple comma-separated values please note that [you will have to add escaped quotes around the string](https://github.com/Microsoft/msbuild/issues/2999#issuecomment-366078677) like this: `/p:Exclude=\"[coverlet.*]*,[*]Coverlet.Core*\"`, `/p:Include=\"[coverlet.*]*,[*]Coverlet.Core*\"`, or `/p:CoverletOutputFormat=\"json,opencover\"`.
226232
227233
##### Note for Powershell / VSTS users
228-
To exclude or include multiple assemblies when using Powershell scripts or creating a .yaml file for a VSTS build ```%2c``` should be used as a separator. Msbuild will translate this symbol to ```,```.
234+
To exclude or include multiple assemblies when using Powershell scripts or creating a .yaml file for a VSTS build ```%2c``` should be used as a separator. Msbuild will translate this symbol to ```,```.
229235
230236
```/p:Exclude="[*]*Examples?%2c[*]*Startup"```
231237
@@ -306,6 +312,12 @@ You can specify multiple values for `ThresholdType` by separating them with comm
306312
307313
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.
308314
315+
You can also ignore additional attributes by using the `ExcludeByAttribute` property (short name or full name supported):
316+
317+
```bash
318+
dotnet test /p:CollectCoverage=true /p:ExcludeByAttribute="Obsolete,GeneratedCodeAttribute,CompilerGeneratedAttribute"
319+
```
320+
309321
#### Source Files
310322
You can also ignore specific source files from code coverage using the `ExcludeByFile` property
311323
- Use single or multiple paths (separate by comma)
@@ -316,7 +328,7 @@ You can also ignore specific source files from code coverage using the `ExcludeB
316328
dotnet test /p:CollectCoverage=true /p:ExcludeByFile=\"../dir1/class1.cs,../dir2/*.cs,../dir3/**/*.cs,\"
317329
```
318330
319-
##### Filters
331+
##### Filters
320332
Coverlet gives the ability to have fine grained control over what gets excluded using "filter expressions".
321333
322334
Syntax: `/p:Exclude=[Assembly-Filter]Type-Filter`
@@ -336,7 +348,7 @@ Examples
336348
dotnet test /p:CollectCoverage=true /p:Exclude="[coverlet.*]Coverlet.Core.Coverage"
337349
```
338350
339-
Coverlet goes a step in the other direction by also letting you explicitly set what can be included using the `Include` property.
351+
Coverlet goes a step in the other direction by also letting you explicitly set what can be included using the `Include` property.
340352
341353
Examples
342354
- `/p:Include="[*]*"` => Includes all types in all assemblies (everything is instrumented)

src/coverlet.console/Program.cs

Lines changed: 171 additions & 170 deletions
Large diffs are not rendered by default.

src/coverlet.core/Coverage.cs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,21 +20,23 @@ public class Coverage
2020
private string[] _includeDirectories;
2121
private string[] _excludedSourceFiles;
2222
private string _mergeWith;
23+
private string[] _excludeAttributes;
2324
private List<InstrumenterResult> _results;
2425

2526
public string Identifier
2627
{
2728
get { return _identifier; }
2829
}
2930

30-
public Coverage(string module, string[] excludeFilters, string[] includeFilters, string[] includeDirectories, string[] excludedSourceFiles, string mergeWith)
31+
public Coverage(string module, string[] excludeFilters, string[] includeFilters, string[] includeDirectories, string[] excludedSourceFiles, string mergeWith, string[] excludeAttributes)
3132
{
3233
_module = module;
3334
_excludeFilters = excludeFilters;
3435
_includeFilters = includeFilters;
3536
_includeDirectories = includeDirectories ?? Array.Empty<string>();
3637
_excludedSourceFiles = excludedSourceFiles;
3738
_mergeWith = mergeWith;
39+
_excludeAttributes = excludeAttributes;
3840

3941
_identifier = Guid.NewGuid().ToString();
4042
_results = new List<InstrumenterResult>();
@@ -53,7 +55,7 @@ public void PrepareModules()
5355
!InstrumentationHelper.IsModuleIncluded(module, _includeFilters))
5456
continue;
5557

56-
var instrumenter = new Instrumenter(module, _identifier, _excludeFilters, _includeFilters, excludes);
58+
var instrumenter = new Instrumenter(module, _identifier, _excludeFilters, _includeFilters, excludes, _excludeAttributes);
5759
if (instrumenter.CanInstrument())
5860
{
5961
InstrumentationHelper.BackupOriginalModule(module, _identifier);

src/coverlet.core/Instrumentation/Instrumenter.cs

Lines changed: 18 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using System;
2+
using System.Collections.Generic;
23
using System.Diagnostics;
34
using System.Diagnostics.CodeAnalysis;
45
using System.IO;
@@ -22,20 +23,22 @@ internal class Instrumenter
2223
private readonly string[] _excludeFilters;
2324
private readonly string[] _includeFilters;
2425
private readonly string[] _excludedFiles;
26+
private readonly string[] _excludedAttributes;
2527
private InstrumenterResult _result;
2628
private FieldDefinition _customTrackerHitsArray;
2729
private FieldDefinition _customTrackerHitsFilePath;
2830
private ILProcessor _customTrackerClassConstructorIl;
2931
private TypeDefinition _customTrackerTypeDef;
3032
private MethodReference _customTrackerRecordHitMethod;
3133

32-
public Instrumenter(string module, string identifier, string[] excludeFilters, string[] includeFilters, string[] excludedFiles)
34+
public Instrumenter(string module, string identifier, string[] excludeFilters, string[] includeFilters, string[] excludedFiles, string[] excludedAttributes)
3335
{
3436
_module = module;
3537
_identifier = identifier;
3638
_excludeFilters = excludeFilters;
3739
_includeFilters = includeFilters;
3840
_excludedFiles = excludedFiles ?? Array.Empty<string>();
41+
_excludedAttributes = excludedAttributes;
3942
}
4043

4144
public bool CanInstrument() => InstrumentationHelper.HasPdb(_module);
@@ -179,7 +182,7 @@ private void AddCustomModuleTrackerToModule(ModuleDefinition module)
179182
{
180183
handler.CatchType = module.ImportReference(handler.CatchType);
181184
}
182-
185+
183186
methodOnCustomType.Body.ExceptionHandlers.Add(handler);
184187
}
185188

@@ -392,19 +395,24 @@ private static void ReplaceExceptionHandlerBoundary(ExceptionHandler handler, In
392395
handler.TryStart = newTarget;
393396
}
394397

395-
private static bool IsExcludeAttribute(CustomAttribute customAttribute)
398+
private bool IsExcludeAttribute(CustomAttribute customAttribute)
396399
{
397-
var excludeAttributeNames = new[]
400+
// The default custom attributes used to exclude from coverage.
401+
IEnumerable<string> excludeAttributeNames = new List<string>()
398402
{
399403
nameof(ExcludeFromCoverageAttribute),
400-
"ExcludeFromCoverage",
401-
nameof(ExcludeFromCodeCoverageAttribute),
402-
"ExcludeFromCodeCoverage"
404+
nameof(ExcludeFromCodeCoverageAttribute)
403405
};
404406

405-
var attributeName = customAttribute.AttributeType.Name;
406-
return excludeAttributeNames.Any(a => a.Equals(attributeName));
407-
}
407+
// Include the other attributes to exclude based on incoming parameters.
408+
if (_excludedAttributes != null)
409+
{
410+
excludeAttributeNames = _excludedAttributes.Union(excludeAttributeNames);
411+
}
412+
413+
return excludeAttributeNames.Any(a =>
414+
customAttribute.AttributeType.Name.Equals(a.EndsWith("Attribute")? a : $"{a}Attribute"));
415+
}
408416

409417
private static Mono.Cecil.Cil.MethodBody GetMethodBody(MethodDefinition method)
410418
{

src/coverlet.msbuild.tasks/InstrumentationTask.cs

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ public class InstrumentationTask : Task
1414
private string _includeDirectory;
1515
private string _excludeByFile;
1616
private string _mergeWith;
17+
private string _excludeByAttribute;
1718

1819
internal static Coverage Coverage
1920
{
@@ -26,7 +27,7 @@ public string Path
2627
get { return _path; }
2728
set { _path = value; }
2829
}
29-
30+
3031
public string Exclude
3132
{
3233
get { return _exclude; }
@@ -57,6 +58,12 @@ public string MergeWith
5758
set { _mergeWith = value; }
5859
}
5960

61+
public string ExcludeByAttribute
62+
{
63+
get { return _excludeByAttribute; }
64+
set { _excludeByAttribute = value; }
65+
}
66+
6067
public override bool Execute()
6168
{
6269
try
@@ -65,8 +72,9 @@ public override bool Execute()
6572
var excludeFilters = _exclude?.Split(',');
6673
var includeFilters = _include?.Split(',');
6774
var includeDirectories = _includeDirectory?.Split(',');
75+
var excludeAttributes = _excludeByAttribute?.Split(',');
6876

69-
_coverage = new Coverage(_path, excludeFilters, includeFilters, includeDirectories, excludedSourceFiles, _mergeWith);
77+
_coverage = new Coverage(_path, excludeFilters, includeFilters, includeDirectories, excludedSourceFiles, _mergeWith, excludeAttributes);
7078
_coverage.PrepareModules();
7179
}
7280
catch (Exception ex)

src/coverlet.msbuild/coverlet.msbuild.props

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,5 +10,6 @@
1010
<Threshold Condition="$(Threshold) == ''">0</Threshold>
1111
<ThresholdType Condition="$(ThresholdType) == ''">line,branch,method</ThresholdType>
1212
<IncludeDirectory Condition="$(IncludeDirectory) == ''"></IncludeDirectory>
13+
<ExcludeByAttribute Condition="$(ExcludeByAttribute) == ''"></ExcludeByAttribute>
1314
</PropertyGroup>
1415
</Project>

src/coverlet.msbuild/coverlet.msbuild.targets

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
Exclude="$(Exclude)"
1212
ExcludeByFile="$(ExcludeByFile)"
1313
MergeWith="$(MergeWith)"
14+
ExcludeByAttribute="$(ExcludeByAttribute)"
1415
Path="$(TargetPath)" />
1516
</Target>
1617

@@ -22,6 +23,7 @@
2223
Exclude="$(Exclude)"
2324
ExcludeByFile="$(ExcludeByFile)"
2425
MergeWith="$(MergeWith)"
26+
ExcludeByAttribute="$(ExcludeByAttribute)"
2527
Path="$(TargetPath)" />
2628
</Target>
2729

test/coverlet.core.tests/CoverageTests.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ public void TestCoverage()
2727
// Since Coverage only instruments dependancies, we need a fake module here
2828
var testModule = Path.Combine(directory.FullName, "test.module.dll");
2929

30-
var coverage = new Coverage(testModule, Array.Empty<string>(), Array.Empty<string>(), Array.Empty<string>(), Array.Empty<string>(), string.Empty);
30+
var coverage = new Coverage(testModule, Array.Empty<string>(), Array.Empty<string>(), Array.Empty<string>(), Array.Empty<string>(), string.Empty, Array.Empty<string>());
3131
coverage.PrepareModules();
3232

3333
var result = coverage.GetCoverageResult();

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

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ public void TestCoreLibInstrumentation()
2727
foreach (var file in files)
2828
File.Copy(Path.Combine(OriginalFilesDir, file), Path.Combine(TestFilesDir, file), overwrite: true);
2929

30-
Instrumenter instrumenter = new Instrumenter(Path.Combine(TestFilesDir, files[0]), "_coverlet_instrumented", Array.Empty<string>(), Array.Empty<string>(), Array.Empty<string>());
30+
Instrumenter instrumenter = new Instrumenter(Path.Combine(TestFilesDir, files[0]), "_coverlet_instrumented", Array.Empty<string>(), Array.Empty<string>(), Array.Empty<string>(), Array.Empty<string>());
3131
Assert.True(instrumenter.CanInstrument());
3232
var result = instrumenter.Instrument();
3333
Assert.NotNull(result);
@@ -76,7 +76,26 @@ public void TestInstrument_ClassesWithExcludeAttributeAreExcluded(Type excludedT
7676
instrumenterTest.Directory.Delete(true);
7777
}
7878

79-
private InstrumenterTest CreateInstrumentor(bool fakeCoreLibModule = false)
79+
80+
[Theory]
81+
[InlineData(nameof(ObsoleteAttribute))]
82+
[InlineData("Obsolete")]
83+
public void TestInstrument_ClassesWithCustomExcludeAttributeAreExcluded(string excludedAttribute)
84+
{
85+
var instrumenterTest = CreateInstrumentor(attributesToIgnore: new string[] { excludedAttribute });
86+
var result = instrumenterTest.Instrumenter.Instrument();
87+
88+
var doc = result.Documents.Values.FirstOrDefault(d => Path.GetFileName(d.Path) == "Samples.cs");
89+
Assert.NotNull(doc);
90+
#pragma warning disable CS0612 // Type or member is obsolete
91+
var found = doc.Lines.Values.Any(l => l.Class.Equals(nameof(ClassExcludedByObsoleteAttr)));
92+
#pragma warning restore CS0612 // Type or member is obsolete
93+
Assert.False(found, "Class decorated with with exclude attribute should be excluded");
94+
95+
instrumenterTest.Directory.Delete(true);
96+
}
97+
98+
private InstrumenterTest CreateInstrumentor(bool fakeCoreLibModule = false, string[] attributesToIgnore = null)
8099
{
81100
string module = GetType().Assembly.Location;
82101
string pdb = Path.Combine(Path.GetDirectoryName(module), Path.GetFileNameWithoutExtension(module) + ".pdb");
@@ -100,7 +119,7 @@ private InstrumenterTest CreateInstrumentor(bool fakeCoreLibModule = false)
100119
File.Copy(pdb, Path.Combine(directory.FullName, destPdb), true);
101120

102121
module = Path.Combine(directory.FullName, destModule);
103-
Instrumenter instrumenter = new Instrumenter(module, identifier, Array.Empty<string>(), Array.Empty<string>(), Array.Empty<string>());
122+
Instrumenter instrumenter = new Instrumenter(module, identifier, Array.Empty<string>(), Array.Empty<string>(), Array.Empty<string>(), attributesToIgnore);
104123
return new InstrumenterTest
105124
{
106125
Instrumenter = instrumenter,

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

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -189,4 +189,17 @@ public string Method(string input)
189189
return input;
190190
}
191191
}
192+
193+
[Obsolete]
194+
public class ClassExcludedByObsoleteAttr
195+
{
196+
197+
public string Method(string input)
198+
{
199+
if (string.IsNullOrEmpty(input))
200+
throw new ArgumentException("Cannot be empty", nameof(input));
201+
202+
return input;
203+
}
204+
}
192205
}

0 commit comments

Comments
 (0)