Skip to content

Commit

Permalink
Merge pull request #131 from lucaslorentz/fix-missed-hits
Browse files Browse the repository at this point in the history
Fix some missed hits
  • Loading branch information
lucaslorentz committed Oct 31, 2020
2 parents dcd021c + 966f5c7 commit fe7467b
Show file tree
Hide file tree
Showing 28 changed files with 255 additions and 230 deletions.
2 changes: 1 addition & 1 deletion global.json
@@ -1,5 +1,5 @@
{
"sdk": {
"version": "3.1.201"
"version": "3.1.403"
}
}
57 changes: 33 additions & 24 deletions src/MiniCover.HitServices/HitContext.cs
@@ -1,4 +1,5 @@
using System.Collections.Generic;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
Expand All @@ -16,37 +17,38 @@ public static HitContext Current
set => _currentAsyncLocal.Value = value;
}

private readonly object _lock = new object();
private int _methodCount;

public HitContext(
string assemblyName,
string className,
string methodName,
IDictionary<int, int> hits = null)
{
Id = Guid.NewGuid().ToString();
AssemblyName = assemblyName;
ClassName = className;
MethodName = methodName;
Hits = hits ?? new Dictionary<int, int>();
Hits = hits != null
? new Dictionary<int, int>(hits)
: new Dictionary<int, int>();
}

public string Id { get; }
public string AssemblyName { get; }
public string ClassName { get; }
public string MethodName { get; }
public IDictionary<int, int> Hits { get; }

public void RecordHit(int id)
{
lock (_lock)
if (Hits.TryGetValue(id, out var count))
{
if (Hits.TryGetValue(id, out var count))
{
Hits[id] = count + 1;
}
else
{
Hits[id] = 1;
}
Hits[id] = count + 1;
}
else
{
Hits[id] = 1;
}
}

Expand All @@ -72,21 +74,28 @@ public static IEnumerable<HitContext> MergeDuplicates(IEnumerable<HitContext> so
)).ToArray();
}

public int EnterMethod()
{
return Interlocked.Increment(ref _methodCount);
}

public int ExitMethod()
{
return Interlocked.Decrement(ref _methodCount);
}

public void Serialize(Stream stream)
{
lock (_lock)
using (var binaryWriter = new BinaryWriter(stream, Encoding.UTF8, true))
{
using (var binaryWriter = new BinaryWriter(stream, Encoding.UTF8, true))
binaryWriter.Write(ClassName);
binaryWriter.Write(MethodName);
binaryWriter.Write(AssemblyName);
binaryWriter.Write(Hits.Count);
foreach (var hitedInstruction in Hits)
{
binaryWriter.Write(ClassName);
binaryWriter.Write(MethodName);
binaryWriter.Write(AssemblyName);
binaryWriter.Write(Hits.Count);
foreach (var hitedInstruction in Hits)
{
binaryWriter.Write(hitedInstruction.Key);
binaryWriter.Write(hitedInstruction.Value);
}
binaryWriter.Write(hitedInstruction.Key);
binaryWriter.Write(hitedInstruction.Value);
}
}
}
Expand Down Expand Up @@ -121,4 +130,4 @@ public static HitContext Read(BinaryReader binaryReader)
return new HitContext(assemblyName, className, methodName, hits);
}
}
}
}
67 changes: 4 additions & 63 deletions src/MiniCover.HitServices/HitService.cs
@@ -1,73 +1,14 @@
using System;
using System.Collections.Concurrent;
using System.IO;

namespace MiniCover.HitServices
namespace MiniCover.HitServices
{
public static class HitService
{
public static MethodContext EnterMethod(
public static MethodScope EnterMethod(
string hitsPath,
string assemblyName,
string className,
string methodName)
{
return new MethodContext(hitsPath, assemblyName, className, methodName);
}

public class MethodContext : IDisposable
{
private static ConcurrentDictionary<string, Stream> _filesStream = new ConcurrentDictionary<string, Stream>();

private readonly string _hitsPath;
private readonly HitContext _hitContext;
private readonly bool _saveHitContext;

public MethodContext(
string hitsPath,
string assemblyName,
string className,
string methodName)
{
_hitsPath = hitsPath;

if (HitContext.Current == null)
{
_hitContext = new HitContext(assemblyName, className, methodName);
HitContext.Current = _hitContext;
_saveHitContext = true;
}
else
{
_hitContext = HitContext.Current;
}
}

public void Hit(int id)
{
_hitContext.RecordHit(id);
}

public void Dispose()
{
if (_saveHitContext)
{
var fileStream = _filesStream.GetOrAdd(_hitsPath, CreateOutputFile);
lock (fileStream)
{
_hitContext.Serialize(fileStream);
fileStream.Flush();
}
HitContext.Current = null;
}
}

private static FileStream CreateOutputFile(string hitsPath)
{
Directory.CreateDirectory(hitsPath);
var filePath = Path.Combine(hitsPath, $"{Guid.NewGuid()}.hits");
return File.Open(filePath, FileMode.CreateNew);
}
return new MethodScope(hitsPath, assemblyName, className, methodName);
}
}
}
}
74 changes: 74 additions & 0 deletions src/MiniCover.HitServices/MethodScope.cs
@@ -0,0 +1,74 @@
using System;
using System.IO;

