Skip to content

Commit 83c47b8

Browse files
Exclude by assembly level System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage (#589)
Exclude by assembly level System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage
1 parent 207f346 commit 83c47b8

File tree

11 files changed

+93
-7
lines changed

11 files changed

+93
-7
lines changed

Documentation/MSBuildIntegration.md

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

110110
### Attributes
111111

112-
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.
112+
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

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

coverlet.sln

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
4141
nuget.config = nuget.config
4242
EndProjectSection
4343
EndProject
44+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "coverlet.tests.projectsample.excludedbyattribute", "test\coverlet.tests.projectsample.excludedbyattribute\coverlet.tests.projectsample.excludedbyattribute.csproj", "{D6B14F2F-9E7D-4D2C-BAC8-48834F853ED6}"
45+
EndProject
4446
Global
4547
GlobalSection(SolutionConfigurationPlatforms) = preSolution
4648
Debug|Any CPU = Debug|Any CPU
@@ -87,6 +89,10 @@ Global
8789
{085A3AFB-C086-4E98-86F1-1B481446EC5E}.Debug|Any CPU.Build.0 = Debug|Any CPU
8890
{085A3AFB-C086-4E98-86F1-1B481446EC5E}.Release|Any CPU.ActiveCfg = Release|Any CPU
8991
{085A3AFB-C086-4E98-86F1-1B481446EC5E}.Release|Any CPU.Build.0 = Release|Any CPU
92+
{D6B14F2F-9E7D-4D2C-BAC8-48834F853ED6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
93+
{D6B14F2F-9E7D-4D2C-BAC8-48834F853ED6}.Debug|Any CPU.Build.0 = Debug|Any CPU
94+
{D6B14F2F-9E7D-4D2C-BAC8-48834F853ED6}.Release|Any CPU.ActiveCfg = Release|Any CPU
95+
{D6B14F2F-9E7D-4D2C-BAC8-48834F853ED6}.Release|Any CPU.Build.0 = Release|Any CPU
9096
EndGlobalSection
9197
GlobalSection(SolutionProperties) = preSolution
9298
HideSolutionNode = FALSE
@@ -102,6 +108,7 @@ Global
102108
{5ED4FA81-8F8C-4211-BA88-7573BD63262E} = {2FEBDE1B-83E3-445B-B9F8-5644B0E0E134}
103109
{3E0F9E47-A1D7-4DF5-841D-A633486E2475} = {2FEBDE1B-83E3-445B-B9F8-5644B0E0E134}
104110
{085A3AFB-C086-4E98-86F1-1B481446EC5E} = {2FEBDE1B-83E3-445B-B9F8-5644B0E0E134}
111+
{D6B14F2F-9E7D-4D2C-BAC8-48834F853ED6} = {2FEBDE1B-83E3-445B-B9F8-5644B0E0E134}
105112
EndGlobalSection
106113
GlobalSection(ExtensibilityGlobals) = postSolution
107114
SolutionGuid = {9CA57C02-97B0-4C38-A027-EA61E8741F10}

src/coverlet.core/Coverage.cs

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -106,9 +106,12 @@ public CoveragePrepareResult PrepareModules()
106106
// Guard code path and restore if instrumentation fails.
107107
try
108108
{
109-
var result = instrumenter.Instrument();
110-
_results.Add(result);
111-
_logger.LogVerbose($"Instrumented module: '{module}'");
109+
InstrumenterResult result = instrumenter.Instrument();
110+
if (!instrumenter.SkipModule)
111+
{
112+
_results.Add(result);
113+
_logger.LogVerbose($"Instrumented module: '{module}'");
114+
}
112115
}
113116
catch (Exception ex)
114117
{

src/coverlet.core/Helpers/FileSystem.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,8 @@ public virtual Stream NewFileStream(string path, FileMode mode)
4242
return new FileStream(path, mode);
4343
}
4444

45-
public Stream NewFileStream(string path, FileMode mode, FileAccess access)
45+
// We need to partial mock this method on tests
46+
public virtual Stream NewFileStream(string path, FileMode mode, FileAccess access)
4647
{
4748
return new FileStream(path, mode, access);
4849
}

src/coverlet.core/Instrumentation/Instrumenter.cs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,8 @@ internal class Instrumenter
4040
private List<string> _excludedSourceFiles;
4141
private List<string> _branchesInCompiledGeneratedClass;
4242

43+
public bool SkipModule { get; set; } = false;
44+
4345
public Instrumenter(
4446
string module,
4547
string identifier,
@@ -151,6 +153,16 @@ private void InstrumentModule()
151153

152154
using (var module = ModuleDefinition.ReadModule(stream, parameters))
153155
{
156+
foreach (CustomAttribute customAttribute in module.Assembly.CustomAttributes)
157+
{
158+
if (customAttribute.AttributeType.FullName == "System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverageAttribute")
159+
{
160+
_logger.LogVerbose($"Excluded module: '{module}' for assembly level attribute 'System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverageAttribute'");
161+
SkipModule = true;
162+
return;
163+
}
164+
}
165+
154166
var containsAppContext = module.GetType(nameof(System), nameof(AppContext)) != null;
155167
var types = module.GetTypes();
156168
AddCustomModuleTrackerToModule(module);

test/coverlet.core.tests/CoverageTests.cs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
using System;
22
using System.IO;
3+
using System.Linq;
34
using System.Threading.Tasks;
45
using Coverlet.Core.Abstracts;
56
using Coverlet.Core.Helpers;
@@ -40,6 +41,25 @@ public void TestCoverage()
4041
directory.Delete(true);
4142
}
4243

44+
[Fact]
45+
public void TestCoverageSkipModule__AssemblyMarkedAsExcludeFromCodeCoverage()
46+
{
47+
Mock<FileSystem> partialMockFileSystem = new Mock<FileSystem>();
48+
partialMockFileSystem.CallBase = true;
49+
partialMockFileSystem.Setup(fs => fs.NewFileStream(It.IsAny<string>(), It.IsAny<FileMode>(), It.IsAny<FileAccess>())).Returns((string path, FileMode mode, FileAccess access) =>
50+
{
51+
return new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read);
52+
});
53+
var loggerMock = new Mock<ILogger>();
54+
55+
string excludedbyattributeDll = Directory.GetFiles(Path.Combine(Directory.GetCurrentDirectory(), "TestAssets"), "coverlet.tests.projectsample.excludedbyattribute.dll").First();
56+
// test skip module includint test assembly feature
57+
var coverage = new Coverage(excludedbyattributeDll, new string[] { "[coverlet.tests.projectsample.excludedbyattribute*]*" }, Array.Empty<string>(), Array.Empty<string>(), Array.Empty<string>(), Array.Empty<string>(), true, false, string.Empty, false, loggerMock.Object, _instrumentationHelper, partialMockFileSystem.Object);
58+
CoveragePrepareResult result = coverage.PrepareModules();
59+
Assert.Empty(result.Results);
60+
loggerMock.Verify(l => l.LogVerbose(It.IsAny<string>()));
61+
}
62+
4363
[Fact]
4464
public void TestCoverageWithTestAssembly()
4565
{

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

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -416,5 +416,22 @@ public void TestInstrument_MissingModule()
416416
loggerMock.Verify(l => l.LogWarning(It.IsAny<string>()));
417417
}
418418

419+
[Fact]
420+
public void TestInstrument_AssemblyMarkedAsExcludeFromCodeCoverage()
421+
{
422+
Mock<FileSystem> partialMockFileSystem = new Mock<FileSystem>();
423+
partialMockFileSystem.CallBase = true;
424+
partialMockFileSystem.Setup(fs => fs.NewFileStream(It.IsAny<string>(), It.IsAny<FileMode>(), It.IsAny<FileAccess>())).Returns((string path, FileMode mode, FileAccess access) =>
425+
{
426+
return new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read);
427+
});
428+
var loggerMock = new Mock<ILogger>();
429+
430+
string excludedbyattributeDll = Directory.GetFiles(Path.Combine(Directory.GetCurrentDirectory(), "TestAssets"), "coverlet.tests.projectsample.excludedbyattribute.dll").First();
431+
Instrumenter instrumenter = new Instrumenter(excludedbyattributeDll, "_xunit_excludedbyattribute", Array.Empty<string>(), Array.Empty<string>(), Array.Empty<string>(), Array.Empty<string>(), false, loggerMock.Object, _instrumentationHelper, partialMockFileSystem.Object);
432+
InstrumenterResult result = instrumenter.Instrument();
433+
Assert.Empty(result.Documents);
434+
loggerMock.Verify(l => l.LogVerbose(It.IsAny<string>()));
435+
}
419436
}
420437
}

