Skip to content

Remove static Coverage object #393

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

Closed
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
11 changes: 7 additions & 4 deletions src/coverlet.console/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
using System.Diagnostics;
using System.IO;
using System.Text;
using System.Threading.Tasks;

using ConsoleTables;
using Coverlet.Console.Logging;
using Coverlet.Core;
Expand Down Expand Up @@ -57,7 +57,7 @@ static int Main(string[] args)
logger.Level = verbosity.ParsedValue;
}

Coverage coverage = new Coverage(module.Value,
IInstrumenter coverage = new Instrumenter(module.Value,
includeFilters.Values.ToArray(),
includeDirectories.Values.ToArray(),
excludeFilters.Values.ToArray(),
Expand All @@ -68,7 +68,8 @@ static int Main(string[] args)
mergeWith.Value(),
useSourceLink.HasValue(),
logger);
coverage.PrepareModules();

InstrumenterState instrumenterState = coverage.PrepareModules();

Process process = new Process();
process.StartInfo.FileName = target.Value();
Expand Down Expand Up @@ -102,7 +103,9 @@ static int Main(string[] args)

logger.LogInformation("\nCalculating coverage result...");

var result = coverage.GetCoverageResult();
ICoverageCalculator coverageResult = new CoverageCalculator(instrumenterState, logger);

CoverageResult result = coverageResult.GetCoverageResult();
var directory = Path.GetDirectoryName(dOutput);
if (directory == string.Empty)
{
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,18 @@
using System;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;

using Coverlet.Core.Enums;
using Coverlet.Core.Instrumentation;
using Coverlet.Core.Symbols;

namespace Coverlet.Core
{
public class CoverageDetails
{
public double Covered { get; internal set; }
public int Total { get; internal set; }
public double Percent => Total == 0 ? 100D : Math.Floor((Covered / Total) * 10000) / 100;
}

public class BranchInfo
{
public int Line { get; set; }
Expand Down Expand Up @@ -257,4 +262,9 @@ public ThresholdTypeFlags GetThresholdTypesBelowThreshold(CoverageSummary summar
return thresholdTypeFlags;
}
}
}

public interface ICoverageCalculator
{
CoverageResult GetCoverageResult();
}
}
126 changes: 126 additions & 0 deletions src/coverlet.core/Abstracts/IInstrumenter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Globalization;
using System.IO;
using Newtonsoft.Json;

namespace Coverlet.Core
{
public class Line
{
public int Number;
public string Class;
public string Method;
public int Hits;
}

public class Branch : Line
{
public int Offset;
public int EndOffset;
public int Path;
public uint Ordinal;
}

public class BranchKeyConverter : TypeConverter
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

we need custom converter because Json serializer does not support serialization of complex dictionary key

{
public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
{
return sourceType == typeof(string);
}

public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
{
return JsonConvert.DeserializeObject<BranchKey>(value.ToString());
}

public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
{
return destinationType == typeof(BranchKey);
}
}

[TypeConverter(typeof(BranchKeyConverter))]
public class BranchKey : IEquatable<BranchKey>
{
public BranchKey(int line, int ordinal) => (Line, Ordinal) = (line, ordinal);

public int Line { get; set; }
public int Ordinal { get; set; }

public override bool Equals(object obj) => Equals(obj);

public bool Equals(BranchKey other) => other is BranchKey branchKey && branchKey.Line == this.Line && branchKey.Ordinal == this.Ordinal;

public override int GetHashCode()
{
return (this.Line, this.Ordinal).GetHashCode();
}

public override string ToString()
{
return JsonConvert.SerializeObject(this);
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

for simplicity I used Json serializer.

}
}

public class Document
{
public Document()
{
Lines = new Dictionary<int, Line>();
Branches = new Dictionary<BranchKey, Branch>();
}

public string Path;
public int Index;
public Dictionary<int, Line> Lines { get; private set; }
public Dictionary<BranchKey, Branch> Branches { get; private set; }
}

public class HitCandidate
{
public HitCandidate(bool isBranch, int docIndex, int start, int end) => (this.IsBranch, this.DocIndex, this.Start, this.End) = (isBranch, docIndex, start, end);

public bool IsBranch { get; set; }
public int DocIndex { get; set; }
public int Start { get; set; }
public int End { get; set; }
}

public class InstrumenterResult
{
public InstrumenterResult()
{
Documents = new Dictionary<string, Document>();
HitCandidates = new List<HitCandidate>();
}

public string Module;
public string[] AsyncMachineStateMethod;
public string HitsFilePath;
public string ModulePath;
public string SourceLink;
public Dictionary<string, Document> Documents { get; private set; }
public List<HitCandidate> HitCandidates { get; private set; }
}

public class InstrumenterState
{
public InstrumenterResult[] InstrumenterResults { get; set; }
public bool UseSourceLink { get; set; }
public string Identifier { get; set; }
public string MergeWith { get; set; }
}

public interface IInstrumenter
{
InstrumenterState PrepareModules();
}

public interface IInstrumentStateSerializer
{
Stream Serialize(InstrumenterState instrumentState);
InstrumenterState Deserialize(Stream serializedInstrumentState);
}
}
Original file line number Diff line number Diff line change
@@ -1,114 +1,28 @@
using System;
using System.Collections.Generic;
using System.Collections.Generic;
using System.IO;
using System.Linq;

using Coverlet.Core.Helpers;
using Coverlet.Core.Instrumentation;
using Coverlet.Core.Logging;

using Newtonsoft.Json;
using Newtonsoft.Json.Linq;

namespace Coverlet.Core
{
public class Coverage
public class CoverageCalculator : ICoverageCalculator
{
private string _module;
private string _identifier;
private string[] _includeFilters;
private string[] _includeDirectories;
private string[] _excludeFilters;
private string[] _excludedSourceFiles;
private string[] _excludeAttributes;
private bool _includeTestAssembly;
private bool _singleHit;
private string _mergeWith;
private bool _useSourceLink;
private ILogger _logger;
private List<InstrumenterResult> _results;

public string Identifier
{
get { return _identifier; }
}

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

_identifier = Guid.NewGuid().ToString();
_results = new List<InstrumenterResult>();
}

public void PrepareModules()
{
string[] modules = InstrumentationHelper.GetCoverableModules(_module, _includeDirectories, _includeTestAssembly);
string[] excludes = InstrumentationHelper.GetExcludedFiles(_excludedSourceFiles);

Array.ForEach(_excludeFilters ?? Array.Empty<string>(), filter => _logger.LogVerbose($"Excluded module filter '{filter}'"));
Array.ForEach(_includeFilters ?? Array.Empty<string>(), filter => _logger.LogVerbose($"Included module filter '{filter}'"));
Array.ForEach(excludes ?? Array.Empty<string>(), filter => _logger.LogVerbose($"Excluded source files '{filter}'"));

_excludeFilters = _excludeFilters?.Where(f => InstrumentationHelper.IsValidFilterExpression(f)).ToArray();
_includeFilters = _includeFilters?.Where(f => InstrumentationHelper.IsValidFilterExpression(f)).ToArray();
private readonly InstrumenterState _instrumenterState;
private readonly ILogger _logger;

foreach (var module in modules)
{
if (InstrumentationHelper.IsModuleExcluded(module, _excludeFilters) ||
!InstrumentationHelper.IsModuleIncluded(module, _includeFilters))
{
_logger.LogVerbose($"Excluded module: '{module}'");
continue;
}

var instrumenter = new Instrumenter(module, _identifier, _excludeFilters, _includeFilters, excludes, _excludeAttributes, _singleHit, _logger);
if (instrumenter.CanInstrument())
{
InstrumentationHelper.BackupOriginalModule(module, _identifier);

// Guard code path and restore if instrumentation fails.
try
{
var result = instrumenter.Instrument();
_results.Add(result);
_logger.LogVerbose($"Instrumented module: '{module}'");
}
catch (Exception ex)
{
_logger.LogWarning($"Unable to instrument module: {module} because : {ex.Message}");
InstrumentationHelper.RestoreOriginalModule(module, _identifier);
}
}
}
}
public CoverageCalculator(InstrumenterState instrumenterState, ILogger logger) => (_instrumenterState, _logger) = (instrumenterState, logger);

public CoverageResult GetCoverageResult()
{
CalculateCoverage();

Modules modules = new Modules();
foreach (var result in _results)
foreach (InstrumenterResult result in _instrumenterState.InstrumenterResults)
{
Documents documents = new Documents();
foreach (var doc in result.Documents.Values)
Expand Down Expand Up @@ -189,14 +103,14 @@ public CoverageResult GetCoverageResult()
}

modules.Add(Path.GetFileName(result.ModulePath), documents);
InstrumentationHelper.RestoreOriginalModule(result.ModulePath, _identifier);
InstrumentationHelper.RestoreOriginalModule(result.ModulePath, _instrumenterState.Identifier);
}

var coverageResult = new CoverageResult { Identifier = _identifier, Modules = modules, InstrumentedResults = _results };
var coverageResult = new CoverageResult { Identifier = _instrumenterState.Identifier, Modules = modules, InstrumentedResults = _instrumenterState.InstrumenterResults.ToList() };

if (!string.IsNullOrEmpty(_mergeWith) && !string.IsNullOrWhiteSpace(_mergeWith) && File.Exists(_mergeWith))
if (!string.IsNullOrEmpty(_instrumenterState.MergeWith) && !string.IsNullOrWhiteSpace(_instrumenterState.MergeWith) && File.Exists(_instrumenterState.MergeWith))
{
string json = File.ReadAllText(_mergeWith);
string json = File.ReadAllText(_instrumenterState.MergeWith);
coverageResult.Merge(JsonConvert.DeserializeObject<Modules>(json));
}

Expand All @@ -205,7 +119,7 @@ public CoverageResult GetCoverageResult()

private void CalculateCoverage()
{
foreach (var result in _results)
foreach (var result in _instrumenterState.InstrumenterResults)
{
if (!File.Exists(result.HitsFilePath))
{
Expand All @@ -214,7 +128,7 @@ private void CalculateCoverage()
}

List<Document> documents = result.Documents.Values.ToList();
if (_useSourceLink && result.SourceLink != null)
if (_instrumenterState.UseSourceLink && result.SourceLink != null)
{
var jObject = JObject.Parse(result.SourceLink)["documents"];
var sourceLinkDocuments = JsonConvert.DeserializeObject<Dictionary<string, string>>(jObject.ToString());
Expand All @@ -236,17 +150,17 @@ private void CalculateCoverage()
for (int i = 0; i < hitCandidatesCount; ++i)
{
var hitLocation = result.HitCandidates[i];
var document = documentsList[hitLocation.docIndex];
var document = documentsList[hitLocation.DocIndex];
int hits = br.ReadInt32();

if (hitLocation.isBranch)
if (hitLocation.IsBranch)
{
var branch = document.Branches[(hitLocation.start, hitLocation.end)];
var branch = document.Branches[new BranchKey(hitLocation.Start, hitLocation.End)];
branch.Hits += hits;
}
else
{
for (int j = hitLocation.start; j <= hitLocation.end; j++)
for (int j = hitLocation.Start; j <= hitLocation.End; j++)
{
var line = document.Lines[j];
line.Hits += hits;
Expand All @@ -259,7 +173,7 @@ private void CalculateCoverage()
// we'll remove all MoveNext() not covered branch
foreach (var document in result.Documents)
{
List<KeyValuePair<(int, int), Branch>> branchesToRemove = new List<KeyValuePair<(int, int), Branch>>();
List<KeyValuePair<BranchKey, Branch>> branchesToRemove = new List<KeyValuePair<BranchKey, Branch>>();
foreach (var branch in document.Value.Branches)
{
//if one branch is covered we search the other one only if it's not covered
Expand Down Expand Up @@ -291,7 +205,7 @@ private bool IsAsyncStateMachineMethod(string method)
return false;
}

foreach (var instrumentationResult in _results)
foreach (var instrumentationResult in _instrumenterState.InstrumenterResults)
{
if (instrumentationResult.AsyncMachineStateMethod.Contains(method))
{
Expand Down
Loading