diff --git a/src/coverlet.core/Coverage.cs b/src/coverlet.core/Coverage.cs index 7818eb147..b2953eb46 100644 --- a/src/coverlet.core/Coverage.cs +++ b/src/coverlet.core/Coverage.cs @@ -277,7 +277,7 @@ public CoverageResult GetCoverageResult() } } - var coverageResult = new CoverageResult { Identifier = _identifier, Modules = modules, InstrumentedResults = _results }; + var coverageResult = new CoverageResult { Identifier = _identifier, Modules = modules, InstrumentedResults = _results, UseSourceLink = _useSourceLink }; if (!string.IsNullOrEmpty(_mergeWith) && !string.IsNullOrWhiteSpace(_mergeWith) && _fileSystem.Exists(_mergeWith)) { diff --git a/src/coverlet.core/CoverageResult.cs b/src/coverlet.core/CoverageResult.cs index 00f9cfabe..712313a97 100644 --- a/src/coverlet.core/CoverageResult.cs +++ b/src/coverlet.core/CoverageResult.cs @@ -41,6 +41,7 @@ public class CoverageResult { public string Identifier; public Modules Modules; + public bool UseSourceLink; internal List InstrumentedResults; internal CoverageResult() { } diff --git a/src/coverlet.core/Reporters/CoberturaReporter.cs b/src/coverlet.core/Reporters/CoberturaReporter.cs index 48e202916..2b29a3422 100644 --- a/src/coverlet.core/Reporters/CoberturaReporter.cs +++ b/src/coverlet.core/Reporters/CoberturaReporter.cs @@ -1,4 +1,6 @@ using System; +using System.Collections.Generic; +using System.Diagnostics; using System.Globalization; using System.IO; using System.Linq; @@ -30,7 +32,8 @@ public string Report(CoverageResult result) coverage.Add(new XAttribute("timestamp", (int)(DateTime.UtcNow - new DateTime(1970, 1, 1)).TotalSeconds)); XElement sources = new XElement("sources"); - sources.Add(new XElement("source", string.Empty)); + var rootDirs = GetRootDirs(result.Modules, result.UseSourceLink).ToList(); + rootDirs.ForEach(x => sources.Add(new XElement("source", x))); XElement packages = new XElement("packages"); foreach (var module in result.Modules) @@ -48,7 +51,7 @@ public string Report(CoverageResult result) { XElement @class = new XElement("class"); @class.Add(new XAttribute("name", cls.Key)); - @class.Add(new XAttribute("filename", document.Key)); + @class.Add(new XAttribute("filename", GetRelativePathFromBase(rootDirs, document.Key, result.UseSourceLink))); @class.Add(new XAttribute("line-rate", (summary.CalculateLineCoverage(cls.Value).Percent / 100).ToString(CultureInfo.InvariantCulture))); @class.Add(new XAttribute("branch-rate", (summary.CalculateBranchCoverage(cls.Value).Percent / 100).ToString(CultureInfo.InvariantCulture))); @class.Add(new XAttribute("complexity", summary.CalculateCyclomaticComplexity(cls.Value))); @@ -129,5 +132,35 @@ public string Report(CoverageResult result) return Encoding.UTF8.GetString(stream.ToArray()); } + + private static IEnumerable GetRootDirs(Modules modules, bool useSourceLink) + { + if (useSourceLink) + { + return new[] { string.Empty }; + } + + return modules.Values.SelectMany(k => k.Keys).Select(Directory.GetDirectoryRoot).Distinct(); + } + + private static string GetRelativePathFromBase(IEnumerable rootPaths, string path, bool useSourceLink) + { + if (useSourceLink) + { + return path; + } + + foreach (var root in rootPaths) + { + if (path.StartsWith(root)) + { + return path.Substring(root.Length); + } + } + + Debug.Assert(false, "Unexpected, we should find at least one path starts with one pre-build roots list"); + + return path; + } } } \ No newline at end of file diff --git a/test/coverlet.core.tests/Reporters/CoberturaReporterTests.cs b/test/coverlet.core.tests/Reporters/CoberturaReporterTests.cs index 913aaa08d..83e4ab057 100644 --- a/test/coverlet.core.tests/Reporters/CoberturaReporterTests.cs +++ b/test/coverlet.core.tests/Reporters/CoberturaReporterTests.cs @@ -3,9 +3,9 @@ using System.Globalization; using System.IO; using System.Linq; +using System.Runtime.InteropServices; using System.Text; using System.Threading; -using System.Xml; using System.Xml.Linq; using Xunit; @@ -37,7 +37,15 @@ public void TestReport() classes.Add("Coverlet.Core.Reporters.Tests.CoberturaReporterTests", methods); Documents documents = new Documents(); - documents.Add("doc.cs", classes); + + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + documents.Add(@"C:\doc.cs", classes); + } + else + { + documents.Add(@"/doc.cs", classes); + } result.Modules = new Modules(); result.Modules.Add("module", documents); @@ -102,7 +110,14 @@ public void TestEnsureParseMethodStringCorrectly( classes.Add("Google.Protobuf.Reflection.MessageDescriptor", methods); Documents documents = new Documents(); - documents.Add("doc.cs", classes); + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + documents.Add(@"C:\doc.cs", classes); + } + else + { + documents.Add(@"/doc.cs", classes); + } result.Modules = new Modules(); result.Modules.Add("module", documents); @@ -120,5 +135,77 @@ public void TestEnsureParseMethodStringCorrectly( Assert.Equal(expectedMethodName, methodAttrs["name"]); Assert.Equal(expectedSignature, methodAttrs["signature"]); } + + [Fact] + public void TestReportWithTwoDifferentDirectories() + { + CoverageResult result = new CoverageResult(); + result.Identifier = Guid.NewGuid().ToString(); + + var isWindows = RuntimeInformation.IsOSPlatform(OSPlatform.Windows); + + string absolutePath1; + string absolutePath2; + + if (isWindows) + { + absolutePath1 = @"C:\projA\file.cs"; + absolutePath2 = @"E:\projB\file.cs"; + } + else + { + absolutePath1 = @"/projA/file.cs"; + absolutePath2 = @"/projB/file.cs"; + } + + var classes = new Classes {{"Class", new Methods()}}; + var documents = new Documents {{absolutePath1, classes}, {absolutePath2, classes}}; + + result.Modules = new Modules {{"Module", documents}}; + + CoberturaReporter reporter = new CoberturaReporter(); + string report = reporter.Report(result); + + var doc = XDocument.Load(new MemoryStream(Encoding.UTF8.GetBytes(report))); + + List rootPaths = doc.Element("coverage").Element("sources").Elements().Select(e => e.Value).ToList(); + List relativePaths = doc.Element("coverage").Element("packages").Element("package") + .Element("classes").Elements().Select(e => e.Attribute("filename").Value).ToList(); + + List possiblePaths = new List(); + foreach (string root in rootPaths) + { + foreach (string relativePath in relativePaths) + { + possiblePaths.Add(Path.Combine(root, relativePath)); + } + } + + Assert.Contains(absolutePath1, possiblePaths); + Assert.Contains(absolutePath2, possiblePaths); + } + + [Fact] + public void TestReportWithSourcelinkPaths() + { + CoverageResult result = new CoverageResult {UseSourceLink = true, Identifier = Guid.NewGuid().ToString()}; + + var absolutePath = + @"https://raw.githubusercontent.com/johndoe/Coverlet/02c09baa8bfdee3b6cdf4be89bd98c8157b0bc08/Demo.cs"; + + var classes = new Classes {{"Class", new Methods()}}; + var documents = new Documents {{absolutePath, classes}}; + + result.Modules = new Modules {{"Module", documents}}; + + CoberturaReporter reporter = new CoberturaReporter(); + string report = reporter.Report(result); + + var doc = XDocument.Load(new MemoryStream(Encoding.UTF8.GetBytes(report))); + var fileName = doc.Element("coverage").Element("packages").Element("package").Element("classes").Elements() + .Select(e => e.Attribute("filename").Value).Single(); + + Assert.Equal(absolutePath, fileName); + } } } \ No newline at end of file