@@ -7,6 +7,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
## Unreleased
|
||||
|
||||
### Fixed
|
||||
-Fix wrong branch coverage with EnumeratorCancellation attribute [#1275](https://github.com/coverlet-coverage/coverlet/issues/1275)
|
||||
-Fix negative coverage exceeding int.MaxValue [#1266](https://github.com/coverlet-coverage/coverlet/issues/1266)
|
||||
-Fix summary output format for culture de-DE [#1263](https://github.com/coverlet-coverage/coverlet/issues/1263)
|
||||
-Fix branch coverage issue for finally block with await [#1233](https://github.com/coverlet-coverage/coverlet/issues/1233)
|
||||
|
||||
@@ -855,7 +855,6 @@ namespace Coverlet.Core.Symbols
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
static bool DisposeCheck(List<Instruction> instructions, Instruction instruction, int currentIndex)
|
||||
{
|
||||
// Within the compiler-generated async iterator, there are at least a
|
||||
@@ -891,6 +890,40 @@ namespace Coverlet.Core.Symbols
|
||||
}
|
||||
}
|
||||
|
||||
private bool SkipGeneratedBranchesForEnumeratorCancellationAttribute(List<Instruction> instructions, Instruction instruction)
|
||||
{
|
||||
// For async-enumerable methods an additional cancellation token despite the default one can be passed.
|
||||
// The EnumeratorCancellation attribute marks the parameter whose value is received by GetAsyncEnumerator(CancellationToken).
|
||||
// Therefore the compiler generates the field x__combinedTokens and generates some additional branch points.
|
||||
//
|
||||
// IL_0118: ldarg.0
|
||||
// IL_0119: ldfld class [System.Runtime]System.Threading.CancellationTokenSource Issue1275.AwaitForeachReproduction/'<AsyncEnumerable>d__1'::'<>x__combinedTokens'
|
||||
// IL_011E: brfalse.s IL_0133
|
||||
//
|
||||
// We'll eliminate these wherever they appear. It's reasonable to just look for a "brfalse" or "brfalse.s" instruction, preceded
|
||||
// immediately by "ldfld" of the compiler-generated "<>x__combinedTokens" field.
|
||||
|
||||
int branchIndex = instructions.BinarySearch(instruction, new InstructionByOffsetComparer());
|
||||
|
||||
if (instruction.OpCode != OpCodes.Brfalse &&
|
||||
instruction.OpCode != OpCodes.Brfalse_S)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (branchIndex >= 2 &&
|
||||
instructions[branchIndex - 1].OpCode == OpCodes.Ldfld &&
|
||||
instructions[branchIndex - 1].Operand is FieldDefinition field &&
|
||||
field.FieldType.FullName.Equals("System.Threading.CancellationTokenSource") &&
|
||||
field.FullName.EndsWith("x__combinedTokens") &&
|
||||
(instructions[branchIndex - 2].OpCode == OpCodes.Ldarg ||
|
||||
instructions[branchIndex - 2].OpCode == OpCodes.Ldarg_0))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// https://github.com/dotnet/roslyn/blob/master/docs/compilers/CSharp/Expression%20Breakpoints.md
|
||||
private static bool SkipExpressionBreakpointsBranches(Instruction instruction) => instruction.Previous is not null && instruction.Previous.OpCode == OpCodes.Ldc_I4 &&
|
||||
instruction.Previous.Operand is int operandValue && operandValue == 1 &&
|
||||
@@ -973,6 +1006,11 @@ namespace Coverlet.Core.Symbols
|
||||
}
|
||||
}
|
||||
|
||||
if (SkipGeneratedBranchesForEnumeratorCancellationAttribute(instructions, instruction))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (SkipBranchGeneratedExceptionFilter(instruction, methodDefinition))
|
||||
{
|
||||
continue;
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
using Coverlet.Core.Samples.Tests;
|
||||
@@ -182,5 +183,35 @@ namespace Coverlet.Core.Tests
|
||||
File.Delete(path);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AsyncAwait_Issue_1275()
|
||||
{
|
||||
string path = Path.GetTempFileName();
|
||||
try
|
||||
{
|
||||
FunctionExecutor.Run(async (string[] pathSerialize) =>
|
||||
{
|
||||
CoveragePrepareResult coveragePrepareResult = await TestInstrumentationHelper.Run<Issue_1275>(instance =>
|
||||
{
|
||||
var cts = new CancellationTokenSource();
|
||||
((Task)instance.Execute(cts.Token)).ConfigureAwait(false).GetAwaiter().GetResult();
|
||||
return Task.CompletedTask;
|
||||
},
|
||||
persistPrepareResultToFile: pathSerialize[0]);
|
||||
|
||||
return 0;
|
||||
}, new string[] { path });
|
||||
|
||||
var document = TestInstrumentationHelper.GetCoverageResult(path).Document("Instrumentation.AsyncAwait.cs");
|
||||
document.AssertLinesCoveredFromTo(BuildConfiguration.Debug, 170, 176);
|
||||
document.AssertBranchesCovered(BuildConfiguration.Debug, (171, 0, 1), (171, 1, 1));
|
||||
Assert.DoesNotContain(document.Branches, x => x.Key.Line == 176);
|
||||
}
|
||||
finally
|
||||
{
|
||||
File.Delete(path);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -151,4 +151,28 @@ namespace Coverlet.Core.Samples.Tests
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class Issue_1275
|
||||
{
|
||||
public async Task<int> Execute(System.Threading.CancellationToken token)
|
||||
{
|
||||
int sum = 0;
|
||||
|
||||
await foreach (int result in AsyncEnumerable(token))
|
||||
{
|
||||
sum += result;
|
||||
}
|
||||
|
||||
return sum;
|
||||
}
|
||||
|
||||
async System.Collections.Generic.IAsyncEnumerable<int> AsyncEnumerable([System.Runtime.CompilerServices.EnumeratorCancellation] System.Threading.CancellationToken cancellationToken)
|
||||
{
|
||||
for (int i = 0; i < 1; i++)
|
||||
{
|
||||
await Task.Delay(1, cancellationToken);
|
||||
yield return i;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user