Skip to content

Commit

Permalink
#348: Added coverage indicator for methods/properties
Browse files Browse the repository at this point in the history
  • Loading branch information
danielpalme committed May 23, 2020
1 parent a325580 commit afa1b81
Show file tree
Hide file tree
Showing 18 changed files with 249 additions and 61 deletions.
Binary file modified docs/resources/SampleReports.zip
Binary file not shown.
6 changes: 3 additions & 3 deletions src/ReportGenerator.Core.Test/Parser/Analysis/CodeFileTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -313,9 +313,9 @@ public void AddCoverageByTestMethod_AddCoverageByTestMethodForExistingMethod_Cov
public void CoveredCodeElements()
{
var sut = new CodeFile("C:\\temp\\Program.cs", new int[] { -1, 0, 2 }, new LineVisitStatus[] { LineVisitStatus.NotCoverable, LineVisitStatus.NotCovered, LineVisitStatus.Covered });
sut.AddCodeElement(new CodeElement("NotCoverable", CodeElementType.Method, 1, 1));
sut.AddCodeElement(new CodeElement("NotCovered", CodeElementType.Method, 2, 2));
sut.AddCodeElement(new CodeElement("Covered", CodeElementType.Method, 3, 3));
sut.AddCodeElement(new CodeElement("NotCoverable", CodeElementType.Method, 1, 1, null));
sut.AddCodeElement(new CodeElement("NotCovered", CodeElementType.Method, 2, 2, null));
sut.AddCodeElement(new CodeElement("Covered", CodeElementType.Method, 3, 3, null));

Assert.Equal(1, sut.CoveredCodeElements);
}
Expand Down
13 changes: 12 additions & 1 deletion src/ReportGenerator.Core/Parser/Analysis/CodeElement.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,17 @@ public class CodeElement
/// <param name="type">The <see cref="Analysis.CodeElementType"/>.</param>
/// <param name="firstLine">The number of the first line.</param>
/// <param name="lastLine">The number of the last line.</param>
internal CodeElement(string name, CodeElementType type, int firstLine, int lastLine)
/// <param name="coverageQuota">The coverage quota.</param>
internal CodeElement(string name, CodeElementType type, int firstLine, int lastLine, decimal? coverageQuota)
{
this.Name = name ?? throw new ArgumentNullException(nameof(name));
this.CodeElementType = type;
this.FirstLine = firstLine;
this.LastLine = lastLine;
if (coverageQuota.HasValue)
{
this.CoverageQuota = Math.Min(100, Math.Max(0, coverageQuota.Value));
}
}

