diff --git a/src/AddIns/Analysis/CodeCoverage/Project/Src/CodeCoverageMethodElement.cs b/src/AddIns/Analysis/CodeCoverage/Project/Src/CodeCoverageMethodElement.cs index 482367ebbc..1508842d6f 100644 --- a/src/AddIns/Analysis/CodeCoverage/Project/Src/CodeCoverageMethodElement.cs +++ b/src/AddIns/Analysis/CodeCoverage/Project/Src/CodeCoverageMethodElement.cs @@ -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(); - this.BranchPoints = new List(); - 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 BranchCoverageRatio { get; private set; } - public bool IsConstructor { get; private set; } - public bool IsStatic { get; private set; } - public List SequencePoints { get; private set; } - public CodeCoverageSequencePoint BodyStartSP { get; private set; } - public CodeCoverageSequencePoint BodyFinalSP { get; private set; } - public List 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 GetSequencePoints() { - - List sps = new List(); - 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 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 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 FilterSequencePoints(List sps) { - - List 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 selected = new List(); - - 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 GetBranchPoints() { - // get all BranchPoints - List bps = new List(); - 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 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 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(); - } - // 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 bpExits = new Dictionary(); - 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(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(); + this.BranchPoints = new List(); + 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 BranchCoverageRatio { get; private set; } + public bool IsConstructor { get; private set; } + public bool IsStatic { get; private set; } + public List SequencePoints { get; private set; } + public CodeCoverageSequencePoint BodyStartSP { get; private set; } + public CodeCoverageSequencePoint BodyFinalSP { get; private set; } + public List 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 GetSequencePoints() { + + List sps = new List(); + 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 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 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 FilterSequencePoints(List sps) { + + List 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 selected = new List(); + + 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 GetBranchPoints() { + // get all BranchPoints + List bps = new List(); + 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 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 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(); + } + // 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 bpExits = new Dictionary(); + 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(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); + } + } }