diff --git a/Documentation/Changelog.md b/Documentation/Changelog.md index d8aa93d5f..45fdb19da 100644 --- a/Documentation/Changelog.md +++ b/Documentation/Changelog.md @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## Unreleased ### Fixed +-Fix ExcludeFromCodeCoverage does not exclude method in a partial class [#1548](https://github.com/coverlet-coverage/coverlet/issues/1548) +-Fix ExcludeFromCodeCoverage does not exclude F# task [#1547](https://github.com/coverlet-coverage/coverlet/issues/1547) +-Fix issues where ExcludeFromCodeCoverage ignored [#1431](https://github.com/coverlet-coverage/coverlet/issues/1431) +-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 606eb40e7..f6f5f9379 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,59 +782,38 @@ 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; + if (_excludedMethodSections is null) return false; - // We check all parent type of current one bottom-up - while (declaringType != null) + 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; + } - // If parent type is excluded return - if (_excludedCompilerGeneratedTypes != null && - _excludedCompilerGeneratedTypes.Any(t => t == declaringType.FullName)) - { - return true; - } + 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) + { + IEnumerable 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) @@ -858,7 +822,8 @@ private static IEnumerable CollectLambdaMethodsInsideLocalFunction(Metho 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; } diff --git a/test/coverlet.core.tests/Coverage/CoverageTests.ExcludeFromCoverageAttribute.cs b/test/coverlet.core.tests/Coverage/CoverageTests.ExcludeFromCoverageAttribute.cs index d8f0800c9..cc0bd2549 100644 --- a/test/coverlet.core.tests/Coverage/CoverageTests.ExcludeFromCoverageAttribute.cs +++ b/test/coverlet.core.tests/Coverage/CoverageTests.ExcludeFromCoverageAttribute.cs @@ -65,7 +65,7 @@ public void ExcludeFromCodeCoverage_CompilerGeneratedMethodsAndTypes() CoveragePrepareResult coveragePrepareResult = await TestInstrumentationHelper.Run(async instance => { await (Task)instance.Test("test"); - }, persistPrepareResultToFile: pathSerialize[0]); + }, persistPrepareResultToFile: pathSerialize[0]); return 0; @@ -75,18 +75,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 { @@ -104,15 +104,16 @@ public void ExcludeFromCodeCoverage_CompilerGeneratedMethodsAndTypes_NestedMembe { CoveragePrepareResult coveragePrepareResult = await TestInstrumentationHelper.Run(instance => { - instance.Test(); - return Task.CompletedTask; - }, persistPrepareResultToFile: pathSerialize[0]); + instance.Test(); + return Task.CompletedTask; + }, persistPrepareResultToFile: pathSerialize[0]); return 0; }, 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)) @@ -134,9 +135,9 @@ public void ExcludeFromCodeCoverageCompilerGeneratedMethodsAndTypes_Issue670() { CoveragePrepareResult coveragePrepareResult = await TestInstrumentationHelper.Run(instance => { - instance.Test("test"); - return Task.CompletedTask; - }, persistPrepareResultToFile: pathSerialize[0]); + instance.Test("test"); + return Task.CompletedTask; + }, persistPrepareResultToFile: pathSerialize[0]); return 0; @@ -174,8 +175,8 @@ public void ExcludeFromCodeCoverageNextedTypes() TestInstrumentationHelper.GetCoverageResult(path) .GenerateReport(show: true) .Document("Instrumentation.ExcludeFromCoverage.cs") - .AssertLinesCovered(BuildConfiguration.Debug, (145, 1)) - .AssertNonInstrumentedLines(BuildConfiguration.Debug, 146, 160); + .AssertLinesCovered(BuildConfiguration.Debug, (148, 1)) + .AssertNonInstrumentedLines(BuildConfiguration.Debug, 153, 163); } finally { @@ -193,8 +194,8 @@ public void ExcludeFromCodeCoverage_Issue809() { CoveragePrepareResult coveragePrepareResult = await TestInstrumentationHelper.Run(async instance => { - Assert.True(await ((Task)instance.EditTask(null, 10))); - }, persistPrepareResultToFile: pathSerialize[0]); + Assert.True(await (Task)instance.EditTask(null, 10)); + }, persistPrepareResultToFile: pathSerialize[0]); return 0; }, new string[] { path }); @@ -227,19 +228,19 @@ public void ExcludeFromCodeCoverageAutoGeneratedGetSet() 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]); + { + 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); + .AssertNonInstrumentedLines(BuildConfiguration.Debug, 170) + .AssertLinesCovered(BuildConfiguration.Debug, 172); } finally { @@ -267,8 +268,8 @@ public void ExcludeFromCodeCoverageAutoGeneratedGet() TestInstrumentationHelper.GetCoverageResult(path) .Document("Instrumentation.ExcludeFromCoverage.cs") - .AssertNonInstrumentedLines(BuildConfiguration.Debug, 177) - .AssertLinesCovered(BuildConfiguration.Debug, 178, 181); + .AssertNonInstrumentedLines(BuildConfiguration.Debug, 180) + .AssertLinesCovered(BuildConfiguration.Debug, 181, 184); } finally { @@ -286,9 +287,9 @@ public void ExcludeFromCodeCoverage_Issue1302() { CoveragePrepareResult coveragePrepareResult = await TestInstrumentationHelper.Run(instance => { - instance.Run(); - return Task.CompletedTask; - }, persistPrepareResultToFile: pathSerialize[0]); + instance.Run(); + return Task.CompletedTask; + }, persistPrepareResultToFile: pathSerialize[0]); return 0; }, new string[] { path }); @@ -302,5 +303,79 @@ 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(async 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); + await (Task)instance.TestAsyncAwait(); + await (Task)instance.TestAsyncAwait(1); + }, + 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(async 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); + await (Task)instance.TestAsyncAwait(); + await (Task)instance.TestAsyncAwait(1); + }, + 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); }