/// <summary>
Expand Down Expand Up @@ -54,6 +59,12 @@ internal CodeElement(string name, CodeElementType type, int firstLine, int lastL
/// </value>
public int LastLine { get; }

/// <summary>
/// Gets the coverage quota of the code element.
/// </summary>
/// <value>The coverage quota.</value>
public decimal? CoverageQuota { get; }

/// <summary>
/// Determines whether the specified <see cref="object"/> is equal to this instance.
/// </summary>
Expand Down
36 changes: 36 additions & 0 deletions src/ReportGenerator.Core/Parser/Analysis/CodeFile.cs
Original file line number Diff line number Diff line change
Expand Up @@ -246,6 +246,42 @@ public override bool Equals(object obj)
/// </returns>
public override int GetHashCode() => this.Path.GetHashCode();

/// <summary>
/// Calculates the coverage quota in a given range of lines.
/// </summary>
/// <param name="firstLine">The first line.</param>
/// <param name="lastLine">The last line.</param>
/// <returns>The coverage quota or <code>null</code> if not applicable.</returns>
internal decimal? CoverageQuota(int firstLine, int lastLine)
{
if (firstLine < 0
|| firstLine >= this.lineVisitStatus.Length
|| lastLine < 0
|| lastLine >= this.lineVisitStatus.Length
|| firstLine > lastLine)
{
return null;
}

int coverableLines = 0;
int coveredLines = 0;

for (int i = firstLine; i <= lastLine; i++)
{
if (this.lineVisitStatus[i] != Analysis.LineVisitStatus.NotCoverable)
{
coverableLines++;
}

if (this.lineVisitStatus[i] > Analysis.LineVisitStatus.NotCovered)
{
coveredLines++;
}
}

return (coverableLines == 0) ? (decimal?)null : (decimal)Math.Truncate(1000 * (double)coveredLines / (double)coverableLines) / 10;
}

/// <summary>
/// Adds the coverage by test method.
/// </summary>
Expand Down
27 changes: 24 additions & 3 deletions src/ReportGenerator.Core/Parser/CloverParser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,7 @@ private void ProcessFile(Assembly assembly, XElement fileElement)

var codeFile = new CodeFile(fileElement.Attribute("path").Value, coverage, lineVisitStatus, branches);

SetCodeElements(codeFile, methodsOfFile);
SetCodeElements(codeFile, methodsOfFile, coverage.Length - 1);

@class.AddFile(codeFile);

Expand Down Expand Up @@ -215,8 +215,11 @@ private void ProcessFile(Assembly assembly, XElement fileElement)
/// </summary>
/// <param name="codeFile">The code file.</param>
/// <param name="methodsOfFile">The methods of the file.</param>
private static void SetCodeElements(CodeFile codeFile, IEnumerable<XElement> methodsOfFile)
/// <param name="numberOrLines">The number of lines in the file.</param>
private static void SetCodeElements(CodeFile codeFile, IEnumerable<XElement> methodsOfFile, int numberOrLines)
{
var codeElements = new List<CodeElementBase>();

foreach (var method in methodsOfFile)
{
var signature = method.Attribute("signature");
Expand All @@ -229,7 +232,7 @@ private static void SetCodeElements(CodeFile codeFile, IEnumerable<XElement> met
string methodName = signature.Value;
int lineNumber = int.Parse(method.Attribute("num").Value, CultureInfo.InvariantCulture);

codeFile.AddCodeElement(new CodeElement(methodName, CodeElementType.Method, lineNumber, lineNumber));
codeElements.Add(new CodeElementBase(methodName, lineNumber));

var complexity = method.Attribute("complexity");

Expand All @@ -253,6 +256,24 @@ private static void SetCodeElements(CodeFile codeFile, IEnumerable<XElement> met
codeFile.AddMethodMetric(methodMetric);
}
}

for (int i = 0; i < codeElements.Count; i++)
{
var codeElement = codeElements[i];

int lastLine = numberOrLines;
if (i < codeElements.Count - 1)
{
lastLine = codeElements[i + 1].FirstLine - 1;
}

codeFile.AddCodeElement(new CodeElement(
codeElement.Name,
CodeElementType.Method,
codeElement.FirstLine,
lastLine,
codeFile.CoverageQuota(codeElement.FirstLine, lastLine)));
}
}
}
}
8 changes: 7 additions & 1 deletion src/ReportGenerator.Core/Parser/CoberturaParser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -355,7 +355,13 @@ private static void SetCodeElements(CodeFile codeFile, IEnumerable<XElement> met
{
int firstLine = int.Parse(lines.First().Attribute("number").Value, CultureInfo.InvariantCulture);
int lastLine = int.Parse(lines.Last().Attribute("number").Value, CultureInfo.InvariantCulture);
codeFile.AddCodeElement(new CodeElement(methodName, CodeElementType.Method, firstLine, lastLine));

codeFile.AddCodeElement(new CodeElement(
methodName,
CodeElementType.Method,
firstLine,
lastLine,
codeFile.CoverageQuota(firstLine, lastLine)));
}
}
}
Expand Down
40 changes: 40 additions & 0 deletions src/ReportGenerator.Core/Parser/CodeElementBase.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
namespace Palmmedia.ReportGenerator.Core.Parser
{
/// <summary>
/// Temporar class to create <see cref="Analysis.CodeElement"/> once all required information is available.
/// </summary>
internal class CodeElementBase
{
/// <summary>
/// Initializes a new instance of the <see cref="CodeElementBase" /> class.
/// </summary>
/// <param name="name">The name of the method.</param>
/// <param name="firstLine">The first line.</param>
public CodeElementBase(string name, int firstLine)
{
this.Name = name;
this.FirstLine = firstLine;
}

/// <summary>
/// Gets the name of the method.
/// </summary>
public string Name { get; }

/// <summary>
/// Gets the first line.
/// </summary>
public int FirstLine { get; }

/// <summary>
/// Returns a <see cref="string"/> that represents this instance.
/// </summary>
/// <returns>
/// A <see cref="string"/> that represents this instance.
/// </returns>
public override string ToString()
{
return this.Name;
}
}
}
10 changes: 9 additions & 1 deletion src/ReportGenerator.Core/Parser/DotCoverParser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -259,7 +259,15 @@ private static void SetCodeElements(CodeFile codeFile, string fileId, IEnumerabl

if (seqpnts.Length > 0)
{
codeFile.AddCodeElement(new CodeElement(methodName, type, seqpnts.Min(s => s.LineNumberStart), seqpnts.Max(s => s.LineNumberEnd)));
int firstLine = seqpnts.Min(s => s.LineNumberStart);
int lastLine = seqpnts.Max(s => s.LineNumberEnd);

codeFile.AddCodeElement(new CodeElement(
methodName,
type,
seqpnts.Min(s => s.LineNumberStart),
seqpnts.Max(s => s.LineNumberEnd),
codeFile.CoverageQuota(firstLine, lastLine)));
}
}
}
Expand Down
10 changes: 9 additions & 1 deletion src/ReportGenerator.Core/Parser/DynamicCodeCoverageParser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -327,7 +327,15 @@ private static void SetCodeElements(CodeFile codeFile, IEnumerable<XElement> met

if (seqpnts.Length > 0)
{
codeFile.AddCodeElement(new CodeElement(methodName, type, seqpnts.Min(s => s.LineNumberStart), seqpnts.Max(s => s.LineNumberEnd)));
int firstLine = seqpnts.Min(s => s.LineNumberStart);
int lastLine = seqpnts.Max(s => s.LineNumberEnd);

codeFile.AddCodeElement(new CodeElement(
methodName,
type,
firstLine,
lastLine,
codeFile.CoverageQuota(firstLine, lastLine)));
}
}
}
Expand Down
25 changes: 19 additions & 6 deletions src/ReportGenerator.Core/Parser/GCovParser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ private void ProcessClass(Assembly assembly, string[] lines)