test/coverlet.core.tests/coverlet.core.tests.csproj

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
<Import Project="$(RepoRoot)src\coverlet.msbuild.tasks\coverlet.msbuild.props" />
33

44
<PropertyGroup>
5-
<TargetFramework>netcoreapp2.0</TargetFramework>
5+
<TargetFramework>netcoreapp2.2</TargetFramework>
66
<IsPackable>false</IsPackable>
77
<LangVersion>preview</LangVersion>
88
<NoWarn>$(NoWarn);CS8002</NoWarn>
@@ -39,6 +39,8 @@
3939
<Target Name="PostBuild" AfterTargets="PostBuildEvent">
4040
<Exec Condition="$([MSBuild]::IsOSPlatform('Windows'))" Command="copy $(ProjectDir)..\coverlet.tests.remoteexecutor\$(OutDir) $(TargetDir) /Y" />
4141
<Exec Condition="$([MSBuild]::IsOSPlatform('OSX')) OR $([MSBuild]::IsOSPlatform('Linux'))" Command="cp -r $(ProjectDir)..\coverlet.tests.remoteexecutor\$(OutDir). $(TargetDir)" />
42+
<Exec Condition="$([MSBuild]::IsOSPlatform('Windows'))" Command="copy $(ProjectDir)..\coverlet.tests.projectsample.excludedbyattribute\$(OutDir) $(TargetDir)TestAssets /Y" />
43+
<Exec Condition="$([MSBuild]::IsOSPlatform('OSX')) OR $([MSBuild]::IsOSPlatform('Linux'))" Command="cp -r $(ProjectDir)..\coverlet.tests.projectsample.excludedbyattribute\$(OutDir). $(TargetDir)TestAssets" />
4244
</Target>
4345

4446
<Import Project="$(RepoRoot)src\coverlet.msbuild.tasks\coverlet.msbuild.targets" />
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
using System;
2+
using System.Diagnostics.CodeAnalysis;
3+
4+
[assembly: ExcludeFromCodeCoverage]
5+
6+
namespace coverlet.tests.projectsample.excludedbyattribute
7+
{
8+
public class SampleClass
9+
{
10+
public int SampleMethod()
11+
{
12+
return new Random().Next();
13+
}
14+
}
15+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<TargetFramework>netcoreapp2.2</TargetFramework>
5+
<IsPackable>false</IsPackable>
6+
<IsTestProject>false</IsTestProject>
7+
</PropertyGroup>
8+
9+
</Project>

0 commit comments

Comments
 (0)