Browse Source

Branch Coverage

Display Branch Coverage % only if Sequence Coverage is 100%
pull/67/head
Dragan 12 years ago
parent
commit
1074b5c682
  1. 6
      src/AddIns/Analysis/CodeCoverage/Project/Src/CodeCoverageClassTreeNode.cs
  2. 15
      src/AddIns/Analysis/CodeCoverage/Project/Src/CodeCoverageMethod.cs
  3. 205
      src/AddIns/Analysis/CodeCoverage/Project/Src/CodeCoverageMethodElement.cs
  4. 4
      src/AddIns/Analysis/CodeCoverage/Project/Src/CodeCoverageMethodTreeNode.cs
  5. 10
      src/AddIns/Analysis/CodeCoverage/Project/Src/CodeCoverageMethodsTreeNode.cs
  6. 15
      src/AddIns/Analysis/CodeCoverage/Project/Src/CodeCoverageModule.cs
  7. 32
      src/AddIns/Analysis/CodeCoverage/Project/Src/CodeCoverageTreeNode.cs
  8. 1
      src/AddIns/Analysis/CodeCoverage/Project/Src/ICodeCoverageWithVisits.cs

6
src/AddIns/Analysis/CodeCoverage/Project/Src/CodeCoverageClassTreeNode.cs

