Skip to content

Commit 86dd4db

Browse files
authored
Merge pull request #297 from kmaddock/fast-load
Improve performance with large solutions
2 parents 8e42405 + 127ed8c commit 86dd4db

19 files changed

+170
-108
lines changed

FineCodeCoverageTests/FCCEngine_Tests.cs

+7-4
Original file line numberDiff line numberDiff line change
@@ -333,7 +333,7 @@ private void VerifyLogsReloadCoverageStatus(ReloadCoverageStatus reloadCoverageS
333333
mocker.Verify<ILogger>(l => l.Log(fccEngine.GetLogReloadCoverageStatusMessage(reloadCoverageStatus)));
334334
}
335335

336-
private async Task<(string reportGeneratedHtmlContent, List<CoverageLine> updatedCoverageLines)> RunToCompletion(bool noCoverageProjects)
336+
private async Task<(string reportGeneratedHtmlContent, FileLineCoverage updatedCoverageLines)> RunToCompletion(bool noCoverageProjects)
337337
{
338338
var coverageProject = CreateSuitableProject().Object;
339339
var mockReportGenerator = mocker.GetMock<IReportGeneratorUtil>();
@@ -352,7 +352,9 @@ private void VerifyLogsReloadCoverageStatus(ReloadCoverageStatus reloadCoverageS
352352

353353
var reportGeneratedHtmlContent = "<somehtml/>";
354354
mockReportGenerator.Setup(rg => rg.ProcessUnifiedHtml("Unified", It.IsAny<string>())).Returns(reportGeneratedHtmlContent);
355-
List<CoverageLine> coverageLines = new List<CoverageLine>() { new CoverageLine() };
355+
var coverageLines = new FileLineCoverage();
356+
coverageLines.Add("test", new[] { new Line() });
357+
coverageLines.Completed();
356358
mocker.GetMock<ICoberturaUtil>().Setup(coberturaUtil => coberturaUtil.ProcessCoberturaXml(It.IsAny<string>())).Returns(coverageLines);
357359
if (noCoverageProjects)
358360
{
@@ -384,8 +386,9 @@ private async Task ThrowReadingReportHtml()
384386
}
385387
);
386388

387-
388-
List<CoverageLine> coverageLines = new List<CoverageLine>() { new CoverageLine() };
389+
var coverageLines = new FileLineCoverage();
390+
coverageLines.Add("test", new[] { new Line() });
391+
coverageLines.Completed();
389392
mocker.GetMock<ICoberturaUtil>().Setup(coberturaUtil => coberturaUtil.ProcessCoberturaXml(It.IsAny<string>())).Returns(coverageLines);
390393

391394
await ReloadInitializedCoverage(passedProject.Object);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Linq;
4+
using FineCodeCoverage.Engine.Model;
5+
using NUnit.Framework;
6+
7+
namespace FineCodeCoverageTests
8+
{
9+
internal class FileLineCoverage_Tests
10+
{
11+
[TestCaseSource(nameof(Cases))]
12+
public void GetLines_Test(IEnumerable<int> lineNumbers, int startLineNumber, int endLineNumber, IEnumerable<int> expectedLineNumbers)
13+
{
14+
var fileLineCoverage = new FileLineCoverage();
15+
fileLineCoverage.Add("fp", lineNumbers.Select(n => new FineCodeCoverage.Engine.Cobertura.Line { Number = n }));
16+
fileLineCoverage.Completed();
17+
18+
var lines = fileLineCoverage.GetLines("fp", startLineNumber, endLineNumber);
19+
Assert.That(lines.Select(l => l.Number), Is.EqualTo(expectedLineNumbers));
20+
}
21+
22+
static object[] Cases =
23+
{
24+
new object[] { new int[] { 0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20}, 19, 20, new int[]{ 19,20} },
25+
new object[] { new int[] { 0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20}, 12, 13, new int[]{ 12,13} },
26+
new object[] { new int[] { 0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20}, 6, 7, new int[]{ 6,7} },
27+
new object[] {Enumerable.Empty<int>(), 0, 4,Enumerable.Empty<int>() },
28+
new object[] { new int[] { 3,2,1}, 0, 4, new int[]{ 1,2,3} },
29+
new object[] { new int[] { 3,2,1}, 0, 3, new int[]{ 1,2,3} },
30+
new object[] { new int[] { 3,2,1}, 1, 2, new int[]{ 1,2} },
31+
new object[] { new int[] { 3,2,1}, 2, 2, new int[]{ 2} },
32+
new object[] { new int[] { 3,2,1}, 4, 5, Enumerable.Empty<int>() }
33+
};
34+
}
35+
}

FineCodeCoverageTests/FineCodeCoverageTests.csproj

+1
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,7 @@
114114
<Compile Include="CoverageToolOutput_Tests\SolutionFolderProvider_Tests.cs" />
115115
<Compile Include="AppOptionsProvider_Tests.cs" />
116116
<Compile Include="CoverageUtilManager_Tests.cs" />
117+
<Compile Include="FileLineCoverage_Tests.cs" />
117118
<Compile Include="MsCodeCoverage\ShimCopier_Tests.cs" />
118119
<Compile Include="MsCodeCoverage\TemplatedRunSettingsService_Tests.cs" />
119120
<Compile Include="MsCodeCoverage\CustomRunSettingsTemplateProvider_Tests.cs" />

SharedProject/Core/Cobertura/CoberturaUtil.cs

+5-55
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
using System.Collections.Generic;
55
using FineCodeCoverage.Engine.Model;
66
using System.ComponentModel.Composition;
7-
using System.IO;
87

98
namespace FineCodeCoverage.Engine.Cobertura
109
{
@@ -25,71 +24,22 @@ private CoverageReport LoadReport(string xmlFile)
2524
}
2625
}
2726

