Skip to content

Commit 511a307

Browse files
tonyhalletttonerdo
andauthored
Assembly attribute exclusion with IsExcludeAttribute. Close #1013 #1011 (#1017)
* Assembly attribute exclusion with IsExcludeAttribute. Close #1013 #1011 * whitespace Co-authored-by: Toni Solarin-Sodara <[email protected]> Co-authored-by: Toni Solarin-Sodara <[email protected]>
1 parent a1d47d3 commit 511a307

File tree

2 files changed

+84
-10
lines changed

2 files changed

+84
-10
lines changed

src/coverlet.core/Instrumentation/Instrumenter.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -230,9 +230,9 @@ private void InstrumentModule()
230230
{
231231
foreach (CustomAttribute customAttribute in module.Assembly.CustomAttributes)
232232
{
233-
if (customAttribute.AttributeType.FullName == "System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverageAttribute")
233+
if (IsExcludeAttribute(customAttribute))
234234
{
235-
_logger.LogVerbose($"Excluded module: '{module}' for assembly level attribute 'System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverageAttribute'");
235+
_logger.LogVerbose($"Excluded module: '{module}' for assembly level attribute {customAttribute.AttributeType.FullName}");
236236
SkipModule = true;
237237
return;
238238
}

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

Lines changed: 82 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,18 @@
2222

2323
namespace Coverlet.Core.Instrumentation.Tests
2424
{
25-
public class InstrumenterTests
25+
public class InstrumenterTests : IDisposable
2626
{
2727
private readonly Mock<ILogger> _mockLogger = new Mock<ILogger>();
28+
private Action disposeAction;
29+
30+
public void Dispose()
31+
{
32+
if (disposeAction != null)
33+
{
34+
disposeAction();
35+
}
36+
}
2837

2938
[ConditionalFact]
3039
[SkipOnOS(OS.Linux)]
@@ -504,29 +513,92 @@ public void TestInstrument_MissingModule()
504513
loggerMock.Verify(l => l.LogWarning(It.IsAny<string>()));
505514
}
506515

507-
[Fact]
508-
public void TestInstrument_AssemblyMarkedAsExcludeFromCodeCoverage()
516+
[Theory]
517+
[InlineData("NotAMatch", new string[] { }, false)]
518+
[InlineData("ExcludeFromCoverageAttribute", new string[] { }, true)]
519+
[InlineData("ExcludeFromCodeCoverageAttribute", new string[] { }, true)]
520+
[InlineData("CustomExclude", new string[] { "CustomExclude" }, true)]
521+
[InlineData("CustomExcludeAttribute", new string[] { "CustomExclude" }, true)]
522+
[InlineData("CustomExcludeAttribute", new string[] { "CustomExcludeAttribute" }, true)]
523+
public void TestInstrument_AssemblyMarkedAsExcludeFromCodeCoverage(string attributeName, string[] excludedAttributes, bool expectedExcludes)
509524
{
525+
string EmitAssemblyToInstrument(string outputFolder)
526+
{
527+
var attributeClassSyntaxTree = CSharpSyntaxTree.ParseText("[System.AttributeUsage(System.AttributeTargets.Assembly)]public class " + attributeName + ":System.Attribute{}");
528+
var instrumentableClassSyntaxTree = CSharpSyntaxTree.ParseText($@"
529+
[assembly:{attributeName}]
530+
namespace coverlet.tests.projectsample.excludedbyattribute{{
531+
public class SampleClass
532+
{{
533+
public int SampleMethod()
534+
{{
535+
return new System.Random().Next();
536+
}}
537+
}}
538+
539+
}}
540+
");
541+
var compilation = CSharpCompilation.Create(attributeName, new List<SyntaxTree>
542+
{
543+
attributeClassSyntaxTree,instrumentableClassSyntaxTree
544+
}).AddReferences(
545+
MetadataReference.CreateFromFile(typeof(Attribute).Assembly.Location)).
546+
WithOptions(new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary, false));
547+
548+
var dllPath = Path.Combine(outputFolder, $"{attributeName}.dll");
549+
var pdbPath = Path.Combine(outputFolder, $"{attributeName}.pdb");
550+
551+
using (var outputStream = File.Create(dllPath))
552+
using (var pdbStream = File.Create(pdbPath))
553+
{
554+
var isWindows = RuntimeInformation.IsOSPlatform(OSPlatform.Windows);
555+
var emitOptions = new EmitOptions(pdbFilePath: pdbPath);
556+
var emitResult = compilation.Emit(outputStream, pdbStream, options: isWindows ? emitOptions : emitOptions.WithDebugInformationFormat(DebugInformationFormat.PortablePdb));
557+
if (!emitResult.Success)
558+
{
559+
var message = "Failure to dynamically create dll";
560+
foreach (var diagnostic in emitResult.Diagnostics)
561+
{
562+
message += Environment.NewLine;
563+
message += diagnostic.GetMessage();
564+
}
565+
throw new Xunit.Sdk.XunitException(message);
566+
}
567+
}
568+
return dllPath;
569+
}
570+
571+
string tempDirectory = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName());
572+
Directory.CreateDirectory(tempDirectory);
573+
disposeAction = () => Directory.Delete(tempDirectory, true);
574+
510575
Mock<FileSystem> partialMockFileSystem = new Mock<FileSystem>();
511576
partialMockFileSystem.CallBase = true;
512577
partialMockFileSystem.Setup(fs => fs.NewFileStream(It.IsAny<string>(), It.IsAny<FileMode>(), It.IsAny<FileAccess>())).Returns((string path, FileMode mode, FileAccess access) =>
513578
{
514-
return new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read);
579+
return new FileStream(path, FileMode.Open, FileAccess.ReadWrite, FileShare.ReadWrite);
515580
});
516581
var loggerMock = new Mock<ILogger>();
517582

518-
string excludedbyattributeDll = Directory.GetFiles(Path.Combine(Directory.GetCurrentDirectory(), "TestAssets"), "coverlet.tests.projectsample.excludedbyattribute.dll").First();
583+
string excludedbyattributeDll = EmitAssemblyToInstrument(tempDirectory);
519584

520585
InstrumentationHelper instrumentationHelper =
521586
new InstrumentationHelper(new ProcessExitHandler(), new RetryHelper(), new FileSystem(), new Mock<ILogger>().Object,
522587
new SourceRootTranslator(new Mock<ILogger>().Object, new FileSystem()));
523588

524589
Instrumenter instrumenter = new Instrumenter(excludedbyattributeDll, "_xunit_excludedbyattribute", Array.Empty<string>(), Array.Empty<string>(), Array.Empty<string>(),
525-
Array.Empty<string>(), Array.Empty<string>(), false, false, loggerMock.Object, instrumentationHelper, partialMockFileSystem.Object, new SourceRootTranslator(loggerMock.Object, new FileSystem()), new CecilSymbolHelper());
590+
excludedAttributes, Array.Empty<string>(), false, false, loggerMock.Object, instrumentationHelper, partialMockFileSystem.Object, new SourceRootTranslator(loggerMock.Object, new FileSystem()), new CecilSymbolHelper());
526591

527592
InstrumenterResult result = instrumenter.Instrument();
528-
Assert.Empty(result.Documents);
529-
loggerMock.Verify(l => l.LogVerbose(It.IsAny<string>()));
593+
if(expectedExcludes)
594+
{
595+
Assert.Empty(result.Documents);
596+
loggerMock.Verify(l => l.LogVerbose(It.IsAny<string>()));
597+
}
598+
else
599+
{
600+
Assert.NotEmpty(result.Documents);
601+
}
530602
}
531603

532604
[Fact]
@@ -710,5 +782,7 @@ public void TestReachabilityHelper()
710782

711783
instrumenterTest.Directory.Delete(true);
712784
}
785+
786+
713787
}
714788
}

0 commit comments

Comments
 (0)