Skip to content

Commit 8f9d705

Browse files
daveMuellerMarcoRossignoli
authored andcommitted
Improve cobertura absolute/relative path report generation (#661)
Improve cobertura absolute/relative path report generation
1 parent 5f997ce commit 8f9d705

File tree

2 files changed

+90
-23
lines changed

2 files changed

+90
-23
lines changed

src/coverlet.core/Reporters/CoberturaReporter.cs

Lines changed: 51 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,8 @@ public string Report(CoverageResult result)
3232
coverage.Add(new XAttribute("timestamp", (int)(DateTime.UtcNow - new DateTime(1970, 1, 1)).TotalSeconds));
3333

3434
XElement sources = new XElement("sources");
35-
var rootDirs = GetRootDirs(result.Modules, result.UseSourceLink).ToList();
36-
rootDirs.ForEach(x => sources.Add(new XElement("source", x)));
35+
var absolutePaths = GetBasePaths(result.Modules, result.UseSourceLink).ToList();
36+
absolutePaths.ForEach(x => sources.Add(new XElement("source", x)));
3737

3838
XElement packages = new XElement("packages");
3939
foreach (var module in result.Modules)
@@ -51,7 +51,7 @@ public string Report(CoverageResult result)
5151
{
5252
XElement @class = new XElement("class");
5353
@class.Add(new XAttribute("name", cls.Key));
54-
@class.Add(new XAttribute("filename", GetRelativePathFromBase(rootDirs, document.Key, result.UseSourceLink)));
54+
@class.Add(new XAttribute("filename", GetRelativePathFromBase(absolutePaths, document.Key, result.UseSourceLink)));
5555
@class.Add(new XAttribute("line-rate", (summary.CalculateLineCoverage(cls.Value).Percent / 100).ToString(CultureInfo.InvariantCulture)));
5656
@class.Add(new XAttribute("branch-rate", (summary.CalculateBranchCoverage(cls.Value).Percent / 100).ToString(CultureInfo.InvariantCulture)));
5757
@class.Add(new XAttribute("complexity", summary.CalculateCyclomaticComplexity(cls.Value)));
@@ -133,28 +133,70 @@ public string Report(CoverageResult result)
133133
return Encoding.UTF8.GetString(stream.ToArray());
134134
}
135135

136-
private static IEnumerable<string> GetRootDirs(Modules modules, bool useSourceLink)
136+
private static IEnumerable<string> GetBasePaths(Modules modules, bool useSourceLink)
137137
{
138+
/*
139+
Workflow
140+
141+
Path1 c:\dir1\dir2\file1.cs
142+
Path2 c:\dir1\file2.cs
143+
Path3 e:\dir1\file2.cs
144+
145+
1) Search for root dir
146+
c:\ -> c:\dir1\dir2\file1.cs
147+
c:\dir1\file2.cs
148+
e:\ -> e:\dir1\file2.cs
149+
150+
2) Split path on directory separator i.e. for record c:\ ordered ascending by fragment elements
151+
Path1 = [c:|dir1|file2.cs]
152+
Path2 = [c:|dir1|dir2|file1.cs]
153+
154+
3) Find longest shared path comparing indexes
155+
Path1[0] = Path2[0], ..., PathY[0] -> add to final fragment list
156+
Path1[n] = Path2[n], ..., PathY[n] -> add to final fragment list
157+
Path1[n+1] != Path2[n+1], ..., PathY[n+1] -> break, Path1[n] was last shared fragment
158+
159+
4) Concat created fragment list
160+
*/
138161
if (useSourceLink)
139162
{
140163
return new[] { string.Empty };
141164
}
142165

143-
return modules.Values.SelectMany(k => k.Keys).Select(Directory.GetDirectoryRoot).Distinct();
166+
return modules.Values.SelectMany(k => k.Keys).GroupBy(Directory.GetDirectoryRoot).Select(group =>
167+
{
168+
var splittedPaths = group.Select(absolutePath => absolutePath.Split(Path.DirectorySeparatorChar))
169+
.OrderBy(absolutePath => absolutePath.Length).ToList();
170+
if (splittedPaths.Count == 1)
171+
{
172+
return group.Key;
173+
}
174+
175+
var basePathFragments = new List<string>();
176+
177+
splittedPaths[0].Select((value, index) => (value, index)).ToList().ForEach(fragmentIndexPair =>
178+
{
179+
if (splittedPaths.All(sp => fragmentIndexPair.value.Equals(sp[fragmentIndexPair.index])))
180+
{
181+
basePathFragments.Add(fragmentIndexPair.value);
182+
}
183+
});
184+
return string.Concat(string.Join(Path.DirectorySeparatorChar.ToString(), basePathFragments), Path.DirectorySeparatorChar);
185+
});
144186
}
145187

146-
private static string GetRelativePathFromBase(IEnumerable<string> rootPaths, string path, bool useSourceLink)
188+
private static string GetRelativePathFromBase(IEnumerable<string> basePaths, string path, bool useSourceLink)
147189
{
148190
if (useSourceLink)
149191
{
150192
return path;
151193
}
152194

153-
foreach (var root in rootPaths)
195+
foreach (var basePath in basePaths)
154196
{
155-
if (path.StartsWith(root))
197+
if (path.StartsWith(basePath))
156198
{
157-
return path.Substring(root.Length);
199+
return path.Substring(basePath.Length);
158200
}
159201
}
160202

test/coverlet.core.tests/Reporters/CoberturaReporterTests.cs

Lines changed: 39 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -137,52 +137,77 @@ public void TestEnsureParseMethodStringCorrectly(
137137
}
138138

139139
[Fact]
140-
public void TestReportWithTwoDifferentDirectories()
140+
public void TestReportWithDifferentDirectories()
141141
{
142142
CoverageResult result = new CoverageResult();
143143
result.Identifier = Guid.NewGuid().ToString();
144144

145-
var isWindows = RuntimeInformation.IsOSPlatform(OSPlatform.Windows);
146-
147145
string absolutePath1;
148146
string absolutePath2;
147+
string absolutePath3;
148+
string absolutePath4;
149+
string absolutePath5;
150+
string absolutePath6;
151+
string absolutePath7;
149152

150-
if (isWindows)
153+
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
151154
{
152-
absolutePath1 = @"C:\projA\file.cs";
153-
absolutePath2 = @"E:\projB\file.cs";
155+
absolutePath1 = @"C:\projA\dir1\dir10\file1.cs";
156+
absolutePath2 = @"C:\projA\dir1\dir10\file2.cs";
157+
absolutePath3 = @"C:\projA\dir1\file3.cs";
158+
absolutePath4 = @"E:\projB\dir1\dir10\file4.cs";
159+
absolutePath5 = @"E:\projB\dir2\file5.cs";
160+
absolutePath6 = @"F:\file6.cs";
161+
absolutePath7 = @"F:\";
154162
}
155163
else
156164
{
157-
absolutePath1 = @"/projA/file.cs";
158-
absolutePath2 = @"/projB/file.cs";
165+
absolutePath1 = @"/projA/dir1/dir10/file1.cs";
166+
absolutePath2 = @"/projA/dir1/file2.cs";
167+
absolutePath3 = @"/projA/dir1/file3.cs";
168+
absolutePath4 = @"/projA/dir2/file4.cs";
169+
absolutePath5 = @"/projA/dir2/file5.cs";
170+
absolutePath6 = @"/file1.cs";
171+
absolutePath7 = @"/";
159172
}
160173

161-
var classes = new Classes {{"Class", new Methods()}};
162-
var documents = new Documents {{absolutePath1, classes}, {absolutePath2, classes}};
174+
var classes = new Classes { { "Class", new Methods() } };
175+
var documents = new Documents { { absolutePath1, classes },
176+
{ absolutePath2, classes },
177+
{ absolutePath3, classes },
178+
{ absolutePath4, classes },
179+
{ absolutePath5, classes },
180+
{ absolutePath6, classes },
181+
{ absolutePath7, classes }
182+
};
163183

164-
result.Modules = new Modules {{"Module", documents}};
184+
result.Modules = new Modules { { "Module", documents } };
165185

166186
CoberturaReporter reporter = new CoberturaReporter();
167187
string report = reporter.Report(result);
168188

169189
var doc = XDocument.Load(new MemoryStream(Encoding.UTF8.GetBytes(report)));
170190

171-
List<string> rootPaths = doc.Element("coverage").Element("sources").Elements().Select(e => e.Value).ToList();
191+
List<string> basePaths = doc.Element("coverage").Element("sources").Elements().Select(e => e.Value).ToList();
172192
List<string> relativePaths = doc.Element("coverage").Element("packages").Element("package")
173193
.Element("classes").Elements().Select(e => e.Attribute("filename").Value).ToList();
174194

175195
List<string> possiblePaths = new List<string>();
176-
foreach (string root in rootPaths)
196+
foreach (string basePath in basePaths)
177197
{
178198
foreach (string relativePath in relativePaths)
179199
{
180-
possiblePaths.Add(Path.Combine(root, relativePath));
200+
possiblePaths.Add(Path.Combine(basePath, relativePath));
181201
}
182202
}
183203

184204
Assert.Contains(absolutePath1, possiblePaths);
185205
Assert.Contains(absolutePath2, possiblePaths);
206+
Assert.Contains(absolutePath3, possiblePaths);
207+
Assert.Contains(absolutePath4, possiblePaths);
208+
Assert.Contains(absolutePath5, possiblePaths);
209+
Assert.Contains(absolutePath6, possiblePaths);
210+
Assert.Contains(absolutePath7, possiblePaths);
186211
}
187212

188213
[Fact]

0 commit comments

Comments
 (0)