Skip to content

Commit

Permalink
Exclude by assembly level System.Diagnostics.CodeAnalysis.ExcludeFrom…
Browse files Browse the repository at this point in the history
…CodeCoverage (#589)

Exclude by assembly level System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage
  • Loading branch information
MarcoRossignoli authored Oct 12, 2019
1 parent 207f346 commit 83c47b8
Show file tree
Hide file tree
Showing 11 changed files with 93 additions and 7 deletions.
2 changes: 1 addition & 1 deletion Documentation/MSBuildIntegration.md
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ dotnet test /p:CollectCoverage=true /p:Threshold=80 /p:ThresholdType=line /p:Thr

### Attributes

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 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):

Expand Down
7 changes: 7 additions & 0 deletions coverlet.sln
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
nuget.config = nuget.config
EndProjectSection
EndProject
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}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -87,6 +89,10 @@ Global
{085A3AFB-C086-4E98-86F1-1B481446EC5E}.Debug|Any CPU.Build.0 = Debug|Any CPU
{085A3AFB-C086-4E98-86F1-1B481446EC5E}.Release|Any CPU.ActiveCfg = Release|Any CPU
{085A3AFB-C086-4E98-86F1-1B481446EC5E}.Release|Any CPU.Build.0 = Release|Any CPU
{D6B14F2F-9E7D-4D2C-BAC8-48834F853ED6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{D6B14F2F-9E7D-4D2C-BAC8-48834F853ED6}.Debug|Any CPU.Build.0 = Debug|Any CPU
{D6B14F2F-9E7D-4D2C-BAC8-48834F853ED6}.Release|Any CPU.ActiveCfg = Release|Any CPU
{D6B14F2F-9E7D-4D2C-BAC8-48834F853ED6}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand All @@ -102,6 +108,7 @@ Global
{5ED4FA81-8F8C-4211-BA88-7573BD63262E} = {2FEBDE1B-83E3-445B-B9F8-5644B0E0E134}
{3E0F9E47-A1D7-4DF5-841D-A633486E2475} = {2FEBDE1B-83E3-445B-B9F8-5644B0E0E134}
{085A3AFB-C086-4E98-86F1-1B481446EC5E} = {2FEBDE1B-83E3-445B-B9F8-5644B0E0E134}
{D6B14F2F-9E7D-4D2C-BAC8-48834F853ED6} = {2FEBDE1B-83E3-445B-B9F8-5644B0E0E134}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {9CA57C02-97B0-4C38-A027-EA61E8741F10}
Expand Down
9 changes: 6 additions & 3 deletions src/coverlet.core/Coverage.cs
Original file line number Diff line number Diff line change
Expand Up @@ -106,9 +106,12 @@ public CoveragePrepareResult PrepareModules()
// Guard code path and restore if instrumentation fails.
try
{
var result = instrumenter.Instrument();
_results.Add(result);
_logger.LogVerbose($"Instrumented module: '{module}'");
InstrumenterResult result = instrumenter.Instrument();
if (!instrumenter.SkipModule)
{
_results.Add(result);
_logger.LogVerbose($"Instrumented module: '{module}'");
}
}
catch (Exception ex)
{
Expand Down
3 changes: 2 additions & 1 deletion src/coverlet.core/Helpers/FileSystem.cs
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,8 @@ public virtual Stream NewFileStream(string path, FileMode mode)
return new FileStream(path, mode);
}

public Stream NewFileStream(string path, FileMode mode, FileAccess access)
// We need to partial mock this method on tests
public virtual Stream NewFileStream(string path, FileMode mode, FileAccess access)
{
return new FileStream(path, mode, access);
}
Expand Down
12 changes: 12 additions & 0 deletions src/coverlet.core/Instrumentation/Instrumenter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@ internal class Instrumenter
private List<string> _excludedSourceFiles;
private List<string> _branchesInCompiledGeneratedClass;

public bool SkipModule { get; set; } = false;

public Instrumenter(
string module,
string identifier,
Expand Down Expand Up @@ -151,6 +153,16 @@ private void InstrumentModule()

using (var module = ModuleDefinition.ReadModule(stream, parameters))
{
foreach (CustomAttribute customAttribute in module.Assembly.CustomAttributes)
{
if (customAttribute.AttributeType.FullName == "System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverageAttribute")
{
_logger.LogVerbose($"Excluded module: '{module}' for assembly level attribute 'System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverageAttribute'");
SkipModule = true;
return;
}
}

var containsAppContext = module.GetType(nameof(System), nameof(AppContext)) != null;
var types = module.GetTypes();
AddCustomModuleTrackerToModule(module);
Expand Down
20 changes: 20 additions & 0 deletions test/coverlet.core.tests/CoverageTests.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using Coverlet.Core.Abstracts;
using Coverlet.Core.Helpers;
Expand Down Expand Up @@ -40,6 +41,25 @@ public void TestCoverage()
directory.Delete(true);
}

[Fact]
public void TestCoverageSkipModule__AssemblyMarkedAsExcludeFromCodeCoverage()
{
Mock<FileSystem> partialMockFileSystem = new Mock<FileSystem>();
partialMockFileSystem.CallBase = true;
partialMockFileSystem.Setup(fs => fs.NewFileStream(It.IsAny<string>(), It.IsAny<FileMode>(), It.IsAny<FileAccess>())).Returns((string path, FileMode mode, FileAccess access) =>
{
return new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read);
});
var loggerMock = new Mock<ILogger>();

string excludedbyattributeDll = Directory.GetFiles(Path.Combine(Directory.GetCurrentDirectory(), "TestAssets"), "coverlet.tests.projectsample.excludedbyattribute.dll").First();
// test skip module includint test assembly feature
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);
CoveragePrepareResult result = coverage.PrepareModules();
Assert.Empty(result.Results);
loggerMock.Verify(l => l.LogVerbose(It.IsAny<string>()));
}

