#develop (short for SharpDevelop) is a free IDE for .NET programming languages.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

416 lines
14 KiB

// Copyright (c) 2014 AlphaSierraPapa for the SharpDevelop Team
//
// 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
// 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
// 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
// substantial portions of the Software.
//
// 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
// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Xml.Linq;
namespace ICSharpCode.CodeCoverage
{
public class CodeCoverageMethodElement
{
readonly XElement element;
readonly CodeCoverageResults results;
/// <summary>Enables CodeCoverage.Test to compile</summary>
/// <param name="element">XMLElement</param>
public CodeCoverageMethodElement(XElement element)
: this (element, null) {}
/// <summary>Create Method Element</summary>
/// <param name="element">XMLElement</param>
/// <param name="results">has .GetFileName(FileID)</param>
public CodeCoverageMethodElement(XElement element, CodeCoverageResults results)
{
this.results = results;
this.element = element;
this.SequencePoints = new List<CodeCoverageSequencePoint>();
Init();
}
// Primary TextSource cache
private static string cacheFileName = String.Empty;
private static CodeCoverageStringTextSource cacheDocument = null;
// Secondary TextSource cache
private static string cache2FileName = String.Empty;
private static CodeCoverageStringTextSource cache2Document = null;
public string FileID { get; private set; }
public string FileName { get; private set; }
public string FileNameExt { 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 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;
this.FileNameExt = String.Empty;
if (!String.IsNullOrEmpty(this.FileID)) {
if (results != null) {
this.FileName = results.GetFileName(this.FileID);
try {
this.FileNameExt = Path.GetExtension(this.FileName).ToLowerInvariant();
}
catch {}
if (cacheFileName != this.FileName) {
cacheFileName = this.FileName;
cacheDocument = GetSource (cacheFileName);
}
}
}
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.GetSequencePoints();
this.GetSequencePointsContent();
this.getBodyStartSP();
this.getBodyFinalSP();
this.GetBranchRatio();
this.GetBranchCoverage();
// SP's are originaly ordered by CIL offset
// because ccrewrite can move offset of
// Contract.Requires before method start ({) SP offset
// Contract.Ensures after method final (}) SP offset
// So sort SP's back by line/column
this.SequencePoints.OrderBy(item => item.Line).OrderBy(item => item.Column);
}
}
private static string cacheGetSource_LastFileName = null;
private static CodeCoverageStringTextSource cacheGetSource_LastSource = null;
static CodeCoverageStringTextSource GetSource(string filename) {
if (filename == cacheGetSource_LastFileName) return cacheGetSource_LastSource;
var retSource = (CodeCoverageStringTextSource)null;
try {
using (Stream stream = new FileStream(filename, FileMode.Open, FileAccess.Read)) {
try {
stream.Position = 0;
string textSource = ICSharpCode.AvalonEdit.Utils.FileReader.ReadFileContent(stream, Encoding.Default);
retSource = new CodeCoverageStringTextSource(textSource);
} catch (Exception) {}
}
} catch (Exception) {}
cacheGetSource_LastFileName = filename;
cacheGetSource_LastSource = retSource;
return retSource;
}
void GetSequencePoints() {
var xSPoints = this.element
.Elements("SequencePoints")
.Elements("SequencePoint");
foreach (XElement xSPoint in xSPoints) {
var sp = new CodeCoverageSequencePoint();
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"));
sp.Offset = (int)GetDecimalAttributeValue(xSPoint.Attribute("offset"));
sp.BranchExitsCount = (int)GetDecimalAttributeValue(xSPoint.Attribute("bec"));
sp.BranchExitsVisit = (int)GetDecimalAttributeValue(xSPoint.Attribute("bev"));
sp.FileID = xSPoint.Attribute("fileid") != null? xSPoint.Attribute("fileid").Value : "0";
if (sp.FileID == "0") {
// SequencePoint from not covered (not runnable) file
// ie: interface with CodeContractClass/CodeContractClassFor
sp.Document = xSPoint.Attribute("fileid") != null? xSPoint.Attribute("fileid").Value : "";
}
else if (sp.FileID == this.FileID) {
// This method SequencePoint (from this.FileName)
sp.Document = this.FileName;
}
else {
// SequencePoint from another method/file
// ie: ccrewriten CodeContractClass/CodeContractClassFor
// [or dependency-injected or fody-weaved???]
sp.Document = results.GetFileName(sp.FileID);
}
sp.BranchCoverage = (sp.BranchExitsCount == sp.BranchExitsVisit);
sp.Content = String.Empty;
sp.Length = 0;
this.SequencePoints.Add(sp);
}
}
void GetSequencePointsContent()
{
foreach (var sp in this.SequencePoints) {
GetSequencePointContent(sp);
}
}
void GetSequencePointContent(CodeCoverageSequencePoint sp)
{
if (cacheFileName == sp.Document) {
// check primary cache (this.Filename)
sp.Content = cacheDocument == null? "" : cacheDocument.GetText(sp);
}
else {
// check & update secondary cache
if (cache2FileName == sp.Document) {
sp.Content = cache2Document == null? "" : cache2Document.GetText(sp);
}
else {
cache2FileName = sp.Document;
cache2Document = GetSource (cache2FileName);
sp.Content = cache2Document == null? "" : cache2Document.GetText(sp);
}
}
if (sp.Content != String.Empty) {
if (sp.Line != sp.EndLine) {
// merge multiple 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 first SequencePoint
// -> this method SP with lowest Line/Column
void getBodyStartSP() {
if (this.SequencePoints.Count != 0) {
if (this.FileNameExt == ".cs") {
foreach (CodeCoverageSequencePoint sp in this.SequencePoints) {
if (sp.FileID != this.FileID) continue;
if (this.BodyStartSP == null || (sp.Line < this.BodyStartSP.Line) ||
(sp.Line == this.BodyStartSP.Line && sp.Column < this.BodyStartSP.Column)
) {
this.BodyStartSP = sp;
}
}
}
else {
this.BodyStartSP = this.SequencePoints.First();
}
}
}
// Find method-body last SequencePoint
// -> this method SP.Content=="}" with highest Line/Column
// and lowest Offset (when duplicated bw ccrewrite)
void getBodyFinalSP() {
if (this.SequencePoints.Count != 0) {
if (this.FileNameExt == ".cs") {
for (int i = this.SequencePoints.Count-1; i > 0; i--) {
var sp = this.SequencePoints[i];
if (sp.FileID != this.FileID) continue;
if (sp.Content != "}") continue;
if (this.BodyFinalSP == null || (sp.Line > this.BodyFinalSP.Line) ||
(sp.Line == this.BodyFinalSP.Line && sp.Column >= this.BodyFinalSP.Column)
) {
// ccrewrite ContractClass/ContractClassFor
// adds duplicate method end-sequence-point "}"
//
// Take duplicate BodyFinalSP with lower Offset
// Because IL.Offset of second duplicate
// will extend branch coverage of this method
// by coverage of ContractClassFor inserted SequencePoint!
if (this.BodyFinalSP != null &&
sp.Line == this.BodyFinalSP.Line &&
sp.Column == this.BodyFinalSP.Column &&
sp.Offset < this.BodyFinalSP.Offset) {
this.SequencePoints.Remove(this.BodyFinalSP); // remove duplicate
}
this.BodyFinalSP = sp;
}
}
}
else {
this.BodyFinalSP = this.SequencePoints.Last();
}
}
}
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;
}
const string @assert = "Assert";
const string @contract = "Contract";
void GetBranchRatio () {
this.BranchCoverageRatio = null;
Debug.Assert (this.SequencePoints != null);
if ( this.SequencePoints.Count == 0 ) return;
// This sequence point offset is used to skip CCRewrite(n) BranchPoint's (Requires)
// and '{' branches at static methods
if (this.BodyStartSP == null) return; // empty body
// This sequence point offset is used to skip CCRewrite(n) BranchPoint's (Ensures)
if (this.BodyFinalSP == null) return; // empty body
// Calculate Method Branch coverage
int totalBranchVisit = 0;
int totalBranchCount = 0;
foreach (var sp in this.SequencePoints) {
// SequencePoint is visited and belongs to this method?
if (sp.VisitCount != 0 && sp.FileID == this.FileID) {
if (this.FileNameExt == ".cs") {
// Only for C#
// Don't want branch coverage of ccrewrite(n)
// SequencePoint(s) with offset before and after method body
if (sp.Offset < BodyStartSP.Offset ||
sp.Offset > BodyFinalSP.Offset) {
sp.BranchCoverage = true;
continue; // skip
}
// 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 + ".", StringComparison.Ordinal) ||
sp.Content.StartsWith(@assert + " ", StringComparison.Ordinal) ||
sp.Content.StartsWith(@contract + ".", StringComparison.Ordinal) ||
sp.Content.StartsWith(@contract + " ", StringComparison.Ordinal)
) {
sp.BranchCoverage = true;
continue; // skip
}
}
totalBranchCount += sp.BranchExitsCount;
totalBranchVisit += sp.BranchExitsVisit;
}
}
this.BranchCoverageRatio = (totalBranchCount!=0) ? new Tuple<int,int>(totalBranchVisit,totalBranchCount) : null;
}
void GetBranchCoverage () {
this.BranchCoverage = 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");
return nameElement != null ? GetMethodName(nameElement.Value) : String.Empty;
}
string GetMethodName(string methodSignature)
{
int startIndex = methodSignature.IndexOf("::");
int endIndex = methodSignature.IndexOf('(', startIndex);
return methodSignature
.Substring(startIndex, endIndex - startIndex)
.Substring(2);
}
}
}