Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
5 changes: 5 additions & 0 deletions Documentation/Changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,11 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## Unreleased

### Fixed
-ExcludeFromCodeCoverage attribute on local functions ignores lambda expression [#1302](https://github.com/coverlet-coverage/coverlet/issues/1302)

## Release date 2022-02-06
### Packages
coverlet.msbuild 3.1.2
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();
}
}
}