From 04b82c23d8e2f5a37ff3f0648dc1aea120859332 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20M=C3=BCller?= Date: Thu, 19 Jan 2023 19:49:32 +0100 Subject: [PATCH] Incorrect coverage for methods returning IAsyncEnumerable in generic classes (#1430) --- Documentation/Changelog.md | 1 + .../Symbols/CecilSymbolHelper.cs | 6 ++- .../CoverageTests.GenericAsyncIterator.cs | 42 +++++++++++++++++++ .../Coverage/InstrumenterHelper.cs | 13 +++++- .../Instrumentation.GenericAsyncIterator.cs | 25 +++++++++++ 5 files changed, 84 insertions(+), 3 deletions(-) create mode 100644 test/coverlet.core.tests/Coverage/CoverageTests.GenericAsyncIterator.cs create mode 100644 test/coverlet.core.tests/Samples/Instrumentation.GenericAsyncIterator.cs diff --git a/Documentation/Changelog.md b/Documentation/Changelog.md index ad6acfc0a..af26e722c 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 +-Incorrect coverage for methods returning IAsyncEnumerable in generic classes [#1383](https://github.com/coverlet-coverage/coverlet/issues/1383) -Allign published nuget package version to github release version [#1413](https://github.com/coverlet-coverage/coverlet/issues/1413) -Sync nuget and github release versions [#1122](https://github.com/coverlet-coverage/coverlet/issues/1122) diff --git a/src/coverlet.core/Symbols/CecilSymbolHelper.cs b/src/coverlet.core/Symbols/CecilSymbolHelper.cs index f5f8458b0..ec77c9fe2 100644 --- a/src/coverlet.core/Symbols/CecilSymbolHelper.cs +++ b/src/coverlet.core/Symbols/CecilSymbolHelper.cs @@ -877,8 +877,10 @@ static bool DisposeCheck(List instructions, Instruction instruction if (currentIndex >= 2 && instructions[currentIndex - 1].OpCode == OpCodes.Ldfld && - instructions[currentIndex - 1].Operand is FieldDefinition field && - IsCompilerGenerated(field) && field.FullName.EndsWith("__disposeMode") && + ( + (instructions[currentIndex - 1].Operand is FieldDefinition field && IsCompilerGenerated(field) && field.FullName.EndsWith("__disposeMode")) || + (instructions[currentIndex - 1].Operand is FieldReference fieldRef && IsCompilerGenerated(fieldRef.Resolve()) && fieldRef.FullName.EndsWith("__disposeMode")) + ) && (instructions[currentIndex - 2].OpCode == OpCodes.Ldarg || instructions[currentIndex - 2].OpCode == OpCodes.Ldarg_0)) { diff --git a/test/coverlet.core.tests/Coverage/CoverageTests.GenericAsyncIterator.cs b/test/coverlet.core.tests/Coverage/CoverageTests.GenericAsyncIterator.cs new file mode 100644 index 000000000..8aa18b285 --- /dev/null +++ b/test/coverlet.core.tests/Coverage/CoverageTests.GenericAsyncIterator.cs @@ -0,0 +1,42 @@ +// Copyright (c) Toni Solarin-Sodara +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System.Collections.Generic; +using System.IO; +using System.Threading.Tasks; +using Coverlet.Core.Samples.Tests; +using Xunit; + +namespace Coverlet.Core.Tests +{ + public partial class CoverageTests + { + [Fact] + public void GenericAsyncIterator() + { + string path = Path.GetTempFileName(); + try + { + FunctionExecutor.Run(async (string[] pathSerialize) => + { + CoveragePrepareResult coveragePrepareResult = await TestInstrumentationHelper.Run>(instance => + { + List res = ((Task>)instance.Issue1383()).GetAwaiter().GetResult(); + + return Task.CompletedTask; + }, persistPrepareResultToFile: pathSerialize[0]); + return 0; + }, new string[] { path }); + + TestInstrumentationHelper.GetCoverageResult(path) + .Document("Instrumentation.GenericAsyncIterator.cs") + .AssertLinesCovered(BuildConfiguration.Debug, (13, 1), (14, 1), (20, 1), (21, 1), (22, 1)) + .ExpectedTotalNumberOfBranches(BuildConfiguration.Debug, 0); + } + finally + { + File.Delete(path); + } + } + } +} diff --git a/test/coverlet.core.tests/Coverage/InstrumenterHelper.cs b/test/coverlet.core.tests/Coverage/InstrumenterHelper.cs index 8cb5eca7f..fd39d8289 100644 --- a/test/coverlet.core.tests/Coverage/InstrumenterHelper.cs +++ b/test/coverlet.core.tests/Coverage/InstrumenterHelper.cs @@ -101,7 +101,7 @@ public static async Task Run(Func callM IncludeFilters = (includeFilter is null ? defaultFilters(fileName) : includeFilter(fileName)).Concat( new string[] { - $"[{Path.GetFileNameWithoutExtension(fileName)}*]{typeof(T).FullName}*" + $"[{Path.GetFileNameWithoutExtension(fileName)}*]{GetTypeFullName()}*" }).ToArray(), IncludeDirectories = Array.Empty(), ExcludeFilters = (excludeFilter is null ? defaultFilters(fileName) : excludeFilter(fileName)).Concat(new string[] @@ -180,6 +180,17 @@ private static void SetTestContainer(string testModule = null, bool disableResto return serviceCollection.BuildServiceProvider(); }); } + + private static string GetTypeFullName() + { + string name = typeof(T).FullName; + if (typeof(T).IsGenericType && name != null) + { + int index = name.IndexOf('`'); + return index == -1 ? name : name[..index]; + } + return name; + } } class CustomProcessExitHandler : IProcessExitHandler diff --git a/test/coverlet.core.tests/Samples/Instrumentation.GenericAsyncIterator.cs b/test/coverlet.core.tests/Samples/Instrumentation.GenericAsyncIterator.cs new file mode 100644 index 000000000..cc6311506 --- /dev/null +++ b/test/coverlet.core.tests/Samples/Instrumentation.GenericAsyncIterator.cs @@ -0,0 +1,25 @@ +// Remember to use full name because adding new using directives change line numbers + +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace Coverlet.Core.Samples.Tests +{ + public class GenericAsyncIterator + { + public async Task> Issue1383() + { + var sequence = await CreateSequenceAsync().ToListAsync(); + return sequence; + } + + + public async IAsyncEnumerable CreateSequenceAsync() + { + await Task.CompletedTask; + yield return 5; + yield return 2; + } + } +}