Skip to content

Commit 226455e

Browse files
authored
Merge pull request #277 from MarcoRossignoli/fixwithmerge
Fix false negative with "/p:MergeWith" async/await branches
2 parents 7feb550 + e63b3f4 commit 226455e

File tree

5 files changed

+110
-8
lines changed

5 files changed

+110
-8
lines changed

src/coverlet.core/Coverage.cs

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -193,7 +193,7 @@ public CoverageResult GetCoverageResult()
193193
InstrumentationHelper.RestoreOriginalModule(result.ModulePath, _identifier);
194194
}
195195

196-
var coverageResult = new CoverageResult { Identifier = _identifier, Modules = modules };
196+
var coverageResult = new CoverageResult { Identifier = _identifier, Modules = modules, InstrumentedResults = _results };
197197

198198
if (!string.IsNullOrEmpty(_mergeWith) && !string.IsNullOrWhiteSpace(_mergeWith) && File.Exists(_mergeWith))
199199
{
@@ -257,14 +257,14 @@ private void CalculateCoverage()
257257
}
258258

259259
// for MoveNext() compiler autogenerated method we need to patch false positive (IAsyncStateMachine for instance)
260-
// we'll remove all MoveNext() not covered branch
260+
// we'll remove all MoveNext() not covered branch
261261
foreach (var document in result.Documents)
262262
{
263263
List<KeyValuePair<(int, int), Branch>> branchesToRemove = new List<KeyValuePair<(int, int), Branch>>();
264264
foreach (var branch in document.Value.Branches)
265265
{
266266
//if one branch is covered we search the other one only if it's not covered
267-
if (CecilSymbolHelper.IsMoveNext(branch.Value.Method) && branch.Value.Hits > 0)
267+
if (IsAsyncStateMachineMethod(branch.Value.Method) && branch.Value.Hits > 0)
268268
{
269269
foreach (var moveNextBranch in document.Value.Branches)
270270
{
@@ -286,6 +286,23 @@ private void CalculateCoverage()
286286
}
287287
}
288288

289+
private bool IsAsyncStateMachineMethod(string method)
290+
{
291+
if (!method.EndsWith("::MoveNext()"))
292+
{
293+
return false;
294+
}
295+
296+
foreach (var instrumentationResult in _results)
297+
{
298+
if (instrumentationResult.AsyncMachineStateMethod.Contains(method))
299+
{
300+
return true;
301+
}
302+
}
303+
return false;
304+
}
305+
289306
private string GetSourceLinkUrl(Dictionary<string, string> sourceLinkDocuments, string document)
290307
{
291308
if (sourceLinkDocuments.TryGetValue(document, out string url))

src/coverlet.core/CoverageResult.cs

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
using System.IO;
44
using System.Linq;
55
using Coverlet.Core.Enums;
6+
using Coverlet.Core.Instrumentation;
7+
using Coverlet.Core.Symbols;
68

79
namespace Coverlet.Core
810
{
@@ -39,6 +41,7 @@ public class CoverageResult
3941
{
4042
public string Identifier;
4143
public Modules Modules;
44+
internal List<InstrumenterResult> InstrumentedResults;
4245

4346
internal CoverageResult() { }
4447

@@ -105,6 +108,57 @@ internal void Merge(Modules modules)
105108
}
106109
}
107110
}
111+
112+
// for MoveNext() compiler autogenerated method we need to patch false positive (IAsyncStateMachine for instance)
113+
// we'll remove all MoveNext() not covered branch
114+
List<BranchInfo> branchesToRemove = new List<BranchInfo>();
115+
foreach (var module in this.Modules)
116+
{
117+
foreach (var document in module.Value)
118+
{
119+
foreach (var @class in document.Value)
120+
{
121+
foreach (var method in @class.Value)
122+
{
123+
foreach (var branch in method.Value.Branches)
124+
{
125+
//if one branch is covered we search the other one only if it's not covered
126+
if (IsAsyncStateMachineMethod(method.Key) && branch.Hits > 0)
127+
{
128+
foreach (var moveNextBranch in method.Value.Branches)
129+
{
130+
if (moveNextBranch != branch && moveNextBranch.Hits == 0)
131+
{
132+
branchesToRemove.Add(moveNextBranch);
133+
}
134+
}
135+
}
136+
}
137+
foreach (var branchToRemove in branchesToRemove)
138+
{
139+
method.Value.Branches.Remove(branchToRemove);
140+
}
141+
}
142+
}
143+
}
144+
}
145+
}
146+
147+
private bool IsAsyncStateMachineMethod(string method)
148+
{
149+
if (!method.EndsWith("::MoveNext()"))
150+
{
151+
return false;
152+
}
153+
154+
foreach (var instrumentedResult in InstrumentedResults)
155+
{
156+
if (instrumentedResult.AsyncMachineStateMethod.Contains(method))
157+
{
158+
return true;
159+
}
160+
}
161+
return false;
108162
}
109163

110164
public ThresholdTypeFlags GetThresholdTypesBelowThreshold(CoverageSummary summary, double threshold, ThresholdTypeFlags thresholdTypes, ThresholdStatistic thresholdStat)

src/coverlet.core/Instrumentation/Instrumenter.cs

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ internal class Instrumenter
3131
private TypeDefinition _customTrackerTypeDef;
3232
private MethodReference _customTrackerRegisterUnloadEventsMethod;
3333
private MethodReference _customTrackerRecordHitMethod;
34+
private List<string> _asyncMachineStateMethod;
3435

3536
public Instrumenter(string module, string identifier, string[] excludeFilters, string[] includeFilters, string[] excludedFiles, string[] excludedAttributes)
3637
{
@@ -61,6 +62,8 @@ public InstrumenterResult Instrument()
6162

6263
InstrumentModule();
6364

65+
_result.AsyncMachineStateMethod = _asyncMachineStateMethod == null ? Array.Empty<string>() : _asyncMachineStateMethod.ToArray();
66+
6467
return _result;
6568
}
6669

@@ -372,6 +375,7 @@ private Instruction AddInstrumentationCode(MethodDefinition method, ILProcessor
372375

373376
var key = (branchPoint.StartLine, (int)branchPoint.Ordinal);
374377
if (!document.Branches.ContainsKey(key))
378+
{
375379
document.Branches.Add(key,
376380
new Branch
377381
{
@@ -385,12 +389,43 @@ private Instruction AddInstrumentationCode(MethodDefinition method, ILProcessor
385389
}
386390
);
387391

392+
if (IsAsyncStateMachineBranch(method.DeclaringType, method))
393+
{
394+
if (_asyncMachineStateMethod == null)
395+
{
396+
_asyncMachineStateMethod = new List<string>();
397+
}
398+
399+
if (!_asyncMachineStateMethod.Contains(method.FullName))
400+
{
401+
_asyncMachineStateMethod.Add(method.FullName);
402+
}
403+
}
404+
}
405+
388406
var entry = (true, document.Index, branchPoint.StartLine, (int)branchPoint.Ordinal);
389407
_result.HitCandidates.Add(entry);
390408

391409
return AddInstrumentationInstructions(method, processor, instruction, _result.HitCandidates.Count - 1);
392410
}
393411

412+
private bool IsAsyncStateMachineBranch(TypeDefinition typeDef, MethodDefinition method)
413+
{
414+
if (!method.FullName.EndsWith("::MoveNext()"))
415+
{
416+
return false;
417+
}
418+
419+
foreach (InterfaceImplementation implementedInterface in typeDef.Interfaces)
420+
{
421+
if (implementedInterface.InterfaceType.FullName == "System.Runtime.CompilerServices.IAsyncStateMachine")
422+
{
423+
return true;
424+
}
425+
}
426+
return false;
427+
}
428+
394429
private Instruction AddInstrumentationInstructions(MethodDefinition method, ILProcessor processor, Instruction instruction, int hitEntryIndex)
395430
{
396431
if (_customTrackerRecordHitMethod == null)

src/coverlet.core/Instrumentation/InstrumenterResult.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ public InstrumenterResult()
4141
}
4242

4343
public string Module;
44+
public string[] AsyncMachineStateMethod;
4445
public string HitsFilePath;
4546
public string HitsResultGuid;
4647
public string ModulePath;

src/coverlet.core/Symbols/CecilSymbolHelper.cs

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,6 @@ public static class CecilSymbolHelper
2020
private const int StepOverLineCode = 0xFEEFEE;
2121
private static readonly Regex IsMovenext = new Regex(@"\<[^\s>]+\>\w__\w(\w)?::MoveNext\(\)$", RegexOptions.Compiled | RegexOptions.ExplicitCapture);
2222

23-
public static bool IsMoveNext(string fullName)
24-
{
25-
return IsMovenext.IsMatch(fullName);
26-
}
27-
2823
public static List<BranchPoint> GetBranchPoints(MethodDefinition methodDefinition)
2924
{
3025
var list = new List<BranchPoint>();

0 commit comments

Comments
 (0)