Dragan 12 years ago
parent
commit
0d3d409975
  1. 816
      src/AddIns/Analysis/CodeCoverage/Project/Src/CodeCoverageMethodElement.cs

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

@ -27,412 +27,412 @@ using System.Xml.Linq; @@ -27,412 +27,412 @@ using System.Xml.Linq;
namespace ICSharpCode.CodeCoverage
{
public class CodeCoverageMethodElement
{
XElement element;
CodeCoverageResults parent;
public CodeCoverageMethodElement(XElement element, CodeCoverageResults parent)
{
this.parent = parent;
this.element = element;
this.SequencePoints = new List<CodeCoverageSequencePoint>();
this.BranchPoints = new List<CodeCoverageBranchPoint>();
Init();
}
private static string cacheFileName = String.Empty;
private static CodeCoverageStringTextSource cacheDocument = null;
public string FileID { get; private set; }
public string FileName { get; private set; }
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 List<CodeCoverageSequencePoint> SequencePoints { get; private set; }
public CodeCoverageSequencePoint BodyStartSP { get; private set; }
public CodeCoverageSequencePoint BodyFinalSP { get; private set; }
public List<CodeCoverageBranchPoint> BranchPoints { get; private set; }
public bool IsGetter { get; private set; }
public bool IsSetter { get; private set; }
public string MethodName { get; private set; }
public bool IsProperty {
get { return IsGetter || IsSetter; }
}
void Init()
{
MethodName = GetMethodName();
IsGetter = GetBooleanAttributeValue("isGetter");
IsSetter = GetBooleanAttributeValue("isSetter");
this.FileID = GetFileRef();
this.FileName = String.Empty;
if (!String.IsNullOrEmpty(this.FileID)) {
this.FileName = parent.GetFileName(this.FileID);
if ( File.Exists(this.FileName) ) {
if (cacheFileName != this.FileName) {
cacheFileName = this.FileName;
cacheDocument = null;
try {
using (Stream stream = new FileStream(this.FileName, FileMode.Open, FileAccess.Read)) {
try {
stream.Position = 0;
string textSource = ICSharpCode.AvalonEdit.Utils.FileReader.ReadFileContent(stream, Encoding.Default);
cacheDocument = new CodeCoverageStringTextSource(textSource);
} catch {}
}
} catch {}
}
}
}
this.IsVisited = this.GetBooleanAttributeValue("visited");
this.CyclomaticComplexity = (int)this.GetDecimalAttributeValue("cyclomaticComplexity");
this.SequencePointsCount = this.GetSequencePointsCount();
this.SequenceCoverage = (int)this.GetDecimalAttributeValue("sequenceCoverage");
this.IsConstructor = this.GetBooleanAttributeValue("isConstructor");
this.IsStatic = this.GetBooleanAttributeValue("isStatic");
if ( !String.IsNullOrEmpty( this.FileID ) ) {
this.SequencePoints = this.GetSequencePoints();
// SP's are originaly ordered by CIL offset
// but ccrewrite can move offset of
// Contract.Requires before method signature SP { and
// Contract.Ensures after method closing SP }
// So sort SP's back by line/column
this.SequencePoints.OrderBy(item => item.Line).OrderBy(item => item.Column);
this.BodyStartSP = getBodyStartSP(this.SequencePoints);
this.BodyFinalSP = getBodyFinalSP(this.SequencePoints);
this.SequencePoints = this.FilterSequencePoints(this.SequencePoints);
this.BranchPoints = this.GetBranchPoints();
this.BranchCoverageRatio = this.GetBranchRatio();
this.BranchCoverage = this.GetBranchCoverage();
}
}
List<CodeCoverageSequencePoint> GetSequencePoints() {
List<CodeCoverageSequencePoint> sps = new List<CodeCoverageSequencePoint>();
var xSPoints = this.element
.Elements("SequencePoints")
.Elements("SequencePoint");
foreach (XElement xSPoint in xSPoints) {
CodeCoverageSequencePoint sp = new CodeCoverageSequencePoint();
sp.FileID = this.FileID;
sp.Document = this.FileName;
sp.Line = (int)GetDecimalAttributeValue(xSPoint.Attribute("sl"));
sp.EndLine = (int)GetDecimalAttributeValue(xSPoint.Attribute("el"));
sp.Column = (int)GetDecimalAttributeValue(xSPoint.Attribute("sc"));
sp.EndColumn = (int)GetDecimalAttributeValue(xSPoint.Attribute("ec"));
sp.VisitCount = (int)GetDecimalAttributeValue(xSPoint.Attribute("vc"));
if (cacheFileName == sp.Document && cacheDocument != null) {
sp.Content = cacheDocument.GetText(sp);
if (sp.Line != sp.EndLine) {
sp.Content = Regex.Replace (sp.Content, @"\s+", " ");
}
sp.Length = Regex.Replace (sp.Content, @"\s", "").Length; // ignore white-space for coverage%
} else {
sp.Content = String.Empty;
sp.Length = 0;
}
sp.Offset = (int)GetDecimalAttributeValue(xSPoint.Attribute("offset"));
sp.BranchCoverage = true;
sps.Add(sp);
}
return sps;
}
// Find method-body start SequencePoint "{"
// Sequence points are ordered by Line/Column
// Cannot just get first one because of ccrewrite&ContractClassFor
// For same reason must abandon constructor method signature SP
public static CodeCoverageSequencePoint getBodyStartSP(IEnumerable<CodeCoverageSequencePoint> sPoints) {
CodeCoverageSequencePoint startSeqPoint = null;
foreach (CodeCoverageSequencePoint sPoint in sPoints) {
if ( sPoint.Content == "{") {
startSeqPoint = sPoint;
break;
}
}
return startSeqPoint;
}
// Find method-body final SequencePoint "}"
// Sequence points are ordered by Line/Column
public static CodeCoverageSequencePoint getBodyFinalSP(IEnumerable<CodeCoverageSequencePoint> sps) {
CodeCoverageSequencePoint finalSeqPoint = null;
foreach (CodeCoverageSequencePoint sp in Enumerable.Reverse(sps)) {
if ( sp.Content == "}") {
if (finalSeqPoint == null) {
finalSeqPoint = sp;
}
// check for ccrewrite duplicate
else if (sp.Line == finalSeqPoint.Line &&
sp.Column == finalSeqPoint.Column &&
sp.EndLine == finalSeqPoint.EndLine &&
sp.EndColumn == finalSeqPoint.EndColumn &&
sp.Offset < finalSeqPoint.Offset) {
finalSeqPoint = sp;
}
else if (sp.Line < finalSeqPoint.Line) {
break;
}
}
}
return finalSeqPoint;
}
List<CodeCoverageSequencePoint> FilterSequencePoints(List<CodeCoverageSequencePoint> sps) {
List<CodeCoverageSequencePoint> returnList = sps;
if (sps.Count > 2 &&
this.BodyStartSP != null &&
this.BodyFinalSP != null ) {
// After ccrewrite ContractClass/ContractClassFor
// sequence point(s) from another file/class/method
// is inserted into this method sequence points
//
// To remove alien sequence points, all sequence points on lines
// before method signature and after end-brackets xxx{} are removed
// If ContractClassFor is in another file but interleaves this method lines
// then, afaik, not much can be done to remove inserted alien SP's
List<CodeCoverageSequencePoint> selected = new List<CodeCoverageSequencePoint>();
foreach (var point in sps) {
if (
(point.Line > BodyStartSP.Line || (point.Line == BodyStartSP.Line && point.Column >= BodyStartSP.Column)) &&
(point.Line < BodyFinalSP.Line || (point.Line == BodyFinalSP.Line && point.Column < BodyFinalSP.Column))
) {
selected.Add (point);
}
// After ccrewrite ContractClass/ContractClassFor
// duplicate method end-sequence-point (}) is added
//
// Add first finalSP (can be a duplicate)
// Note: IL.Offset of second duplicate finalSP will
// extend branch coverage outside method-end "}",
// and that can lead to wrong branch coverage display!
if (object.ReferenceEquals (point, this.BodyFinalSP)) {
selected.Add (point);
}
}
returnList = selected;
}
return returnList;
}
int GetSequencePointsCount() {
XElement summary = this.element.Element("Summary");
if ( summary != null ) {
XAttribute nsp = summary.Attribute("numSequencePoints");
if ( nsp != null ) {
return (int)GetDecimalAttributeValue( nsp );
}
}
return 0;
}
List<CodeCoverageBranchPoint> GetBranchPoints() {
// get all BranchPoints
List<CodeCoverageBranchPoint> bps = new List<CodeCoverageBranchPoint>();
var xBPoints = this.element
.Elements("BranchPoints")
.Elements("BranchPoint");
foreach (XElement xBPoint in xBPoints) {
CodeCoverageBranchPoint bp = new CodeCoverageBranchPoint();
bp.VisitCount = (int)GetDecimalAttributeValue(xBPoint.Attribute("vc"));
bp.Offset = (int)GetDecimalAttributeValue(xBPoint.Attribute("offset"));
bp.Path = (int)GetDecimalAttributeValue(xBPoint.Attribute("path"));
bp.OffsetEnd = (int)GetDecimalAttributeValue(xBPoint.Attribute("offsetend"));
bps.Add(bp);
}
return bps;
}
Tuple<int,int> GetBranchRatio () {
// goal: Get branch ratio and exclude (rewriten) Code Contracts branches
if ( this.BranchPoints == null
|| this.BranchPoints.Count() == 0
|| this.SequencePoints == null
|| this.SequencePoints.Count == 0
)
{
return null;
}
// This sequence point offset is used to skip CCRewrite(n) BranchPoint's (Requires)
// and '{' branches at static methods
if (this.BodyStartSP == null) { return null; } // empty body
// This sequence point offset is used to skip CCRewrite(n) BranchPoint's (Ensures)
if (this.BodyFinalSP == null) { return null; } // empty body
// Connect Sequence & Branches
IEnumerator<CodeCoverageSequencePoint> SPEnumerator = this.SequencePoints.GetEnumerator();
CodeCoverageSequencePoint currSeqPoint = BodyStartSP;
int nextSeqPointOffset = BodyStartSP.Offset;
foreach (var bp in this.BranchPoints) {
// ignore branches outside of method body offset range
if (bp.Offset < BodyStartSP.Offset)
continue;
if (bp.Offset > BodyFinalSP.Offset)
break;
// Sync with SequencePoint
while ( nextSeqPointOffset < bp.Offset ) {
currSeqPoint = SPEnumerator.Current;
if ( SPEnumerator.MoveNext() ) {
nextSeqPointOffset = SPEnumerator.Current.Offset;
} else {
nextSeqPointOffset = int.MaxValue;
}
}
if (currSeqPoint.Branches == null) {
currSeqPoint.Branches = new List<CodeCoverageBranchPoint>();
}
// Add Branch to Branches
currSeqPoint.Branches.Add(bp);
}
// Merge sp.Branches on exit-offset
// Calculate Method Branch coverage
int totalBranchVisit = 0;
int totalBranchCount = 0;
int pointBranchVisit = 0;
int pointBranchCount = 0;
Dictionary<int, CodeCoverageBranchPoint> bpExits = new Dictionary<int, CodeCoverageBranchPoint>();
foreach (var sp in this.SequencePoints) {
// SequencePoint covered & has branches?
if (sp.VisitCount != 0 && sp.Branches != null) {
// 1) Generated "in" code for IEnumerables contains hidden "try/catch/finally" branches that
// one do not want or cannot cover by test-case because is handled earlier at same method.
// ie: NullReferenceException in foreach loop is pre-handled at method entry, ie. by Contract.Require(items!=null)
// 2) Branches within sequence points "{" and "}" are not source branches but compiler generated branches
// ie: static methods start sequence point "{" contains compiler generated branches
// 3) Exclude Contract class (EnsuresOnThrow/Assert/Assume is inside method body)
// 4) Exclude NUnit Assert(.Throws) class
if (sp.Content == "in" || sp.Content == "{" || sp.Content == "}" ||
sp.Content.StartsWith("Assert.") ||
sp.Content.StartsWith("Assert ") ||
sp.Content.StartsWith("Contract.") ||
sp.Content.StartsWith("Contract ")
) {
sp.Branches = null;
continue; // skip
}
// Merge sp.Branches on OffsetEnd using bpExits key
bpExits.Clear();
foreach (var bp in sp.Branches) {
if (!bpExits.ContainsKey(bp.OffsetEnd)) {
bpExits[bp.OffsetEnd] = bp; // insert branch
} else {
bpExits[bp.OffsetEnd].VisitCount += bp.VisitCount; // update branch
}
}
// Compute branch coverage
pointBranchVisit = 0;
pointBranchCount = 0;
foreach (var bp in bpExits.Values) {
pointBranchVisit += bp.VisitCount == 0? 0 : 1 ;
pointBranchCount += 1;
}
// Not full coverage?
if (pointBranchVisit != pointBranchCount) {
sp.BranchCoverage = false; // => part-covered
}
totalBranchVisit += pointBranchVisit;
totalBranchCount += pointBranchCount;
}
if (sp.Branches != null)
sp.Branches = null; // release memory
}
return (totalBranchCount!=0) ? new Tuple<int,int>(totalBranchVisit,totalBranchCount) : null;
}
decimal GetBranchCoverage () {
return this.BranchCoverageRatio == null ? 0m : ((decimal)(this.BranchCoverageRatio.Item1*100))/((decimal)this.BranchCoverageRatio.Item2);
}
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)
{
return GetBooleanAttributeValue(element.Attribute(name));
}
bool GetBooleanAttributeValue(XAttribute attribute)
{
if (attribute != null) {
bool value = false;
if (Boolean.TryParse(attribute.Value, out value)) {
return value;
}
}
return false;
}
string GetFileRef() {
XElement fileId = element.Element("FileRef");
if (fileId != null) {
return fileId.Attribute("uid").Value;
}
return String.Empty;
}
string GetMethodName()
{
XElement nameElement = element.Element("Name");
if (nameElement != null) {
return GetMethodName(nameElement.Value);
}
return String.Empty;
}
string GetMethodName(string methodSignature)
{
int startIndex = methodSignature.IndexOf("::");
int endIndex = methodSignature.IndexOf('(', startIndex);
return methodSignature
.Substring(startIndex, endIndex - startIndex)
.Substring(2);
}
}
public class CodeCoverageMethodElement
{
XElement element;
CodeCoverageResults parent;
public CodeCoverageMethodElement(XElement element, CodeCoverageResults parent)
{
this.parent = parent;
this.element = element;
this.SequencePoints = new List<CodeCoverageSequencePoint>();
this.BranchPoints = new List<CodeCoverageBranchPoint>();
Init();
}
private static string cacheFileName = String.Empty;
private static CodeCoverageStringTextSource cacheDocument = null;
public string FileID { get; private set; }
public string FileName { get; private set; }
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 List<CodeCoverageSequencePoint> SequencePoints { get; private set; }
public CodeCoverageSequencePoint BodyStartSP { get; private set; }
public CodeCoverageSequencePoint BodyFinalSP { get; private set; }
public List<CodeCoverageBranchPoint> BranchPoints { get; private set; }
public bool IsGetter { get; private set; }
public bool IsSetter { get; private set; }
public string MethodName { get; private set; }
public bool IsProperty {
get { return IsGetter || IsSetter; }
}
void Init()
{
MethodName = GetMethodName();
IsGetter = GetBooleanAttributeValue("isGetter");
IsSetter = GetBooleanAttributeValue("isSetter");
this.FileID = GetFileRef();
this.FileName = String.Empty;
if (!String.IsNullOrEmpty(this.FileID)) {
this.FileName = parent.GetFileName(this.FileID);
if ( File.Exists(this.FileName) ) {
if (cacheFileName != this.FileName) {
cacheFileName = this.FileName;
cacheDocument = null;
try {
using (Stream stream = new FileStream(this.FileName, FileMode.Open, FileAccess.Read)) {
try {
stream.Position = 0;
string textSource = ICSharpCode.AvalonEdit.Utils.FileReader.ReadFileContent(stream, Encoding.Default);
cacheDocument = new CodeCoverageStringTextSource(textSource);
} catch {}
}
} catch {}
}
}
}
this.IsVisited = this.GetBooleanAttributeValue("visited");
this.CyclomaticComplexity = (int)this.GetDecimalAttributeValue("cyclomaticComplexity");
this.SequencePointsCount = this.GetSequencePointsCount();
this.SequenceCoverage = (int)this.GetDecimalAttributeValue("sequenceCoverage");
this.IsConstructor = this.GetBooleanAttributeValue("isConstructor");
this.IsStatic = this.GetBooleanAttributeValue("isStatic");
if ( !String.IsNullOrEmpty( this.FileID ) ) {
this.SequencePoints = this.GetSequencePoints();
// SP's are originaly ordered by CIL offset
// but ccrewrite can move offset of
// Contract.Requires before method signature SP { and
// Contract.Ensures after method closing SP }
// So sort SP's back by line/column
this.SequencePoints.OrderBy(item => item.Line).OrderBy(item => item.Column);
this.BodyStartSP = getBodyStartSP(this.SequencePoints);
this.BodyFinalSP = getBodyFinalSP(this.SequencePoints);
this.SequencePoints = this.FilterSequencePoints(this.SequencePoints);
this.BranchPoints = this.GetBranchPoints();
this.BranchCoverageRatio = this.GetBranchRatio();
this.BranchCoverage = this.GetBranchCoverage();
}
}
List<CodeCoverageSequencePoint> GetSequencePoints() {
List<CodeCoverageSequencePoint> sps = new List<CodeCoverageSequencePoint>();
var xSPoints = this.element
.Elements("SequencePoints")
.Elements("SequencePoint");
foreach (XElement xSPoint in xSPoints) {
CodeCoverageSequencePoint sp = new CodeCoverageSequencePoint();
sp.FileID = this.FileID;
sp.Document = this.FileName;
sp.Line = (int)GetDecimalAttributeValue(xSPoint.Attribute("sl"));
sp.EndLine = (int)GetDecimalAttributeValue(xSPoint.Attribute("el"));
sp.Column = (int)GetDecimalAttributeValue(xSPoint.Attribute("sc"));
sp.EndColumn = (int)GetDecimalAttributeValue(xSPoint.Attribute("ec"));
sp.VisitCount = (int)GetDecimalAttributeValue(xSPoint.Attribute("vc"));
if (cacheFileName == sp.Document && cacheDocument != null) {
sp.Content = cacheDocument.GetText(sp);
if (sp.Line != sp.EndLine) {
sp.Content = Regex.Replace (sp.Content, @"\s+", " ");
}
sp.Length = Regex.Replace (sp.Content, @"\s", "").Length; // ignore white-space for coverage%
} else {
sp.Content = String.Empty;
sp.Length = 0;
}
sp.Offset = (int)GetDecimalAttributeValue(xSPoint.Attribute("offset"));
sp.BranchCoverage = true;
sps.Add(sp);
}
return sps;
}
// Find method-body start SequencePoint "{"
// Sequence points are ordered by Line/Column
// Cannot just get first one because of ccrewrite&ContractClassFor
// For same reason must abandon constructor method signature SP
public static CodeCoverageSequencePoint getBodyStartSP(IEnumerable<CodeCoverageSequencePoint> sPoints) {
CodeCoverageSequencePoint startSeqPoint = null;
foreach (CodeCoverageSequencePoint sPoint in sPoints) {
if ( sPoint.Content == "{") {
startSeqPoint = sPoint;
break;
}
}
return startSeqPoint;
}
// Find method-body final SequencePoint "}"
// Sequence points are ordered by Line/Column
public static CodeCoverageSequencePoint getBodyFinalSP(IEnumerable<CodeCoverageSequencePoint> sps) {
CodeCoverageSequencePoint finalSeqPoint = null;
foreach (CodeCoverageSequencePoint sp in Enumerable.Reverse(sps)) {
if ( sp.Content == "}") {
if (finalSeqPoint == null) {
finalSeqPoint = sp;
}
// check for ccrewrite duplicate
else if (sp.Line == finalSeqPoint.Line &&
sp.Column == finalSeqPoint.Column &&
sp.EndLine == finalSeqPoint.EndLine &&
sp.EndColumn == finalSeqPoint.EndColumn &&
sp.Offset < finalSeqPoint.Offset) {
finalSeqPoint = sp;
}
else if (sp.Line < finalSeqPoint.Line) {
break;
}
}
}
return finalSeqPoint;
}
List<CodeCoverageSequencePoint> FilterSequencePoints(List<CodeCoverageSequencePoint> sps) {
List<CodeCoverageSequencePoint> returnList = sps;
if (sps.Count > 2 &&
this.BodyStartSP != null &&
this.BodyFinalSP != null ) {
// After ccrewrite ContractClass/ContractClassFor
// sequence point(s) from another file/class/method
// is inserted into this method sequence points
//
// To remove alien sequence points, all sequence points on lines
// before method signature and after end-brackets xxx{} are removed
// If ContractClassFor is in another file but interleaves this method lines
// then, afaik, not much can be done to remove inserted alien SP's
List<CodeCoverageSequencePoint> selected = new List<CodeCoverageSequencePoint>();
foreach (var point in sps) {
if (
(point.Line > BodyStartSP.Line || (point.Line == BodyStartSP.Line && point.Column >= BodyStartSP.Column)) &&
(point.Line < BodyFinalSP.Line || (point.Line == BodyFinalSP.Line && point.Column < BodyFinalSP.Column))
) {
selected.Add (point);
}
// After ccrewrite ContractClass/ContractClassFor
// duplicate method end-sequence-point (}) is added
//
// Add first finalSP (can be a duplicate)
// Note: IL.Offset of second duplicate finalSP will
// extend branch coverage outside method-end "}",
// and that can lead to wrong branch coverage display!
if (object.ReferenceEquals (point, this.BodyFinalSP)) {
selected.Add (point);
}
}
returnList = selected;
}
return returnList;
}
int GetSequencePointsCount() {
XElement summary = this.element.Element("Summary");
if ( summary != null ) {
XAttribute nsp = summary.Attribute("numSequencePoints");
if ( nsp != null ) {
return (int)GetDecimalAttributeValue( nsp );
}
}
return 0;
}
List<CodeCoverageBranchPoint> GetBranchPoints() {
// get all BranchPoints
List<CodeCoverageBranchPoint> bps = new List<CodeCoverageBranchPoint>();
var xBPoints = this.element
.Elements("BranchPoints")
.Elements("BranchPoint");
foreach (XElement xBPoint in xBPoints) {
CodeCoverageBranchPoint bp = new CodeCoverageBranchPoint();
bp.VisitCount = (int)GetDecimalAttributeValue(xBPoint.Attribute("vc"));
bp.Offset = (int)GetDecimalAttributeValue(xBPoint.Attribute("offset"));
bp.Path = (int)GetDecimalAttributeValue(xBPoint.Attribute("path"));
bp.OffsetEnd = (int)GetDecimalAttributeValue(xBPoint.Attribute("offsetend"));
bps.Add(bp);
}
return bps;
}
Tuple<int,int> GetBranchRatio () {
// goal: Get branch ratio and exclude (rewriten) Code Contracts branches
if ( this.BranchPoints == null
|| this.BranchPoints.Count() == 0
|| this.SequencePoints == null
|| this.SequencePoints.Count == 0
)
{
return null;
}
// This sequence point offset is used to skip CCRewrite(n) BranchPoint's (Requires)
// and '{' branches at static methods
if (this.BodyStartSP == null) { return null; } // empty body
// This sequence point offset is used to skip CCRewrite(n) BranchPoint's (Ensures)
if (this.BodyFinalSP == null) { return null; } // empty body
// Connect Sequence & Branches
IEnumerator<CodeCoverageSequencePoint> SPEnumerator = this.SequencePoints.GetEnumerator();
CodeCoverageSequencePoint currSeqPoint = BodyStartSP;
int nextSeqPointOffset = BodyStartSP.Offset;
foreach (var bp in this.BranchPoints) {
// ignore branches outside of method body offset range
if (bp.Offset < BodyStartSP.Offset)
continue;
if (bp.Offset > BodyFinalSP.Offset)
break;
// Sync with SequencePoint
while ( nextSeqPointOffset < bp.Offset ) {
currSeqPoint = SPEnumerator.Current;
if ( SPEnumerator.MoveNext() ) {
nextSeqPointOffset = SPEnumerator.Current.Offset;
} else {
nextSeqPointOffset = int.MaxValue;
}
}
if (currSeqPoint.Branches == null) {
currSeqPoint.Branches = new List<CodeCoverageBranchPoint>();
}
// Add Branch to Branches
currSeqPoint.Branches.Add(bp);
}
// Merge sp.Branches on exit-offset
// Calculate Method Branch coverage
int totalBranchVisit = 0;
int totalBranchCount = 0;
int pointBranchVisit = 0;
int pointBranchCount = 0;
Dictionary<int, CodeCoverageBranchPoint> bpExits = new Dictionary<int, CodeCoverageBranchPoint>();
foreach (var sp in this.SequencePoints) {
// SequencePoint covered & has branches?
if (sp.VisitCount != 0 && sp.Branches != null) {
// 1) Generated "in" code for IEnumerables contains hidden "try/catch/finally" branches that
// one do not want or cannot cover by test-case because is handled earlier at same method.
// ie: NullReferenceException in foreach loop is pre-handled at method entry, ie. by Contract.Require(items!=null)
// 2) Branches within sequence points "{" and "}" are not source branches but compiler generated branches
// ie: static methods start sequence point "{" contains compiler generated branches
// 3) Exclude Contract class (EnsuresOnThrow/Assert/Assume is inside method body)
// 4) Exclude NUnit Assert(.Throws) class
if (sp.Content == "in" || sp.Content == "{" || sp.Content == "}" ||
sp.Content.StartsWith("Assert.") ||
sp.Content.StartsWith("Assert ") ||
sp.Content.StartsWith("Contract.") ||
sp.Content.StartsWith("Contract ")
) {
sp.Branches = null;
continue; // skip
}
// Merge sp.Branches on OffsetEnd using bpExits key
bpExits.Clear();
foreach (var bp in sp.Branches) {
if (!bpExits.ContainsKey(bp.OffsetEnd)) {
bpExits[bp.OffsetEnd] = bp; // insert branch
} else {
bpExits[bp.OffsetEnd].VisitCount += bp.VisitCount; // update branch
}
}
// Compute branch coverage
pointBranchVisit = 0;
pointBranchCount = 0;
foreach (var bp in bpExits.Values) {
pointBranchVisit += bp.VisitCount == 0? 0 : 1 ;
pointBranchCount += 1;
}
// Not full coverage?
if (pointBranchVisit != pointBranchCount) {
sp.BranchCoverage = false; // => part-covered
}
totalBranchVisit += pointBranchVisit;
totalBranchCount += pointBranchCount;
}
if (sp.Branches != null)
sp.Branches = null; // release memory
}
return (totalBranchCount!=0) ? new Tuple<int,int>(totalBranchVisit,totalBranchCount) : null;
}
decimal GetBranchCoverage () {
return this.BranchCoverageRatio == null ? 0m : ((decimal)(this.BranchCoverageRatio.Item1*100))/((decimal)this.BranchCoverageRatio.Item2);
}
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)
{
return GetBooleanAttributeValue(element.Attribute(name));
}
bool GetBooleanAttributeValue(XAttribute attribute)
{
if (attribute != null) {
bool value = false;
if (Boolean.TryParse(attribute.Value, out value)) {
return value;
}
}
return false;
}
string GetFileRef() {
XElement fileId = element.Element("FileRef");
if (fileId != null) {
return fileId.Attribute("uid").Value;
}
return String.Empty;
}
string GetMethodName()
{
XElement nameElement = element.Element("Name");
if (nameElement != null) {
return GetMethodName(nameElement.Value);
}
return String.Empty;
}
string GetMethodName(string methodSignature)
{
int startIndex = methodSignature.IndexOf("::");
int endIndex = methodSignature.IndexOf('(', startIndex);
return methodSignature
.Substring(startIndex, endIndex - startIndex)
.Substring(2);
}
}
}

Loading…
Cancel
Save