Skip to content

Commit

Permalink
Add a "single hit" collection mode
Browse files Browse the repository at this point in the history
Fixes #306
  • Loading branch information
sharwell committed Jan 27, 2019
1 parent c2ba965 commit 76de86e
Show file tree
Hide file tree
Showing 9 changed files with 66 additions and 12 deletions.
3 changes: 2 additions & 1 deletion src/coverlet.console/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ static int Main(string[] args)
CommandOption excludedSourceFiles = app.Option("--exclude-by-file", "Glob patterns specifying source files to exclude.", CommandOptionType.MultipleValue);
CommandOption includeDirectories = app.Option("--include-directory", "Include directories containing additional assemblies to be instrumented.", CommandOptionType.MultipleValue);
CommandOption excludeAttributes = app.Option("--exclude-by-attribute", "Attributes to exclude from code coverage.", CommandOptionType.MultipleValue);
CommandOption singleHit = app.Option("--single-hit", "Specifies whether to limit code coverage hit reporting to a single hit for each location", CommandOptionType.NoValue);
CommandOption mergeWith = app.Option("--merge-with", "Path to existing coverage result to merge.", CommandOptionType.SingleValue);
CommandOption useSourceLink = app.Option("--use-source-link", "Specifies whether to use SourceLink URIs in place of file system paths.", CommandOptionType.NoValue);

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

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());
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());
coverage.PrepareModules();

Process process = new Process();
Expand Down
6 changes: 4 additions & 2 deletions src/coverlet.core/Coverage.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ public class Coverage
private string[] _excludeFilters;
private string[] _excludedSourceFiles;
private string[] _excludeAttributes;
private bool _singleHit;
private string _mergeWith;
private bool _useSourceLink;
private List<InstrumenterResult> _results;
Expand All @@ -31,14 +32,15 @@ public string Identifier
get { return _identifier; }
}