28-
//private void CoverageXmlFileToJsonFile(string xmlFile, string jsonFile, bool formattedJson = false)
29-
//{
30-
// var xmlText = File.ReadAllText(xmlFile);
31-
// var jsonText = CoverageXmlTextToJsonText(xmlText, formattedJson);
32-
// File.WriteAllText(jsonFile, jsonText);
33-
//}
34-
35-
//private string CoverageXmlTextToJsonText(string xmlText, bool formattedJson = false)
36-
//{
37-
// long count = 0;
38-
39-
// var xmlLines = xmlText
40-
// .Split('\r', '\n')
41-
// .Select(x => x.Trim())
42-
// .Where(x => !x.StartsWith("<?xml"))
43-
// .Where(x => !x.StartsWith("<!DOCTYPE"))
44-
// .Where(x => !x.StartsWith("<sources>") && !x.StartsWith("</sources>") && !x.StartsWith("<source>"))
45-
// .Where(x => !x.StartsWith("<packages>") && !x.StartsWith("</packages>"))
46-
// .Where(x => !x.StartsWith("<classes>") && !x.StartsWith("</classes>"))
47-
// .Where(x => !x.StartsWith("<methods>") && !x.StartsWith("</methods>"))
48-
// .Where(x => !x.StartsWith("<lines>") && !x.StartsWith("</lines>"))
49-
// .Select(x => x
50-
// .Replace("<package ", $"<packages id=\"p{count++}\" json:Array='true' ").Replace("</package>", "</packages>")
51-
// .Replace("<class ", $"<classes id=\"c{count++}\" json:Array='true' ").Replace("</class>", "</classes>")
52-
// .Replace("<method ", $"<methods id=\"m{count++}\" json:Array='true' ").Replace("</method>", "</methods>")
53-
// .Replace("<line ", $"<lines id=\"l{count++}\" json:Array='true' ").Replace("</line>", "</lines>")
54-
// );
55-
56-
// var processedXmlText = string
57-
// .Join(Environment.NewLine, xmlLines)
58-
// .Replace("<coverage ", "<coverage xmlns:json='http://james.newtonking.com/projects/json' ");
59-
60-
// var xmlDocument = new XmlDocument();
61-
// xmlDocument.LoadXml(processedXmlText);
62-
63-
// string jsonText = JsonConvert
64-
// .SerializeXmlNode(xmlDocument, formattedJson ? Newtonsoft.Json.Formatting.Indented : Newtonsoft.Json.Formatting.None, true)
65-
// .Replace("\"@", "\"");
66-
67-
// return jsonText;
68-
//}
69-
70-
public List<CoverageLine> ProcessCoberturaXml(string xmlFile)
27+
public FileLineCoverage ProcessCoberturaXml(string xmlFile)
7128
{
72-
var coverageLines = new List<CoverageLine>();
29+
var fileLineCoverage = new FileLineCoverage();
7330

7431
coverageReport = LoadReport(xmlFile);
7532

7633
foreach (var package in coverageReport.Packages.Package)
7734
{
7835
foreach (var classs in package.Classes.Class)
7936
{
80-
foreach (var line in classs.Lines.Line)
81-
{
82-
coverageLines.Add(new CoverageLine
83-
{
84-
Package = package,
85-
Class = classs,
86-
Line = line
87-
});
88-
}
37+
fileLineCoverage.Add(classs.Filename, classs.Lines.Line);
8938
}
9039
}
9140

92-
return coverageLines;
41+
fileLineCoverage.Completed();
42+
return fileLineCoverage;
9343
}
9444

