Skip to content
Closed
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.collector/DataCollection/CoverageWrapper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,8 @@ public Coverage CreateCoverage(CoverletSettings settings, ILogger coverletLogger
settings.UseSourceLink,
coverletLogger,
DependencyInjection.Current.GetService<IInstrumentationHelper>(),
DependencyInjection.Current.GetService<IFileSystem>());
DependencyInjection.Current.GetService<IFileSystem>(),
true);
}

/// <summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,10 +53,10 @@ public void TestSessionEnd(TestSessionEndArgs testSessionEndArgs)

try
{
_eqtTrace.Verbose($"Calling ModuleTrackerTemplate.UnloadModule for '{injectedInstrumentationClass.Assembly.FullName}'");
var unloadModule = injectedInstrumentationClass.GetMethod(nameof(ModuleTrackerTemplate.UnloadModule), new[] { typeof(object), typeof(EventArgs) });
unloadModule.Invoke(null, new[] { null, EventArgs.Empty });
_eqtTrace.Verbose($"Called ModuleTrackerTemplate.UnloadModule for '{injectedInstrumentationClass.Assembly.FullName}'");
_eqtTrace.Verbose($"Calling ModuleTrackerTemplate.InProcessCollectorFlush for '{injectedInstrumentationClass.Assembly.FullName}'");
var unloadModule = injectedInstrumentationClass.GetMethod(nameof(ModuleTrackerTemplate.InProcessCollectorFlush));
unloadModule.Invoke(null, new object[0]);
_eqtTrace.Verbose($"Called ModuleTrackerTemplate.InProcessCollectorFlush for '{injectedInstrumentationClass.Assembly.FullName}'");
}
catch (Exception ex)
{
Expand Down
18 changes: 16 additions & 2 deletions src/coverlet.core/Coverage.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ internal class Coverage
private string[] _excludeAttributes;
private bool _includeTestAssembly;
private bool _singleHit;
private bool _isInstrumentedByOutOfProcessCollector;
private string _mergeWith;
private bool _useSourceLink;
private ILogger _logger;
Expand All @@ -45,7 +46,8 @@ public Coverage(string module,
bool useSourceLink,
ILogger logger,
IInstrumentationHelper instrumentationHelper,
IFileSystem fileSystem)
IFileSystem fileSystem,
bool isInstrumentedByOutOfProcessCollector = false)
{
_module = module;
_includeFilters = includeFilters;
Expand All @@ -55,6 +57,7 @@ public Coverage(string module,
_excludeAttributes = excludeAttributes;
_includeTestAssembly = includeTestAssembly;
_singleHit = singleHit;
_isInstrumentedByOutOfProcessCollector = isInstrumentedByOutOfProcessCollector;
_mergeWith = mergeWith;
_useSourceLink = useSourceLink;
_logger = logger;
Expand Down Expand Up @@ -97,7 +100,18 @@ public CoveragePrepareResult PrepareModules()
continue;
}

var instrumenter = new Instrumenter(module, _identifier, _excludeFilters, _includeFilters, _excludedSourceFiles, _excludeAttributes, _singleHit, _logger, _instrumentationHelper, _fileSystem);
var instrumenter = new Instrumenter(module,
_identifier,
_excludeFilters,
_includeFilters,
_excludedSourceFiles,
_excludeAttributes,
_singleHit,
_logger,
_instrumentationHelper,
_fileSystem,
_isInstrumentedByOutOfProcessCollector);

if (instrumenter.CanInstrument())
{
_instrumentationHelper.BackupOriginalModule(module, _identifier);
Expand Down
12 changes: 10 additions & 2 deletions src/coverlet.core/Instrumentation/Instrumenter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ internal class Instrumenter
private readonly ExcludedFilesHelper _excludedFilesHelper;
private readonly string[] _excludedAttributes;
private readonly bool _singleHit;
private readonly bool _isInstrumentedByOutOfProcessCollector;
private readonly bool _isCoreLibrary;
private readonly ILogger _logger;
private readonly IInstrumentationHelper _instrumentationHelper;
Expand All @@ -33,6 +34,7 @@ internal class Instrumenter
private FieldDefinition _customTrackerHitsArray;
private FieldDefinition _customTrackerHitsFilePath;
private FieldDefinition _customTrackerSingleHit;
private FieldDefinition _customIsCalledByInProcessCollector;
private ILProcessor _customTrackerClassConstructorIl;
private TypeDefinition _customTrackerTypeDef;
private MethodReference _customTrackerRegisterUnloadEventsMethod;
Expand All @@ -54,7 +56,8 @@ public Instrumenter(
bool singleHit,
ILogger logger,
IInstrumentationHelper instrumentationHelper,
IFileSystem fileSystem)
IFileSystem fileSystem,
bool isInstrumentedByOutOfProcessCollector = false)
{
_module = module;
_identifier = identifier;
Expand All @@ -63,6 +66,7 @@ public Instrumenter(
_excludedFilesHelper = new ExcludedFilesHelper(excludedFiles, logger);
_excludedAttributes = excludedAttributes;
_singleHit = singleHit;
_isInstrumentedByOutOfProcessCollector = isInstrumentedByOutOfProcessCollector;
_isCoreLibrary = Path.GetFileNameWithoutExtension(_module) == "System.Private.CoreLib";
_logger = logger;
_instrumentationHelper = instrumentationHelper;
Expand Down Expand Up @@ -240,6 +244,8 @@ private void InstrumentModule()
_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));
_customTrackerClassConstructorIl.InsertBefore(lastInstr, Instruction.Create(_isInstrumentedByOutOfProcessCollector ? OpCodes.Ldc_I4_1 : OpCodes.Ldc_I4_0));
_customTrackerClassConstructorIl.InsertBefore(lastInstr, Instruction.Create(OpCodes.Stsfld, _customIsCalledByInProcessCollector));

if (containsAppContext)
{
Expand All @@ -248,7 +254,7 @@ private void InstrumentModule()
// initialization of the custom tracker and the static initialization of the hosting AppDomain
// (which for the core library case will be instrumented code).
var eventArgsType = new TypeReference(nameof(System), nameof(EventArgs), module, module.TypeSystem.CoreLibrary);
var customTrackerUnloadModule = new MethodReference(nameof(ModuleTrackerTemplate.UnloadModule), module.TypeSystem.Void, _customTrackerTypeDef);
var customTrackerUnloadModule = new MethodReference(nameof(ModuleTrackerTemplate.AppContextOnProcessExitEvent), module.TypeSystem.Void, _customTrackerTypeDef);
customTrackerUnloadModule.Parameters.Add(new ParameterDefinition(module.TypeSystem.Object));
customTrackerUnloadModule.Parameters.Add(new ParameterDefinition(eventArgsType));

Expand Down Expand Up @@ -291,6 +297,8 @@ private void AddCustomModuleTrackerToModule(ModuleDefinition module)
_customTrackerHitsFilePath = fieldClone;
else if (fieldClone.Name == nameof(ModuleTrackerTemplate.SingleHit))
_customTrackerSingleHit = fieldClone;
else if (fieldClone.Name == nameof(ModuleTrackerTemplate.IsCalledByInProcessCollector))
_customIsCalledByInProcessCollector = fieldClone;
}

foreach (MethodDefinition methodDef in moduleTrackerTemplate.Methods)
Expand Down
37 changes: 32 additions & 5 deletions src/coverlet.core/Instrumentation/ModuleTrackerTemplate.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ internal static class ModuleTrackerTemplate
public static string HitsFilePath;
public static int[] HitsArray;
public static bool SingleHit;
public static bool IsCalledByInProcessCollector;
private static readonly bool _enableLog = int.TryParse(Environment.GetEnvironmentVariable("COVERLET_ENABLETRACKERLOG"), out int result) ? result == 1 : false;

static ModuleTrackerTemplate()
Expand All @@ -35,8 +36,8 @@ static ModuleTrackerTemplate()
// to UnloadModule will be injected in System.AppContext.OnProcessExit.
public static void RegisterUnloadEvents()
{
AppDomain.CurrentDomain.ProcessExit += new EventHandler(UnloadModule);
AppDomain.CurrentDomain.DomainUnload += new EventHandler(UnloadModule);
AppDomain.CurrentDomain.ProcessExit += new EventHandler(ProcessExitEvent);
AppDomain.CurrentDomain.DomainUnload += new EventHandler(DomainUnloadEvent);
}

public static void RecordHitInCoreLibrary(int hitLocationIndex)
Expand Down Expand Up @@ -73,11 +74,37 @@ public static void RecordSingleHit(int hitLocationIndex)
location = 1;
}