[Fact]
public void TestCoverageWithTestAssembly()
{
Expand Down
17 changes: 17 additions & 0 deletions test/coverlet.core.tests/Instrumentation/InstrumenterTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -416,5 +416,22 @@ public void TestInstrument_MissingModule()
loggerMock.Verify(l => l.LogWarning(It.IsAny<string>()));
}

[Fact]
public void TestInstrument_AssemblyMarkedAsExcludeFromCodeCoverage()
{
Mock<FileSystem> partialMockFileSystem = new Mock<FileSystem>();
partialMockFileSystem.CallBase = true;
partialMockFileSystem.Setup(fs => fs.NewFileStream(It.IsAny<string>(), It.IsAny<FileMode>(), It.IsAny<FileAccess>())).Returns((string path, FileMode mode, FileAccess access) =>
{
return new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read);
});
var loggerMock = new Mock<ILogger>();

string excludedbyattributeDll = Directory.GetFiles(Path.Combine(Directory.GetCurrentDirectory(), "TestAssets"), "coverlet.tests.projectsample.excludedbyattribute.dll").First();
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);
InstrumenterResult result = instrumenter.Instrument();
Assert.Empty(result.Documents);
loggerMock.Verify(l => l.LogVerbose(It.IsAny<string>()));
}
}
}
4 changes: 3 additions & 1 deletion test/coverlet.core.tests/coverlet.core.tests.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
<Import Project="$(RepoRoot)src\coverlet.msbuild.tasks\coverlet.msbuild.props" />

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

<Import Project="$(RepoRoot)src\coverlet.msbuild.tasks\coverlet.msbuild.targets" />
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
using System;
using System.Diagnostics.CodeAnalysis;

[assembly: ExcludeFromCodeCoverage]

namespace coverlet.tests.projectsample.excludedbyattribute
{
public class SampleClass
{
public int SampleMethod()
{
return new Random().Next();
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>netcoreapp2.2</TargetFramework>
<IsPackable>false</IsPackable>
<IsTestProject>false</IsTestProject>
</PropertyGroup>

</Project>
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>netcoreapp2.0</TargetFramework>
<TargetFramework>netcoreapp2.2</TargetFramework>
<RootNamespace>Coverlet.Tests.RemoteExecutor</RootNamespace>
<IsPackable>false</IsPackable>
<IsTestProject>false</IsTestProject>
Expand Down

0 comments on commit 83c47b8

Please sign in to comment.