9545
public string[] GetSourceFiles(string assemblyName, string qualifiedClassName, int file)

SharedProject/Core/Cobertura/ICoberturaUtil.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ namespace FineCodeCoverage.Engine.Cobertura
55
{
66
interface ICoberturaUtil
77
{
8-
List<CoverageLine> ProcessCoberturaXml(string xmlFile);
8+
FileLineCoverage ProcessCoberturaXml(string xmlFile);
99
string[] GetSourceFiles(string assemblyName, string qualifiedClassName, int file);
1010
}
1111
}

SharedProject/Core/FCCEngine.cs

+8-8
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ internal enum ReloadCoverageStatus { Start, Done, Cancelled, Error, Initializing
1919

2020
internal class NewCoverageLinesMessage
2121
{
22-
public List<CoverageLine> CoverageLines { get; set; }
22+
public FileLineCoverage CoverageLines { get; set; }
2323
}
2424

2525
internal class DisplayCoverageResultState
@@ -202,12 +202,12 @@ private void ClearCoverageLines()
202202
RaiseCoverageLines(null);
203203
}
204204

205-
private void RaiseCoverageLines(List<CoverageLine> coverageLines)
205+
private void RaiseCoverageLines(FileLineCoverage coverageLines)
206206
{
207207
eventAggregator.SendMessage(new NewCoverageLinesMessage { CoverageLines = coverageLines});
208208
}
209209

210-
private void UpdateUI(List<CoverageLine> coverageLines, string reportHtml)
210+
private void UpdateUI(FileLineCoverage coverageLines, string reportHtml)
211211
{
212212
RaiseCoverageLines(coverageLines);
213213
if (reportHtml == null)
@@ -217,7 +217,7 @@ private void UpdateUI(List<CoverageLine> coverageLines, string reportHtml)
217217
RaiseUpdateOutputWindow(reportHtml);
218218
}
219219

220-
private async System.Threading.Tasks.Task<(List<CoverageLine> coverageLines,string reportFilePath)> RunAndProcessReportAsync(string[] coverOutputFiles, CancellationToken vsShutdownLinkedCancellationToken)
220+
private async System.Threading.Tasks.Task<(FileLineCoverage coverageLines,string reportFilePath)> RunAndProcessReportAsync(string[] coverOutputFiles, CancellationToken vsShutdownLinkedCancellationToken)
221221
{
222222
var reportOutputFolder = coverageOutputManager.GetReportOutputFolder();
223223
vsShutdownLinkedCancellationToken.ThrowIfCancellationRequested();
@@ -262,7 +262,7 @@ private async System.Threading.Tasks.Task PrepareCoverageProjectsAsync(List<ICov
262262
}
263263
}
264264

