Skip to content

Commit

Permalink
#691 Cobertura parser: Performance and memory improvements (contribut…
Browse files Browse the repository at this point in the history
…ed by @afscrome)
  • Loading branch information
danielpalme committed Aug 28, 2024
2 parents 4444f14 + e5e2775 commit 57a7c43
Show file tree
Hide file tree
Showing 2 changed files with 42 additions and 46 deletions.
1 change: 1 addition & 0 deletions src/Readme.txt
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ CHANGELOG

* New: #685 Extended "raw mode" for dotCover format (settings:rawMode=true) to disable that coverage data of nested or compiler generated
classes is included in the parent class.
* New: #691 Cobertura parser: Performance and memory improvements (contributed by @afscrome)

5.3.8.0

Expand Down
87 changes: 41 additions & 46 deletions src/ReportGenerator.Core/Parser/CoberturaParser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -68,19 +68,14 @@ public ParserResult Parse(XContainer report)

var assemblies = new List<Assembly>();

var modules = report.Descendants("package")
.ToArray();

var assemblyNames = modules
.Select(m => m.Attribute("name").Value)
.Distinct()
.Where(a => this.AssemblyFilter.IsElementIncludedInReport(a))
.OrderBy(a => a)
var assemblyElementGrouping = report.Descendants("package")
.GroupBy(m => m.Attribute("name").Value)
.Where(a => this.AssemblyFilter.IsElementIncludedInReport(a.Key))
.ToArray();

foreach (var assemblyName in assemblyNames)
foreach (var elements in assemblyElementGrouping)
{
assemblies.Add(this.ProcessAssembly(modules, assemblyName));
assemblies.Add(this.ProcessAssembly(elements.ToArray(), elements.Key));
}

var result = new ParserResult(assemblies.OrderBy(a => a.Name).ToList(), true, this.ToString());
Expand All @@ -92,7 +87,7 @@ public ParserResult Parse(XContainer report)

try
{
if (report.Element("sources").Parent.Attribute("timestamp") != null)
if (report.Element("sources")?.Parent.Attribute("timestamp") != null)
{
DateTime timeStamp = new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc);
timeStamp = timeStamp.AddSeconds(double.Parse(report.Element("sources").Parent.Attribute("timestamp").Value)).ToLocalTime();
Expand All @@ -119,10 +114,12 @@ private Assembly ProcessAssembly(XElement[] modules, string assemblyName)
{
Logger.DebugFormat(Resources.CurrentAssembly, assemblyName);

var classNames = modules
.Where(m => m.Attribute("name").Value.Equals(assemblyName))
var classes = modules
.Elements("classes")
.Elements("class")
.ToArray();

var classNames = classes
.Select(c => ClassNameParser.ParseClassName(c.Attribute("name").Value, this.RawMode))
.Where(c => c.Include)
.Distinct()
Expand All @@ -132,29 +129,37 @@ private Assembly ProcessAssembly(XElement[] modules, string assemblyName)

var assembly = new Assembly(assemblyName);

Parallel.ForEach(classNames, c => this.ProcessClass(modules, assembly, c.Name, c.DisplayName));
Parallel.ForEach(classNames, c => this.ProcessClass(classes, assembly, c.Name, c.DisplayName));

return assembly;
}

/// <summary>
/// Processes the given class.
/// </summary>
/// <param name="modules">The modules.</param>
/// <param name="allClasses">All class elements.</param>
/// <param name="assembly">The assembly.</param>
/// <param name="className">Name of the class.</param>
/// <param name="classDisplayName">Diesplay name of the class.</param>
private void ProcessClass(XElement[] modules, Assembly assembly, string className, string classDisplayName)
private void ProcessClass(XElement[] allClasses, Assembly assembly, string className, string classDisplayName)
{
var files = modules
.Where(m => m.Attribute("name").Value.Equals(assembly.Name))
.Elements("classes")
.Elements("class")
.Where(c => c.Attribute("name").Value.Equals(className)
bool FilterClass(XElement element)
{
var name = element.Attribute("name").Value;

return name.Equals(className)
|| (!this.RawMode
&& (c.Attribute("name").Value.StartsWith(className + "$", StringComparison.Ordinal)
|| c.Attribute("name").Value.StartsWith(className + "/", StringComparison.Ordinal)
|| c.Attribute("name").Value.StartsWith(className + ".", StringComparison.Ordinal))))
&& name.StartsWith(className, StringComparison.Ordinal)
&& (name[className.Length] == '$'
|| name[className.Length] == '/'
|| name[className.Length] == '.'));
}

var classes = allClasses
.Where(FilterClass)
.ToArray();

var files = classes
.Select(c => c.Attribute("filename").Value)
.Distinct()
.ToArray();
Expand All @@ -170,7 +175,10 @@ private void ProcessClass(XElement[] modules, Assembly assembly, string classNam

foreach (var file in filteredFiles)
{
@class.AddFile(this.ProcessFile(modules, @class, className, file));
var fileClasses = classes
.Where(c => c.Attribute("filename").Value.Equals(file))
.ToArray();
@class.AddFile(this.ProcessFile(fileClasses, @class, className, file));
}

assembly.AddClass(@class);
Expand All @@ -180,35 +188,27 @@ private void ProcessClass(XElement[] modules, Assembly assembly, string classNam
/// <summary>
/// Processes the file.
/// </summary>
/// <param name="modules">The modules.</param>
/// <param name="classElements">The class elements for the file.</param>
/// <param name="class">The class.</param>
/// <param name="className">Name of the class.</param>
/// <param name="filePath">The file path.</param>
/// <returns>The <see cref="CodeFile"/>.</returns>
private CodeFile ProcessFile(XElement[] modules, Class @class, string className, string filePath)
private CodeFile ProcessFile(XElement[] classElements, Class @class, string className, string filePath)
{
var classes = modules
.Where(m => m.Attribute("name").Value.Equals(@class.Assembly.Name))
.Elements("classes")
.Elements("class")
.Where(c => c.Attribute("name").Value.Equals(className)
|| (!this.RawMode
&& (c.Attribute("name").Value.StartsWith(className + "$", StringComparison.Ordinal)
|| c.Attribute("name").Value.StartsWith(className + "/", StringComparison.Ordinal)
|| c.Attribute("name").Value.StartsWith(className + ".", StringComparison.Ordinal))))
.Where(c => c.Attribute("filename").Value.Equals(filePath))
.ToArray();

var lines = classes.Elements("lines")
var lines = classElements.Elements("lines")
.Elements("line")
.ToArray();

var lineNumbers = lines
.Select(l => l.Attribute("number").Value)
.ToHashSet();

var additionalLinesInMethodElement = classes.Elements("methods")
var methodsOfFile = classElements
.Elements("methods")
.Elements("method")
.ToArray();

var additionalLinesInMethodElement = methodsOfFile
.Elements("lines")
.Elements("line")
.Where(l => !lineNumbers.Contains(l.Attribute("number").Value))
Expand Down Expand Up @@ -254,11 +254,6 @@ private CodeFile ProcessFile(XElement[] modules, Class @class, string className,
}
}

var methodsOfFile = classes
.Elements("methods")
.Elements("method")
.ToArray();

var codeFile = new CodeFile(filePath, coverage, lineVisitStatus, branches);

SetMethodMetrics(codeFile, methodsOfFile);
Expand Down

0 comments on commit 57a7c43

Please sign in to comment.