public static void UnloadModule(object sender, EventArgs e)
private static bool IsDotNetCore()
{
// object for .NET Framework is inside mscorlib.dll
return Path.GetFileName(typeof(object).Assembly.Location) == "System.Private.CoreLib.dll";
}

public static void ProcessExitEvent(object sender, EventArgs e)
{
Flush(nameof(ProcessExitEvent));
}

public static void DomainUnloadEvent(object sender, EventArgs e)
{
Flush(nameof(DomainUnloadEvent));
}

public static void AppContextOnProcessExitEvent(object sender, EventArgs e)
{
Flush(nameof(AppContextOnProcessExitEvent));
}

public static void InProcessCollectorFlush()
{
Flush(nameof(InProcessCollectorFlush));
}

private static void Flush(string flushType)
{
try
{
WriteLog($"Unload called for '{Assembly.GetExecutingAssembly().Location}'");
WriteLog($"Unload called for '{Assembly.GetExecutingAssembly().Location}' FlushType: {flushType}");
// Claim the current hits array and reset it to prevent double-counting scenarios.
int[] hitsArray = Interlocked.Exchange(ref HitsArray, new int[HitsArray.Length]);

Expand Down Expand Up @@ -108,7 +135,7 @@ public static void UnloadModule(object sender, EventArgs e)
failedToCreateNewHitsFile = true;
}

if (failedToCreateNewHitsFile)
if (failedToCreateNewHitsFile && !IsDotNetCore())
{
// Update the number of hits by adding value on disk with the ones on memory.
// This path should be triggered only in the case of multiple AppDomain unloads.
Expand Down
3 changes: 2 additions & 1 deletion test/coverlet.core.tests/Coverage/InstrumenterHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@

using Coverlet.Core.Abstracts;
using Coverlet.Core.Helpers;
using Coverlet.Core.Instrumentation;
using Coverlet.Core.Reporters;
using Microsoft.Extensions.DependencyInjection;
using Moq;
Expand Down Expand Up @@ -117,7 +118,7 @@ async public static Task<CoveragePrepareResult> Run<T>(Func<dynamic, Task> callM
// string hitsFilePath = (string)tracker.GetField("HitsFilePath").GetValue(null);

// Void UnloadModule(System.Object, System.EventArgs)
tracker.GetTypeInfo().GetMethod("UnloadModule").Invoke(null, new object[2] { null, null });
tracker.GetTypeInfo().GetMethod(nameof(ModuleTrackerTemplate.InProcessCollectorFlush)).Invoke(null, new object[0]);

// Persist CoveragePrepareResult
using (FileStream fs = new FileStream(persistPrepareResultToFile, FileMode.Open))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,8 @@ public TrackerContext()
public void Dispose()
{
File.Delete(ModuleTrackerTemplate.HitsFilePath);
AppDomain.CurrentDomain.ProcessExit -= ModuleTrackerTemplate.UnloadModule;
AppDomain.CurrentDomain.DomainUnload -= ModuleTrackerTemplate.UnloadModule;
AppDomain.CurrentDomain.ProcessExit -= ModuleTrackerTemplate.ProcessExitEvent;
AppDomain.CurrentDomain.DomainUnload -= ModuleTrackerTemplate.DomainUnloadEvent;
}
}

Expand All @@ -36,7 +36,7 @@ public void HitsFileCorrectlyWritten()
{
using var ctx = new TrackerContext();
ModuleTrackerTemplate.HitsArray = new[] { 1, 2, 0, 3 };
ModuleTrackerTemplate.UnloadModule(null, null);
ModuleTrackerTemplate.InProcessCollectorFlush();

var expectedHitsArray = new[] { 1, 2, 0, 3 };
Assert.Equal(expectedHitsArray, ReadHitsFile());
Expand All @@ -54,7 +54,7 @@ public void HitsFileWithDifferentNumberOfEntriesCausesExceptionOnUnload()
using var ctx = new TrackerContext();
WriteHitsFile(new[] { 1, 2, 3 });
ModuleTrackerTemplate.HitsArray = new[] { 1 };
Assert.Throws<InvalidOperationException>(() => ModuleTrackerTemplate.UnloadModule(null, null));
Assert.Throws<InvalidOperationException>(() => ModuleTrackerTemplate.InProcessCollectorFlush());
return _success;
});
}
Expand All @@ -73,7 +73,7 @@ public void HitsOnMultipleThreadsCorrectlyCounted()
t.Start(i);
}

