diff --git a/build.proj b/build.proj index f9c9390de..72e630f05 100644 --- a/build.proj +++ b/build.proj @@ -9,6 +9,7 @@ + diff --git a/src/coverlet.console/Program.cs b/src/coverlet.console/Program.cs index 867bd131d..3f2ac498a 100644 --- a/src/coverlet.console/Program.cs +++ b/src/coverlet.console/Program.cs @@ -34,6 +34,7 @@ static int Main(string[] args) CommandOption excludeFilters = app.Option("--exclude", "Filter expressions to exclude specific modules and types.", CommandOptionType.MultipleValue); CommandOption includeFilters = app.Option("--include", "Filter expressions to include only specific modules and types.", CommandOptionType.MultipleValue); CommandOption excludedSourceFiles = app.Option("--exclude-by-file", "Glob patterns specifying source files to exclude.", CommandOptionType.MultipleValue); + CommandOption mergeWith = app.Option("--merge-with", "Path to existing coverage result to merge.", CommandOptionType.SingleValue); app.OnExecute(() => { @@ -43,7 +44,7 @@ static int Main(string[] args) if (!target.HasValue()) throw new CommandParsingException(app, "Target must be specified."); - Coverage coverage = new Coverage(module.Value, excludeFilters.Values.ToArray(), includeFilters.Values.ToArray(), excludedSourceFiles.Values.ToArray()); + Coverage coverage = new Coverage(module.Value, excludeFilters.Values.ToArray(), includeFilters.Values.ToArray(), excludedSourceFiles.Values.ToArray(), mergeWith.Value()); coverage.PrepareModules(); Process process = new Process(); diff --git a/src/coverlet.core/Coverage.cs b/src/coverlet.core/Coverage.cs index 459d82914..5904a96de 100644 --- a/src/coverlet.core/Coverage.cs +++ b/src/coverlet.core/Coverage.cs @@ -6,6 +6,8 @@ using Coverlet.Core.Helpers; using Coverlet.Core.Instrumentation; +using Newtonsoft.Json; + namespace Coverlet.Core { public class Coverage @@ -15,6 +17,7 @@ public class Coverage private string[] _excludeFilters; private string[] _includeFilters; private string[] _excludedSourceFiles; + private string _mergeWith; private List _results; public string Identifier @@ -22,12 +25,14 @@ public string Identifier get { return _identifier; } } - public Coverage(string module, string[] excludeFilters, string[] includeFilters, string[] excludedSourceFiles) + public Coverage(string module, string[] excludeFilters, string[] includeFilters, string[] excludedSourceFiles, string mergeWith) { _module = module; _excludeFilters = excludeFilters; _includeFilters = includeFilters; _excludedSourceFiles = excludedSourceFiles; + _mergeWith = mergeWith; + _identifier = Guid.NewGuid().ToString(); _results = new List(); } @@ -144,11 +149,14 @@ public CoverageResult GetCoverageResult() InstrumentationHelper.RestoreOriginalModule(result.ModulePath, _identifier); } - return new CoverageResult + var coverageResult = new CoverageResult { Identifier = _identifier, Modules = modules }; + if (!string.IsNullOrEmpty(_mergeWith) && !string.IsNullOrWhiteSpace(_mergeWith)) { - Identifier = _identifier, - Modules = modules - }; + string json = File.ReadAllText(_mergeWith); + coverageResult.Merge(JsonConvert.DeserializeObject(json)); + } + + return coverageResult; } private void CalculateCoverage() diff --git a/src/coverlet.core/CoverageResult.cs b/src/coverlet.core/CoverageResult.cs index 9f176ae61..b7caf6f5e 100644 --- a/src/coverlet.core/CoverageResult.cs +++ b/src/coverlet.core/CoverageResult.cs @@ -1,5 +1,6 @@ using System.Collections.Generic; using System.IO; +using System.Linq; namespace Coverlet.Core { @@ -38,5 +39,70 @@ public class CoverageResult public Modules Modules; internal CoverageResult() { } + + internal void Merge(Modules modules) + { + foreach (var module in modules) + { + if (!this.Modules.ContainsKey(module.Key)) + { + this.Modules.Add(module.Key, module.Value); + } + else + { + foreach (var document in module.Value) + { + if (!this.Modules[module.Key].ContainsKey(document.Key)) + { + this.Modules[module.Key].Add(document.Key, document.Value); + } + else + { + foreach (var @class in document.Value) + { + if (!this.Modules[module.Key][document.Key].ContainsKey(@class.Key)) + { + this.Modules[module.Key][document.Key].Add(@class.Key, @class.Value); + } + else + { + foreach (var method in @class.Value) + { + if (!this.Modules[module.Key][document.Key][@class.Key].ContainsKey(method.Key)) + { + this.Modules[module.Key][document.Key][@class.Key].Add(method.Key, method.Value); + } + else + { + foreach (var line in method.Value.Lines) + { + if (!this.Modules[module.Key][document.Key][@class.Key][method.Key].Lines.ContainsKey(line.Key)) + { + this.Modules[module.Key][document.Key][@class.Key][method.Key].Lines.Add(line.Key, line.Value); + } + else + { + this.Modules[module.Key][document.Key][@class.Key][method.Key].Lines[line.Key] += line.Value; + } + } + + foreach (var branch in method.Value.Branches) + { + var branches = this.Modules[module.Key][document.Key][@class.Key][method.Key].Branches; + var branchInfo = branches.FirstOrDefault(b => b.EndOffset == branch.EndOffset && b.Line == branch.Line && b.Offset == branch.Offset && b.Ordinal == branch.Ordinal && b.Path == branch.Path); + if (branchInfo == null) + branches.Add(branch); + else + branchInfo.Hits += branch.Hits; + } + } + } + } + } + } + } + } + } + } } } \ No newline at end of file diff --git a/src/coverlet.msbuild.tasks/InstrumentationTask.cs b/src/coverlet.msbuild.tasks/InstrumentationTask.cs index 60514c7c7..e53c87666 100644 --- a/src/coverlet.msbuild.tasks/InstrumentationTask.cs +++ b/src/coverlet.msbuild.tasks/InstrumentationTask.cs @@ -12,6 +12,7 @@ public class InstrumentationTask : Task private string _exclude; private string _include; private string _excludeByFile; + private string _mergeWith; internal static Coverage Coverage { @@ -43,6 +44,12 @@ public string ExcludeByFile set { _excludeByFile = value; } } + public string MergeWith + { + get { return _mergeWith; } + set { _mergeWith = value; } + } + public override bool Execute() { try @@ -51,7 +58,7 @@ public override bool Execute() var excludeFilters = _exclude?.Split(','); var includeFilters = _include?.Split(','); - _coverage = new Coverage(_path, excludeFilters, includeFilters, excludedSourceFiles); + _coverage = new Coverage(_path, excludeFilters, includeFilters, excludedSourceFiles, _mergeWith); _coverage.PrepareModules(); } catch (Exception ex) diff --git a/src/coverlet.msbuild/coverlet.msbuild.props b/src/coverlet.msbuild/coverlet.msbuild.props index 3dcb69948..e3a616a66 100644 --- a/src/coverlet.msbuild/coverlet.msbuild.props +++ b/src/coverlet.msbuild/coverlet.msbuild.props @@ -5,6 +5,7 @@ $([MSBuild]::EnsureTrailingSlash('$(MSBuildProjectDirectory)')) + 0 line,branch,method diff --git a/src/coverlet.msbuild/coverlet.msbuild.targets b/src/coverlet.msbuild/coverlet.msbuild.targets index e77b7a0c8..8c4930822 100644 --- a/src/coverlet.msbuild/coverlet.msbuild.targets +++ b/src/coverlet.msbuild/coverlet.msbuild.targets @@ -8,6 +8,7 @@ Condition="'$(VSTestNoBuild)' == 'true' and $(CollectCoverage) == 'true'" Exclude="$(Exclude)" ExcludeByFile="$(ExcludeByFile)" + MergeWith="$(MergeWith)" Path="$(TargetPath)" /> @@ -16,6 +17,7 @@ Condition="'$(VSTestNoBuild)' != 'true' and $(CollectCoverage) == 'true'" Exclude="$(Exclude)" ExcludeByFile="$(ExcludeByFile)" + MergeWith="$(MergeWith)" Path="$(TargetPath)" /> diff --git a/test/coverlet.core.tests/CoverageTests.cs b/test/coverlet.core.tests/CoverageTests.cs index 45d8fdbd6..fc9d26f60 100644 --- a/test/coverlet.core.tests/CoverageTests.cs +++ b/test/coverlet.core.tests/CoverageTests.cs @@ -27,7 +27,7 @@ public void TestCoverage() // Since Coverage only instruments dependancies, we need a fake module here var testModule = Path.Combine(directory.FullName, "test.module.dll"); - var coverage = new Coverage(testModule, Array.Empty(), Array.Empty(), Array.Empty()); + var coverage = new Coverage(testModule, Array.Empty(), Array.Empty(), Array.Empty(), string.Empty); coverage.PrepareModules(); var result = coverage.GetCoverageResult();