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 @@ -51,8 +51,10 @@ namespace ICSharpCode.CodeCoverage
// Add methods.
CodeCoveragePropertyCollection properties = new CodeCoveragePropertyCollection();
foreach (CodeCoverageMethod method in Methods) {
//if ( !method.Name.Contains("<") && !method.Name.Contains("__") ) {
if ( !method.Name.Contains("__") ) {
// method name that is generated by compiler, contains "__" (double underscore)
// 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) {
properties.Add(method);
} else {

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

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

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

@ -2,6 +2,9 @@ @@ -2,6 +2,9 @@
// This code is distributed under the GNU LGPL (for details please see \doc\license.txt)
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Xml.Linq;
namespace ICSharpCode.CodeCoverage
@ -9,13 +12,27 @@ namespace ICSharpCode.CodeCoverage @@ -9,13 +12,27 @@ namespace ICSharpCode.CodeCoverage
public class CodeCoverageMethodElement
{
XElement element;
private class branch {
public int Visit;
public int Count;
}
public CodeCoverageMethodElement(XElement element)
{
this.element = element;
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 IsSetter { get; private set; }
public string MethodName { get; private set; }
@ -26,9 +43,193 @@ namespace ICSharpCode.CodeCoverage @@ -26,9 +43,193 @@ namespace ICSharpCode.CodeCoverage
void Init()
{
MethodName = GetMethodName();
IsGetter = GetBooleanAttributeValue("isGetter");
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)

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

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

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

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

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

@ -46,6 +46,21 @@ namespace ICSharpCode.CodeCoverage @@ -46,6 +46,21 @@ namespace ICSharpCode.CodeCoverage
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)
{
List<CodeCoverageSequencePoint> sequencePoints = new List<CodeCoverageSequencePoint>();

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

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

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

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

Loading…
Cancel
Save