@ -51,8 +51,10 @@ namespace ICSharpCode.CodeCoverage
// Add methods. // Add methods.
CodeCoveragePropertyCollection properties = new CodeCoveragePropertyCollection(); CodeCoveragePropertyCollection properties = new CodeCoveragePropertyCollection();
foreach (CodeCoverageMethod method in Methods) { foreach (CodeCoverageMethod method in Methods) {
//if ( !method.Name.Contains("<") && !method.Name.Contains("__") ) { // method name that is generated by compiler, contains "__" (double underscore)
if ( !method.Name.Contains("__") ) { // method that contains no sequence points cannot be subject to "code coverage"
// ie: "yield" enumerator method is replaced by compiler generated method
if ( !method.Name.Contains("__") ) { // && method.SequencePointsCount != 0) {
if (method.IsProperty) { if (method.IsProperty) {
properties.Add(method); properties.Add(method);
} else { } else {

15
src/AddIns/Analysis/CodeCoverage/Project/Src/CodeCoverageMethod.cs

@ -43,6 +43,12 @@ namespace ICSharpCode.CodeCoverage
IsProperty = element.IsProperty && IsPropertyMethodName(); IsProperty = element.IsProperty && IsPropertyMethodName();
IsGetter = element.IsGetter; IsGetter = element.IsGetter;
IsSetter = element.IsSetter; IsSetter = element.IsSetter;
this.IsVisited = element.IsVisited;
this.BranchCoverage = element.BranchCoverage;
this.BranchCoverageRatio = element.BranchCoverageRatio;
this.SequencePointsCount = element.SequencePointsCount;
} }
/// <summary> /// <summary>
@ -52,6 +58,11 @@ namespace ICSharpCode.CodeCoverage
public bool IsGetter { get; private set; } public bool IsGetter { get; private set; }
public bool IsSetter { get; private set; } public bool IsSetter { get; private set; }
public bool IsVisited { get; private set; }
public decimal BranchCoverage { get; private set; }
public Tuple<int,int> BranchCoverageRatio { get; private set; }
public int SequencePointsCount { get; private set; }
bool IsPropertyMethodName() bool IsPropertyMethodName()
{ {
return name.Contains("get_") || name.Contains("set_"); return name.Contains("get_") || name.Contains("set_");
@ -97,7 +108,7 @@ namespace ICSharpCode.CodeCoverage
get { get {
int count = 0; int count = 0;
foreach (CodeCoverageSequencePoint sequencePoint in sequencePoints) { foreach (CodeCoverageSequencePoint sequencePoint in sequencePoints) {
if (sequencePoint.VisitCount > 0) { if (sequencePoint.VisitCount != 0) {
count++; count++;
} }
} }
@ -121,7 +132,7 @@ namespace ICSharpCode.CodeCoverage
{ {
int total = 0; int total = 0;
foreach (CodeCoverageSequencePoint sequencePoint in sequencePoints) { foreach (CodeCoverageSequencePoint sequencePoint in sequencePoints) {
if (sequencePoint.VisitCount > 0) { if (sequencePoint.VisitCount != 0) {
total += sequencePoint.Length; total += sequencePoint.Length;
} }
} }

205
src/AddIns/Analysis/CodeCoverage/Project/Src/CodeCoverageMethodElement.cs

@ -2,6 +2,9 @@
// This code is distributed under the GNU LGPL (for details please see \doc\license.txt) // This code is distributed under the GNU LGPL (for details please see \doc\license.txt)
using System; using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Xml.Linq; using System.Xml.Linq;
namespace ICSharpCode.CodeCoverage namespace ICSharpCode.CodeCoverage
@ -9,13 +12,27 @@ namespace ICSharpCode.CodeCoverage
public class CodeCoverageMethodElement public class CodeCoverageMethodElement
{ {
XElement element; XElement element;
private class branch {
public int Visit;
public int Count;
}
public CodeCoverageMethodElement(XElement element) public CodeCoverageMethodElement(XElement element)
{ {
this.element = element; this.element = element;
Init(); Init();
} }
public bool IsVisited { get; private set; }
public int CyclomaticComplexity { get; private set; }
public decimal SequenceCoverage { get; private set; }
public int SequencePointsCount { get; private set; }
public decimal BranchCoverage { get; private set; }
public Tuple<int,int> BranchCoverageRatio { get; private set; }
public bool IsConstructor { get; private set; }
public bool IsStatic { get; private set; }
public bool IsGetter { get; private set; } public bool IsGetter { get; private set; }
public bool IsSetter { get; private set; } public bool IsSetter { get; private set; }
public string MethodName { get; private set; } public string MethodName { get; private set; }
@ -26,9 +43,193 @@ namespace ICSharpCode.CodeCoverage
void Init() void Init()
{ {
MethodName = GetMethodName();
IsGetter = GetBooleanAttributeValue("isGetter"); IsGetter = GetBooleanAttributeValue("isGetter");
IsSetter = GetBooleanAttributeValue("isSetter"); IsSetter = GetBooleanAttributeValue("isSetter");
MethodName = GetMethodName();
this.IsVisited = this.GetBooleanAttributeValue("visited");
this.CyclomaticComplexity = (int)this.GetDecimalAttributeValue("cyclomaticComplexity");
this.SequencePointsCount = this.GetSequencePointsCount();
this.SequenceCoverage = this.GetDecimalAttributeValue("sequenceCoverage");
this.BranchCoverageRatio = this.GetBranchRatio();
this.BranchCoverage = this.GetBranchCoverage();
this.IsConstructor = this.GetBooleanAttributeValue("isConstructor");
this.IsStatic = this.GetBooleanAttributeValue("isStatic");
}
int GetSequencePointsCount() {
XElement summary = this.element.Element("Summary");
if ( summary != null ) {
XAttribute nsp = summary.Attribute("numSequencePoints");
if ( nsp != null ) {
return (int)this.GetDecimalAttributeValue( nsp );
}
}
return 0;
}
Tuple<int,int> GetBranchRatio () {
// goal: Get branch ratio and exclude (rewriten) Code Contracts branches
// get all BranchPoints
var bPoints = this.element
.Elements("BranchPoints")
.Elements("BranchPoint");
if ( bPoints == null || bPoints.Count() == 0 ) {
return null;
}
// get all SequencePoints
var sPoints = this.element
.Elements("SequencePoints")
.Elements("SequencePoint");
// start & final method sequence points line and offset
int methodStartLine = 0;
int methodStartOffset = 0;
int methodFinalLine = 0;
int methodFinalOffset = 0;
// find start & final method sequence points and offsets
bool nextMatch = false;
int startLine = 0;
int finalLine = 0;
int startChar = 0;
int finalChar = 0;
// Find method body SequencePoint ({)
// and then first next sequencePoint (and offset of first instruction in body)
// This will skip Contract.Require SequencePoints
// and help to filter-out Contract.Require BranchPoint's
foreach (XElement sPoint in sPoints) {
startLine = (int)GetDecimalAttributeValue(sPoint.Attribute("sl"));
finalLine = (int)GetDecimalAttributeValue(sPoint.Attribute("el"));
startChar = (int)GetDecimalAttributeValue(sPoint.Attribute("sc"));
finalChar = (int)GetDecimalAttributeValue(sPoint.Attribute("ec"));
if ( nextMatch ) {
methodStartLine = startLine;
methodStartOffset = (int)GetDecimalAttributeValue(sPoint.Attribute("offset"));
break;
}
if ( startLine==finalLine && (finalChar-startChar)==1 ) {
nextMatch = true;
}
}
// find method body last SequencePoint and final offset (})
// This will skip Contract.Ensures SequencePoints
// and help to filter-out Contract.Ensures BranchPoint's
foreach (XElement sPoint in sPoints.Reverse()) {
startLine = (int)GetDecimalAttributeValue(sPoint.Attribute("sl"));
finalLine = (int)GetDecimalAttributeValue(sPoint.Attribute("el"));
startChar = (int)GetDecimalAttributeValue(sPoint.Attribute("sc"));
finalChar = (int)GetDecimalAttributeValue(sPoint.Attribute("ec"));
if ( startLine==finalLine && (finalChar-startChar)==1 ) {
methodFinalLine = finalLine;
methodFinalOffset = (int)GetDecimalAttributeValue(sPoint.Attribute("offset"));
break;
}
}
// Find compiler inserted code (=> inserted hidden branches)
// SequencePoints are ordered by offset and that order exposes reverse ordered lines
// ie: In code: foreach ( var item in items )
// Keyword "in" code is generated after loop with lower line number than previous line (reversed line order).
// That "in" code contains hidden branches that we
// do not want or cannot cover by test-case because is handled by earlier line
// ie: Possible NullReferenceException in foreach loop is pre-handled at method entry, ie. by Contract.Require(items!=null)
nextMatch = false;
int lastOffset = 0;
int lastLine = methodStartLine;
int currLine = 0;
List<Tuple<int,int>> excludeList = new List<Tuple<int, int>>();
foreach (XElement sPoint in sPoints) {
currLine = (int)GetDecimalAttributeValue(sPoint.Attribute("sl"));
if ( (currLine < methodStartLine) || currLine > methodFinalLine ) { continue ; }
if (nextMatch && (currLine > lastLine)) {
nextMatch = false;
excludeList.Add(new Tuple<int, int> ( lastOffset , (int)GetDecimalAttributeValue(sPoint.Attribute("offset"))));
}
// reversed line number?
if (!nextMatch && (currLine < lastLine)) {
nextMatch = true;
lastOffset = (int)GetDecimalAttributeValue(sPoint.Attribute("offset"));
}
lastLine = currLine;
}
int visited = 0;
int offset = 0;
// collect all branch offsets
Dictionary<int, branch> branches = new Dictionary<int, branch>();
foreach (XElement bPoint in bPoints) {
visited = (int)GetDecimalAttributeValue(bPoint.Attribute("vc"));
offset = (int)GetDecimalAttributeValue(bPoint.Attribute("offset"));
if ( offset > methodStartOffset && offset < methodFinalOffset ) {
// Apply exclude BranchPoint filter
nextMatch = true;
foreach (var range in excludeList) {
if (offset > range.Item1 && offset < range.Item2) {
nextMatch = false; break;
}
} if (!nextMatch) { continue; }
// Add/insert coverage data
if ( branches.ContainsKey( offset ) ) {
branch update = branches[offset];
update.Visit += visited!=0?1:0;
update.Count += 1;
} else {
// Insert first branch
branches[offset] = new branch{Visit=visited!=0?1:0,Count=1};
}
}
}
int totalVisit = 0;
int totalCount = 0;
// Branch percentage will display only if code SequencePoints coverage is 100%, so ...
// Do not add branch if branch is not visited at all because ... :
// If "branch" is completely unvisited and 100% SequencePoints covered,
// then that "branch" does not really exists in SOURCE code we try to cover
foreach ( branch item in branches.Values ) {
if ( item.Visit != 0 ) {
totalVisit += item.Visit;
totalCount += item.Count;
}
}
return (totalCount!=0) ? new Tuple<int,int>(totalVisit,totalCount) : null;
}
decimal GetBranchCoverage () {
return this.BranchCoverageRatio != null ? decimal.Round( ((decimal)(this.BranchCoverageRatio.Item1*100))/(decimal)this.BranchCoverageRatio.Item2, 2) : 0m;
}
decimal GetDecimalAttributeValue(string name)
{
return GetDecimalAttributeValue(element.Attribute(name));
}
decimal GetDecimalAttributeValue(XAttribute attribute)
{
if (attribute != null) {
decimal value = 0;
if (Decimal.TryParse(attribute.Value, out value)) {
return value;
}
}
return 0;
} }
bool GetBooleanAttributeValue(string name) bool GetBooleanAttributeValue(string name)

4
src/AddIns/Analysis/CodeCoverage/Project/Src/CodeCoverageMethodTreeNode.cs

@ -15,7 +15,9 @@ namespace ICSharpCode.CodeCoverage
: base(method.Name, : base(method.Name,
CodeCoverageImageListIndex.Method, CodeCoverageImageListIndex.Method,
method.GetVisitedCodeLength(), method.GetVisitedCodeLength(),
method.GetUnvisitedCodeLength()) method.GetUnvisitedCodeLength(),
method.BranchCoverage
)
{ {
this.method = method; this.method = method;
} }

10
src/AddIns/Analysis/CodeCoverage/Project/Src/CodeCoverageMethodsTreeNode.cs

@ -23,14 +23,24 @@ namespace ICSharpCode.CodeCoverage
int visitedCodeLength = 0; int visitedCodeLength = 0;
int unvisitedCodeLength = 0; int unvisitedCodeLength = 0;
decimal branchCoverage = 0;
int branchCoverageCount = 0;
foreach (CodeCoverageMethod method in methods) { foreach (CodeCoverageMethod method in methods) {
if (method.Name.Contains("__")) {
continue;
}
visitedCodeLength += method.GetVisitedCodeLength(); visitedCodeLength += method.GetVisitedCodeLength();
unvisitedCodeLength += method.GetUnvisitedCodeLength(); unvisitedCodeLength += method.GetUnvisitedCodeLength();
if ( method.IsVisited ) {
branchCoverageCount += 1;
branchCoverage += method.BranchCoverage == 0 ? 100 : method.BranchCoverage ;
}
} }
Name = name; Name = name;
VisitedCodeLength = visitedCodeLength; VisitedCodeLength = visitedCodeLength;
UnvisitedCodeLength = unvisitedCodeLength; UnvisitedCodeLength = unvisitedCodeLength;
VisitedBranchCoverage = branchCoverageCount == 0 ? 100 : decimal.Round(branchCoverage/branchCoverageCount,2);
} }
void AddDummyNodeIfHasNoMethods() void AddDummyNodeIfHasNoMethods()

15
src/AddIns/Analysis/CodeCoverage/Project/Src/CodeCoverageModule.cs

@ -46,6 +46,21 @@ namespace ICSharpCode.CodeCoverage
return total; return total;
} }
public decimal GetVisitedBranchCoverage() {
decimal total = 0;
int count = 0;
foreach (CodeCoverageMethod method in methods) {
if (method.IsVisited) {
++count;
total += method.BranchCoverage == 0 ? 100 : method.BranchCoverage ;
}
}
if (count!=0) {
return total/count;
}
return 0;
}
public List<CodeCoverageSequencePoint> GetSequencePoints(string fileName) public List<CodeCoverageSequencePoint> GetSequencePoints(string fileName)
{ {
List<CodeCoverageSequencePoint> sequencePoints = new List<CodeCoverageSequencePoint>(); List<CodeCoverageSequencePoint> sequencePoints = new List<CodeCoverageSequencePoint>();

32
src/AddIns/Analysis/CodeCoverage/Project/Src/CodeCoverageTreeNode.cs

@ -15,18 +15,24 @@ namespace ICSharpCode.CodeCoverage
/// Code coverage is less than one hundred percent. /// Code coverage is less than one hundred percent.
/// </summary> /// </summary>
public static readonly Color PartialCoverageTextColor = Color.Red; public static readonly Color PartialCoverageTextColor = Color.Red;
/// <summary>
/// Code coverage is 100% but branch coverage is not 0%(no branches present) or 100%(all branches covered)
/// </summary>
public static readonly Color PartialBranchesTextColor = Color.DarkGreen;
/// <summary> /// <summary>
/// Code coverage is zero. /// Code coverage is zero.
/// </summary> /// </summary>
public static readonly Color ZeroCoverageTextColor = Color.Gray; public static readonly Color ZeroCoverageTextColor = Color.Gray;
int visitedCodeLength; int visitedCodeLength;
int unvisitedCodeLength; int unvisitedCodeLength;
decimal visitedBranchCoverage;
int baseImageIndex; int baseImageIndex;
public CodeCoverageTreeNode(string name, CodeCoverageImageListIndex index) public CodeCoverageTreeNode(string name, CodeCoverageImageListIndex index)
: this(name, index, 0, 0) : this(name, index, 0, 0, 0)
{ {
} }
@ -34,15 +40,18 @@ namespace ICSharpCode.CodeCoverage
: this(codeCoverageWithVisits.Name, : this(codeCoverageWithVisits.Name,
index, index,
codeCoverageWithVisits.GetVisitedCodeLength(), codeCoverageWithVisits.GetVisitedCodeLength(),
codeCoverageWithVisits.GetUnvisitedCodeLength()) codeCoverageWithVisits.GetUnvisitedCodeLength(),
codeCoverageWithVisits.GetVisitedBranchCoverage()
)
{ {
} }
public CodeCoverageTreeNode(string name, CodeCoverageImageListIndex index, int visitedCodeLength, int unvisitedCodeLength) public CodeCoverageTreeNode(string name, CodeCoverageImageListIndex index, int visitedCodeLength, int unvisitedCodeLength, decimal visitedBranchCoverage)
{ {
sortOrder = 10; sortOrder = 10;
this.visitedCodeLength = visitedCodeLength; this.visitedCodeLength = visitedCodeLength;
this.unvisitedCodeLength = unvisitedCodeLength; this.unvisitedCodeLength = unvisitedCodeLength;
this.visitedBranchCoverage = visitedBranchCoverage;
Name = name; Name = name;
SetText(); SetText();
@ -63,6 +72,8 @@ namespace ICSharpCode.CodeCoverage
ForeColor = ZeroCoverageTextColor; ForeColor = ZeroCoverageTextColor;
} else if(TotalCodeLength != visitedCodeLength) { } else if(TotalCodeLength != visitedCodeLength) {
ForeColor = PartialCoverageTextColor; ForeColor = PartialCoverageTextColor;
} else if(TotalCodeLength == visitedCodeLength && VisitedBranchCoverage != 0 && VisitedBranchCoverage != 100 ) {
ForeColor = PartialBranchesTextColor;
} else { } else {
ForeColor = Color.Empty; ForeColor = Color.Empty;
} }
@ -76,6 +87,9 @@ namespace ICSharpCode.CodeCoverage
string GetNodeText() string GetNodeText()
{ {
if (TotalCodeLength > 0) { if (TotalCodeLength > 0) {
if ( visitedCodeLength == TotalCodeLength && visitedBranchCoverage != 0 && visitedBranchCoverage != 100 ) {
return String.Format("{0} (100%/{1}%)", Name, visitedBranchCoverage);
}
int percentage = GetPercentage(); int percentage = GetPercentage();
return String.Format("{0} ({1}%)", Name, percentage); return String.Format("{0} ({1}%)", Name, percentage);
} }
@ -118,6 +132,14 @@ namespace ICSharpCode.CodeCoverage
get { return visitedCodeLength + unvisitedCodeLength; } get { return visitedCodeLength + unvisitedCodeLength; }
} }
public decimal VisitedBranchCoverage {
get { return visitedBranchCoverage; }
set {
visitedBranchCoverage = value;
SetText();
}
}
/// <summary> /// <summary>
/// Gets the string to use when sorting the code coverage tree node. /// Gets the string to use when sorting the code coverage tree node.
/// </summary> /// </summary>

1
src/AddIns/Analysis/CodeCoverage/Project/Src/ICodeCoverageWithVisits.cs

@ -10,5 +10,6 @@ namespace ICSharpCode.CodeCoverage
string Name { get; } string Name { get; }
int GetVisitedCodeLength(); int GetVisitedCodeLength();
int GetUnvisitedCodeLength(); int GetUnvisitedCodeLength();
decimal GetVisitedBranchCoverage();
} }
} }

Loading…
Cancel
Save