Browse Source

Merge pull request #335 from ddur/master

CodeCoverage again
pull/341/head
Matt Ward 12 years ago
parent
commit
5752cd368e
  1. 197
      src/AddIns/Analysis/CodeCoverage/Project/Src/CodeCoverageMethodElement.cs
  2. 28
      src/AddIns/Analysis/CodeCoverage/Project/Src/CodeCoverageStringTextSource.cs

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

@ -1,14 +1,14 @@
// Copyright (c) 2014 AlphaSierraPapa for the SharpDevelop Team // Copyright (c) 2014 AlphaSierraPapa for the SharpDevelop Team
// //
// Permission is hereby granted, free of charge, to any person obtaining a copy of this // Permission is hereby granted, free of charge, to any person obtaining a copy of this
// software and associated documentation files (the "Software"), to deal in the Software // software and associated documentation files (the "Software"), to deal in the Software
// without restriction, including without limitation the rights to use, copy, modify, merge, // without restriction, including without limitation the rights to use, copy, modify, merge,
// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons // publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
// to whom the Software is furnished to do so, subject to the following conditions: // to whom the Software is furnished to do so, subject to the following conditions:
// //
// The above copyright notice and this permission notice shall be included in all copies or // The above copyright notice and this permission notice shall be included in all copies or
// substantial portions of the Software. // substantial portions of the Software.
// //
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR // INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE // PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
@ -32,8 +32,8 @@ namespace ICSharpCode.CodeCoverage
XElement element; XElement element;
CodeCoverageResults parent; CodeCoverageResults parent;
public CodeCoverageMethodElement(XElement element) public CodeCoverageMethodElement(XElement element)
: this (element, null) {} : this (element, null) {}
public CodeCoverageMethodElement(XElement element, CodeCoverageResults parent) public CodeCoverageMethodElement(XElement element, CodeCoverageResults parent)
{ {
this.parent = parent; this.parent = parent;
@ -67,7 +67,7 @@ namespace ICSharpCode.CodeCoverage
public bool IsProperty { public bool IsProperty {
get { return IsGetter || IsSetter; } get { return IsGetter || IsSetter; }
} }
void Init() void Init()
{ {
MethodName = GetMethodName(); MethodName = GetMethodName();
@ -77,24 +77,24 @@ namespace ICSharpCode.CodeCoverage
this.FileID = GetFileRef(); this.FileID = GetFileRef();
this.FileName = String.Empty; this.FileName = String.Empty;
if (!String.IsNullOrEmpty(this.FileID)) { if (!String.IsNullOrEmpty(this.FileID)) {
if (parent != null) { if (parent != null) {
this.FileName = parent.GetFileName(this.FileID); this.FileName = parent.GetFileName(this.FileID);
if ( File.Exists(this.FileName) ) { if ( File.Exists(this.FileName) ) {
if (cacheFileName != this.FileName) { if (cacheFileName != this.FileName) {
cacheFileName = this.FileName; cacheFileName = this.FileName;
cacheDocument = null; cacheDocument = null;
try { try {
using (Stream stream = new FileStream(this.FileName, FileMode.Open, FileAccess.Read)) { using (Stream stream = new FileStream(this.FileName, FileMode.Open, FileAccess.Read)) {
try { try {
stream.Position = 0; stream.Position = 0;
string textSource = ICSharpCode.AvalonEdit.Utils.FileReader.ReadFileContent(stream, Encoding.Default); string textSource = ICSharpCode.AvalonEdit.Utils.FileReader.ReadFileContent(stream, Encoding.Default);
cacheDocument = new CodeCoverageStringTextSource(textSource); cacheDocument = new CodeCoverageStringTextSource(textSource);
} catch {} } catch {}
} }
} catch {} } catch {}
} }
} }
} }
} }
this.IsVisited = this.GetBooleanAttributeValue("visited"); this.IsVisited = this.GetBooleanAttributeValue("visited");
@ -104,25 +104,26 @@ namespace ICSharpCode.CodeCoverage
this.IsConstructor = this.GetBooleanAttributeValue("isConstructor"); this.IsConstructor = this.GetBooleanAttributeValue("isConstructor");
this.IsStatic = this.GetBooleanAttributeValue("isStatic"); this.IsStatic = this.GetBooleanAttributeValue("isStatic");
if ( !String.IsNullOrEmpty( this.FileID ) ) { if ( !String.IsNullOrEmpty( this.FileID ) ) {
this.SequencePoints = this.GetSequencePoints(); this.GetSequencePoints();
this.BodyStartSP = getBodyStartSP(); // before OrderBy Line/Col this.GetSequencePointsContent();
this.getBodyStartSP(); // before OrderBy Line/Col
this.getBodyFinalSP(); // before orderBy Line/Col
this.FilterSequencePoints(); // before orderBy Line/Col
this.GetBranchPoints();
this.GetBranchRatio();
this.GetBranchCoverage();
// SP's are originaly ordered by CIL offset // SP's are originaly ordered by CIL offset
// but ccrewrite can move offset of // but ccrewrite can move offset of
// Contract.Requires before method signature SP { and // Contract.Requires before method signature SP { and
// Contract.Ensures after method closing SP } // Contract.Ensures after method closing SP }
// So sort SP's back by line/column // So sort SP's back by line/column
this.SequencePoints.OrderBy(item => item.Line).OrderBy(item => item.Column); this.SequencePoints.OrderBy(item => item.Line).OrderBy(item => item.Column);
this.BodyFinalSP = getBodyFinalSP(); // after orderBy Line/Col
this.SequencePoints = this.FilterSequencePoints(this.SequencePoints);
this.BranchPoints = this.GetBranchPoints();
this.BranchCoverageRatio = this.GetBranchRatio();
this.BranchCoverage = this.GetBranchCoverage();
} }
} }
List<CodeCoverageSequencePoint> GetSequencePoints() { void GetSequencePoints() {
List<CodeCoverageSequencePoint> sps = new List<CodeCoverageSequencePoint>();
var xSPoints = this.element var xSPoints = this.element
.Elements("SequencePoints") .Elements("SequencePoints")
.Elements("SequencePoint"); .Elements("SequencePoint");
@ -136,52 +137,68 @@ namespace ICSharpCode.CodeCoverage
sp.Column = (int)GetDecimalAttributeValue(xSPoint.Attribute("sc")); sp.Column = (int)GetDecimalAttributeValue(xSPoint.Attribute("sc"));
sp.EndColumn = (int)GetDecimalAttributeValue(xSPoint.Attribute("ec")); sp.EndColumn = (int)GetDecimalAttributeValue(xSPoint.Attribute("ec"));
sp.VisitCount = (int)GetDecimalAttributeValue(xSPoint.Attribute("vc")); 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.Offset = (int)GetDecimalAttributeValue(xSPoint.Attribute("offset"));
sp.BranchCoverage = true; sp.BranchCoverage = true;
sp.Content = String.Empty;
sp.Length = 0;
sps.Add(sp); this.SequencePoints.Add(sp);
} }
return sps;
} }
// Find method-body start SequencePoint "{" void GetSequencePointsContent()
{
if (cacheFileName == this.FileName && cacheDocument != null) {
foreach (var sp in this.SequencePoints) {
GetSequencePointContent(sp);
}
}
}
void GetSequencePointContent(CodeCoverageSequencePoint sp)
{
// ccrewrite will cause lots of invalid calls to GetText()!
if (cacheFileName == sp.Document && cacheDocument != null) {
sp.Content = cacheDocument.GetText(sp); // never returns null
if (sp.Content != String.Empty) {
if (sp.Line != sp.EndLine) {
// merge lines to single line
sp.Content = Regex.Replace(sp.Content, @"\s+", " ");
}
// SequencePoint.Length counts all but whitespace
sp.Length = Regex.Replace(sp.Content, @"\s", "").Length;
}
}
}
// Find method-body start SequencePoint "{" (sp.Content required)
// Sequence points expected to be ordered by Offset // Sequence points expected to be ordered by Offset
// Cannot just get first one because of ccrewrite&ContractClassFor // Cannot just get first one because of ccrewrite&ContractClassFor
public CodeCoverageSequencePoint getBodyStartSP() { void getBodyStartSP() {
bool startPointFound = false; bool startPointFound = false;
CodeCoverageSequencePoint startSeqPoint = null; CodeCoverageSequencePoint startSeqPoint = null;
foreach (CodeCoverageSequencePoint sPoint in this.SequencePoints) { foreach (CodeCoverageSequencePoint sp in this.SequencePoints) {
if ( sPoint.Content == "{") { if ( sp.Content == "{") {
if ( this.IsConstructor ) { if ( this.IsConstructor ) {
// take previous/last one if not null // take previous/last one if not null
startSeqPoint = startSeqPoint?? sPoint; startSeqPoint = startSeqPoint?? sp;
} }
else { else {
startSeqPoint = sPoint; startSeqPoint = sp;
} }
startPointFound = true; startPointFound = true;
break; break;
} }
startSeqPoint = sPoint; startSeqPoint = sp;
} }
return startPointFound? startSeqPoint: null; this.BodyStartSP = startPointFound? startSeqPoint: null;
} }
// Find method-body final SequencePoint "}" // Find method-body final SequencePoint "}" (sp.Content required)
// Sequence points expected to be ordered by Line/Column // Sequence points expected to be ordered by Offset
public CodeCoverageSequencePoint getBodyFinalSP() { void getBodyFinalSP() {
CodeCoverageSequencePoint finalSeqPoint = null; CodeCoverageSequencePoint finalSeqPoint = null;
foreach (CodeCoverageSequencePoint sp in this.SequencePoints) { foreach (CodeCoverageSequencePoint sp in ((IEnumerable<CodeCoverageSequencePoint>)this.SequencePoints).Reverse()) {
if ( sp.Content == "}") { if ( sp.Content == "}") {
if (finalSeqPoint == null) { if (finalSeqPoint == null) {
finalSeqPoint = sp; finalSeqPoint = sp;
@ -193,20 +210,17 @@ namespace ICSharpCode.CodeCoverage
sp.EndColumn == finalSeqPoint.EndColumn && sp.EndColumn == finalSeqPoint.EndColumn &&
sp.Offset < finalSeqPoint.Offset) { sp.Offset < finalSeqPoint.Offset) {
finalSeqPoint = sp; finalSeqPoint = sp;
} // duplicate found, so far no reason to expect "triplicate" :)
else if (sp.Line < finalSeqPoint.Line) {
break; break;
} }
} }
} }
return finalSeqPoint; this.BodyFinalSP = finalSeqPoint;
} }
List<CodeCoverageSequencePoint> FilterSequencePoints(List<CodeCoverageSequencePoint> sps) { void FilterSequencePoints() {
List<CodeCoverageSequencePoint> returnList = sps;
if (sps.Count > 2 && if (this.SequencePoints.Count != 0 &&
this.BodyStartSP != null && this.BodyStartSP != null &&
this.BodyFinalSP != null ) { this.BodyFinalSP != null ) {
@ -218,31 +232,31 @@ namespace ICSharpCode.CodeCoverage
// before method signature and after end-brackets xxx{} are removed // before method signature and after end-brackets xxx{} are removed
// If ContractClassFor is in another file but interleaves this method lines // If ContractClassFor is in another file but interleaves this method lines
// then, afaik, not much can be done to remove inserted alien SP's // then, afaik, not much can be done to remove inserted alien SP's
List<CodeCoverageSequencePoint> selected = new List<CodeCoverageSequencePoint>(); var selected = new List<CodeCoverageSequencePoint>();
foreach (var point in sps) { foreach (var point in this.SequencePoints) {
if (
(point.Line > BodyStartSP.Line || (point.Line == BodyStartSP.Line && point.Column >= BodyStartSP.Column)) && // if Content.Length is 0, GetText() is failed by ccrewrite inserted invalid SequencePoint
(point.Line < BodyFinalSP.Line || (point.Line == BodyFinalSP.Line && point.Column < BodyFinalSP.Column)) if (point.Content.Length != 0
) { && (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); selected.Add (point);
} }
// After ccrewrite ContractClass/ContractClassFor // After ccrewrite ContractClass/ContractClassFor
// duplicate method end-sequence-point (}) is added // duplicate method end-sequence-point "}" is added
// //
// Add first finalSP (can be a duplicate) // Add only first finalSP (can be a duplicate)
// Note: IL.Offset of second duplicate finalSP will // Note: IL.Offset of second duplicate finalSP will
// extend branch coverage outside method-end "}", // extend branch coverage outside method-end "}",
// and that can lead to wrong branch coverage display! // and that can lead to wrong branch coverage display!
if (object.ReferenceEquals (point, this.BodyFinalSP)) { if (object.ReferenceEquals (point, this.BodyFinalSP)) {
selected.Add (point); selected.Add (point);
} }
} }
returnList = selected; this.SequencePoints = selected;
} }
return returnList;
} }
int GetSequencePointsCount() { int GetSequencePointsCount() {
@ -256,9 +270,8 @@ namespace ICSharpCode.CodeCoverage
return 0; return 0;
} }
List<CodeCoverageBranchPoint> GetBranchPoints() { void GetBranchPoints() {
// get all BranchPoints // get all BranchPoints
List<CodeCoverageBranchPoint> bps = new List<CodeCoverageBranchPoint>();
var xBPoints = this.element var xBPoints = this.element
.Elements("BranchPoints") .Elements("BranchPoints")
.Elements("BranchPoint"); .Elements("BranchPoint");
@ -268,34 +281,34 @@ namespace ICSharpCode.CodeCoverage
bp.Offset = (int)GetDecimalAttributeValue(xBPoint.Attribute("offset")); bp.Offset = (int)GetDecimalAttributeValue(xBPoint.Attribute("offset"));
bp.Path = (int)GetDecimalAttributeValue(xBPoint.Attribute("path")); bp.Path = (int)GetDecimalAttributeValue(xBPoint.Attribute("path"));
bp.OffsetEnd = (int)GetDecimalAttributeValue(xBPoint.Attribute("offsetend")); bp.OffsetEnd = (int)GetDecimalAttributeValue(xBPoint.Attribute("offsetend"));
bps.Add(bp); this.BranchPoints.Add(bp);
} }
return bps;
} }
Tuple<int,int> GetBranchRatio () { void GetBranchRatio () {
// goal: Get branch ratio and exclude (rewriten) Code Contracts branches
// goal: Get branch ratio, merge branch-exits and exclude (rewriten) Code Contracts branches
this.BranchCoverageRatio = null;
if ( this.BranchPoints == null if ( this.BranchPoints == null
|| this.BranchPoints.Count() == 0 || this.BranchPoints.Count == 0
|| this.SequencePoints == null || this.SequencePoints == null
|| this.SequencePoints.Count == 0 || this.SequencePoints.Count == 0
) )
{ {
return null; return;
} }
// This sequence point offset is used to skip CCRewrite(n) BranchPoint's (Requires) // This sequence point offset is used to skip CCRewrite(n) BranchPoint's (Requires)
// and '{' branches at static methods // and '{' branches at static methods
if (this.BodyStartSP == null) { return null; } // empty body if (this.BodyStartSP == null) { return; } // empty body
// This sequence point offset is used to skip CCRewrite(n) BranchPoint's (Ensures) // This sequence point offset is used to skip CCRewrite(n) BranchPoint's (Ensures)
if (this.BodyFinalSP == null) { return null; } // empty body if (this.BodyFinalSP == null) { return; } // empty body
// Connect Sequence & Branches // Connect Sequence & Branches
IEnumerator<CodeCoverageSequencePoint> SPEnumerator = this.SequencePoints.GetEnumerator(); IEnumerator<CodeCoverageSequencePoint> SPEnumerator = this.SequencePoints.GetEnumerator();
CodeCoverageSequencePoint currSeqPoint = BodyStartSP; CodeCoverageSequencePoint currSeqPoint = this.BodyStartSP;
int nextSeqPointOffset = BodyStartSP.Offset; int nextSeqPointOffset = BodyStartSP.Offset;
foreach (var bp in this.BranchPoints) { foreach (var bp in this.BranchPoints) {
@ -379,13 +392,13 @@ namespace ICSharpCode.CodeCoverage
sp.Branches = null; // release memory sp.Branches = null; // release memory
} }
return (totalBranchCount!=0) ? new Tuple<int,int>(totalBranchVisit,totalBranchCount) : null; this.BranchCoverageRatio = (totalBranchCount!=0) ? new Tuple<int,int>(totalBranchVisit,totalBranchCount) : null;
} }
decimal GetBranchCoverage () { void GetBranchCoverage () {
return this.BranchCoverageRatio == null ? 0m : ((decimal)(this.BranchCoverageRatio.Item1*100))/((decimal)this.BranchCoverageRatio.Item2); this.BranchCoverage = this.BranchCoverageRatio == null ? 0m : ((decimal)(this.BranchCoverageRatio.Item1*100))/((decimal)this.BranchCoverageRatio.Item2);
} }