namespace MiniCover.HitServices
{
public class MethodScope : IDisposable
{
private readonly HitContext _hitContext;
private readonly string _hitsPath;
private readonly bool _isEntryMethod;

public MethodScope(
string hitsPath,
string assemblyName,
string className,
string methodName)
{
_hitContext = HitContext.Current;

if (_hitContext == null)
{
_hitContext = new HitContext(assemblyName, className, methodName);
_isEntryMethod = true;
HitContext.Current = _hitContext;
}

_hitsPath = hitsPath;

lock (_hitContext)
{
_hitContext.EnterMethod();
}
}

public void Hit(int id)
{
lock (_hitContext)
{
_hitContext.RecordHit(id);
}
}

public void Dispose()
{
if (_isEntryMethod)
HitContext.Current = null;

lock (_hitContext)
{
if (_hitContext.ExitMethod() == 0)
SaveHitContext();
}
}

private void SaveHitContext()
{
lock (_hitContext)
{
if (_hitContext.Hits.Count == 0)
return;

Directory.CreateDirectory(_hitsPath);

var fileName = Path.Combine(_hitsPath, $"{_hitContext.Id}.hits");

using (var fileStream = File.Open(fileName, FileMode.Create))
{
_hitContext.Serialize(fileStream);
fileStream.Flush();
}
}
}
}
}
8 changes: 5 additions & 3 deletions src/MiniCover/CommandLine/Options/ThresholdOption.cs
@@ -1,4 +1,6 @@
namespace MiniCover.CommandLine.Options
using System.Globalization;

namespace MiniCover.CommandLine.Options
{
class ThresholdOption : ISingleValueOption, IThresholdOption
{
Expand All @@ -10,12 +12,12 @@ class ThresholdOption : ISingleValueOption, IThresholdOption

public void ReceiveValue(string value)
{
if (!float.TryParse(value, out var threshold))
if (!float.TryParse(value, NumberStyles.Any, CultureInfo.InvariantCulture, out var threshold))
{
threshold = _defaultValue;
}

Value = threshold / 100;
}
}
}
}
8 changes: 4 additions & 4 deletions src/MiniCover/Instrumentation/MethodInstrumenter.cs
Expand Up @@ -18,10 +18,10 @@ namespace MiniCover.Instrumentation
public class MethodInstrumenter
{
private static readonly Type hitServiceType = typeof(HitService);
private static readonly Type methodContextType = typeof(HitService.MethodContext);
private static readonly MethodInfo hitMethodInfo = methodContextType.GetMethod(nameof(HitService.MethodContext.Hit));
private static readonly Type methodScopeType = typeof(MethodScope);
private static readonly MethodInfo hitMethodInfo = methodScopeType.GetMethod(nameof(MethodScope.Hit));
private static readonly MethodInfo enterMethodInfo = hitServiceType.GetMethod(nameof(HitService.EnterMethod));
private static readonly MethodInfo disposeMethodInfo = methodContextType.GetMethod(nameof(IDisposable.Dispose));
private static readonly MethodInfo disposeMethodInfo = methodScopeType.GetMethod(nameof(IDisposable.Dispose));

private readonly ILogger<MethodInstrumenter> _logger;
private readonly IFileReader _fileReader;
Expand Down Expand Up @@ -49,7 +49,7 @@ public class MethodInstrumenter
FullName = originalMethod.FullName,
});

var methodContextClassReference = methodDefinition.Module.GetOrImportReference(methodContextType);
var methodContextClassReference = methodDefinition.Module.GetOrImportReference(methodScopeType);
var enterMethodReference = methodDefinition.Module.GetOrImportReference(enterMethodInfo);
var disposeMethodReference = methodDefinition.Module.GetOrImportReference(disposeMethodInfo);

Expand Down
2 changes: 1 addition & 1 deletion tests/MiniCover.HitServices.UnitTests/HitContextTests.cs
Expand Up @@ -35,4 +35,4 @@ public void BinarySerializationShouldWork()
}
}
}
}
}

0 comments on commit fe7467b

Please sign in to comment.