From 9c82e64c616a7d777d7494ea949ee8e69559540c Mon Sep 17 00:00:00 2001 From: Phani Vanka Date: Mon, 8 Mar 2021 00:04:51 -0500 Subject: [PATCH] Refactored functions to determine base paths from CoberturaReporter into base class for Reporter classes (#263) --- .../Reporters/CoberturaReporter.cs | 91 +--------------- src/coverlet.core/Reporters/JsonReporter.cs | 10 +- src/coverlet.core/Reporters/LcovReporter.cs | 13 +-- .../Reporters/OpenCoverReporter.cs | 10 +- src/coverlet.core/Reporters/ReporterBase.cs | 101 ++++++++++++++++++ .../Reporters/TeamCityReporter.cs | 10 +- .../Reporters/LcovReporterTests.cs | 10 +- 7 files changed, 137 insertions(+), 108 deletions(-) create mode 100644 src/coverlet.core/Reporters/ReporterBase.cs diff --git a/src/coverlet.core/Reporters/CoberturaReporter.cs b/src/coverlet.core/Reporters/CoberturaReporter.cs index 291462299..7794fd55e 100644 --- a/src/coverlet.core/Reporters/CoberturaReporter.cs +++ b/src/coverlet.core/Reporters/CoberturaReporter.cs @@ -9,15 +9,15 @@ namespace Coverlet.Core.Reporters { - internal class CoberturaReporter : IReporter + internal class CoberturaReporter : ReporterBase { - public ReporterOutputType OutputType => ReporterOutputType.File; + public override ReporterOutputType OutputType => ReporterOutputType.File; - public string Format => "cobertura"; + public override string Format => "cobertura"; - public string Extension => "cobertura.xml"; + public override string Extension => "cobertura.xml"; - public string Report(CoverageResult result) + public override string Report(CoverageResult result) { CoverageSummary summary = new CoverageSummary(); @@ -133,86 +133,5 @@ public string Report(CoverageResult result) return Encoding.UTF8.GetString(stream.ToArray()); } - - private static IEnumerable GetBasePaths(Modules modules, bool useSourceLink) - { - /* - Workflow - - Path1 c:\dir1\dir2\file1.cs - Path2 c:\dir1\file2.cs - Path3 e:\dir1\file2.cs - - 1) Search for root dir - c:\ -> c:\dir1\dir2\file1.cs - c:\dir1\file2.cs - e:\ -> e:\dir1\file2.cs - - 2) Split path on directory separator i.e. for record c:\ ordered ascending by fragment elements - Path1 = [c:|dir1|file2.cs] - Path2 = [c:|dir1|dir2|file1.cs] - - 3) Find longest shared path comparing indexes - Path1[0] = Path2[0], ..., PathY[0] -> add to final fragment list - Path1[n] = Path2[n], ..., PathY[n] -> add to final fragment list - Path1[n+1] != Path2[n+1], ..., PathY[n+1] -> break, Path1[n] was last shared fragment - - 4) Concat created fragment list - */ - if (useSourceLink) - { - return new[] { string.Empty }; - } - - return modules.Values.SelectMany(k => k.Keys).GroupBy(Directory.GetDirectoryRoot).Select(group => - { - var splittedPaths = group.Select(absolutePath => absolutePath.Split(Path.DirectorySeparatorChar)) - .OrderBy(absolutePath => absolutePath.Length).ToList(); - if (splittedPaths.Count == 1) - { - return group.Key; - } - - var basePathFragments = new List(); - bool stopSearch = false; - splittedPaths[0].Select((value, index) => (value, index)).ToList().ForEach(fragmentIndexPair => - { - if (stopSearch) - { - return; - } - - if (splittedPaths.All(sp => fragmentIndexPair.value.Equals(sp[fragmentIndexPair.index]))) - { - basePathFragments.Add(fragmentIndexPair.value); - } - else - { - stopSearch = true; - } - }); - return string.Concat(string.Join(Path.DirectorySeparatorChar.ToString(), basePathFragments), Path.DirectorySeparatorChar); - }); - } - - private static string GetRelativePathFromBase(IEnumerable basePaths, string path, bool useSourceLink) - { - if (useSourceLink) - { - return path; - } - - foreach (var basePath in basePaths) - { - if (path.StartsWith(basePath)) - { - return path.Substring(basePath.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/src/coverlet.core/Reporters/JsonReporter.cs b/src/coverlet.core/Reporters/JsonReporter.cs index 09e23e83f..abda9ebc4 100644 --- a/src/coverlet.core/Reporters/JsonReporter.cs +++ b/src/coverlet.core/Reporters/JsonReporter.cs @@ -2,15 +2,15 @@ namespace Coverlet.Core.Reporters { - internal class JsonReporter : IReporter + internal class JsonReporter : ReporterBase { - public ReporterOutputType OutputType => ReporterOutputType.File; + public override ReporterOutputType OutputType => ReporterOutputType.File; - public string Format => "json"; + public override string Format => "json"; - public string Extension => "json"; + public override string Extension => "json"; - public string Report(CoverageResult result) + public override string Report(CoverageResult result) { return JsonConvert.SerializeObject(result.Modules, Formatting.Indented); } diff --git a/src/coverlet.core/Reporters/LcovReporter.cs b/src/coverlet.core/Reporters/LcovReporter.cs index e8e94b68d..9b4a3dba9 100644 --- a/src/coverlet.core/Reporters/LcovReporter.cs +++ b/src/coverlet.core/Reporters/LcovReporter.cs @@ -4,18 +4,19 @@ namespace Coverlet.Core.Reporters { - internal class LcovReporter : IReporter + internal class LcovReporter : ReporterBase { - public ReporterOutputType OutputType => ReporterOutputType.File; + public override ReporterOutputType OutputType => ReporterOutputType.File; - public string Format => "lcov"; + public override string Format => "lcov"; - public string Extension => "info"; + public override string Extension => "info"; - public string Report(CoverageResult result) + public override string Report(CoverageResult result) { CoverageSummary summary = new CoverageSummary(); List lcov = new List(); + var absolutePaths = GetBasePaths(result.Modules, result.UseSourceLink).ToList(); foreach (var module in result.Modules) { @@ -25,7 +26,7 @@ public string Report(CoverageResult result) var docBranchCoverage = summary.CalculateBranchCoverage(doc.Value); var docMethodCoverage = summary.CalculateMethodCoverage(doc.Value); - lcov.Add("SF:" + doc.Key); + lcov.Add("SF:" + GetRelativePathFromBase(absolutePaths, doc.Key, result.UseSourceLink)); foreach (var @class in doc.Value) { foreach (var method in @class.Value) diff --git a/src/coverlet.core/Reporters/OpenCoverReporter.cs b/src/coverlet.core/Reporters/OpenCoverReporter.cs index 3c4c66b45..2989b6291 100644 --- a/src/coverlet.core/Reporters/OpenCoverReporter.cs +++ b/src/coverlet.core/Reporters/OpenCoverReporter.cs @@ -7,15 +7,15 @@ namespace Coverlet.Core.Reporters { - internal class OpenCoverReporter : IReporter + internal class OpenCoverReporter : ReporterBase { - public ReporterOutputType OutputType => ReporterOutputType.File; + public override ReporterOutputType OutputType => ReporterOutputType.File; - public string Format => "opencover"; + public override string Format => "opencover"; - public string Extension => "opencover.xml"; + public override string Extension => "opencover.xml"; - public string Report(CoverageResult result) + public override string Report(CoverageResult result) { CoverageSummary summary = new CoverageSummary(); XDocument xml = new XDocument(); diff --git a/src/coverlet.core/Reporters/ReporterBase.cs b/src/coverlet.core/Reporters/ReporterBase.cs new file mode 100644 index 000000000..c863c5cb9 --- /dev/null +++ b/src/coverlet.core/Reporters/ReporterBase.cs @@ -0,0 +1,101 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Text; + +namespace Coverlet.Core.Reporters +{ + internal abstract class ReporterBase : IReporter + { + public abstract ReporterOutputType OutputType { get; } + + public abstract string Format { get; } + + public abstract string Extension { get; } + + public abstract string Report(CoverageResult result); + + protected static IEnumerable GetBasePaths(Modules modules, bool useSourceLink) + { + /* + Workflow + + Path1 c:\dir1\dir2\file1.cs + Path2 c:\dir1\file2.cs + Path3 e:\dir1\file2.cs + + 1) Search for root dir + c:\ -> c:\dir1\dir2\file1.cs + c:\dir1\file2.cs + e:\ -> e:\dir1\file2.cs + + 2) Split path on directory separator i.e. for record c:\ ordered ascending by fragment elements + Path1 = [c:|dir1|file2.cs] + Path2 = [c:|dir1|dir2|file1.cs] + + 3) Find longest shared path comparing indexes + Path1[0] = Path2[0], ..., PathY[0] -> add to final fragment list + Path1[n] = Path2[n], ..., PathY[n] -> add to final fragment list + Path1[n+1] != Path2[n+1], ..., PathY[n+1] -> break, Path1[n] was last shared fragment + + 4) Concat created fragment list + */ + if (useSourceLink) + { + return new[] { string.Empty }; + } + + return modules.Values.SelectMany(k => k.Keys).GroupBy(Directory.GetDirectoryRoot).Select(group => + { + var splittedPaths = group.Select(absolutePath => absolutePath.Split(Path.DirectorySeparatorChar)) + .OrderBy(absolutePath => absolutePath.Length).ToList(); + if (splittedPaths.Count == 1) + { + return group.Key; + } + + var basePathFragments = new List(); + bool stopSearch = false; + splittedPaths[0].Select((value, index) => (value, index)).ToList().ForEach(fragmentIndexPair => + { + if (stopSearch) + { + return; + } + + if (splittedPaths.All(sp => fragmentIndexPair.value.Equals(sp[fragmentIndexPair.index]))) + { + basePathFragments.Add(fragmentIndexPair.value); + } + else + { + stopSearch = true; + } + }); + return string.Concat(string.Join(Path.DirectorySeparatorChar.ToString(), basePathFragments), Path.DirectorySeparatorChar); + }); + } + + protected static string GetRelativePathFromBase(IEnumerable basePaths, string path, bool useSourceLink) + { + if (useSourceLink) + { + return path; + } + + foreach (var basePath in basePaths) + { + if (path.StartsWith(basePath)) + { + return path.Substring(basePath.Length); + } + } + + Debug.Assert(false, "Unexpected, we should find at least one path starts with one pre-build roots list"); + + return path; + } + } +} diff --git a/src/coverlet.core/Reporters/TeamCityReporter.cs b/src/coverlet.core/Reporters/TeamCityReporter.cs index a12a8f96c..2943f64b0 100644 --- a/src/coverlet.core/Reporters/TeamCityReporter.cs +++ b/src/coverlet.core/Reporters/TeamCityReporter.cs @@ -5,15 +5,15 @@ namespace Coverlet.Core.Reporters { - internal class TeamCityReporter : IReporter + internal class TeamCityReporter : ReporterBase { - public ReporterOutputType OutputType => ReporterOutputType.Console; + public override ReporterOutputType OutputType => ReporterOutputType.Console; - public string Format => "teamcity"; + public override string Format => "teamcity"; - public string Extension => null; + public override string Extension => null; - public string Report(CoverageResult result) + public override string Report(CoverageResult result) { // Calculate coverage var summary = new CoverageSummary(); diff --git a/test/coverlet.core.tests/Reporters/LcovReporterTests.cs b/test/coverlet.core.tests/Reporters/LcovReporterTests.cs index f5c888bf1..43b38ed21 100644 --- a/test/coverlet.core.tests/Reporters/LcovReporterTests.cs +++ b/test/coverlet.core.tests/Reporters/LcovReporterTests.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Runtime.InteropServices; using Xunit; namespace Coverlet.Core.Reporters.Tests @@ -30,7 +31,14 @@ public void TestReport() classes.Add("Coverlet.Core.Reporters.Tests.LcovReporterTests", 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);