Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Performance improvements to CoberturaParser #691

Merged
merged 1 commit into from
Aug 28, 2024
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
90 changes: 42 additions & 48 deletions src/ReportGenerator.Core/Parser/CoberturaParser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -68,19 +68,13 @@ public ParserResult Parse(XContainer report)

var assemblies = new List<Assembly>();

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

var assemblyNames = modules
.Select(m => m.Attribute("name").Value)
.Distinct()
.Where(a => this.AssemblyFilter.IsElementIncludedInReport(a))
.OrderBy(a => a)
.ToArray();

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

var result = new ParserResult(assemblies.OrderBy(a => a.Name).ToList(), true, this.ToString());
Expand All @@ -92,7 +86,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 @@ -115,14 +109,16 @@ public ParserResult Parse(XContainer report)
/// <param name="modules">The modules.</param>
/// <param name="assemblyName">Name of the assembly.</param>
/// <returns>The <see cref="Assembly"/>.</returns>
private Assembly ProcessAssembly(XElement[] modules, string assemblyName)
private Assembly ProcessAssembly(IEnumerable<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 +128,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 +174,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 +187,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 +253,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
Loading