private void ProcessCoverage(Class @class, string fileName, string[] lines)
{
var codeElements = new List<CodeElement>();
var codeElements = new List<CodeElementBase>();
int maxiumLineNumber = -1;
var visitsByLine = new Dictionary<int, int>();

Expand Down Expand Up @@ -167,7 +167,7 @@ private void ProcessCoverage(Class @class, string fileName, string[] lines)
{
string name = line.Substring(9, line.IndexOf(' ', 9) - 9);

codeElements.Add(new CodeElement(name, CodeElementType.Method, maxiumLineNumber + 1, maxiumLineNumber + 1));
codeElements.Add(new CodeElementBase(name, maxiumLineNumber + 1));
}
}
}
Expand Down Expand Up @@ -200,14 +200,27 @@ private void ProcessCoverage(Class @class, string fileName, string[] lines)
}
}

var file = new CodeFile(fileName, coverage, lineVisitStatus, branchesByLineNumber);
var codeFile = new CodeFile(fileName, coverage, lineVisitStatus, branchesByLineNumber);

foreach (var codeElement in codeElements)
for (int i = 0; i < codeElements.Count; i++)
{
file.AddCodeElement(codeElement);
var codeElement = codeElements[i];

int lastLine = maxiumLineNumber;
if (i < codeElements.Count - 1)
{
lastLine = codeElements[i + 1].FirstLine - 1;
}

codeFile.AddCodeElement(new CodeElement(
codeElement.Name,
CodeElementType.Method,
codeElement.FirstLine,
lastLine,
codeFile.CoverageQuota(codeElement.FirstLine, lastLine)));
}