public Coverage(string module, string[] includeFilters, string[] includeDirectories, string[] excludeFilters, string[] excludedSourceFiles, string[] excludeAttributes, string mergeWith, bool useSourceLink)
public Coverage(string module, string[] includeFilters, string[] includeDirectories, string[] excludeFilters, string[] excludedSourceFiles, string[] excludeAttributes, bool singleHit, string mergeWith, bool useSourceLink)
{
_module = module;
_includeFilters = includeFilters;
_includeDirectories = includeDirectories ?? Array.Empty<string>();
_excludeFilters = excludeFilters;
_excludedSourceFiles = excludedSourceFiles;
_excludeAttributes = excludeAttributes;
_singleHit = singleHit;
_mergeWith = mergeWith;
_useSourceLink = useSourceLink;

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

var instrumenter = new Instrumenter(module, _identifier, _excludeFilters, _includeFilters, excludes, _excludeAttributes);
var instrumenter = new Instrumenter(module, _identifier, _excludeFilters, _includeFilters, excludes, _excludeAttributes, _singleHit);
if (instrumenter.CanInstrument())
{
InstrumentationHelper.BackupOriginalModule(module, _identifier);
Expand Down
26 changes: 22 additions & 4 deletions src/coverlet.core/Instrumentation/Instrumenter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,24 +23,27 @@ internal class Instrumenter
private readonly string[] _includeFilters;
private readonly string[] _excludedFiles;
private readonly string[] _excludedAttributes;
private readonly bool _singleHit;
private readonly bool _isCoreLibrary;
private InstrumenterResult _result;
private FieldDefinition _customTrackerHitsArray;
private FieldDefinition _customTrackerHitsFilePath;
private FieldDefinition _customTrackerSingleHit;
private ILProcessor _customTrackerClassConstructorIl;
private TypeDefinition _customTrackerTypeDef;
private MethodReference _customTrackerRegisterUnloadEventsMethod;
private MethodReference _customTrackerRecordHitMethod;
private List<string> _asyncMachineStateMethod;

public Instrumenter(string module, string identifier, string[] excludeFilters, string[] includeFilters, string[] excludedFiles, string[] excludedAttributes)
public Instrumenter(string module, string identifier, string[] excludeFilters, string[] includeFilters, string[] excludedFiles, string[] excludedAttributes, bool singleHit)
{
_module = module;
_identifier = identifier;
_excludeFilters = excludeFilters;
_includeFilters = includeFilters;
_excludedFiles = excludedFiles ?? Array.Empty<string>();
_excludedAttributes = excludedAttributes;
_singleHit = singleHit;

_isCoreLibrary = Path.GetFileNameWithoutExtension(_module) == "System.Private.CoreLib";
}
Expand Down Expand Up @@ -125,6 +128,8 @@ private void InstrumentModule()
_customTrackerClassConstructorIl.InsertBefore(lastInstr, Instruction.Create(OpCodes.Stsfld, _customTrackerHitsArray));
_customTrackerClassConstructorIl.InsertBefore(lastInstr, Instruction.Create(OpCodes.Ldstr, _result.HitsFilePath));
_customTrackerClassConstructorIl.InsertBefore(lastInstr, Instruction.Create(OpCodes.Stsfld, _customTrackerHitsFilePath));
_customTrackerClassConstructorIl.InsertBefore(lastInstr, Instruction.Create(_singleHit ? OpCodes.Ldc_I4_1 : OpCodes.Ldc_I4_0));
_customTrackerClassConstructorIl.InsertBefore(lastInstr, Instruction.Create(OpCodes.Stsfld, _customTrackerSingleHit));

if (containsAppContext)
{
Expand Down Expand Up @@ -174,6 +179,8 @@ private void AddCustomModuleTrackerToModule(ModuleDefinition module)
_customTrackerHitsArray = fieldClone;
else if (fieldClone.Name == "HitsFilePath")
_customTrackerHitsFilePath = fieldClone;
else if (fieldClone.Name == nameof(ModuleTrackerTemplate.SingleHit))
_customTrackerSingleHit = fieldClone;
}

foreach (MethodDefinition methodDef in moduleTrackerTemplate.Methods)
Expand Down Expand Up @@ -426,9 +433,20 @@ private Instruction AddInstrumentationInstructions(MethodDefinition method, ILPr
{
if (_customTrackerRecordHitMethod == null)
{
var recordHitMethodName = _isCoreLibrary
? nameof(ModuleTrackerTemplate.RecordHitInCoreLibrary)
: nameof(ModuleTrackerTemplate.RecordHit);
string recordHitMethodName;
if (_singleHit)
{
recordHitMethodName = _isCoreLibrary
? nameof(ModuleTrackerTemplate.RecordSingleHitInCoreLibrary)
: nameof(ModuleTrackerTemplate.RecordSingleHit);
}
else
{
recordHitMethodName = _isCoreLibrary
? nameof(ModuleTrackerTemplate.RecordHitInCoreLibrary)
: nameof(ModuleTrackerTemplate.RecordHit);
}

_customTrackerRecordHitMethod = new MethodReference(
recordHitMethodName, method.Module.TypeSystem.Void, _customTrackerTypeDef);
_customTrackerRecordHitMethod.Parameters.Add(new ParameterDefinition("hitLocationIndex", ParameterAttributes.None, method.Module.TypeSystem.Int32));
Expand Down
9 changes: 8 additions & 1 deletion src/coverlet.msbuild.tasks/InstrumentationTask.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ public class InstrumentationTask : Task
private string _exclude;
private string _excludeByFile;
private string _excludeByAttribute;
private bool _singleHit;
private string _mergeWith;
private bool _useSourceLink;

Expand Down Expand Up @@ -59,6 +60,12 @@ public string ExcludeByAttribute
set { _excludeByAttribute = value; }
}

public bool SingleHit
{
get { return _singleHit; }
set { _singleHit = value; }
}

public string MergeWith
{
get { return _mergeWith; }
Expand All @@ -81,7 +88,7 @@ public override bool Execute()
var excludedSourceFiles = _excludeByFile?.Split(',');
var excludeAttributes = _excludeByAttribute?.Split(',');

_coverage = new Coverage(_path, includeFilters, includeDirectories, excludeFilters, excludedSourceFiles, excludeAttributes, _mergeWith, _useSourceLink);
_coverage = new Coverage(_path, includeFilters, includeDirectories, excludeFilters, excludedSourceFiles, excludeAttributes, _singleHit, _mergeWith, _useSourceLink);
_coverage.PrepareModules();
}
catch (Exception ex)
Expand Down
1 change: 1 addition & 0 deletions src/coverlet.msbuild/coverlet.msbuild.props
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
<Exclude Condition="$(Exclude) == ''"></Exclude>
<ExcludeByFile Condition="$(ExcludeByFile) == ''"></ExcludeByFile>
<ExcludeByAttribute Condition="$(ExcludeByAttribute) == ''"></ExcludeByAttribute>
<CoverletSingleHit Condition="'$(CoverletSingleHit)' == ''">false</CoverletSingleHit>
<MergeWith Condition="$(MergeWith) == ''"></MergeWith>
<UseSourceLink Condition="$(UseSourceLink) == ''">false</UseSourceLink>
<CoverletOutputFormat Condition="$(CoverletOutputFormat) == ''">json</CoverletOutputFormat>
Expand Down
2 changes: 2 additions & 0 deletions src/coverlet.msbuild/coverlet.msbuild.targets
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
Exclude="$(Exclude)"
ExcludeByFile="$(ExcludeByFile)"
ExcludeByAttribute="$(ExcludeByAttribute)"
SingleHit="$(CoverletSingleHit)"
MergeWith="$(MergeWith)"
UseSourceLink="$(UseSourceLink)" />
</Target>
Expand All @@ -25,6 +26,7 @@
Exclude="$(Exclude)"
ExcludeByFile="$(ExcludeByFile)"
ExcludeByAttribute="$(ExcludeByAttribute)"
SingleHit="$(CoverletSingleHit)"
MergeWith="$(MergeWith)"
UseSourceLink="$(UseSourceLink)" />
</Target>
Expand Down
25 changes: 24 additions & 1 deletion src/coverlet.template/ModuleTrackerTemplate.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ public static class ModuleTrackerTemplate
{
public static string HitsFilePath;
public static int[] HitsArray;
public static bool SingleHit;

static ModuleTrackerTemplate()
{
Expand Down Expand Up @@ -50,6 +51,25 @@ public static void RecordHit(int hitLocationIndex)
Interlocked.Increment(ref HitsArray[hitLocationIndex]);
}

public static void RecordSingleHitInCoreLibrary(int hitLocationIndex)
{
// Make sure to avoid recording if this is a call to RecordHit within the AppDomain setup code in an
// instrumented build of System.Private.CoreLib.
if (HitsArray is null)
return;

ref int location = ref HitsArray[hitLocationIndex];
if (location == 0)
location = 1;
}

public static void RecordSingleHit(int hitLocationIndex)
{
ref int location = ref HitsArray[hitLocationIndex];
if (location == 0)
location = 1;
}

public static void UnloadModule(object sender, EventArgs e)
{
// Claim the current hits array and reset it to prevent double-counting scenarios.
Expand Down Expand Up @@ -99,7 +119,10 @@ public static void UnloadModule(object sender, EventArgs e)
{
int oldHitCount = br.ReadInt32();
bw.Seek(-sizeof(int), SeekOrigin.Current);
bw.Write(hitsArray[i] + oldHitCount);
if (SingleHit)
bw.Write(hitsArray[i] + oldHitCount > 0 ? 1 : 0);
else
bw.Write(hitsArray[i] + oldHitCount);
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion test/coverlet.core.tests/CoverageTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ public void TestCoverage()
// Since Coverage only instruments dependancies, we need a fake module here
var testModule = Path.Combine(directory.FullName, "test.module.dll");

var coverage = new Coverage(testModule, Array.Empty<string>(), Array.Empty<string>(), Array.Empty<string>(), Array.Empty<string>(), Array.Empty<string>(), string.Empty, false);
var coverage = new Coverage(testModule, Array.Empty<string>(), Array.Empty<string>(), Array.Empty<string>(), Array.Empty<string>(), Array.Empty<string>(), false, string.Empty, false);
coverage.PrepareModules();

var result = coverage.GetCoverageResult();
Expand Down
4 changes: 2 additions & 2 deletions test/coverlet.core.tests/Instrumentation/InstrumenterTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ public void TestCoreLibInstrumentation()
foreach (var file in files)
File.Copy(Path.Combine(OriginalFilesDir, file), Path.Combine(TestFilesDir, file), overwrite: true);

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

module = Path.Combine(directory.FullName, destModule);
Instrumenter instrumenter = new Instrumenter(module, identifier, Array.Empty<string>(), Array.Empty<string>(), Array.Empty<string>(), attributesToIgnore);
Instrumenter instrumenter = new Instrumenter(module, identifier, Array.Empty<string>(), Array.Empty<string>(), Array.Empty<string>(), attributesToIgnore, false);
return new InstrumenterTest
{
Instrumenter = instrumenter,
Expand Down

0 comments on commit 76de86e

Please sign in to comment.