ModuleTrackerTemplate.UnloadModule(null, null);
ModuleTrackerTemplate.InProcessCollectorFlush();
var expectedHitsArray = new[] { 4, 3, 2, 1 };
Assert.Equal(expectedHitsArray, ReadHitsFile());

Expand All @@ -98,10 +98,10 @@ public void MultipleSequentialUnloadsHaveCorrectTotalData()
{
using var ctx = new TrackerContext();
ModuleTrackerTemplate.HitsArray = new[] { 0, 3, 2, 1 };
ModuleTrackerTemplate.UnloadModule(null, null);
ModuleTrackerTemplate.InProcessCollectorFlush();

ModuleTrackerTemplate.HitsArray = new[] { 0, 1, 2, 3 };
ModuleTrackerTemplate.UnloadModule(null, null);
ModuleTrackerTemplate.InProcessCollectorFlush();

var expectedHitsArray = new[] { 0, 4, 4, 4 };
Assert.Equal(expectedHitsArray, ReadHitsFile());
Expand All @@ -123,7 +123,7 @@ public void MutexBlocksMultipleWriters()
Assert.True(createdNew);

ModuleTrackerTemplate.HitsArray = new[] { 0, 1, 2, 3 };
var unloadTask = Task.Run(() => ModuleTrackerTemplate.UnloadModule(null, null));
var unloadTask = Task.Run(() => ModuleTrackerTemplate.InProcessCollectorFlush());

Assert.False(unloadTask.Wait(5));

Expand Down
1 change: 1 addition & 0 deletions test/coverlet.integration.tests/BaseTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ private protected bool RunCommand(string command, string arguments, out string s
psi.WorkingDirectory = workingDirectory;
psi.RedirectStandardError = true;
psi.RedirectStandardOutput = true;
psi.EnvironmentVariables.Add("COVERLET_ENABLETRACKERLOG", "1");
Process commandProcess = Process.Start(psi);
if (!commandProcess.WaitForExit((int)TimeSpan.FromMinutes(5).TotalMilliseconds))
{
Expand Down