@class.AddFile(file);
@class.AddFile(codeFile);
}
}
}
37 changes: 30 additions & 7 deletions src/ReportGenerator.Core/Parser/JaCoCoParser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,6 @@ internal JaCoCoParser(IFilter assemblyFilter, IFilter classFilter, IFilter fileF
/// Parses the given XML report.
/// </summary>
/// <param name="report">The XML report.</param>
/// <param name="innerMaxDegreeOfParallism">The max degree of parallism for the class iteration foreach loop</param>
/// <returns>The parser result.</returns>
public ParserResult Parse(XContainer report)
{
Expand Down Expand Up @@ -77,7 +76,6 @@ public ParserResult Parse(XContainer report)
/// </summary>
/// <param name="modules">The modules.</param>
/// <param name="assemblyName">Name of the assembly.</param>
/// <param name="innerMaxDegreeOfParallism">The max degree of parallism for the class iteration foreach loop</param>
/// <returns>The <see cref="Assembly"/>.</returns>
private Assembly ProcessAssembly(XElement[] modules, string assemblyName)
{
Expand Down Expand Up @@ -132,15 +130,15 @@ private void ProcessClass(XElement[] modules, Assembly assembly, string classNam

foreach (var file in filteredFiles)
{
var codeFile = ProcessFile(modules, @class, file);
var codeFile = ProcessFile(modules, @class, file, out int numberOrLines);

var methodsOfFile = classes
.Where(c => c.Attribute("sourcefilename") != null && c.Attribute("sourcefilename").Value.Equals(file))
.Elements("method")
.ToArray();

SetMethodMetrics(codeFile, methodsOfFile);
SetCodeElements(codeFile, methodsOfFile);
SetCodeElements(codeFile, methodsOfFile, numberOrLines);

@class.AddFile(codeFile);
}
Expand All @@ -155,8 +153,9 @@ private void ProcessClass(XElement[] modules, Assembly assembly, string classNam
/// <param name="modules">The modules.</param>
/// <param name="class">The class.</param>
/// <param name="filePath">The file path.</param>
/// <param name="numberOrLines">The number of lines in the file.</param>
/// <returns>The <see cref="CodeFile"/>.</returns>
private static CodeFile ProcessFile(XElement[] modules, Class @class, string filePath)
private static CodeFile ProcessFile(XElement[] modules, Class @class, string filePath, out int numberOrLines)
{
var linesOfFile = modules
.Where(m => m.Attribute("name").Value.Equals(@class.Assembly.Name))
Expand Down Expand Up @@ -200,6 +199,8 @@ private static CodeFile ProcessFile(XElement[] modules, Class @class, string fil
}
}

numberOrLines = coverage.Length - 1;

return new CodeFile(filePath, coverage, lineVisitStatus, branches);
}

Expand Down Expand Up @@ -283,8 +284,11 @@ private static void SetMethodMetrics(CodeFile codeFile, IEnumerable<XElement> me
/// </summary>
/// <param name="codeFile">The code file.</param>
/// <param name="methodsOfFile">The methods of the file.</param>
private static void SetCodeElements(CodeFile codeFile, IEnumerable<XElement> methodsOfFile)
/// <param name="numberOrLines">The number of lines in the file.</param>
private static void SetCodeElements(CodeFile codeFile, IEnumerable<XElement> methodsOfFile, int numberOrLines)
{
var codeElements = new List<CodeElementBase>();

foreach (var method in methodsOfFile)
{
string methodName = method.Attribute("name").Value + method.Attribute("desc").Value;
Expand All @@ -298,7 +302,26 @@ private static void SetCodeElements(CodeFile codeFile, IEnumerable<XElement> met

int lineNumber = int.Parse(method.Attribute("line")?.Value ?? "0", CultureInfo.InvariantCulture);

codeFile.AddCodeElement(new CodeElement(methodName, CodeElementType.Method, lineNumber, lineNumber));
codeElements.Add(new CodeElementBase(methodName, lineNumber));
}

codeElements.Sort((x, y) => x.FirstLine.CompareTo(y.FirstLine));
for (int i = 0; i < codeElements.Count; i++)
{
var codeElement = codeElements[i];

int lastLine = numberOrLines;
if (i < codeElements.Count - 1)
{
lastLine = codeElements[i + 1].FirstLine - 1;
}

codeFile.AddCodeElement(new CodeElement(
codeElement.Name,
CodeElementType.Method,
codeElement.FirstLine,
lastLine,
codeFile.CoverageQuota(codeElement.FirstLine, lastLine)));
}
}

Expand Down
Loading

0 comments on commit afa1b81

Please sign in to comment.