From 3b9382fc6df8c4519813e4e997cf4589316a56af Mon Sep 17 00:00:00 2001 From: Toni Solarin-Sodara Date: Sun, 9 Dec 2018 11:44:21 +0100 Subject: [PATCH 1/5] include sourcelink debug information in instrumenter result --- src/coverlet.core/Instrumentation/Instrumenter.cs | 6 ++++++ src/coverlet.core/Instrumentation/InstrumenterResult.cs | 1 + 2 files changed, 7 insertions(+) diff --git a/src/coverlet.core/Instrumentation/Instrumenter.cs b/src/coverlet.core/Instrumentation/Instrumenter.cs index 0b1a0dbae..05169c8dc 100644 --- a/src/coverlet.core/Instrumentation/Instrumenter.cs +++ b/src/coverlet.core/Instrumentation/Instrumenter.cs @@ -79,6 +79,12 @@ private void InstrumentModule() var types = module.GetTypes(); AddCustomModuleTrackerToModule(module); + var sourceLinkDebugInfo = module.CustomDebugInformations.FirstOrDefault(c => c.Kind == CustomDebugInformationKind.SourceLink); + if (sourceLinkDebugInfo != null) + { + _result.SourceLink = ((SourceLinkDebugInformation)sourceLinkDebugInfo).Content; + } + foreach (TypeDefinition type in types) { var actualType = type.DeclaringType ?? type; diff --git a/src/coverlet.core/Instrumentation/InstrumenterResult.cs b/src/coverlet.core/Instrumentation/InstrumenterResult.cs index 0060b47a8..e1d264a79 100644 --- a/src/coverlet.core/Instrumentation/InstrumenterResult.cs +++ b/src/coverlet.core/Instrumentation/InstrumenterResult.cs @@ -43,6 +43,7 @@ public InstrumenterResult() public string Module; public string HitsFilePath; 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; } } From ac808ca585517fc0636eb10d2e293fb3e7a12e58 Mon Sep 17 00:00:00 2001 From: Toni Solarin-Sodara Date: Sun, 9 Dec 2018 11:47:13 +0100 Subject: [PATCH 2/5] add method to retrieve document url from sourcelink debug information --- src/coverlet.core/Coverage.cs | 41 +++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/src/coverlet.core/Coverage.cs b/src/coverlet.core/Coverage.cs index c3a6f9cee..bb3176561 100644 --- a/src/coverlet.core/Coverage.cs +++ b/src/coverlet.core/Coverage.cs @@ -248,5 +248,46 @@ private void CalculateCoverage() InstrumentationHelper.DeleteHitsFile(result.HitsFilePath); } } + + private string GetSourceLinkUrl(Dictionary sourceLinkDocuments, string document) + { + if (sourceLinkDocuments.TryGetValue(document, out string url)) + { + return url; + } + + var keyWithBestMatch = string.Empty; + var relativePathOfBestMatch = string.Empty; + + foreach (var sourceLinkDocument in sourceLinkDocuments) + { + string key = sourceLinkDocument.Key; + if (Path.GetFileName(key) != "*") continue; + + string relativePath = Path.GetRelativePath(Path.GetDirectoryName(key), Path.GetDirectoryName(document)); + + if (relativePath.Contains("..")) continue; + + if (relativePathOfBestMatch.Length == 0) + { + keyWithBestMatch = sourceLinkDocument.Key; + relativePathOfBestMatch = relativePath; + } + + if (relativePath.Length < relativePathOfBestMatch.Length) + { + keyWithBestMatch = sourceLinkDocument.Key; + relativePathOfBestMatch = relativePath; + } + } + + relativePathOfBestMatch = relativePathOfBestMatch == "." ? string.Empty : relativePathOfBestMatch; + + string replacement = Path.Combine(relativePathOfBestMatch, Path.GetFileName(document)); + replacement = replacement.Replace('\\', '/'); + + url = sourceLinkDocuments[keyWithBestMatch]; + return url.Replace("*", replacement); + } } } From 01c53f6ec896a594cc7d37bb9bf4b9928281f1d5 Mon Sep 17 00:00:00 2001 From: Toni Solarin-Sodara Date: Sun, 9 Dec 2018 11:47:40 +0100 Subject: [PATCH 3/5] add use-source-link option --- src/coverlet.console/Program.cs | 3 +- src/coverlet.core/Coverage.cs | 24 +++++++++---- src/coverlet.core/coverlet.core.csproj | 4 ++- .../InstrumentationTask.cs | 35 +++++++++++-------- .../coverlet.msbuild.tasks.csproj | 3 +- src/coverlet.msbuild/coverlet.msbuild.props | 9 ++--- src/coverlet.msbuild/coverlet.msbuild.targets | 10 +++--- test/coverlet.core.tests/CoverageTests.cs | 2 +- 8 files changed, 57 insertions(+), 33 deletions(-) diff --git a/src/coverlet.console/Program.cs b/src/coverlet.console/Program.cs index a3da65737..f58965a90 100644 --- a/src/coverlet.console/Program.cs +++ b/src/coverlet.console/Program.cs @@ -36,6 +36,7 @@ static int Main(string[] args) CommandOption includeDirectories = app.Option("--include-directory", "Include directories containing additional assemblies to be instrumented.", CommandOptionType.MultipleValue); CommandOption excludeAttributes = app.Option("--exclude-by-attribute", "Attributes to exclude from code coverage.", CommandOptionType.MultipleValue); CommandOption mergeWith = app.Option("--merge-with", "Path to existing coverage result to merge.", CommandOptionType.SingleValue); + CommandOption useSourceLink = app.Option("--use-source-link", "Specifies whether to use SourceLink URIs in place of file system paths.", CommandOptionType.NoValue); app.OnExecute(() => { @@ -45,7 +46,7 @@ static int Main(string[] args) if (!target.HasValue()) throw new CommandParsingException(app, "Target must be specified."); - Coverage coverage = new Coverage(module.Value, includeFilters.Values.ToArray(), includeDirectories.Values.ToArray(), excludeFilters.Values.ToArray(), excludedSourceFiles.Values.ToArray(), excludeAttributes.Values.ToArray(), mergeWith.Value()); + Coverage coverage = new Coverage(module.Value, includeFilters.Values.ToArray(), includeDirectories.Values.ToArray(), excludeFilters.Values.ToArray(), excludedSourceFiles.Values.ToArray(), excludeAttributes.Values.ToArray(), mergeWith.Value(), useSourceLink.HasValue()); coverage.PrepareModules(); Process process = new Process(); diff --git a/src/coverlet.core/Coverage.cs b/src/coverlet.core/Coverage.cs index bb3176561..ab3d800e7 100644 --- a/src/coverlet.core/Coverage.cs +++ b/src/coverlet.core/Coverage.cs @@ -8,6 +8,7 @@ using Coverlet.Core.Symbols; using Newtonsoft.Json; +using Newtonsoft.Json.Linq; namespace Coverlet.Core { @@ -15,12 +16,13 @@ public class Coverage { private string _module; private string _identifier; - private string[] _excludeFilters; private string[] _includeFilters; private string[] _includeDirectories; + private string[] _excludeFilters; private string[] _excludedSourceFiles; - private string _mergeWith; private string[] _excludeAttributes; + private string _mergeWith; + private bool _useSourceLink; private List _results; public string Identifier @@ -28,15 +30,16 @@ public string Identifier get { return _identifier; } } - public Coverage(string module, string[] includeFilters, string[] includeDirectories, string[] excludeFilters, string[] excludedSourceFiles, string[] excludeAttributes, string mergeWith) + public Coverage(string module, string[] includeFilters, string[] includeDirectories, string[] excludeFilters, string[] excludedSourceFiles, string[] excludeAttributes, string mergeWith, bool useSourceLink) { _module = module; - _excludeFilters = excludeFilters; _includeFilters = includeFilters; _includeDirectories = includeDirectories ?? Array.Empty(); + _excludeFilters = excludeFilters; _excludedSourceFiles = excludedSourceFiles; - _mergeWith = mergeWith; _excludeAttributes = excludeAttributes; + _mergeWith = mergeWith; + _useSourceLink = useSourceLink; _identifier = Guid.NewGuid().ToString(); _results = new List(); @@ -186,6 +189,15 @@ private void CalculateCoverage() } List documents = result.Documents.Values.ToList(); + if (_useSourceLink && result.SourceLink != null) + { + var jObject = JObject.Parse(result.SourceLink)["documents"]; + var sourceLinkDocuments = JsonConvert.DeserializeObject>(jObject.ToString()); + foreach (var document in documents) + { + document.Path = GetSourceLinkUrl(sourceLinkDocuments, document.Path); + } + } using (var fs = new FileStream(result.HitsFilePath, FileMode.Open)) using (var br = new BinaryReader(fs)) @@ -199,9 +211,7 @@ private void CalculateCoverage() for (int i = 0; i < hitCandidatesCount; ++i) { var hitLocation = result.HitCandidates[i]; - var document = documentsList[hitLocation.docIndex]; - int hits = br.ReadInt32(); if (hitLocation.isBranch) diff --git a/src/coverlet.core/coverlet.core.csproj b/src/coverlet.core/coverlet.core.csproj index 1a96965e7..611fd24df 100644 --- a/src/coverlet.core/coverlet.core.csproj +++ b/src/coverlet.core/coverlet.core.csproj @@ -1,7 +1,8 @@ - netstandard2.0 + Library + netcoreapp2.0 4.0.0 @@ -9,6 +10,7 @@ + diff --git a/src/coverlet.msbuild.tasks/InstrumentationTask.cs b/src/coverlet.msbuild.tasks/InstrumentationTask.cs index 96ffeb7ae..542f98c1f 100644 --- a/src/coverlet.msbuild.tasks/InstrumentationTask.cs +++ b/src/coverlet.msbuild.tasks/InstrumentationTask.cs @@ -9,12 +9,13 @@ public class InstrumentationTask : Task { private static Coverage _coverage; private string _path; - private string _exclude; private string _include; private string _includeDirectory; + private string _exclude; private string _excludeByFile; - private string _mergeWith; private string _excludeByAttribute; + private string _mergeWith; + private bool _useSourceLink; internal static Coverage Coverage { @@ -28,12 +29,6 @@ public string Path set { _path = value; } } - public string Exclude - { - get { return _exclude; } - set { _exclude = value; } - } - public string Include { get { return _include; } @@ -46,35 +41,47 @@ public string IncludeDirectory set { _includeDirectory = value; } } + public string Exclude + { + get { return _exclude; } + set { _exclude = value; } + } + public string ExcludeByFile { get { return _excludeByFile; } set { _excludeByFile = value; } } + public string ExcludeByAttribute + { + get { return _excludeByAttribute; } + set { _excludeByAttribute = value; } + } + public string MergeWith { get { return _mergeWith; } set { _mergeWith = value; } } - public string ExcludeByAttribute + public bool UseSourceLink { - get { return _excludeByAttribute; } - set { _excludeByAttribute = value; } + get { return _useSourceLink; } + set { _useSourceLink = value; } } public override bool Execute() { try { - var excludedSourceFiles = _excludeByFile?.Split(','); - var excludeFilters = _exclude?.Split(','); var includeFilters = _include?.Split(','); var includeDirectories = _includeDirectory?.Split(','); + var excludeFilters = _exclude?.Split(','); + var excludedSourceFiles = _excludeByFile?.Split(','); var excludeAttributes = _excludeByAttribute?.Split(','); - _coverage = new Coverage(_path, includeFilters, includeDirectories, excludeFilters, excludedSourceFiles, excludeAttributes, _mergeWith); + _coverage = new Coverage(_path, includeFilters, includeDirectories, excludeFilters, excludedSourceFiles, excludeAttributes, _mergeWith, _useSourceLink); _coverage.PrepareModules(); } catch (Exception ex) diff --git a/src/coverlet.msbuild.tasks/coverlet.msbuild.tasks.csproj b/src/coverlet.msbuild.tasks/coverlet.msbuild.tasks.csproj index fcd9992dd..0f00c5de6 100644 --- a/src/coverlet.msbuild.tasks/coverlet.msbuild.tasks.csproj +++ b/src/coverlet.msbuild.tasks/coverlet.msbuild.tasks.csproj @@ -1,7 +1,8 @@ - netstandard2.0 + Library + netcoreapp2.0 2.3.0 diff --git a/src/coverlet.msbuild/coverlet.msbuild.props b/src/coverlet.msbuild/coverlet.msbuild.props index 58121f036..54e86f18d 100644 --- a/src/coverlet.msbuild/coverlet.msbuild.props +++ b/src/coverlet.msbuild/coverlet.msbuild.props @@ -1,15 +1,16 @@ false - json - $([MSBuild]::EnsureTrailingSlash('$(MSBuildProjectDirectory)')) + + + false + json + $([MSBuild]::EnsureTrailingSlash('$(MSBuildProjectDirectory)')) 0 line,branch,method - - diff --git a/src/coverlet.msbuild/coverlet.msbuild.targets b/src/coverlet.msbuild/coverlet.msbuild.targets index c5e26000c..08fa79d49 100644 --- a/src/coverlet.msbuild/coverlet.msbuild.targets +++ b/src/coverlet.msbuild/coverlet.msbuild.targets @@ -6,25 +6,27 @@ + MergeWith="$(MergeWith)" + UseSourceLink="$(UseSourceLink)" /> + MergeWith="$(MergeWith)" + UseSourceLink="$(UseSourceLink)" /> diff --git a/test/coverlet.core.tests/CoverageTests.cs b/test/coverlet.core.tests/CoverageTests.cs index a27222310..5aba981fa 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(), Array.Empty(), Array.Empty(), string.Empty); + var coverage = new Coverage(testModule, Array.Empty(), Array.Empty(), Array.Empty(), Array.Empty(), Array.Empty(), string.Empty, false); coverage.PrepareModules(); var result = coverage.GetCoverageResult(); From de558c5f62b46d30fb2ba15231e50fc7f41877f7 Mon Sep 17 00:00:00 2001 From: Toni Solarin-Sodara Date: Sun, 9 Dec 2018 22:11:53 +0100 Subject: [PATCH 4/5] use absolute path/url in class filename attribute --- .../Reporters/CoberturaReporter.cs | 33 +------------------ 1 file changed, 1 insertion(+), 32 deletions(-) diff --git a/src/coverlet.core/Reporters/CoberturaReporter.cs b/src/coverlet.core/Reporters/CoberturaReporter.cs index 8b928c406..b20813ee0 100644 --- a/src/coverlet.core/Reporters/CoberturaReporter.cs +++ b/src/coverlet.core/Reporters/CoberturaReporter.cs @@ -31,8 +31,6 @@ public string Report(CoverageResult result) coverage.Add(new XAttribute("timestamp", ((int)(DateTime.UtcNow - new DateTime(1970, 1, 1)).TotalSeconds).ToString())); XElement sources = new XElement("sources"); - var basePath = GetBasePath(result.Modules); - sources.Add(new XElement("source", basePath)); XElement packages = new XElement("packages"); foreach (var module in result.Modules) @@ -50,7 +48,7 @@ public string Report(CoverageResult result) { XElement @class = new XElement("class"); @class.Add(new XAttribute("name", cls.Key)); - @class.Add(new XAttribute("filename", GetRelativePathFromBase(basePath, document.Key))); + @class.Add(new XAttribute("filename", document.Key)); @class.Add(new XAttribute("line-rate", summary.CalculateLineCoverage(cls.Value).Percent.ToString())); @class.Add(new XAttribute("branch-rate", summary.CalculateBranchCoverage(cls.Value).Percent.ToString())); @class.Add(new XAttribute("complexity", summary.CalculateCyclomaticComplexity(cls.Value).ToString())); @@ -131,34 +129,5 @@ public string Report(CoverageResult result) return Encoding.UTF8.GetString(stream.ToArray()); } - - private string GetBasePath(Modules modules) - { - List sources = new List(); - string path = string.Empty; - - foreach (var module in modules) - { - sources.AddRange( - module.Value.Select(d => Path.GetDirectoryName(d.Key))); - } - - sources = sources.Distinct().ToList(); - var segments = sources[0].Split(Path.DirectorySeparatorChar); - - foreach (var segment in segments) - { - var startsWith = sources.All(s => s.StartsWith(path + segment)); - if (!startsWith) - break; - - path += segment + Path.DirectorySeparatorChar; - } - - return path; - } - - private string GetRelativePathFromBase(string basePath, string path) - => basePath == string.Empty ? path : path.Replace(basePath, string.Empty); } } \ No newline at end of file From 1b2d8ba37bbe752688f740f2353b102c898feda6 Mon Sep 17 00:00:00 2001 From: Toni Solarin-Sodara Date: Sun, 9 Dec 2018 23:19:23 +0100 Subject: [PATCH 5/5] remove netstandard specific additions --- src/coverlet.core/coverlet.core.csproj | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/coverlet.core/coverlet.core.csproj b/src/coverlet.core/coverlet.core.csproj index 611fd24df..0744ce083 100644 --- a/src/coverlet.core/coverlet.core.csproj +++ b/src/coverlet.core/coverlet.core.csproj @@ -9,8 +9,6 @@ - -