forked from coverlet-coverage/coverlet
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Coverage for "await foreach" loops (issue coverlet-coverage#1104)
This commit attempts to fix code coverage for the "await foreach" loop introduced in C# 8. The presence of an "await foreach" loop causes four new kinds of branch patterns to be generated in the async state machine. Three of these are to be eliminated, while the fourth is actually the "should I stay in the loop or not?" branch and is best left in a code coverage report. CecilSymbolHelper now eliminates the three branch patterns that need to be eliminated. There are tests both in CecilSymbolHelperTests as well as a new set of coverage tests in CoverageTests.AsyncForeach.cs.
- Loading branch information
1 parent
1ab6b17
commit 039e615
Showing
6 changed files
with
377 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
73 changes: 73 additions & 0 deletions
73
test/coverlet.core.tests/Coverage/CoverageTests.AsyncForeach.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,73 @@ | ||
using System.Collections.Generic; | ||
using System.IO; | ||
using System.Threading.Tasks; | ||
|
||
using Coverlet.Core.Samples.Tests; | ||
using Coverlet.Tests.Xunit.Extensions; | ||
using Xunit; | ||
|
||
namespace Coverlet.Core.Tests | ||
{ | ||
public partial class CoverageTests | ||
{ | ||
async private static IAsyncEnumerable<T> ToAsync<T>(IEnumerable<T> values) | ||
{ | ||
foreach (T value in values) | ||
{ | ||
yield return await Task.FromResult(value); | ||
} | ||
} | ||
|
||
[Fact] | ||
public void AsyncForeach() | ||
{ | ||
string path = Path.GetTempFileName(); | ||
try | ||
{ | ||
FunctionExecutor.Run(async (string[] pathSerialize) => | ||
{ | ||
CoveragePrepareResult coveragePrepareResult = await TestInstrumentationHelper.Run<AsyncForeach>(instance => | ||
{ | ||
int res = ((ValueTask<int>)instance.SumWithATwist(ToAsync(new int[] { 1, 2, 3, 4, 5 }))).GetAwaiter().GetResult(); | ||
res += ((ValueTask<int>)instance.Sum(ToAsync(new int[] { 1, 2, 3 }))).GetAwaiter().GetResult(); | ||
res += ((ValueTask<int>)instance.SumEmpty()).GetAwaiter().GetResult(); | ||
|
||
return Task.CompletedTask; | ||
}, persistPrepareResultToFile: pathSerialize[0]); | ||
return 0; | ||
}, new string[] { path }); | ||
|
||
TestInstrumentationHelper.GetCoverageResult(path) | ||
.Document("Instrumentation.AsyncForeach.cs") | ||
.AssertLinesCovered(BuildConfiguration.Debug, | ||
// SumWithATwist(IAsyncEnumerable<int>) | ||
// Apparently due to entering and exiting the async state machine, line 17 | ||
// (the top of an "await foreach" loop) is reached three times *plus* twice | ||
// per loop iteration. So, in this case, with five loop iterations, we end | ||
// up with 3 + 5 * 2 = 13 hits. | ||
(14, 1), (15, 1), (17, 13), (18, 5), (19, 5), (20, 5), (21, 5), (22, 5), | ||
(24, 0), (25, 0), (26, 0), (27, 5), (29, 1), (30, 1), | ||
// Sum(IAsyncEnumerable<int>) | ||
(34, 1), (35, 1), (37, 9), (38, 3), (39, 3), (40, 3), (42, 1), (43, 1), | ||
// SumEmpty() | ||
(47, 1), (48, 1), (50, 3), (51, 0), (52, 0), (53, 0), (55, 1), (56, 1) | ||
) | ||
.AssertBranchesCovered(BuildConfiguration.Debug, | ||
// SumWithATwist(IAsyncEnumerable<int>) | ||
(17, 2, 1), (17, 3, 5), (19, 0, 5), (19, 1, 0), | ||
// Sum(IAsyncEnumerable<int>) | ||
(37, 0, 1), (37, 1, 3), | ||
// SumEmpty() | ||
// If we never entered the loop, that's a branch not taken, which is | ||
// what we want to see. | ||
(50, 0, 1), (50, 1, 0) | ||
) | ||
.ExpectedTotalNumberOfBranches(BuildConfiguration.Debug, 4); | ||
} | ||
finally | ||
{ | ||
File.Delete(path); | ||
} | ||
} | ||
} | ||
} |
58 changes: 58 additions & 0 deletions
58
test/coverlet.core.tests/Samples/Instrumentation.AsyncForeach.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,58 @@ | ||
// Remember to use full name because adding new using directives change line numbers | ||
|
||
using System; | ||
using System.Collections.Generic; | ||
using System.Linq; | ||
using System.Threading; | ||
using System.Threading.Tasks; | ||
|
||
namespace Coverlet.Core.Samples.Tests | ||
{ | ||
public class AsyncForeach | ||
{ | ||
async public ValueTask<int> SumWithATwist(IAsyncEnumerable<int> ints) | ||
{ | ||
int sum = 0; | ||
|
||
await foreach (int i in ints) | ||
{ | ||
if (i > 0) | ||
{ | ||
sum += i; | ||
} | ||
else | ||
{ | ||
sum = 0; | ||
} | ||
} | ||
|
||
return sum; | ||
} | ||
|
||
|
||
async public ValueTask<int> Sum(IAsyncEnumerable<int> ints) | ||
{ | ||
int sum = 0; | ||
|
||
await foreach (int i in ints) | ||
{ | ||
sum += i; | ||
} | ||
|
||
return sum; | ||
} | ||
|
||
|
||
async public ValueTask<int> SumEmpty() | ||
{ | ||
int sum = 0; | ||
|
||
await foreach (int i in AsyncEnumerable.Empty<int>()) | ||
{ | ||
sum += i; | ||
} | ||
|
||
return sum; | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.