Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Single hit #309

Merged
merged 1 commit into from
Feb 7, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 == nameof(ModuleTrackerTemplate.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;
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📝 The main value here is avoiding this write invalidating cache lines. OpenCover uses a configurable threshold value. I could rewrite it to use a configurable threshold, though most uses I've seen in practice either omit this value or set it to 1.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why don't pass _singleHit as parameter or use static var and have less methods?Inlining?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's always a possibility to come back and do this if it proves to be equally fast. Since the expanded form is at least that fast and not difficult to work with, it seemed like the straightforward initial approach.

}

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 @@ -24,7 +24,7 @@ public void TestCoverage()

// TODO: Find a way to mimick hits

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);
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);
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