Skip to content

Commit c43fd24

Browse files
authored
Merge pull request #309 from sharwell/single-hit
Single hit
2 parents 38a2104 + 51ea793 commit c43fd24

File tree

9 files changed

+66
-12
lines changed

9 files changed

+66
-12
lines changed

src/coverlet.console/Program.cs

+2-1
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ static int Main(string[] args)
3737
CommandOption excludedSourceFiles = app.Option("--exclude-by-file", "Glob patterns specifying source files to exclude.", CommandOptionType.MultipleValue);
3838
CommandOption includeDirectories = app.Option("--include-directory", "Include directories containing additional assemblies to be instrumented.", CommandOptionType.MultipleValue);
3939
CommandOption excludeAttributes = app.Option("--exclude-by-attribute", "Attributes to exclude from code coverage.", CommandOptionType.MultipleValue);
40+
CommandOption singleHit = app.Option("--single-hit", "Specifies whether to limit code coverage hit reporting to a single hit for each location", CommandOptionType.NoValue);
4041
CommandOption mergeWith = app.Option("--merge-with", "Path to existing coverage result to merge.", CommandOptionType.SingleValue);
4142
CommandOption useSourceLink = app.Option("--use-source-link", "Specifies whether to use SourceLink URIs in place of file system paths.", CommandOptionType.NoValue);
4243

@@ -48,7 +49,7 @@ static int Main(string[] args)
4849
if (!target.HasValue())
4950
throw new CommandParsingException(app, "Target must be specified.");
5051

51-
Coverage coverage = new Coverage(module.Value, includeFilters.Values.ToArray(), includeDirectories.Values.ToArray(), excludeFilters.Values.ToArray(), excludedSourceFiles.Values.ToArray(), excludeAttributes.Values.ToArray(), mergeWith.Value(), useSourceLink.HasValue());
52+
Coverage coverage = new Coverage(module.Value, includeFilters.Values.ToArray(), includeDirectories.Values.ToArray(), excludeFilters.Values.ToArray(), excludedSourceFiles.Values.ToArray(), excludeAttributes.Values.ToArray(), singleHit.HasValue(), mergeWith.Value(), useSourceLink.HasValue());
5253
coverage.PrepareModules();
5354

5455
Process process = new Process();

src/coverlet.core/Coverage.cs

+4-2
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ public class Coverage
2222
private string[] _excludeFilters;
2323
private string[] _excludedSourceFiles;
2424
private string[] _excludeAttributes;
25+
private bool _singleHit;
2526
private string _mergeWith;
2627
private bool _useSourceLink;
2728
private List<InstrumenterResult> _results;
@@ -31,14 +32,15 @@ public string Identifier
3132
get { return _identifier; }
3233
}
3334

34-
public Coverage(string module, string[] includeFilters, string[] includeDirectories, string[] excludeFilters, string[] excludedSourceFiles, string[] excludeAttributes, string mergeWith, bool useSourceLink)
35+
public Coverage(string module, string[] includeFilters, string[] includeDirectories, string[] excludeFilters, string[] excludedSourceFiles, string[] excludeAttributes, bool singleHit, string mergeWith, bool useSourceLink)
3536
{
3637
_module = module;
3738
_includeFilters = includeFilters;
3839
_includeDirectories = includeDirectories ?? Array.Empty<string>();
3940
_excludeFilters = excludeFilters;
4041
_excludedSourceFiles = excludedSourceFiles;
4142
_excludeAttributes = excludeAttributes;
43+
_singleHit = singleHit;
4244
_mergeWith = mergeWith;
4345
_useSourceLink = useSourceLink;
4446

@@ -59,7 +61,7 @@ public void PrepareModules()
5961
!InstrumentationHelper.IsModuleIncluded(module, _includeFilters))
6062
continue;
6163

