From 585eed2d89dfeb1590cfea82b6556d046bf40cf1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20M=C3=BCller?= Date: Thu, 21 Sep 2023 07:45:49 +0200 Subject: [PATCH] changed algorithem to exclude methods marked with ExcludeFromCodeCoverage attribute --- Documentation/Changelog.md | 1 + coverlet.sln | 11 + .../Instrumentation/Instrumenter.cs | 115 +++------ ...erageTests.ExcludeFromCoverageAttribute.cs | 231 ++++++++++++------ .../Instrumentation/InstrumenterTests.cs | 88 ------- .../Instrumentation.ExcludeFromCoverage.cs | 3 + .../coverlet.core.tests.csproj | 1 - 7 files changed, 209 insertions(+), 241 deletions(-) diff --git a/Documentation/Changelog.md b/Documentation/Changelog.md index d8aa93d5f..a645c81a5 100644 --- a/Documentation/Changelog.md +++ b/Documentation/Changelog.md @@ -7,6 +7,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## Unreleased ### Fixed +-Fix issues with ExcludeFromCodeCoverage attribute [#1484](https://github.com/coverlet-coverage/coverlet/issues/1484) -Fix broken links in documentation [#1514](https://github.com/coverlet-coverage/coverlet/issues/1514) -Fix problem with coverage for .net5 WPF application [#1221](https://github.com/coverlet-coverage/coverlet/issues/1221) by https://github.com/lg2de -Fix unable to instrument module for Microsoft.AspNetCore.Mvc.Razor [#1459](https://github.com/coverlet-coverage/coverlet/issues/1459) by https://github.com/lg2de diff --git a/coverlet.sln b/coverlet.sln index ec2a99ff5..0bde166bf 100644 --- a/coverlet.sln +++ b/coverlet.sln @@ -74,6 +74,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "coverlet.tests.projectsampl EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "coverlet.msbuild.tasks.tests", "test\coverlet.msbuild.tasks.tests\coverlet.msbuild.tasks.tests.csproj", "{351A034E-E642-4DB9-A21D-F71C8151C243}" EndProject +Project("{778DAE3C-4631-46EA-AA77-85C1314464D9}") = "coverlet.tests.projectsample.vbmynamespace", "test\coverlet.tests.projectsample.vbmynamespace\coverlet.tests.projectsample.vbmynamespace.vbproj", "{03400776-1F9A-4326-B927-1CA9B64B42A1}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -168,6 +170,14 @@ Global {351A034E-E642-4DB9-A21D-F71C8151C243}.Debug|Any CPU.Build.0 = Debug|Any CPU {351A034E-E642-4DB9-A21D-F71C8151C243}.Release|Any CPU.ActiveCfg = Release|Any CPU {351A034E-E642-4DB9-A21D-F71C8151C243}.Release|Any CPU.Build.0 = Release|Any CPU + {3ABC2066-D1C5-4CAA-8867-9C5DC777CBF8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {3ABC2066-D1C5-4CAA-8867-9C5DC777CBF8}.Debug|Any CPU.Build.0 = Debug|Any CPU + {3ABC2066-D1C5-4CAA-8867-9C5DC777CBF8}.Release|Any CPU.ActiveCfg = Release|Any CPU + {3ABC2066-D1C5-4CAA-8867-9C5DC777CBF8}.Release|Any CPU.Build.0 = Release|Any CPU + {03400776-1F9A-4326-B927-1CA9B64B42A1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {03400776-1F9A-4326-B927-1CA9B64B42A1}.Debug|Any CPU.Build.0 = Debug|Any CPU + {03400776-1F9A-4326-B927-1CA9B64B42A1}.Release|Any CPU.ActiveCfg = Release|Any CPU + {03400776-1F9A-4326-B927-1CA9B64B42A1}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -195,6 +205,7 @@ Global {988A5FF0-4326-4F5B-9F05-CB165543A555} = {2FEBDE1B-83E3-445B-B9F8-5644B0E0E134} {6ACF69B1-C01F-44A4-8F8E-2501884238D4} = {2FEBDE1B-83E3-445B-B9F8-5644B0E0E134} {F508CCDD-5BC8-4AB6-97B3-D37498813C41} = {2FEBDE1B-83E3-445B-B9F8-5644B0E0E134} + {03400776-1F9A-4326-B927-1CA9B64B42A1} = {2FEBDE1B-83E3-445B-B9F8-5644B0E0E134} {351A034E-E642-4DB9-A21D-F71C8151C243} = {2FEBDE1B-83E3-445B-B9F8-5644B0E0E134} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution diff --git a/src/coverlet.core/Instrumentation/Instrumenter.cs b/src/coverlet.core/Instrumentation/Instrumenter.cs index df43496c9..f2ffca781 100644 --- a/src/coverlet.core/Instrumentation/Instrumenter.cs +++ b/src/coverlet.core/Instrumentation/Instrumenter.cs @@ -47,9 +47,8 @@ internal class Instrumenter private MethodReference _customTrackerRecordHitMethod; private List _excludedSourceFiles; private List _branchesInCompiledGeneratedClass; - private List<(MethodDefinition, int)> _excludedMethods; + private List<(SequencePoint firstSequencePoint, SequencePoint lastSequencePoint)> _excludedMethodSections; private List _excludedLambdaMethods; - private List _excludedCompilerGeneratedTypes; private ReachabilityHelper _reachabilityHelper; public bool SkipModule { get; set; } @@ -242,14 +241,7 @@ private void InstrumentModule() _instrumentationHelper.IsTypeIncluded(_module, type.FullName, _parameters.IncludeFilters) ) { - if (IsSynthesizedMemberToBeExcluded(type)) - { - (_excludedCompilerGeneratedTypes ??= new List()).Add(type.FullName); - } - else - { - InstrumentType(type); - } + InstrumentType(type); } } @@ -467,9 +459,6 @@ private void InstrumentType(TypeDefinition type) { IEnumerable methods = type.GetMethods(); - // We keep ordinal index because it's the way used by compiler for generated types/methods to - // avoid ambiguity - int ordinal = -1; foreach (MethodDefinition method in methods) { MethodDefinition actualMethod = method; @@ -490,18 +479,11 @@ private void InstrumentType(TypeDefinition type) customAttributes = customAttributes.Union(prop.CustomAttributes); } - ordinal++; - if (IsMethodOfCompilerGeneratedClassOfAsyncStateMachineToBeExcluded(method)) { continue; } - if (IsSynthesizedMemberToBeExcluded(method)) - { - continue; - } - if (_excludedLambdaMethods != null && _excludedLambdaMethods.Contains(method.FullName)) { continue; @@ -514,7 +496,9 @@ private void InstrumentType(TypeDefinition type) else { (_excludedLambdaMethods ??= new List()).AddRange(CollectLambdaMethodsInsideLocalFunction(method)); - (_excludedMethods ??= new List<(MethodDefinition, int)>()).Add((method, ordinal)); + _excludedMethodSections ??= new List<(SequencePoint firstSequencePoint, SequencePoint lastSequencePoint)>(); + AnalyzeCompileGeneratedTypesForExcludedMethod(method); + CacheExcludedMethodSection(method); } } @@ -604,7 +588,8 @@ private void InstrumentIL(MethodDefinition method) if (sequencePoint != null && !sequencePoint.IsHidden) { - if (_cecilSymbolHelper.SkipInlineAssignedAutoProperty(_parameters.SkipAutoProps, method, currentInstruction)) + if (_cecilSymbolHelper.SkipInlineAssignedAutoProperty(_parameters.SkipAutoProps, method, + currentInstruction) || IsInsideExcludedMethodSection(sequencePoint)) { index++; continue; @@ -797,72 +782,52 @@ private static MethodBody GetMethodBody(MethodDefinition method) } } - // Check if the member (type or method) is generated by the compiler from a method excluded from code coverage - private bool IsSynthesizedMemberToBeExcluded(IMemberDefinition definition) + private bool IsInsideExcludedMethodSection(SequencePoint sequencePoint) { - if (_excludedMethods is null) - { - return false; - } - - TypeDefinition declaringType = definition.DeclaringType; - - // We check all parent type of current one bottom-up - while (declaringType != null) - { - - // If parent type is excluded return - if (_excludedCompilerGeneratedTypes != null && - _excludedCompilerGeneratedTypes.Any(t => t == declaringType.FullName)) - { - return true; - } + if (_excludedMethodSections is null) return false; + + bool IsInsideExcludedSection(SequencePoint firstSequencePoint, SequencePoint lastSequencePoint) + { + bool isInsideSameSourceFile = sequencePoint.Document.Url.Equals(firstSequencePoint.Document.Url); + bool isInsideExcludedMethod = sequencePoint.StartLine >= Math.Min(firstSequencePoint.StartLine, firstSequencePoint.EndLine) && + sequencePoint.StartLine <= Math.Max(lastSequencePoint.StartLine, lastSequencePoint.EndLine); + return isInsideExcludedMethod && isInsideSameSourceFile; + } + + return _excludedMethodSections + .Where(x => x is { firstSequencePoint: not null, lastSequencePoint: not null }) + .Any(x => IsInsideExcludedSection(x.firstSequencePoint, x.lastSequencePoint)); + } - // Check methods members and compiler generated types - foreach ((MethodDefinition, int) excludedMethods in _excludedMethods) - { - // Exclude this member if declaring type is the same of the excluded method and - // the name is synthesized from the name of the excluded method. - // - if (declaringType.FullName == excludedMethods.Item1.DeclaringType.FullName && - IsSynthesizedNameOf(definition.Name, excludedMethods.Item1.Name, excludedMethods.Item2)) - { - return true; - } - } - declaringType = declaringType.DeclaringType; - } + private void AnalyzeCompileGeneratedTypesForExcludedMethod(MethodDefinition method) + { + var referencedTypes = method.CustomAttributes.Where(x => x.HasConstructorArguments) + .SelectMany(x => x.ConstructorArguments.Select(y => y.Value as TypeDefinition)); - return false; + referencedTypes.ToList().ForEach(x => + x?.Methods.Where(y => y.FullName.Contains("MoveNext")).ToList().ForEach(CacheExcludedMethodSection) + ); } - // Check if the name is synthesized by the compiler - // Refer to https://github.com/dotnet/roslyn/blob/master/src/Compilers/CSharp/Portable/Symbols/Synthesized/GeneratedNames.cs - // to see how the compiler generate names for lambda, local function, yield or async/await expressions - internal bool IsSynthesizedNameOf(string name, string methodName, int methodOrdinal) + private void CacheExcludedMethodSection(MethodDefinition method) { - return - // Lambda method - name.IndexOf($"<{methodName}>b__{methodOrdinal}") != -1 || - // Lambda display class - name.IndexOf($"<>c__DisplayClass{methodOrdinal}_") != -1 || - // State machine - name.IndexOf($"<{methodName}>d__{methodOrdinal}") != -1 || - // Local function - (name.IndexOf($"<{methodName}>g__") != -1 && name.IndexOf($"|{methodOrdinal}_") != -1); + _excludedMethodSections.Add(( + method.DebugInformation.SequencePoints.FirstOrDefault(x => !x.IsHidden), + method.DebugInformation.SequencePoints.LastOrDefault(x => !x.IsHidden))); } private static IEnumerable CollectLambdaMethodsInsideLocalFunction(MethodDefinition methodDefinition) { - if (!methodDefinition.Name.Contains(">g__")) yield break; + if (!methodDefinition.Name.Contains(">g__")) yield break; - foreach (Instruction instruction in methodDefinition.Body.Instructions.ToList()) + foreach (Instruction instruction in methodDefinition.Body.Instructions.ToList()) + { + if (instruction.OpCode == OpCodes.Ldftn && instruction.Operand is MethodReference mr && + mr.Name.Contains(">b__")) { - if (instruction.OpCode == OpCodes.Ldftn && instruction.Operand is MethodReference mr && mr.Name.Contains(">b__")) - { - yield return mr.FullName; - } + yield return mr.FullName; } + } } /// diff --git a/test/coverlet.core.tests/Coverage/CoverageTests.ExcludeFromCoverageAttribute.cs b/test/coverlet.core.tests/Coverage/CoverageTests.ExcludeFromCoverageAttribute.cs index ed8906a5f..e18d3a9d9 100644 --- a/test/coverlet.core.tests/Coverage/CoverageTests.ExcludeFromCoverageAttribute.cs +++ b/test/coverlet.core.tests/Coverage/CoverageTests.ExcludeFromCoverageAttribute.cs @@ -76,18 +76,18 @@ public void ExcludeFromCodeCoverage_CompilerGeneratedMethodsAndTypes() Core.Instrumentation.Document document = result.Document("Instrumentation.ExcludeFromCoverage.cs"); - // Invoking method "Test" of class "MethodsWithExcludeFromCodeCoverageAttr" we expect to cover 100% lines for MethodsWithExcludeFromCodeCoverageAttr - Assert.DoesNotContain(document.Lines, l => - (l.Value.Class == "Coverlet.Core.Samples.Tests.MethodsWithExcludeFromCodeCoverageAttr" || - // Compiler generated - l.Value.Class.StartsWith("Coverlet.Core.Samples.Tests.MethodsWithExcludeFromCodeCoverageAttr/")) && - l.Value.Hits == 0); - // and 0% for MethodsWithExcludeFromCodeCoverageAttr2 - Assert.DoesNotContain(document.Lines, l => - (l.Value.Class == "Coverlet.Core.Samples.Tests.MethodsWithExcludeFromCodeCoverageAttr2" || - // Compiler generated - l.Value.Class.StartsWith("Coverlet.Core.Samples.Tests.MethodsWithExcludeFromCodeCoverageAttr2/")) && - l.Value.Hits == 1); + int[] coveredLines = document.Lines.Where(x => + x.Value.Class == "Coverlet.Core.Samples.Tests.MethodsWithExcludeFromCodeCoverageAttr" || + x.Value.Class.StartsWith("Coverlet.Core.Samples.Tests.MethodsWithExcludeFromCodeCoverageAttr/")) + .Select(x => x.Value.Number).ToArray(); + + int[] notCoveredLines = document.Lines.Where(x => + x.Value.Class == "Coverlet.Core.Samples.Tests.MethodsWithExcludeFromCodeCoverageAttr2" || + x.Value.Class.StartsWith("Coverlet.Core.Samples.Tests.MethodsWithExcludeFromCodeCoverageAttr2/")) + .Select(x => x.Value.Number).ToArray(); + + document.AssertLinesCovered(BuildConfiguration.Debug, coveredLines); + document.AssertLinesNotCovered(BuildConfiguration.Debug, notCoveredLines); } finally { @@ -113,7 +113,8 @@ public void ExcludeFromCodeCoverage_CompilerGeneratedMethodsAndTypes_NestedMembe }, new string[] { path }); - CoverageResult result = TestInstrumentationHelper.GetCoverageResult(path); + CoverageResult result = TestInstrumentationHelper.GetCoverageResult(path) + .GenerateReport(show:true); result.Document("Instrumentation.ExcludeFromCoverage.NestedStateMachines.cs") .AssertLinesCovered(BuildConfiguration.Debug, (14, 1), (15, 1), (16, 1)) @@ -158,30 +159,30 @@ public void ExcludeFromCodeCoverageCompilerGeneratedMethodsAndTypes_Issue670() [Fact] public void ExcludeFromCodeCoverageNextedTypes() { - string path = Path.GetTempFileName(); - try + string path = Path.GetTempFileName(); + try + { + FunctionExecutor.Run(async (string[] pathSerialize) => { - FunctionExecutor.Run(async (string[] pathSerialize) => - { - CoveragePrepareResult coveragePrepareResult = await TestInstrumentationHelper.Run(instance => - { + CoveragePrepareResult coveragePrepareResult = await TestInstrumentationHelper.Run(instance => + { Assert.Equal(42, instance.Run()); return Task.CompletedTask; - }, persistPrepareResultToFile: pathSerialize[0]); - - return 0; - }, new string[] { path }); - - TestInstrumentationHelper.GetCoverageResult(path) - .GenerateReport(show:true) - .Document("Instrumentation.ExcludeFromCoverage.cs") - .AssertLinesCovered(BuildConfiguration.Debug, (145, 1)) - .AssertNonInstrumentedLines(BuildConfiguration.Debug, 146, 160); - } - finally - { - File.Delete(path); - } + }, persistPrepareResultToFile: pathSerialize[0]); + + return 0; + }, new string[] { path }); + + TestInstrumentationHelper.GetCoverageResult(path) + .GenerateReport(show: true) + .Document("Instrumentation.ExcludeFromCoverage.cs") + .AssertLinesCovered(BuildConfiguration.Debug, (148, 1)) + .AssertNonInstrumentedLines(BuildConfiguration.Debug, 153, 163); + } + finally + { + File.Delete(path); + } } [Fact] @@ -223,59 +224,59 @@ public void ExcludeFromCodeCoverage_Issue809() [Fact] public void ExcludeFromCodeCoverageAutoGeneratedGetSet() { - string path = Path.GetTempFileName(); - try - { - FunctionExecutor.Run(async (string[] pathSerialize) => - { - CoveragePrepareResult coveragePrepareResult = await TestInstrumentationHelper.Run(instance => - { - instance.SetId(10); - Assert.Equal(10, instance.Id); - return Task.CompletedTask; - }, persistPrepareResultToFile: pathSerialize[0]); - - return 0; - }, new string[] { path }); - - TestInstrumentationHelper.GetCoverageResult(path) - .Document("Instrumentation.ExcludeFromCoverage.cs") - .AssertNonInstrumentedLines(BuildConfiguration.Debug, 167) - .AssertLinesCovered(BuildConfiguration.Debug, 169); - } - finally + string path = Path.GetTempFileName(); + try + { + FunctionExecutor.Run(async (string[] pathSerialize) => { - File.Delete(path); - } + CoveragePrepareResult coveragePrepareResult = await TestInstrumentationHelper.Run(instance => + { + instance.SetId(10); + Assert.Equal(10, instance.Id); + return Task.CompletedTask; + }, persistPrepareResultToFile: pathSerialize[0]); + + return 0; + }, new string[] { path }); + + TestInstrumentationHelper.GetCoverageResult(path) + .Document("Instrumentation.ExcludeFromCoverage.cs") + .AssertNonInstrumentedLines(BuildConfiguration.Debug, 170) + .AssertLinesCovered(BuildConfiguration.Debug, 172); + } + finally + { + File.Delete(path); + } } [Fact] public void ExcludeFromCodeCoverageAutoGeneratedGet() { - string path = Path.GetTempFileName(); - try + string path = Path.GetTempFileName(); + try + { + FunctionExecutor.Run(async (string[] pathSerialize) => { - FunctionExecutor.Run(async (string[] pathSerialize) => - { - CoveragePrepareResult coveragePrepareResult = await TestInstrumentationHelper.Run(instance => - { + CoveragePrepareResult coveragePrepareResult = await TestInstrumentationHelper.Run(instance => + { instance.SetId(10); Assert.Equal(10, instance.Id); return Task.CompletedTask; - }, persistPrepareResultToFile: pathSerialize[0]); - - return 0; - }, new string[] { path }); - - TestInstrumentationHelper.GetCoverageResult(path) - .Document("Instrumentation.ExcludeFromCoverage.cs") - .AssertNonInstrumentedLines(BuildConfiguration.Debug, 177) - .AssertLinesCovered(BuildConfiguration.Debug, 178, 181); - } - finally - { - File.Delete(path); - } + }, persistPrepareResultToFile: pathSerialize[0]); + + return 0; + }, new string[] { path }); + + TestInstrumentationHelper.GetCoverageResult(path) + .Document("Instrumentation.ExcludeFromCoverage.cs") + .AssertNonInstrumentedLines(BuildConfiguration.Debug, 180) + .AssertLinesCovered(BuildConfiguration.Debug, 181, 184); + } + finally + { + File.Delete(path); + } } [Fact] @@ -304,5 +305,81 @@ public void ExcludeFromCodeCoverage_Issue1302() File.Delete(path); } } + + [Fact] + public void MethodsWithExcludeFromCodeCoverageAttr() + { + string path = Path.GetTempFileName(); + try + { + FunctionExecutor.Run(async (string[] pathSerialize) => + { + CoveragePrepareResult coveragePrepareResult = + await TestInstrumentationHelper.Run(instance => + { + instance.TestLambda(string.Empty); + instance.TestLambda(string.Empty, 1); + foreach (dynamic _ in instance.TestYield("abc")) ; + foreach (dynamic _ in instance.TestYield("abc", 1)) ; + instance.TestLocalFunction(string.Empty); + instance.TestLocalFunction(string.Empty, 1); + ((Task)instance.TestAsyncAwait()).ConfigureAwait(false).GetAwaiter().GetResult(); + ((Task)instance.TestAsyncAwait(1)).ConfigureAwait(false).GetAwaiter().GetResult(); + return Task.CompletedTask; + }, + persistPrepareResultToFile: pathSerialize[0]); + + return 0; + }, new string[] { path }); + + TestInstrumentationHelper.GetCoverageResult(path) + .GenerateReport(show: true) + .Document("Instrumentation.ExcludeFromCoverage.cs") + .AssertNonInstrumentedLines(BuildConfiguration.Debug, 15, 16, 28, 29, 30, 31, 45, 56, 58, 59, 60, 61) + .AssertLinesCovered(BuildConfiguration.Debug, 21, 22, 36, 37, 38, 39, 50, 66, 69, 70, 71); + } + finally + { + File.Delete(path); + } + } + + [Fact] + public void MethodsWithExcludeFromCodeCoverageAttr2() + { + string path = Path.GetTempFileName(); + try + { + FunctionExecutor.Run(async (string[] pathSerialize) => + { + CoveragePrepareResult coveragePrepareResult = + await TestInstrumentationHelper.Run(instance => + { + instance.TestLambda(string.Empty); + instance.TestLambda(string.Empty, 1); + foreach (dynamic _ in instance.TestYield("abc")) ; + foreach (dynamic _ in instance.TestYield("abc", 1)) ; + instance.TestLocalFunction(string.Empty); + instance.TestLocalFunction(string.Empty, 1); + ((Task)instance.TestAsyncAwait()).ConfigureAwait(false).GetAwaiter().GetResult(); + ((Task)instance.TestAsyncAwait(1)).ConfigureAwait(false).GetAwaiter().GetResult(); + return Task.CompletedTask; + }, + persistPrepareResultToFile: pathSerialize[0]); + + return 0; + }, new string[] { path }); + + TestInstrumentationHelper.GetCoverageResult(path) + .GenerateReport(show: true) + .Document("Instrumentation.ExcludeFromCoverage.cs") + .AssertNonInstrumentedLines(BuildConfiguration.Debug, 92, 93, 107, 108, 109, 110, 121, 137, 140, 141, 142) + .AssertLinesCovered(BuildConfiguration.Debug, 85, 86, 98, 99, 100, 101, 115, 126, 129, 130, 131); + } + finally + { + File.Delete(path); + } } + } } diff --git a/test/coverlet.core.tests/Instrumentation/InstrumenterTests.cs b/test/coverlet.core.tests/Instrumentation/InstrumenterTests.cs index 50b6a867c..1a2b40491 100644 --- a/test/coverlet.core.tests/Instrumentation/InstrumenterTests.cs +++ b/test/coverlet.core.tests/Instrumentation/InstrumenterTests.cs @@ -621,94 +621,6 @@ public void TestInstrument_NetstandardAwareAssemblyResolver_PreserveCompilationC Assert.NotNull(asm); } - [Fact] - public void TestInstrument_LambdaInsideMethodWithExcludeAttributeAreExcluded() - { - InstrumenterTest instrumenterTest = CreateInstrumentor(); - InstrumenterResult result = instrumenterTest.Instrumenter.Instrument(); - - Document doc = result.Documents.Values.FirstOrDefault(d => Path.GetFileName(d.Path) == "Instrumentation.ExcludeFromCoverage.cs"); - Assert.NotNull(doc); - - Assert.Contains(doc.Lines.Values, l => l.Method == "System.Int32 Coverlet.Core.Samples.Tests.MethodsWithExcludeFromCodeCoverageAttr::TestLambda(System.String,System.Int32)"); - Assert.DoesNotContain(doc.Lines.Values, l => l.Method == "System.Int32 Coverlet.Core.Samples.Tests.MethodsWithExcludeFromCodeCoverageAttr::TestLambda(System.String)"); - Assert.DoesNotContain(doc.Lines.Values, l => l.Class.StartsWith("Coverlet.Core.Samples.Tests.MethodsWithExcludeFromCodeCoverageAttr/") && - instrumenterTest.Instrumenter.IsSynthesizedNameOf(l.Method, "TestLambda", 0)); - Assert.DoesNotContain(doc.Lines.Values, l => l.Method == "System.Int32 Coverlet.Core.Samples.Tests.MethodsWithExcludeFromCodeCoverageAttr2::TestLambda(System.String,System.Int32)"); - Assert.DoesNotContain(doc.Lines.Values, l => l.Class.StartsWith("Coverlet.Core.Samples.Tests.MethodsWithExcludeFromCodeCoverageAttr2/") && - instrumenterTest.Instrumenter.IsSynthesizedNameOf(l.Method, "TestLambda", 1)); - Assert.Contains(doc.Lines.Values, l => l.Method == "System.Int32 Coverlet.Core.Samples.Tests.MethodsWithExcludeFromCodeCoverageAttr2::TestLambda(System.String)"); - - instrumenterTest.Directory.Delete(true); - } - - [Fact] - public void TestInstrument_LocalFunctionInsideMethodWithExcludeAttributeAreExcluded() - { - InstrumenterTest instrumenterTest = CreateInstrumentor(); - InstrumenterResult result = instrumenterTest.Instrumenter.Instrument(); - - Document doc = result.Documents.Values.FirstOrDefault(d => Path.GetFileName(d.Path) == "Instrumentation.ExcludeFromCoverage.cs"); - Assert.NotNull(doc); - - Assert.Contains(doc.Lines.Values, l => l.Method == "System.Int32 Coverlet.Core.Samples.Tests.MethodsWithExcludeFromCodeCoverageAttr::TestLocalFunction(System.String,System.Int32)"); - Assert.DoesNotContain(doc.Lines.Values, l => l.Method == "System.Int32 Coverlet.Core.Samples.Tests.MethodsWithExcludeFromCodeCoverageAttr::TestLocalFunction(System.String)"); - Assert.DoesNotContain(doc.Lines.Values, l => l.Class == "Coverlet.Core.Samples.Tests.MethodsWithExcludeFromCodeCoverageAttr" && - instrumenterTest.Instrumenter.IsSynthesizedNameOf(l.Method, "TestLocalFunction", 6)); - Assert.Contains(doc.Lines.Values, l => l.Class == "Coverlet.Core.Samples.Tests.MethodsWithExcludeFromCodeCoverageAttr" && - instrumenterTest.Instrumenter.IsSynthesizedNameOf(l.Method, "TestLocalFunction", 7)); - Assert.DoesNotContain(doc.Lines.Values, l => l.Method == "System.Int32 Coverlet.Core.Samples.Tests.MethodsWithExcludeFromCodeCoverageAttr2::TestLocalFunction(System.String,System.Int32)"); - Assert.DoesNotContain(doc.Lines.Values, l => l.Class == "Coverlet.Core.Samples.Tests.MethodsWithExcludeFromCodeCoverageAttr2" && - instrumenterTest.Instrumenter.IsSynthesizedNameOf(l.Method, "TestLocalFunction", 7)); - Assert.Contains(doc.Lines.Values, l => l.Method == "System.Int32 Coverlet.Core.Samples.Tests.MethodsWithExcludeFromCodeCoverageAttr2::TestLocalFunction(System.String)"); - Assert.Contains(doc.Lines.Values, l => l.Class == "Coverlet.Core.Samples.Tests.MethodsWithExcludeFromCodeCoverageAttr2" && - instrumenterTest.Instrumenter.IsSynthesizedNameOf(l.Method, "TestLocalFunction", 6)); - - instrumenterTest.Directory.Delete(true); - } - - [Fact] - public void TestInstrument_YieldInsideMethodWithExcludeAttributeAreExcluded() - { - InstrumenterTest instrumenterTest = CreateInstrumentor(); - InstrumenterResult result = instrumenterTest.Instrumenter.Instrument(); - - Document doc = result.Documents.Values.FirstOrDefault(d => Path.GetFileName(d.Path) == "Instrumentation.ExcludeFromCoverage.cs"); - Assert.NotNull(doc); - - Assert.DoesNotContain(doc.Lines.Values, l => l.Class.StartsWith("Coverlet.Core.Samples.Tests.MethodsWithExcludeFromCodeCoverageAttr/") && - instrumenterTest.Instrumenter.IsSynthesizedNameOf(l.Method, "TestYield", 2)); - Assert.Contains(doc.Lines.Values, l => l.Class.StartsWith("Coverlet.Core.Samples.Tests.MethodsWithExcludeFromCodeCoverageAttr/") && - instrumenterTest.Instrumenter.IsSynthesizedNameOf(l.Method, "TestYield", 3)); - Assert.Contains(doc.Lines.Values, l => l.Class.StartsWith("Coverlet.Core.Samples.Tests.MethodsWithExcludeFromCodeCoverageAttr2/") && - instrumenterTest.Instrumenter.IsSynthesizedNameOf(l.Method, "TestYield", 2)); - Assert.DoesNotContain(doc.Lines.Values, l => l.Class.StartsWith("Coverlet.Core.Samples.Tests.MethodsWithExcludeFromCodeCoverageAttr2/") && - instrumenterTest.Instrumenter.IsSynthesizedNameOf(l.Method, "TestYield", 3)); - - instrumenterTest.Directory.Delete(true); - } - - [Fact] - public void TestInstrument_AsyncAwaitInsideMethodWithExcludeAttributeAreExcluded() - { - InstrumenterTest instrumenterTest = CreateInstrumentor(); - InstrumenterResult result = instrumenterTest.Instrumenter.Instrument(); - - Document doc = result.Documents.Values.FirstOrDefault(d => Path.GetFileName(d.Path) == "Instrumentation.ExcludeFromCoverage.cs"); - Assert.NotNull(doc); - - Assert.DoesNotContain(doc.Lines.Values, l => l.Class.StartsWith("Coverlet.Core.Samples.Tests.MethodsWithExcludeFromCodeCoverageAttr/") && - instrumenterTest.Instrumenter.IsSynthesizedNameOf(l.Method, "TestAsyncAwait", 4)); - Assert.Contains(doc.Lines.Values, l => l.Class.StartsWith("Coverlet.Core.Samples.Tests.MethodsWithExcludeFromCodeCoverageAttr/") && - instrumenterTest.Instrumenter.IsSynthesizedNameOf(l.Method, "TestAsyncAwait", 5)); - Assert.Contains(doc.Lines.Values, l => l.Class.StartsWith("Coverlet.Core.Samples.Tests.MethodsWithExcludeFromCodeCoverageAttr2/") && - instrumenterTest.Instrumenter.IsSynthesizedNameOf(l.Method, "TestAsyncAwait", 4)); - Assert.DoesNotContain(doc.Lines.Values, l => l.Class.StartsWith("Coverlet.Core.Samples.Tests.MethodsWithExcludeFromCodeCoverageAttr2/") && - instrumenterTest.Instrumenter.IsSynthesizedNameOf(l.Method, "TestAsyncAwait", 5)); - - instrumenterTest.Directory.Delete(true); - } - [Fact] public void TestReachabilityHelper() { diff --git a/test/coverlet.core.tests/Samples/Instrumentation.ExcludeFromCoverage.cs b/test/coverlet.core.tests/Samples/Instrumentation.ExcludeFromCoverage.cs index 46151d572..1f2d2fe00 100644 --- a/test/coverlet.core.tests/Samples/Instrumentation.ExcludeFromCoverage.cs +++ b/test/coverlet.core.tests/Samples/Instrumentation.ExcludeFromCoverage.cs @@ -6,9 +6,12 @@ namespace Coverlet.Core.Samples.Tests { public class MethodsWithExcludeFromCodeCoverageAttr { + private string _fieldToInfluenceSynthesizedName; + [System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public int TestLambda(string input) { + _fieldToInfluenceSynthesizedName = string.Empty; System.Func lambdaFunc = s => s.Length; return lambdaFunc(input); } diff --git a/test/coverlet.core.tests/coverlet.core.tests.csproj b/test/coverlet.core.tests/coverlet.core.tests.csproj index d62220efa..a15e95a14 100644 --- a/test/coverlet.core.tests/coverlet.core.tests.csproj +++ b/test/coverlet.core.tests/coverlet.core.tests.csproj @@ -8,7 +8,6 @@ NU1702;NU1504;NU1008 true - false