From 9e04fe810d9dd0ee851a62a2db506931b3ed19f8 Mon Sep 17 00:00:00 2001 From: Dragan Date: Thu, 26 Dec 2013 18:52:10 +0100 Subject: [PATCH] CodeCoverage: Merge branch points on OffsetEnd --- .../Project/Src/CodeCoverageBranchPoint.cs | 3 +- .../Project/Src/CodeCoverageMethodElement.cs | 150 ++++++++++-------- .../Project/Src/CodeCoverageSequencePoint.cs | 2 + .../Src/CodeCoverageStringTextSource.cs | 48 +++--- 4 files changed, 116 insertions(+), 87 deletions(-) diff --git a/src/AddIns/Analysis/CodeCoverage/Project/Src/CodeCoverageBranchPoint.cs b/src/AddIns/Analysis/CodeCoverage/Project/Src/CodeCoverageBranchPoint.cs index 2d78b31111..a3e37ebafa 100644 --- a/src/AddIns/Analysis/CodeCoverage/Project/Src/CodeCoverageBranchPoint.cs +++ b/src/AddIns/Analysis/CodeCoverage/Project/Src/CodeCoverageBranchPoint.cs @@ -11,8 +11,9 @@ namespace ICSharpCode.CodeCoverage public class CodeCoverageBranchPoint { public int VisitCount { get; set; } - public int Offset { get; set; } public int Path { get; set; } + public int Offset { get; set; } + public int OffsetEnd { get; set; } } } diff --git a/src/AddIns/Analysis/CodeCoverage/Project/Src/CodeCoverageMethodElement.cs b/src/AddIns/Analysis/CodeCoverage/Project/Src/CodeCoverageMethodElement.cs index 9f1878485b..92b986b606 100644 --- a/src/AddIns/Analysis/CodeCoverage/Project/Src/CodeCoverageMethodElement.cs +++ b/src/AddIns/Analysis/CodeCoverage/Project/Src/CodeCoverageMethodElement.cs @@ -17,16 +17,6 @@ namespace ICSharpCode.CodeCoverage XElement element; CodeCoverageResults parent; - private class branchOffset { - public int Offset; - public int Visit; - public int Count; - public CodeCoverageSequencePoint SeqPoint; - public branchOffset( int offset ) { - this.Offset = offset; - } - } - public CodeCoverageMethodElement(XElement element, CodeCoverageResults parent) { this.parent = parent; @@ -156,6 +146,7 @@ namespace ICSharpCode.CodeCoverage 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; @@ -176,19 +167,24 @@ namespace ICSharpCode.CodeCoverage // Find method-body first SequencePoint "{" // and then first next sequencePoint (and offset of that instruction in body) - // This will be used to skip CCRewrite(n) BranchPoint's (Requires) + // This is used to skip CCRewrite(n) BranchPoint's (Requires) + // and '{' branches at static methods + bool startSPFound = false; CodeCoverageSequencePoint startSeqPoint = null; foreach (CodeCoverageSequencePoint sp in this.SequencePoints) { - if ( sp.Content == "{") { + if (startSPFound) { startSeqPoint = sp; break; } + if ( sp.Content == "{") { + startSPFound = true; + } } Debug.Assert (!Object.ReferenceEquals(null, startSeqPoint)); if (Object.ReferenceEquals(null, startSeqPoint)) { return null; } // Find method-body last SequencePoint "}" and offset - // This will be used to skip CCRewrite(n) BranchPoint's (Ensures) + // This is used to skip CCRewrite(n) BranchPoint's (Ensures) CodeCoverageSequencePoint finalSeqPoint = null; foreach (CodeCoverageSequencePoint sp in Enumerable.Reverse(this.SequencePoints)) { if ( sp.Content == "}") { @@ -199,74 +195,94 @@ namespace ICSharpCode.CodeCoverage Debug.Assert ( !Object.ReferenceEquals( null, finalSeqPoint) ); if (Object.ReferenceEquals(null, finalSeqPoint)) { return null; } + // Connect Sequence & Branches IEnumerator SPEnumerator = this.SequencePoints.GetEnumerator(); - CodeCoverageSequencePoint branchSeqPoint = startSeqPoint; - int nextOffset = branchSeqPoint.Offset; - - // Merge BranchPoints on same offset - // Exclude BranchPoints outside of method boundary {...} => exclude CCRewrite(n) Contracts - // Exclude BranchPoints from excludeOffsetList - Dictionary branchDictionary = new Dictionary(); - foreach (CodeCoverageBranchPoint bp in this.BranchPoints) { - - // exclude CCRewrite(n) contracts + CodeCoverageSequencePoint currSeqPoint = startSeqPoint; + int nextSeqPointOffset = startSeqPoint.Offset; + + foreach (var bp in this.BranchPoints) { + + // exclude CCRewrite(n) contracts if (bp.Offset < startSeqPoint.Offset) continue; if (bp.Offset > finalSeqPoint.Offset) break; - // merge BranchPoint's with same offset - if ( !branchDictionary.ContainsKey( bp.Offset ) ) { - // Insert BranchPoint coverage at offset - branchOffset insert = new branchOffset(bp.Offset); - insert.Visit = bp.VisitCount!=0?1:0; - insert.Count = 1; - - // attach Sequence to Branch-Offset - while ( nextOffset < insert.Offset ) { - branchSeqPoint = SPEnumerator.Current; - if ( SPEnumerator.MoveNext() ) { - nextOffset = SPEnumerator.Current.Offset; - } else { - nextOffset = int.MaxValue; - } + // Sync with SequencePoint + while ( nextSeqPointOffset < bp.Offset ) { + currSeqPoint = SPEnumerator.Current; + if ( SPEnumerator.MoveNext() ) { + nextSeqPointOffset = SPEnumerator.Current.Offset; + } else { + nextSeqPointOffset = int.MaxValue; } - insert.SeqPoint = branchSeqPoint; - branchDictionary[insert.Offset] = insert; - - } else { - // Update BranchPoint coverage at offset - branchOffset update = branchDictionary[bp.Offset]; - update.Visit += bp.VisitCount!=0?1:0; - update.Count += 1; } + if (currSeqPoint.Branches == null) { + currSeqPoint.Branches = new List(); + } + // Add Branch to Branches + currSeqPoint.Branches.Add(bp); } - + +// for (int i = this.SequencePoints.Count-1; i >= 0; i--) { +// if (this.SequencePoints[i].Content.StartsWith("Assert")) { +// this.SequencePoints.RemoveAt(i); +// } +// } + + // Merge sp.Branches on exit-offset // Calculate Method Branch coverage int totalBranchVisit = 0; int totalBranchCount = 0; - foreach ( branchOffset uniqueBranch in branchDictionary.Values ) { + int pointBranchVisit = 0; + int pointBranchCount = 0; + Dictionary bpExits = new Dictionary(); + foreach (var sp in this.SequencePoints) { - // 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) - if (uniqueBranch.SeqPoint.Content != "in") { + // SequencePoint covered & has branches? + if (sp.VisitCount != 0 && sp.Branches != null) { - // Branch coverage will display only if sequence coverage is 100%, so ... - // Ignore branch-offset if is not visited at all because ... : - // if SequencePoint is covered and branch-offset within that SequencePoint not visited (uncovered), - // then that branch-offset does not really exists in SOURCE code we try to cover - if ( uniqueBranch.Visit != 0 ) { - totalBranchVisit += uniqueBranch.Visit; - totalBranchCount += uniqueBranch.Count; - - // not full branch coverage? - if ( uniqueBranch.Visit != uniqueBranch.Count ) { - // update attached SequencePoint.BranchCoverage to false (== partial branch coverage) - uniqueBranch.SeqPoint.BranchCoverage = false; - } - } - } + // 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) + // exclude Contract class (EnsuresOnThrow) + // exclude NUnit Assert class + if (sp.Content == "in" || + 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(totalBranchVisit,totalBranchCount) : null; diff --git a/src/AddIns/Analysis/CodeCoverage/Project/Src/CodeCoverageSequencePoint.cs b/src/AddIns/Analysis/CodeCoverage/Project/Src/CodeCoverageSequencePoint.cs index 092807e4be..11cbbb3030 100644 --- a/src/AddIns/Analysis/CodeCoverage/Project/Src/CodeCoverageSequencePoint.cs +++ b/src/AddIns/Analysis/CodeCoverage/Project/Src/CodeCoverageSequencePoint.cs @@ -2,6 +2,7 @@ // This code is distributed under the GNU LGPL (for details please see \doc\license.txt) using System; +using System.Collections.Generic; using System.Xml.Linq; namespace ICSharpCode.CodeCoverage @@ -75,6 +76,7 @@ namespace ICSharpCode.CodeCoverage public int Length { get; set; } public int Offset { get; set; } public bool BranchCoverage { get; set; } + public List Branches { get; set; } public override bool Equals(object obj) { diff --git a/src/AddIns/Analysis/CodeCoverage/Project/Src/CodeCoverageStringTextSource.cs b/src/AddIns/Analysis/CodeCoverage/Project/Src/CodeCoverageStringTextSource.cs index d6ab6734ee..07dd58a354 100644 --- a/src/AddIns/Analysis/CodeCoverage/Project/Src/CodeCoverageStringTextSource.cs +++ b/src/AddIns/Analysis/CodeCoverage/Project/Src/CodeCoverageStringTextSource.cs @@ -22,44 +22,44 @@ namespace ICSharpCode.CodeCoverage private readonly lineInfo[] lines; public CodeCoverageStringTextSource(string source) { - - this.textSource = source.Clone() as string; // disconnect from source + // protect readonly field from source mutation(s) + this.textSource = string.Copy(source); + lineInfo line; List lineInfoList = new List(); int offset = 0; int counter = 0; bool nl = false; bool cr = false; bool lf = false; - lineInfo sl; - foreach ( short ch in textSource ) { + foreach ( ushort ch in textSource ) { switch (ch) { case 13: - if (cr||lf) { - nl = true; + if (lf || cr) { + nl = true; // cr after [cr]lf } else { - cr = true; + cr = true; // cr found } break; case 10: if (lf) { - nl = true; + nl = true; // lf after lf } else { - lf = true; + lf = true; // lf found } break; default: if (cr||lf) { - nl = true; + nl = true; // any noncrlf char after cr|lf } break; } - if (nl) { - sl = new lineInfo(); - sl.Offset = offset; - sl.Length = counter - offset; - lineInfoList.Add(sl); + if (nl) { // nl detected - add line + line = new lineInfo(); + line.Offset = offset; + line.Length = counter - offset; + lineInfoList.Add(line); offset = counter; cr = false; lf = false; @@ -67,10 +67,14 @@ namespace ICSharpCode.CodeCoverage } ++counter; } - sl = new lineInfo(); - sl.Offset = offset; - sl.Length = counter - offset; - lineInfoList.Add(sl); + + // Add last line + line = new lineInfo(); + line.Offset = offset; + line.Length = counter - offset; + lineInfoList.Add(line); + + // Store to readonly field lines = lineInfoList.ToArray(); } @@ -158,6 +162,12 @@ namespace ICSharpCode.CodeCoverage return text.ToString(); } + public int LinesCount { + get { + return lines.Length; + } + } + /// Return SequencePoint enumerated line /// ///