diff --git a/src/coverlet.core/Coverage.cs b/src/coverlet.core/Coverage.cs index fb5b67fc6..663550e18 100644 --- a/src/coverlet.core/Coverage.cs +++ b/src/coverlet.core/Coverage.cs @@ -61,7 +61,17 @@ public Coverage(string module, _results = new List(); } - public void PrepareModules() + public Coverage(CoveragePrepareResult prepareResult, ILogger logger) + { + this._identifier = prepareResult.Identifier; + this._module = prepareResult.Module; + this._mergeWith = prepareResult.MergeWith; + this._useSourceLink = prepareResult.UseSourceLink; + this._results = new List(prepareResult.Results); + _logger = logger; + } + + public CoveragePrepareResult PrepareModules() { string[] modules = InstrumentationHelper.GetCoverableModules(_module, _includeDirectories, _includeTestAssembly); string[] excludes = InstrumentationHelper.GetExcludedFiles(_excludedSourceFiles); @@ -101,6 +111,15 @@ public void PrepareModules() } } } + + return new CoveragePrepareResult() + { + Identifier = _identifier, + Module = _module, + MergeWith = _mergeWith, + UseSourceLink = _useSourceLink, + Results = _results.ToArray() + }; } public CoverageResult GetCoverageResult() @@ -245,7 +264,7 @@ private void CalculateCoverage() 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 @@ -263,7 +282,7 @@ private void CalculateCoverage() // we'll remove all MoveNext() not covered branch foreach (var document in result.Documents) { - List> branchesToRemove = new List>(); + List> branchesToRemove = new List>(); foreach (var branch in document.Value.Branches) { //if one branch is covered we search the other one only if it's not covered diff --git a/src/coverlet.core/CoveragePrepareResult.cs b/src/coverlet.core/CoveragePrepareResult.cs new file mode 100644 index 000000000..fad6dd0c5 --- /dev/null +++ b/src/coverlet.core/CoveragePrepareResult.cs @@ -0,0 +1,40 @@ +using System.IO; +using System.Text; + +using Coverlet.Core.Instrumentation; +using Newtonsoft.Json; + +namespace Coverlet.Core +{ + public class CoveragePrepareResult + { + public string Identifier { get; set; } + public string Module { get; set; } + public string MergeWith { get; set; } + public bool UseSourceLink { get; set; } + public InstrumenterResult[] Results { get; set; } + + public static CoveragePrepareResult Deserialize(Stream serializedInstrumentState) + { + var serializer = new JsonSerializer(); + using (var sr = new StreamReader(serializedInstrumentState)) + using (var jsonTextReader = new JsonTextReader(sr)) + { + return serializer.Deserialize(jsonTextReader); + } + } + + public static Stream Serialize(CoveragePrepareResult instrumentState) + { + var serializer = new JsonSerializer(); + MemoryStream ms = new MemoryStream(); + using (var sw = new StreamWriter(ms, Encoding.UTF8, 1024, true)) + { + serializer.Serialize(sw, instrumentState); + sw.Flush(); + ms.Position = 0; + return ms; + } + } + } +} diff --git a/src/coverlet.core/Instrumentation/Instrumenter.cs b/src/coverlet.core/Instrumentation/Instrumenter.cs index 9698ba207..5308c961b 100644 --- a/src/coverlet.core/Instrumentation/Instrumenter.cs +++ b/src/coverlet.core/Instrumentation/Instrumenter.cs @@ -377,8 +377,7 @@ private Instruction AddInstrumentationCode(MethodDefinition method, ILProcessor document.Lines.Add(i, new Line { Number = i, Class = method.DeclaringType.FullName, Method = method.FullName }); } - var entry = (false, document.Index, sequencePoint.StartLine, sequencePoint.EndLine); - _result.HitCandidates.Add(entry); + _result.HitCandidates.Add(new HitCandidate(false, document.Index, sequencePoint.StartLine, sequencePoint.EndLine)); return AddInstrumentationInstructions(method, processor, instruction, _result.HitCandidates.Count - 1); } @@ -392,7 +391,7 @@ private Instruction AddInstrumentationCode(MethodDefinition method, ILProcessor _result.Documents.Add(document.Path, document); } - var key = (branchPoint.StartLine, (int)branchPoint.Ordinal); + BranchKey key = new BranchKey(branchPoint.StartLine, (int)branchPoint.Ordinal); if (!document.Branches.ContainsKey(key)) { document.Branches.Add(key, @@ -422,8 +421,7 @@ private Instruction AddInstrumentationCode(MethodDefinition method, ILProcessor } } - var entry = (true, document.Index, branchPoint.StartLine, (int)branchPoint.Ordinal); - _result.HitCandidates.Add(entry); + _result.HitCandidates.Add(new HitCandidate(true, document.Index, branchPoint.StartLine, (int)branchPoint.Ordinal)); return AddInstrumentationInstructions(method, processor, instruction, _result.HitCandidates.Count - 1); } diff --git a/src/coverlet.core/Instrumentation/InstrumenterResult.cs b/src/coverlet.core/Instrumentation/InstrumenterResult.cs index 5c3d374e2..b86d3d9c0 100644 --- a/src/coverlet.core/Instrumentation/InstrumenterResult.cs +++ b/src/coverlet.core/Instrumentation/InstrumenterResult.cs @@ -1,8 +1,13 @@ +using System; using System.Collections.Generic; +using System.ComponentModel; +using System.Globalization; +using System.IO; +using Newtonsoft.Json; namespace Coverlet.Core.Instrumentation { - internal class Line + public class Line { public int Number; public string Class; @@ -10,7 +15,7 @@ internal class Line public int Hits; } - internal class Branch : Line + public class Branch : Line { public int Offset; public int EndOffset; @@ -18,26 +23,77 @@ internal class Branch : Line public uint Ordinal; } - internal class Document + public class BranchKeyConverter : TypeConverter + { + 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(value.ToString()); + } + + public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType) + { + return destinationType == typeof(BranchKey); + } + } + + [TypeConverter(typeof(BranchKeyConverter))] + public class BranchKey : IEquatable + { + 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); + } + } + + public class Document { public Document() { Lines = new Dictionary(); - Branches = new Dictionary<(int Line, int Ordinal), Branch>(); + Branches = new Dictionary(); } public string Path; public int Index; public Dictionary Lines { get; private set; } - public Dictionary<(int Line, int Ordinal), Branch> Branches { get; private set; } + public Dictionary 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; } } - internal class InstrumenterResult + public class InstrumenterResult { public InstrumenterResult() { Documents = new Dictionary(); - HitCandidates = new List<(bool isBranch, int docIndex, int start, int end)>(); + HitCandidates = new List(); } public string Module; @@ -46,6 +102,6 @@ public InstrumenterResult() public string ModulePath; public string SourceLink; public Dictionary Documents { get; private set; } - public List<(bool isBranch, int docIndex, int start, int end)> HitCandidates { get; private set; } + public List HitCandidates { get; private set; } } -} \ No newline at end of file +} diff --git a/src/coverlet.msbuild.tasks/CoverageResultTask.cs b/src/coverlet.msbuild.tasks/CoverageResultTask.cs index d5ba9e67b..68d6559bc 100644 --- a/src/coverlet.msbuild.tasks/CoverageResultTask.cs +++ b/src/coverlet.msbuild.tasks/CoverageResultTask.cs @@ -18,6 +18,7 @@ public class CoverageResultTask : Task private double _threshold; private string _thresholdType; private string _thresholdStat; + private ITaskItem _instrumenterState; private MSBuildLogger _logger; [Required] @@ -55,6 +56,13 @@ public string ThresholdStat set { _thresholdStat = value; } } + [Required] + public ITaskItem InstrumenterState + { + get { return _instrumenterState; } + set { _instrumenterState = value; } + } + public CoverageResultTask() { _logger = new MSBuildLogger(Log); @@ -66,7 +74,13 @@ public override bool Execute() { Console.WriteLine("\nCalculating coverage result..."); - var coverage = InstrumentationTask.Coverage; + if (InstrumenterState is null || !File.Exists(InstrumenterState.ItemSpec)) + { + _logger.LogError("Result of instrumentation task not found"); + return false; + } + + var coverage = new Coverage(CoveragePrepareResult.Deserialize(new FileStream(InstrumenterState.ItemSpec, FileMode.Open)), this._logger); var result = coverage.GetCoverageResult(); var directory = Path.GetDirectoryName(_output); diff --git a/src/coverlet.msbuild.tasks/InstrumentationTask.cs b/src/coverlet.msbuild.tasks/InstrumentationTask.cs index ebe7903d8..5242bdb03 100644 --- a/src/coverlet.msbuild.tasks/InstrumentationTask.cs +++ b/src/coverlet.msbuild.tasks/InstrumentationTask.cs @@ -1,4 +1,5 @@ using System; +using System.IO; using Coverlet.Core; using Microsoft.Build.Framework; using Microsoft.Build.Utilities; @@ -7,7 +8,6 @@ namespace Coverlet.MSbuild.Tasks { public class InstrumentationTask : Task { - private static Coverage _coverage; private string _path; private string _include; private string _includeDirectory; @@ -18,13 +18,9 @@ public class InstrumentationTask : Task private bool _singleHit; private string _mergeWith; private bool _useSourceLink; + private ITaskItem _instrumenterState; private readonly MSBuildLogger _logger; - internal static Coverage Coverage - { - get { return _coverage; } - } - [Required] public string Path { @@ -86,6 +82,13 @@ public bool UseSourceLink set { _useSourceLink = value; } } + [Output] + public ITaskItem InstrumenterState + { + get { return _instrumenterState; } + set { _instrumenterState = value; } + } + public InstrumentationTask() { _logger = new MSBuildLogger(Log); @@ -101,8 +104,16 @@ public override bool Execute() var excludedSourceFiles = _excludeByFile?.Split(','); var excludeAttributes = _excludeByAttribute?.Split(','); - _coverage = new Coverage(_path, includeFilters, includeDirectories, excludeFilters, excludedSourceFiles, excludeAttributes, _includeTestAssembly, _singleHit, _mergeWith, _useSourceLink, _logger); - _coverage.PrepareModules(); + Coverage coverage = new Coverage(_path, includeFilters, includeDirectories, excludeFilters, excludedSourceFiles, excludeAttributes, _includeTestAssembly, _singleHit, _mergeWith, _useSourceLink, _logger); + CoveragePrepareResult prepareResult = coverage.PrepareModules(); + InstrumenterState = new TaskItem(System.IO.Path.GetTempFileName()); + using (var instrumentedStateFile = new FileStream(InstrumenterState.ItemSpec, FileMode.Open, FileAccess.Write)) + { + using (Stream serializedState = CoveragePrepareResult.Serialize(prepareResult)) + { + serializedState.CopyTo(instrumentedStateFile); + } + } } catch (Exception ex) { diff --git a/src/coverlet.msbuild.tasks/coverlet.msbuild.targets b/src/coverlet.msbuild.tasks/coverlet.msbuild.targets index 1b1f4f73c..effbc421c 100644 --- a/src/coverlet.msbuild.tasks/coverlet.msbuild.targets +++ b/src/coverlet.msbuild.tasks/coverlet.msbuild.targets @@ -15,7 +15,9 @@ IncludeTestAssembly="$(IncludeTestAssembly)" SingleHit="$(SingleHit)" MergeWith="$(MergeWith)" - UseSourceLink="$(UseSourceLink)" /> + UseSourceLink="$(UseSourceLink)" > + + @@ -30,7 +32,9 @@ IncludeTestAssembly="$(IncludeTestAssembly)" SingleHit="$(SingleHit)" MergeWith="$(MergeWith)" - UseSourceLink="$(UseSourceLink)" /> + UseSourceLink="$(UseSourceLink)" > + + @@ -40,7 +44,8 @@ OutputFormat="$(CoverletOutputFormat)" Threshold="$(Threshold)" ThresholdType="$(ThresholdType)" - ThresholdStat="$(ThresholdStat)" /> + ThresholdStat="$(ThresholdStat)" + InstrumenterState="$(InstrumenterState)"/> - + \ No newline at end of file