Skip to content

Commit eb5969d

Browse files
authored
Merge pull request #12 from tonerdo/master
Update fork
2 parents 566262b + 528a769 commit eb5969d

20 files changed

+797
-479
lines changed

Directory.Build.targets

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
<!-- Do not upgrade this version or we won't support old SDK -->
1515
<PackageReference Update="Newtonsoft.Json" Version="9.0.1" />
1616
<PackageReference Update="NuGet.Packaging" Version="5.4.0" />
17-
<PackageReference Update="ReportGenerator.Core" Version="4.4.4" />
17+
<PackageReference Update="ReportGenerator.Core" Version="4.4.5" />
1818
<!--
1919
Do not change System.Reflection.Metadata version since we need to support VSTest DataCollectors. Goto https://www.nuget.org/packages/System.Reflection.Metadata to check versions.
2020
We need to load assembly version 1.4.2.0 to properly work

Documentation/Changelog.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
99
### Fixed
1010

1111
-Fix ExcludeFromCodeCoverage attribute bugs [#129](https://github.com/tonerdo/coverlet/issues/129) and [#670](https://github.com/tonerdo/coverlet/issues/670) with [#671](https://github.com/tonerdo/coverlet/pull/671) by https://github.com/matteoerigozzi
12-
-Fix bug with nested types filtering [#689](https://github.com/tonerdo/coverlet/issues/689)
12+
-Fix bug with nested types filtering [#689](https://github.com/tonerdo/coverlet/issues/689)
13+
-Fix Coverage Issue - New Using + Async/Await + ConfigureAwait [#669](https://github.com/tonerdo/coverlet/issues/669)
14+
1315

1416
### Improvements
1517

Documentation/Troubleshooting.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -222,7 +222,7 @@ We can collect logs from trackers through an enviroment variable
222222
set COVERLET_ENABLETRACKERLOG=1
223223
```
224224
When enabled, tracking event will be collected in log file near to module location.
225-
File name will be something like `moduleName.dll_tracker.txt`
225+
File name will be something like `moduleName.dll_tracker.txt` and files with detailed hits will be in a folder named `TrackersHitsLog`.
226226

227227
## Enable msbuild task instrumentation debugging
228228

Documentation/VSTestIntegration.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ How to specify these options via runsettings?
5454
<IncludeDirectory>../dir1/,../dir2/,</IncludeDirectory>
5555
<SingleHit>false</SingleHit>
5656
<UseSourceLink>true</UseSourceLink>
57-
<IncludeTestAssembly>true<IncludeTestAssembly>
57+
<IncludeTestAssembly>true</IncludeTestAssembly>
5858
</Configuration>
5959
</DataCollector>
6060
</DataCollectors>
@@ -63,9 +63,9 @@ How to specify these options via runsettings?
6363
```
6464
This runsettings file can easily be provided using command line option as given :
6565

66-
1. `dotnet test --settings coverletArgs.runsettings`
66+
1. `dotnet test --settings coverlet.runsettings`
6767

68-
2. `dotnet vstest --settings coverletArgs.runsettings`
68+
2. `dotnet vstest --settings coverlet.runsettings`
6969

7070
Take a look at our [`HelloWorld`](Examples/VSTest/HelloWorld/HowTo.md) sample.
7171

@@ -83,7 +83,7 @@ The datacollectors will be bundled as a separate NuGet package, the reference to
8383
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="x.x.x" />
8484
<PackageReference Include="MSTest.TestAdapter" Version="x.x.x" />
8585
<PackageReference Include="MSTest.TestFramework" Version="x.x.x" />
86-
<PackageReference Include="coverlet.collector" Version="1.0.0" />
86+
<PackageReference Include="coverlet.collector" Version="x.x.x" />
8787
</ItemGroup>
8888
```
8989

README.md

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# Coverlet
22

3-
[![Build Status](https://dev.azure.com/tonerdo/coverlet/_apis/build/status/tonerdo.coverlet?branchName=master)](https://dev.azure.com/tonerdo/coverlet/_build/latest?definitionId=3&branchName=master)
3+
[![Build Status](https://dev.azure.com/tonerdo/coverlet/_apis/build/status/tonerdo.coverlet?branchName=master)](https://dev.azure.com/tonerdo/coverlet/_build/latest?definitionId=3&branchName=master) [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://github.com/tonerdo/coverlet/blob/master/LICENSE)
44

55
Coverlet is a cross platform code coverage framework for .NET, with support for line, branch and method coverage. It works with .NET Framework on Windows and .NET Core on all supported platforms.
66

@@ -37,7 +37,7 @@ Coverlet is integrated into the Visual Studio Test Platform as a [data collector
3737
dotnet test --collect:"XPlat Code Coverage"
3838
```
3939

40-
After the above command is run, a `coverage.cobertura.json` file containing the results will be published to the `TestResults` directory as an attachment. A summary of the results will also be displayed in the terminal.
40+
After the above command is run, a `coverage.cobertura.xml` file containing the results will be published to the `TestResults` directory as an attachment.
4141

4242
See [documentation](Documentation/VSTestIntegration.md) for advanced usage.
4343

@@ -48,7 +48,13 @@ See [documentation](Documentation/VSTestIntegration.md) for advanced usage.
4848
```
4949
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.1.0" />
5050
```
51-
* Important [know issue](Documentation/KnowIssues.md#2-upgrade-coverletcollector-to-version--100)
51+
#### Important temporary [know issue](Documentation/KnowIssues.md#2-upgrade-coverletcollector-to-version--100)
52+
53+
*Current* recommended test sdk package to reference
54+
```
55+
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.5.0-preview-20200116-01" />
56+
```
57+
5258
### MSBuild Integration
5359

5460
### Installation

coverlet.sln

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "coverlet.integration.tests"
4646
EndProject
4747
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "coverlet.integration.template", "test\coverlet.integration.template\coverlet.integration.template.csproj", "{F6FE7678-C662-43D3-AC6A-64F6AC5A5935}"
4848
EndProject
49+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "coverlet.core.tests.samples.netstandard", "test\coverlet.core.tests.samples.netstandard\coverlet.core.tests.samples.netstandard.csproj", "{5FF404AD-7C0B-465A-A1E9-558CDC642B0C}"
50+
EndProject
4951
Global
5052
GlobalSection(SolutionConfigurationPlatforms) = preSolution
5153
Debug|Any CPU = Debug|Any CPU
@@ -104,6 +106,10 @@ Global
104106
{F6FE7678-C662-43D3-AC6A-64F6AC5A5935}.Debug|Any CPU.Build.0 = Debug|Any CPU
105107
{F6FE7678-C662-43D3-AC6A-64F6AC5A5935}.Release|Any CPU.ActiveCfg = Release|Any CPU
106108
{F6FE7678-C662-43D3-AC6A-64F6AC5A5935}.Release|Any CPU.Build.0 = Release|Any CPU
109+
{5FF404AD-7C0B-465A-A1E9-558CDC642B0C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
110+
{5FF404AD-7C0B-465A-A1E9-558CDC642B0C}.Debug|Any CPU.Build.0 = Debug|Any CPU
111+
{5FF404AD-7C0B-465A-A1E9-558CDC642B0C}.Release|Any CPU.ActiveCfg = Release|Any CPU
112+
{5FF404AD-7C0B-465A-A1E9-558CDC642B0C}.Release|Any CPU.Build.0 = Release|Any CPU
107113
EndGlobalSection
108114
GlobalSection(SolutionProperties) = preSolution
109115
HideSolutionNode = FALSE
@@ -122,6 +128,7 @@ Global
122128
{D6B14F2F-9E7D-4D2C-BAC8-48834F853ED6} = {2FEBDE1B-83E3-445B-B9F8-5644B0E0E134}
123129
{99B4059C-B25C-4B82-8117-A0E9DC9B0949} = {2FEBDE1B-83E3-445B-B9F8-5644B0E0E134}
124130
{F6FE7678-C662-43D3-AC6A-64F6AC5A5935} = {2FEBDE1B-83E3-445B-B9F8-5644B0E0E134}
131+
{5FF404AD-7C0B-465A-A1E9-558CDC642B0C} = {2FEBDE1B-83E3-445B-B9F8-5644B0E0E134}
125132
EndGlobalSection
126133
GlobalSection(ExtensibilityGlobals) = postSolution
127134
SolutionGuid = {9CA57C02-97B0-4C38-A027-EA61E8741F10}

eng/build.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,5 +22,5 @@ steps:
2222
displayName: Test
2323
inputs:
2424
command: test
25-
arguments: -c $(BuildConfiguration) --no-build /p:CollectCoverage=true /p:CoverletOutputFormat=opencover /p:Include=[coverlet.*]* /p:Exclude=[coverlet.tests.remoteexecutor]*
25+
arguments: -c $(BuildConfiguration) --no-build /p:CollectCoverage=true /p:CoverletOutputFormat=opencover /p:Include="[coverlet.collector]*%2c[coverlet.core]*%2c[coverlet.msbuild.tasks]*" /p:Exclude="[coverlet.core.tests.samples.netstandard]*%2c[coverlet.tests.remoteexecutor]*"
2626
testRunTitle: $(Agent.JobName)

src/coverlet.core/Instrumentation/ModuleTrackerTemplate.cs

Lines changed: 33 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using System;
2+
using System.Diagnostics;
23
using System.Diagnostics.CodeAnalysis;
34
using System.IO;
45
using System.Reflection;
@@ -20,7 +21,7 @@ internal static class ModuleTrackerTemplate
2021
public static string HitsFilePath;
2122
public static int[] HitsArray;
2223
public static bool SingleHit;
23-
private static bool _enableLog = int.TryParse(Environment.GetEnvironmentVariable("COVERLET_ENABLETRACKERLOG"), out int result) ? result == 1 : false;
24+
private static readonly bool _enableLog = int.TryParse(Environment.GetEnvironmentVariable("COVERLET_ENABLETRACKERLOG"), out int result) ? result == 1 : false;
2425

2526
static ModuleTrackerTemplate()
2627
{
@@ -116,6 +117,8 @@ public static void UnloadModule(object sender, EventArgs e)
116117
using (var bw = new BinaryWriter(fs))
117118
{
118119
int hitsLength = br.ReadInt32();
120+
WriteLog($"Current hits found '{hitsLength}'");
121+
119122
if (hitsLength != hitsArray.Length)
120123
{
121124
throw new InvalidOperationException(
@@ -134,6 +137,8 @@ public static void UnloadModule(object sender, EventArgs e)
134137
}
135138
}
136139

140+
WriteHits();
141+
137142
// On purpose this is not under a try-finally: it is better to have an exception if there was any error writing the hits file
138143
// this case is relevant when instrumenting corelib since multiple processes can be running against the same instrumented dll.
139144
mutex.ReleaseMutex();
@@ -147,21 +152,38 @@ public static void UnloadModule(object sender, EventArgs e)
147152
}
148153
}
149154

150-
private static void WriteLog(string logText)
155+
private static void WriteHits()
151156
{
152157
if (_enableLog)
153158
{
154-
try
155-
{
156-
// We don't set path as global var to keep benign possible errors inside try/catch
157-
// I'm not sure that location will be ok in every scenario
158-
string location = Assembly.GetExecutingAssembly().Location;
159-
File.AppendAllText(Path.Combine(Path.GetDirectoryName(location), Path.GetFileName(location) + "_tracker.txt"), $"[{DateTime.UtcNow} {Thread.CurrentThread.ManagedThreadId}]{logText}{Environment.NewLine}");
160-
}
161-
catch
159+
Assembly currentAssembly = Assembly.GetExecutingAssembly();
160+
DirectoryInfo location = new DirectoryInfo(Path.Combine(Path.GetDirectoryName(currentAssembly.Location), "TrackersHitsLog"));
161+
location.Create();
162+
string logFile = Path.Combine(location.FullName, $"{Path.GetFileName(currentAssembly.Location)}_{DateTime.UtcNow.Ticks}_{Process.GetCurrentProcess().Id}.txt");
163+
using (var fs = new FileStream(HitsFilePath, FileMode.Open, FileAccess.ReadWrite, FileShare.None))
164+
using (var log = new FileStream(logFile, FileMode.CreateNew, FileAccess.ReadWrite, FileShare.None))
165+
using (var logWriter = new StreamWriter(log))
166+
using (var br = new BinaryReader(fs))
162167
{
163-
// do nothing if log fail
168+
int hitsLength = br.ReadInt32();
169+
for (int i = 0; i < hitsLength; ++i)
170+
{
171+
logWriter.WriteLine($"{i},{br.ReadInt32()}");
172+
}
164173
}
174+
175+
File.AppendAllText(logFile, "Hits flushed");
176+
}
177+
}
178+
179+
private static void WriteLog(string logText)
180+
{
181+
if (_enableLog)
182+
{
183+
// We don't set path as global var to keep benign possible errors inside try/catch
184+
// I'm not sure that location will be ok in every scenario
185+
string location = Assembly.GetExecutingAssembly().Location;
186+
File.AppendAllText(Path.Combine(Path.GetDirectoryName(location), Path.GetFileName(location) + "_tracker.txt"), $"[{DateTime.UtcNow} P:{Process.GetCurrentProcess().Id} T:{Thread.CurrentThread.ManagedThreadId}]{logText}{Environment.NewLine}");
165187
}
166188
}
167189
}

src/coverlet.core/Symbols/CecilSymbolHelper.cs

Lines changed: 95 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,25 @@ private static bool IsMoveNextInsideEnumerator(MethodDefinition methodDefinition
6060
return false;
6161
}
6262

63+
private static bool IsRecognizedMoveNextInsideAsyncStateMachineProlog(MethodDefinition methodDefinition)
64+
{
65+
/*
66+
int num = <>1__state;
67+
IL_0000: ldarg.0
68+
IL_0001: ldfld ...::'<>1__state'
69+
IL_0006: stloc.0
70+
*/
71+
return (methodDefinition.Body.Instructions[0].OpCode == OpCodes.Ldarg_0 ||
72+
methodDefinition.Body.Instructions[0].OpCode == OpCodes.Ldarg) &&
73+
74+
methodDefinition.Body.Instructions[1].OpCode == OpCodes.Ldfld &&
75+
methodDefinition.Body.Instructions[1].Operand is FieldDefinition fd && fd.Name == "<>1__state" &&
76+
77+
(methodDefinition.Body.Instructions[2].OpCode == OpCodes.Stloc &&
78+
methodDefinition.Body.Instructions[2].Operand is VariableDefinition vd && vd.Index == 0) ||
79+
methodDefinition.Body.Instructions[2].OpCode == OpCodes.Stloc_0;
80+
}
81+
6382
public static List<BranchPoint> GetBranchPoints(MethodDefinition methodDefinition)
6483
{
6584
var list = new List<BranchPoint>();
@@ -69,9 +88,9 @@ public static List<BranchPoint> GetBranchPoints(MethodDefinition methodDefinitio
6988
UInt32 ordinal = 0;
7089
var instructions = methodDefinition.Body.Instructions;
7190

72-
// if method is a generated MoveNext skip first branch (could be a switch or a branch)
7391
bool isAsyncStateMachineMoveNext = IsMoveNextInsideAsyncStateMachine(methodDefinition);
74-
bool skipFirstBranch = isAsyncStateMachineMoveNext || IsMoveNextInsideEnumerator(methodDefinition);
92+
bool isRecognizedMoveNextInsideAsyncStateMachineProlog = isAsyncStateMachineMoveNext && IsRecognizedMoveNextInsideAsyncStateMachineProlog(methodDefinition);
93+
bool skipFirstBranch = IsMoveNextInsideEnumerator(methodDefinition);
7594

7695
foreach (Instruction instruction in instructions.Where(instruction => instruction.OpCode.FlowControl == FlowControl.Cond_Branch))
7796
{
@@ -83,15 +102,87 @@ public static List<BranchPoint> GetBranchPoints(MethodDefinition methodDefinitio
83102
continue;
84103
}
85104

105+
/*
106+
If method is a generated MoveNext we'll skip first branches (could be a switch or a series of branches)
107+
that check state machine value to jump to correct state(for instance after a true async call)
108+
Check if it's a Cond_Branch on state machine current value int num = <>1__state;
109+
We are on branch OpCode so we need to go back by max 2 operation to reach ldloc.0 the load of "num"
110+
Max 2 because we handle following patterns
111+
112+
Swich
113+
114+
// switch (num)
115+
IL_0007: ldloc.0 2
116+
// (no C# code)
117+
IL_0008: switch (IL_0037, IL_003c, ... 1
118+
...
119+
120+
Single branch
121+
122+
// if (num != 0)
123+
IL_0007: ldloc.0 2
124+
// (no C# code)
125+
IL_0008: brfalse.s IL_000c 1
126+
IL_000a: br.s IL_000e
127+
IL_000c: br.s IL_0049
128+
IL_000e: nop
129+
...
130+
131+
More tha one branch
132+
133+
// if (num != 0)
134+
IL_0007: ldloc.0
135+
// (no C# code)
136+
IL_0008: brfalse.s IL_0012
137+
IL_000a: br.s IL_000c
138+
// if (num == 1)
139+
IL_000c: ldloc.0 3
140+
IL_000d: ldc.i4.1 2
141+
IL_000e: beq.s IL_0014 1
142+
// (no C# code)
143+
IL_0010: br.s IL_0019
144+
IL_0012: br.s IL_0060
145+
IL_0014: br IL_00e5
146+
IL_0019: nop
147+
...
148+
149+
so we know that current branch are checking that field and we're not interested in.
150+
*/
151+
if (isAsyncStateMachineMoveNext && isRecognizedMoveNextInsideAsyncStateMachineProlog)
152+
{
153+
bool skipInstruction = false;
154+
Instruction current = instruction.Previous;
155+
for (int instructionBefore = 2; instructionBefore > 0 && current.Previous != null; current = current.Previous, instructionBefore--)
156+
{
157+
if (
158+
(current.OpCode == OpCodes.Ldloc && current.Operand is VariableDefinition vo && vo.Index == 0) ||
159+
current.OpCode == OpCodes.Ldloc_0
160+
)
161+
{
162+
skipInstruction = true;
163+
break;
164+
}
165+
}
166+
if (skipInstruction)
167+
{
168+
continue;
169+
}
170+
}
171+
86172
// Skip get_IsCompleted to avoid unuseful branch due to async/await state machine
87-
if (isAsyncStateMachineMoveNext && instruction.Previous.Operand is MethodReference operand &&
173+
if (
174+
isAsyncStateMachineMoveNext && instruction.Previous.Operand is MethodReference operand &&
88175
operand.Name == "get_IsCompleted" &&
89176
(
90177
operand.DeclaringType.FullName.StartsWith("System.Runtime.CompilerServices.TaskAwaiter") ||
91178
operand.DeclaringType.FullName.StartsWith("System.Runtime.CompilerServices.ConfiguredTaskAwaitable")
92179
)
93180
&&
94-
operand.DeclaringType.Scope.Name == "System.Runtime")
181+
(
182+
operand.DeclaringType.Scope.Name == "System.Runtime" ||
183+
operand.DeclaringType.Scope.Name == "netstandard"
184+
)
185+
)
95186
{
96187
continue;
97188
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
// Remember to use full name because adding new using directives change line numbers
2+
3+
namespace Coverlet.Core.Tests
4+
{
5+
public class Issue_669_2
6+
{
7+
private readonly System.Net.Http.HttpClient _httpClient = new System.Net.Http.HttpClient();
8+
9+
async public System.Threading.Tasks.ValueTask<System.Net.Http.HttpResponseMessage> SendRequest()
10+
{
11+
using (var requestMessage = new System.Net.Http.HttpRequestMessage(System.Net.Http.HttpMethod.Get, "https://www.google.it"))
12+
{
13+
return await _httpClient.SendAsync(requestMessage).ConfigureAwait(false);
14+
}
15+
}
16+
}
17+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
<PropertyGroup>
3+
<TargetFramework>netstandard2.0</TargetFramework>
4+
<IsPackable>false</IsPackable>
5+
<IsTestProject>false</IsTestProject>
6+
</PropertyGroup>
7+
8+
<ItemGroup>
9+
<PackageReference Include="System.Threading.Tasks.Extensions" Version="4.5.3" />
10+
</ItemGroup>
11+
12+
</Project>

0 commit comments

Comments
 (0)