Skip to content

ExcludeFromCodeCoverage attribute on local functions ignores lambda expression #1306

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Documentation/Changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Fixed
-Await foreach has wrong branch coverage when method is generic [#1210](https://github.com/coverlet-coverage/coverlet/issues/1210)
-ExcludeFromCodeCoverage attribute on local functions ignores lambda expression [#1302](https://github.com/coverlet-coverage/coverlet/issues/1302)

## Release date 2022-02-06
### Packages
Expand Down
20 changes: 20 additions & 0 deletions src/coverlet.core/Instrumentation/Instrumenter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ internal class Instrumenter
private List<string> _excludedSourceFiles;
private List<string> _branchesInCompiledGeneratedClass;
private List<(MethodDefinition, int)> _excludedMethods;
private List<string> _excludedLambdaMethods;
private List<string> _excludedCompilerGeneratedTypes;
private readonly string[] _doesNotReturnAttributes;
private ReachabilityHelper _reachabilityHelper;
Expand Down Expand Up @@ -500,12 +501,18 @@ private void InstrumentType(TypeDefinition type)
continue;
}

if (_excludedLambdaMethods != null && _excludedLambdaMethods.Contains(method.FullName))
{
continue;
}

if (!customAttributes.Any(IsExcludeAttribute))
{
InstrumentMethod(method);
}
else
{
(_excludedLambdaMethods ??= new List<string>()).AddRange(CollectLambdaMethodsInsideLocalFunction(method));
(_excludedMethods ??= new List<(MethodDefinition, int)>()).Add((method, ordinal));
}
}
Expand Down Expand Up @@ -842,6 +849,19 @@ internal bool IsSynthesizedNameOf(string name, string methodName, int methodOrdi
(name.IndexOf($"<{methodName}>g__") != -1 && name.IndexOf($"|{methodOrdinal}_") != -1);
}

private static IEnumerable<string> CollectLambdaMethodsInsideLocalFunction(MethodDefinition methodDefinition)
{
if (!methodDefinition.Name.Contains(">g__")) yield break;

foreach (Instruction instruction in methodDefinition.Body.Instructions.ToList())
{
if (instruction.OpCode == OpCodes.Ldftn && instruction.Operand is MethodReference mr && mr.Name.Contains(">b__"))
{
yield return mr.FullName;
}
}
}

/// <summary>
/// A custom importer created specifically to allow the instrumentation of System.Private.CoreLib by
/// removing the external references to netstandard that are generated when instrumenting a typical
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -277,5 +277,32 @@ public void ExcludeFromCodeCoverageAutoGeneratedGet()
File.Delete(path);
}
}

[Fact]
public void ExcludeFromCodeCoverage_Issue1302()
{
string path = Path.GetTempFileName();
try
{
FunctionExecutor.Run(async (string[] pathSerialize) =>
{
CoveragePrepareResult coveragePrepareResult = await TestInstrumentationHelper.Run<Issue1302>(instance =>
{
instance.Run();
return Task.CompletedTask;
}, persistPrepareResultToFile: pathSerialize[0]);

return 0;
}, new string[] { path });

TestInstrumentationHelper.GetCoverageResult(path)
.Document("Instrumentation.ExcludeFromCoverage.Issue1302.cs")
.AssertNonInstrumentedLines(BuildConfiguration.Debug, 10, 13);
}
finally
{
File.Delete(path);
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
using System;

namespace Coverlet.Core.Samples.Tests
{
public class Issue1302
{
public void Run()
{
[System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
static Func<string, bool> LocalFunction()
{
return myString => myString.Length == 10;
}

LocalFunction();
}
}
}