62-
var instrumenter = new Instrumenter(module, _identifier, _excludeFilters, _includeFilters, excludes, _excludeAttributes);
64+
var instrumenter = new Instrumenter(module, _identifier, _excludeFilters, _includeFilters, excludes, _excludeAttributes, _singleHit);
6365
if (instrumenter.CanInstrument())
6466
{
6567
InstrumentationHelper.BackupOriginalModule(module, _identifier);

src/coverlet.core/Instrumentation/Instrumenter.cs

+22-4
Original file line numberDiff line numberDiff line change
@@ -23,24 +23,27 @@ internal class Instrumenter
2323
private readonly string[] _includeFilters;
2424
private readonly string[] _excludedFiles;
2525
private readonly string[] _excludedAttributes;
26+
private readonly bool _singleHit;
2627
private readonly bool _isCoreLibrary;
2728
private InstrumenterResult _result;
2829
private FieldDefinition _customTrackerHitsArray;
2930
private FieldDefinition _customTrackerHitsFilePath;
31+
private FieldDefinition _customTrackerSingleHit;
3032
private ILProcessor _customTrackerClassConstructorIl;
3133
private TypeDefinition _customTrackerTypeDef;
3234
private MethodReference _customTrackerRegisterUnloadEventsMethod;
3335
private MethodReference _customTrackerRecordHitMethod;
3436
private List<string> _asyncMachineStateMethod;
3537

36-
public Instrumenter(string module, string identifier, string[] excludeFilters, string[] includeFilters, string[] excludedFiles, string[] excludedAttributes)
38+
public Instrumenter(string module, string identifier, string[] excludeFilters, string[] includeFilters, string[] excludedFiles, string[] excludedAttributes, bool singleHit)
3739
{
3840
_module = module;
3941
_identifier = identifier;
4042
_excludeFilters = excludeFilters;
4143
_includeFilters = includeFilters;
4244
_excludedFiles = excludedFiles ?? Array.Empty<string>();
4345
_excludedAttributes = excludedAttributes;
46+
_singleHit = singleHit;
4447

4548
_isCoreLibrary = Path.GetFileNameWithoutExtension(_module) == "System.Private.CoreLib";
4649
}
@@ -125,6 +128,8 @@ private void InstrumentModule()
125128
_customTrackerClassConstructorIl.InsertBefore(lastInstr, Instruction.Create(OpCodes.Stsfld, _customTrackerHitsArray));
126129
_customTrackerClassConstructorIl.InsertBefore(lastInstr, Instruction.Create(OpCodes.Ldstr, _result.HitsFilePath));
127130
_customTrackerClassConstructorIl.InsertBefore(lastInstr, Instruction.Create(OpCodes.Stsfld, _customTrackerHitsFilePath));
131+
_customTrackerClassConstructorIl.InsertBefore(lastInstr, Instruction.Create(_singleHit ? OpCodes.Ldc_I4_1 : OpCodes.Ldc_I4_0));
132+
_customTrackerClassConstructorIl.InsertBefore(lastInstr, Instruction.Create(OpCodes.Stsfld, _customTrackerSingleHit));
128133

129134
if (containsAppContext)
130135
{
@@ -174,6 +179,8 @@ private void AddCustomModuleTrackerToModule(ModuleDefinition module)
174179
_customTrackerHitsArray = fieldClone;
175180
else if (fieldClone.Name == nameof(ModuleTrackerTemplate.HitsFilePath))
176181
_customTrackerHitsFilePath = fieldClone;
182+
else if (fieldClone.Name == nameof(ModuleTrackerTemplate.SingleHit))
183+
_customTrackerSingleHit = fieldClone;
177184
}
178185

179186
foreach (MethodDefinition methodDef in moduleTrackerTemplate.Methods)
@@ -426,9 +433,20 @@ private Instruction AddInstrumentationInstructions(MethodDefinition method, ILPr
426433
{
427434
if (_customTrackerRecordHitMethod == null)
428435
{
429-
var recordHitMethodName = _isCoreLibrary
430-
? nameof(ModuleTrackerTemplate.RecordHitInCoreLibrary)
431-
: nameof(ModuleTrackerTemplate.RecordHit);
436+
string recordHitMethodName;
437+
if (_singleHit)
438+
{
439+
recordHitMethodName = _isCoreLibrary
440+
? nameof(ModuleTrackerTemplate.RecordSingleHitInCoreLibrary)
441+
: nameof(ModuleTrackerTemplate.RecordSingleHit);
442+
}
443+
else
444+
{
445+
recordHitMethodName = _isCoreLibrary
446+
? nameof(ModuleTrackerTemplate.RecordHitInCoreLibrary)
447+
: nameof(ModuleTrackerTemplate.RecordHit);
448+
}
449+
432450
_customTrackerRecordHitMethod = new MethodReference(
433451
recordHitMethodName, method.Module.TypeSystem.Void, _customTrackerTypeDef);
434452
_customTrackerRecordHitMethod.Parameters.Add(new ParameterDefinition("hitLocationIndex", ParameterAttributes.None, method.Module.TypeSystem.Int32));

src/coverlet.msbuild.tasks/InstrumentationTask.cs

+8-1
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ public class InstrumentationTask : Task
1414
private string _exclude;
1515
private string _excludeByFile;
1616
private string _excludeByAttribute;
17+
private bool _singleHit;
1718
private string _mergeWith;
1819
private bool _useSourceLink;
1920

@@ -59,6 +60,12 @@ public string ExcludeByAttribute
5960
set { _excludeByAttribute = value; }
6061
}
6162

63+
public bool SingleHit
64+
{
65+
get { return _singleHit; }
66+
set { _singleHit = value; }
67+
}
68+
6269
public string MergeWith
6370
{
6471
get { return _mergeWith; }
@@ -81,7 +88,7 @@ public override bool Execute()
8188
var excludedSourceFiles = _excludeByFile?.Split(',');
8289
var excludeAttributes = _excludeByAttribute?.Split(',');
8390

84-
_coverage = new Coverage(_path, includeFilters, includeDirectories, excludeFilters, excludedSourceFiles, excludeAttributes, _mergeWith, _useSourceLink);
91+
_coverage = new Coverage(_path, includeFilters, includeDirectories, excludeFilters, excludedSourceFiles, excludeAttributes, _singleHit, _mergeWith, _useSourceLink);
8592
_coverage.PrepareModules();
8693
}
8794
catch (Exception ex)

src/coverlet.msbuild/coverlet.msbuild.props

+1
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
<Exclude Condition="$(Exclude) == ''"></Exclude>
77
<ExcludeByFile Condition="$(ExcludeByFile) == ''"></ExcludeByFile>
88
<ExcludeByAttribute Condition="$(ExcludeByAttribute) == ''"></ExcludeByAttribute>
9+
<CoverletSingleHit Condition="'$(CoverletSingleHit)' == ''">false</CoverletSingleHit>
910
<MergeWith Condition="$(MergeWith) == ''"></MergeWith>
1011
<UseSourceLink Condition="$(UseSourceLink) == ''">false</UseSourceLink>
1112
<CoverletOutputFormat Condition="$(CoverletOutputFormat) == ''">json</CoverletOutputFormat>

src/coverlet.msbuild/coverlet.msbuild.targets

+2
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
Exclude="$(Exclude)"
1313
ExcludeByFile="$(ExcludeByFile)"
1414
ExcludeByAttribute="$(ExcludeByAttribute)"
15+
SingleHit="$(CoverletSingleHit)"
1516
MergeWith="$(MergeWith)"
1617
UseSourceLink="$(UseSourceLink)" />
1718
</Target>
@@ -25,6 +26,7 @@
2526
Exclude="$(Exclude)"
2627
ExcludeByFile="$(ExcludeByFile)"
2728
ExcludeByAttribute="$(ExcludeByAttribute)"
29+
SingleHit="$(CoverletSingleHit)"
2830
MergeWith="$(MergeWith)"
2931
UseSourceLink="$(UseSourceLink)" />
3032
</Target>

src/coverlet.template/ModuleTrackerTemplate.cs

+24-1
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ public static class ModuleTrackerTemplate
1818
{
1919
public static string HitsFilePath;
2020
public static int[] HitsArray;
21+
public static bool SingleHit;
2122

2223
static ModuleTrackerTemplate()
2324
{
@@ -50,6 +51,25 @@ public static void RecordHit(int hitLocationIndex)
5051
Interlocked.Increment(ref HitsArray[hitLocationIndex]);
5152
}
5253

54+
public static void RecordSingleHitInCoreLibrary(int hitLocationIndex)
55+
{
56+
// Make sure to avoid recording if this is a call to RecordHit within the AppDomain setup code in an
57+
// instrumented build of System.Private.CoreLib.
58+
if (HitsArray is null)
59+
return;
60+
61+
ref int location = ref HitsArray[hitLocationIndex];
62+
if (location == 0)
63+
location = 1;
64+
}
65+
66+
public static void RecordSingleHit(int hitLocationIndex)
67+
{
68+
ref int location = ref HitsArray[hitLocationIndex];
69+
if (location == 0)
70+
location = 1;
71+
}
72+
5373
public static void UnloadModule(object sender, EventArgs e)
5474
{
5575
// Claim the current hits array and reset it to prevent double-counting scenarios.
@@ -99,7 +119,10 @@ public static void UnloadModule(object sender, EventArgs e)
99119
{
100120
int oldHitCount = br.ReadInt32();
101121
bw.Seek(-sizeof(int), SeekOrigin.Current);
102-
bw.Write(hitsArray[i] + oldHitCount);
122+
if (SingleHit)
123+
bw.Write(hitsArray[i] + oldHitCount > 0 ? 1 : 0);
124+
else
125+
bw.Write(hitsArray[i] + oldHitCount);
103126
}
104127
}
105128
}

test/coverlet.core.tests/CoverageTests.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ public void TestCoverage()
2424

2525
// TODO: Find a way to mimick hits
2626

27-
var coverage = new Coverage(Path.Combine(directory.FullName, Path.GetFileName(module)), Array.Empty<string>(), Array.Empty<string>(), Array.Empty<string>(), Array.Empty<string>(), Array.Empty<string>(), string.Empty, false);
27+
var coverage = new Coverage(Path.Combine(directory.FullName, Path.GetFileName(module)), Array.Empty<string>(), Array.Empty<string>(), Array.Empty<string>(), Array.Empty<string>(), Array.Empty<string>(), false, string.Empty, false);
2828
coverage.PrepareModules();
2929

3030
var result = coverage.GetCoverageResult();

test/coverlet.core.tests/Instrumentation/InstrumenterTests.cs

+2-2
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ public void TestCoreLibInstrumentation()
2727
foreach (var file in files)
2828
File.Copy(Path.Combine(OriginalFilesDir, file), Path.Combine(TestFilesDir, file), overwrite: true);
2929

30-
Instrumenter instrumenter = new Instrumenter(Path.Combine(TestFilesDir, files[0]), "_coverlet_instrumented", Array.Empty<string>(), Array.Empty<string>(), Array.Empty<string>(), Array.Empty<string>());
30+
Instrumenter instrumenter = new Instrumenter(Path.Combine(TestFilesDir, files[0]), "_coverlet_instrumented", Array.Empty<string>(), Array.Empty<string>(), Array.Empty<string>(), Array.Empty<string>(), false);
3131
Assert.True(instrumenter.CanInstrument());
3232
var result = instrumenter.Instrument();
3333
Assert.NotNull(result);
@@ -119,7 +119,7 @@ private InstrumenterTest CreateInstrumentor(bool fakeCoreLibModule = false, stri
119119
File.Copy(pdb, Path.Combine(directory.FullName, destPdb), true);
120120

121121
module = Path.Combine(directory.FullName, destModule);
122-
Instrumenter instrumenter = new Instrumenter(module, identifier, Array.Empty<string>(), Array.Empty<string>(), Array.Empty<string>(), attributesToIgnore);
122+
Instrumenter instrumenter = new Instrumenter(module, identifier, Array.Empty<string>(), Array.Empty<string>(), Array.Empty<string>(), attributesToIgnore, false);
123123
return new InstrumenterTest
124124
{
125125
Instrumenter = instrumenter,

0 commit comments

Comments
 (0)