265-
private void DisplayCoverageResult(System.Threading.Tasks.Task<(List<CoverageLine> coverageLines, string reportHtml)> t, object state)
265+
private void DisplayCoverageResult(System.Threading.Tasks.Task<(FileLineCoverage coverageLines, string reportHtml)> t, object state)
266266
{
267267
var displayCoverageResultState = (DisplayCoverageResultState)state;
268268
if (!IsVsShutdown)
@@ -321,7 +321,7 @@ public void RunAndProcessReport(string[] coberturaFiles, Action cleanUp = null)
321321
{
322322
RunCancellableCoverageTask(async (vsShutdownLinkedCancellationToken) =>
323323
{
324-
List<CoverageLine> coverageLines = null;
324+
FileLineCoverage coverageLines = null;
325325
string reportHtml = null;
326326

327327
if (coberturaFiles.Any())
@@ -333,7 +333,7 @@ public void RunAndProcessReport(string[] coberturaFiles, Action cleanUp = null)
333333
}
334334

335335
private void RunCancellableCoverageTask(
336-
Func<CancellationToken,System.Threading.Tasks.Task<(List<CoverageLine>, string)>> taskCreator, Action cleanUp)
336+
Func<CancellationToken,System.Threading.Tasks.Task<(FileLineCoverage, string)>> taskCreator, Action cleanUp)
337337
{
338338
var vsLinkedCancellationTokenSource = Reset();
339339
var vsShutdownLinkedCancellationToken = vsLinkedCancellationTokenSource.Token;
@@ -355,7 +355,7 @@ public void ReloadCoverage(Func<System.Threading.Tasks.Task<List<ICoverageProjec
355355
{
356356
RunCancellableCoverageTask(async (vsShutdownLinkedCancellationToken) =>
357357
{
358-
List<CoverageLine> coverageLines = null;
358+
FileLineCoverage coverageLines = null;
359359
string reportHtml = null;
360360

361361
await PollInitializedStatusAsync(vsShutdownLinkedCancellationToken);

SharedProject/Core/Model/CoverageLine.cs

-11
This file was deleted.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
using FineCodeCoverage.Core.Utilities;
2+
using FineCodeCoverage.Engine.Cobertura;
3+
using System;
4+
using System.Collections.Generic;
5+
6+
namespace FineCodeCoverage.Engine.Model
7+
{
8+
// FileLineCoverage maps from a filename to the list of lines in the file
9+
internal class FileLineCoverage
10+
{
11+
private Dictionary<string, List<Line>> m_coverageLines = new Dictionary<string, List<Line>>(StringComparer.OrdinalIgnoreCase);
12+
13+
public void Add(string filename, IEnumerable<Line> lines)
14+
{
15+
if (!m_coverageLines.TryGetValue(filename, out var fileCoverageLines))
16+
{
17+
fileCoverageLines = new List<Line>();
18+
m_coverageLines.Add(filename, fileCoverageLines);
19+
}
20+
21+
fileCoverageLines.AddRange(lines);
22+
}
23+
24+
public void Completed()
25+
{
26+
foreach (var lines in m_coverageLines.Values)
27+
lines.Sort((a, b) => a.Number - b.Number);
28+
}
29+
30+
public IEnumerable<Line> GetLines(string filePath, int startLineNumber, int endLineNumber)
31+
{
32+
if (!m_coverageLines.TryGetValue(filePath, out var lines))
33+
yield break;
34+
35+
int first = lines.LowerBound(line => startLineNumber - line.Number);
36+
if (first != -1)
37+
{
38+
for (int it = first; it < lines.Count && lines[it].Number <= endLineNumber; ++it)
39+
yield return lines[it];
40+
}
41+
42+
}
43+
}
44+
45+
46+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
using System;
2+
using System.Collections.Generic;
3+
4+
namespace FineCodeCoverage.Core.Utilities
5+
{
6+
public static class ListExtensions
7+
{
8+
// To be performed on a sorted list
9+
// Returns -1 for empty list or when all elements are outside the lower bounds
10+
// Compare fn to return 0 for element considered the lower bound
11+
// > 0 for lower bound greater than element
12+
13+
public static int LowerBound<T>(this IList<T> list, Func<T, int> compare)
14+
{
15+
int first = 0;
16+
int count = list.Count;
17+
if (count == 0) return -1;
18+
19+
while (count > 0)
20+
{
21+
int step = count / 2;
22+
int index = first + step;
23+
var result = compare(list[index]);
24+
if (result == 0)
25+
{
26+
return index;
27+
}
28+
else if (result > 0)
29+
{
30+
first = ++index;
31+
count -= step + 1;
32+
}
33+
else
34+
{
35+
count = step;
36+
}
37+
}
38+
39+
return first != list.Count ? first : -1;
40+
}
41+
}
42+
}

SharedProject/Impl/CoverageColour/GlyphMargin/CoverageLineGlyphTag.cs

+4-3
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
1-
using FineCodeCoverage.Engine.Model;
1+
using FineCodeCoverage.Engine.Cobertura;
2+
using FineCodeCoverage.Engine.Model;
23
using Microsoft.VisualStudio.Text.Editor;
34

45
namespace FineCodeCoverage.Impl
56
{
67
internal class CoverageLineGlyphTag : IGlyphTag
78
{
8-
public CoverageLine CoverageLine { get; }
9+
public Line CoverageLine { get; }
910

10-
public CoverageLineGlyphTag(CoverageLine coverageLine)
11+
public CoverageLineGlyphTag(Line coverageLine)
1112
{
1213
CoverageLine = coverageLine;
1314
}

SharedProject/Impl/CoverageColour/GlyphMargin/CoverageLineGlyphTagger.cs

+2-2
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ namespace FineCodeCoverage.Impl
88
{
99
internal class CoverageLineGlyphTagger : CoverageLineTaggerBase<CoverageLineGlyphTag>, IListener<RefreshCoverageGlyphsMessage>
1010
{
11-
public CoverageLineGlyphTagger(ITextBuffer textBuffer, List<CoverageLine> lastCoverageLines) : base(textBuffer, lastCoverageLines)
11+
public CoverageLineGlyphTagger(ITextBuffer textBuffer, FileLineCoverage lastCoverageLines) : base(textBuffer, lastCoverageLines)
1212
{
1313
}
1414

@@ -17,7 +17,7 @@ public void Handle(RefreshCoverageGlyphsMessage message)
1717
RaiseTagsChanged();
1818
}
1919

20-
protected override TagSpan<CoverageLineGlyphTag> GetTagSpan(CoverageLine coverageLine, SnapshotSpan span)
20+
protected override TagSpan<CoverageLineGlyphTag> GetTagSpan(Engine.Cobertura.Line coverageLine, SnapshotSpan span)
2121
{
2222
return new TagSpan<CoverageLineGlyphTag>(span, new CoverageLineGlyphTag(coverageLine));
2323
}

SharedProject/Impl/CoverageColour/GlyphMargin/CoverageLineGlyphTaggerProvider.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ protected override void NewCoverageLinesMessageReceived()
4141
});
4242
}
4343

44-
protected override CoverageLineGlyphTagger CreateTagger(ITextBuffer textBuffer, List<CoverageLine> lastCoverageLines)
44+
protected override CoverageLineGlyphTagger CreateTagger(ITextBuffer textBuffer, FileLineCoverage lastCoverageLines)
4545
{
4646
return new CoverageLineGlyphTagger(textBuffer, lastCoverageLines);
4747
}

0 commit comments

Comments
 (0)