28
src/AddIns/Analysis/CodeCoverage/Project/Src/CodeCoverageStringTextSource.cs

@ -25,7 +25,7 @@ namespace ICSharpCode.CodeCoverage
this.textSource = source; this.textSource = source;
lineInfo line; lineInfo line;
List<lineInfo> lineInfoList = new List<lineInfo>(); var lineInfoList = new List<lineInfo>();
int offset = 0; int offset = 0;
int counter = 0; int counter = 0;
bool newLine = false; bool newLine = false;
@ -95,7 +95,7 @@ namespace ICSharpCode.CodeCoverage
/// <returns></returns> /// <returns></returns>
public string GetText(int Line, int Column, int EndLine, int EndColumn) { public string GetText(int Line, int Column, int EndLine, int EndColumn) {
StringBuilder text = new StringBuilder(); var text = new StringBuilder();
string line; string line;
bool argOutOfRange; bool argOutOfRange;
@ -104,9 +104,10 @@ namespace ICSharpCode.CodeCoverage
#region One-Line request #region One-Line request
line = GetLine(Line); line = GetLine(Line);
Debug.Assert(!(Column < 1), "Column < 1"); //Debug.Assert(!(Column < 1), "Column < 1");
Debug.Assert(!(Column > EndColumn), "Column > EndColumn"); //Debug.Assert(!(Column > EndColumn), "Column > EndColumn");
Debug.Assert(!(EndColumn > line.Length), "EndColumn > line.Length"); //Debug.Assert(!(EndColumn > line.Length + 1), string.Format ("Single Line EndColumn({0}) > line.Length({1})",EndColumn, line.Length ));
//Debug.Assert(!(EndColumn > line.Length + 1), line);
argOutOfRange = Column < 1 argOutOfRange = Column < 1
|| Column > EndColumn || Column > EndColumn
@ -123,12 +124,11 @@ namespace ICSharpCode.CodeCoverage
#region First line #region First line
line = GetLine(Line); line = GetLine(Line);
Debug.Assert(!(Column < 1), "Column < 1"); //Debug.Assert(!(Column < 1), "Column < 1");
Debug.Assert(!(Column > line.Length), "Column > line.Length"); //Debug.Assert(!(Column > line.Length), string.Format ("First MultiLine EndColumn({0}) > line.Length({1})",EndColumn, line.Length ));
argOutOfRange = Column < 1 argOutOfRange = Column < 1
|| Column > line.Length; || Column > line.Length;
if (!argOutOfRange) { if (!argOutOfRange) {
text.Append(line.Substring(Column-1)); text.Append(line.Substring(Column-1));
} }
@ -143,8 +143,8 @@ namespace ICSharpCode.CodeCoverage
#region Last line #region Last line
line = GetLine(EndLine); line = GetLine(EndLine);
Debug.Assert(!(EndColumn < 1), "EndColumn < 1"); //Debug.Assert(!(EndColumn < 1), "EndColumn < 1");
Debug.Assert(!(EndColumn > line.Length), "EndColumn > line.Length"); //Debug.Assert(!(EndColumn > line.Length), string.Format ("Last MultiLine EndColumn({0}) > line.Length({1})",EndColumn, line.Length ));
argOutOfRange = EndColumn < 1 argOutOfRange = EndColumn < 1
|| EndColumn > line.Length; || EndColumn > line.Length;
@ -156,7 +156,7 @@ namespace ICSharpCode.CodeCoverage
#endregion #endregion
} else { } else {
Debug.Fail("Line > EndLine"); //Debug.Fail("Line > EndLine");
} }
return text.ToString(); return text.ToString();
} }
@ -169,7 +169,7 @@ namespace ICSharpCode.CodeCoverage
/// <summary>Return SequencePoint enumerated line /// <summary>Return SequencePoint enumerated line
/// </summary> /// </summary>
/// <param name="lineNr"></param> /// <param name="LineNo"></param>
/// <returns></returns> /// <returns></returns>
public string GetLine ( int LineNo ) { public string GetLine ( int LineNo ) {
@ -179,7 +179,7 @@ namespace ICSharpCode.CodeCoverage
lineInfo lineInfo = lines[LineNo-1]; lineInfo lineInfo = lines[LineNo-1];
retString = textSource.Substring(lineInfo.Offset, lineInfo.Length); retString = textSource.Substring(lineInfo.Offset, lineInfo.Length);
} else { } else {
Debug.Fail( "Line number out of range" ); //Debug.Fail( "Line number out of range" );
} }
return retString; return retString;
@ -193,7 +193,7 @@ namespace ICSharpCode.CodeCoverage
int remains = 0; int remains = 0;
int repeat = 0; int repeat = 0;
char prevChar = char.MinValue; char prevChar = char.MinValue;
StringBuilder indented = new StringBuilder(); var indented = new StringBuilder();
foreach ( char currChar in ToIndent ) { foreach ( char currChar in ToIndent ) {
if ( currChar == '\t' ) { if ( currChar == '\t' ) {
remains = counter % TabSize; remains = counter % TabSize;

Loading…
Cancel
Save