Skip to content

Commit 04b82c2

Browse files
authored
Incorrect coverage for methods returning IAsyncEnumerable in generic classes (#1430)
1 parent 72d5ee2 commit 04b82c2

File tree

5 files changed

+84
-3
lines changed

5 files changed

+84
-3
lines changed

Documentation/Changelog.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77
## Unreleased
88

99
### Fixed
10+
-Incorrect coverage for methods returning IAsyncEnumerable in generic classes [#1383](https://github.com/coverlet-coverage/coverlet/issues/1383)
1011
-Allign published nuget package version to github release version [#1413](https://github.com/coverlet-coverage/coverlet/issues/1413)
1112
-Sync nuget and github release versions [#1122](https://github.com/coverlet-coverage/coverlet/issues/1122)
1213

src/coverlet.core/Symbols/CecilSymbolHelper.cs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -877,8 +877,10 @@ static bool DisposeCheck(List<Instruction> instructions, Instruction instruction
877877

878878
if (currentIndex >= 2 &&
879879
instructions[currentIndex - 1].OpCode == OpCodes.Ldfld &&
880-
instructions[currentIndex - 1].Operand is FieldDefinition field &&
881-
IsCompilerGenerated(field) && field.FullName.EndsWith("__disposeMode") &&
880+
(
881+
(instructions[currentIndex - 1].Operand is FieldDefinition field && IsCompilerGenerated(field) && field.FullName.EndsWith("__disposeMode")) ||
882+
(instructions[currentIndex - 1].Operand is FieldReference fieldRef && IsCompilerGenerated(fieldRef.Resolve()) && fieldRef.FullName.EndsWith("__disposeMode"))
883+
) &&
882884
(instructions[currentIndex - 2].OpCode == OpCodes.Ldarg ||
883885
instructions[currentIndex - 2].OpCode == OpCodes.Ldarg_0))
884886
{
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
// Copyright (c) Toni Solarin-Sodara
2+
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
3+
4+
using System.Collections.Generic;
5+
using System.IO;
6+
using System.Threading.Tasks;
7+
using Coverlet.Core.Samples.Tests;
8+
using Xunit;
9+
10+
namespace Coverlet.Core.Tests
11+
{
12+
public partial class CoverageTests
13+
{
14+
[Fact]
15+
public void GenericAsyncIterator()
16+
{
17+
string path = Path.GetTempFileName();
18+
try
19+
{
20+
FunctionExecutor.Run(async (string[] pathSerialize) =>
21+
{
22+
CoveragePrepareResult coveragePrepareResult = await TestInstrumentationHelper.Run<GenericAsyncIterator<int>>(instance =>
23+
{
24+
List<int> res = ((Task<List<int>>)instance.Issue1383()).GetAwaiter().GetResult();
25+
26+
return Task.CompletedTask;
27+
}, persistPrepareResultToFile: pathSerialize[0]);
28+
return 0;
29+
}, new string[] { path });
30+
31+
TestInstrumentationHelper.GetCoverageResult(path)
32+
.Document("Instrumentation.GenericAsyncIterator.cs")
33+
.AssertLinesCovered(BuildConfiguration.Debug, (13, 1), (14, 1), (20, 1), (21, 1), (22, 1))
34+
.ExpectedTotalNumberOfBranches(BuildConfiguration.Debug, 0);
35+
}
36+
finally
37+
{
38+
File.Delete(path);
39+
}
40+
}
41+
}
42+
}

test/coverlet.core.tests/Coverage/InstrumenterHelper.cs

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,7 @@ public static async Task<CoveragePrepareResult> Run<T>(Func<dynamic, Task> callM
101101
IncludeFilters = (includeFilter is null ? defaultFilters(fileName) : includeFilter(fileName)).Concat(
102102
new string[]
103103
{
104-
$"[{Path.GetFileNameWithoutExtension(fileName)}*]{typeof(T).FullName}*"
104+
$"[{Path.GetFileNameWithoutExtension(fileName)}*]{GetTypeFullName<T>()}*"
105105
}).ToArray(),
106106
IncludeDirectories = Array.Empty<string>(),
107107
ExcludeFilters = (excludeFilter is null ? defaultFilters(fileName) : excludeFilter(fileName)).Concat(new string[]
@@ -180,6 +180,17 @@ private static void SetTestContainer(string testModule = null, bool disableResto
180180
return serviceCollection.BuildServiceProvider();
181181
});
182182
}
183+
184+
private static string GetTypeFullName<T>()
185+
{
186+
string name = typeof(T).FullName;
187+
if (typeof(T).IsGenericType && name != null)
188+
{
189+
int index = name.IndexOf('`');
190+
return index == -1 ? name : name[..index];
191+
}
192+
return name;
193+
}
183194
}
184195

185196
class CustomProcessExitHandler : IProcessExitHandler
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
// Remember to use full name because adding new using directives change line numbers
2+
3+
using System.Collections.Generic;
4+
using System.Linq;
5+
using System.Threading.Tasks;
6+
7+
namespace Coverlet.Core.Samples.Tests
8+
{
9+
public class GenericAsyncIterator<T>
10+
{
11+
public async Task<List<int>> Issue1383()
12+
{
13+
var sequence = await CreateSequenceAsync().ToListAsync();
14+
return sequence;
15+
}
16+
17+
18+
public async IAsyncEnumerable<int> CreateSequenceAsync()
19+
{
20+
await Task.CompletedTask;
21+
yield return 5;
22+
yield return 2;
23+
}
24+
}
25+
}

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy