Skip to content

Instantly share code, notes, and snippets.

@jonorossi
Last active January 2, 2016 16:59
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jonorossi/8333504 to your computer and use it in GitHub Desktop.
Save jonorossi/8333504 to your computer and use it in GitHub Desktop.
Bullseye Coverage - XML coverage file parsing (extracted from an in-house tool written several years ago)
// Files:
// * CoverageDetails.cs - CoverageDetails has a bunch of simple attributes read from the coverage file, and some simple structs and enums
// * CoveredObject - base class of the next three classes
// * CoveredProject - a collection of modules
// * CoveredModule - a collection of functions
// * CoveredFunction - holds coverage information about the function
// * ReportHelper - some utility methods
// After running the binary coverage file through covxml with the --file argument
// construct the model using an XmlDocument object
// Create one set of these models
CoverageDetails details = new CoverageDetails(xmlDocument);
CoveredProject project = new CoveredProject("name here", details);
// Add your modules to the project model, you'll need to load each module up from separate coverage files
project.AddModule(xmlDocument, "moduleName");
using System;
using System.Text.RegularExpressions;
using System.Xml;
namespace Tsd.Tools.Build.TsdCover.Models
{
public class CoverageDetails
{
public Version CoverageVersion { get; private set; }
public Version ReportVersion { get; private set; }
public CoverageDetails(XmlDocument document)
{
// <!-- BullseyeCoverage 7.13.25 Windows License xxxx -->
Regex version = new Regex(@"<!-- BullseyeCoverage (?<match>\d+\.\d+\.\d+) Windows License .*? -->");
Match versionMatch = version.Match(document.InnerXml);
if (versionMatch.Groups["match"] != null)
{
CoverageVersion = new Version(versionMatch.Groups["match"].Value);
}
//ReportVersion = new Version(Application.ProductVersion);
}
}
public enum CoveredCoditionalSequence
{
None,
True,
False,
Full
}
public enum CoveredKind
{
Function,
Decision,
Condition,
Try,
Catch,
Switch,
Constant
}
public class Probe
{
public int Line;
public int Column;
public CoveredKind Kind;
public CoveredCoditionalSequence Covered;
}
public struct Metrics
{
public int FunctionsCovered;
public int FunctionsTotal;
public int ConditionalsCovered;
public int ConditionalsTotal;
}
}
using System;
using System.Collections.Generic;
using System.Xml;
namespace Tsd.Tools.Build.TsdCover.Models
{
public class CoveredFunction : CoveredObject
{
public string SourceFile { get; set; }
public override int ConditionalsCovered
{
get
{
int coveredConditionals = 0;
foreach (Probe probe in Probes)
{
if ((probe.Kind == CoveredKind.Decision) || (probe.Kind == CoveredKind.Condition))
{
if ((probe.Covered == CoveredCoditionalSequence.False) || (probe.Covered == CoveredCoditionalSequence.True))
coveredConditionals++;
else if (probe.Covered == CoveredCoditionalSequence.Full)
coveredConditionals += 2;
}
else if ((probe.Kind == CoveredKind.Try) || (probe.Kind == CoveredKind.Catch) || (probe.Kind == CoveredKind.Switch))
{
if (!(probe.Covered == CoveredCoditionalSequence.None))
coveredConditionals++;
}
}
return coveredConditionals;
}
}
public override int ConditionalsTotal
{
get
{
int countConditionals = 0;
foreach (Probe probe in Probes)
{
if ((probe.Kind == CoveredKind.Decision) || (probe.Kind == CoveredKind.Condition))
countConditionals += 2;
else if ((probe.Kind == CoveredKind.Try) || (probe.Kind == CoveredKind.Catch) || (probe.Kind == CoveredKind.Switch))
countConditionals++;
}
return countConditionals;
}
}
public override int FunctionsCovered
{
get
{
int coveredFunctions = 0;
foreach (Probe probe in Probes)
if (probe.Kind == CoveredKind.Function)
if (probe.Covered == CoveredCoditionalSequence.Full)
coveredFunctions++;
return coveredFunctions;
}
}
public override int FunctionsTotal
{
get
{
int countFunctions = 0;
foreach (Probe probe in Probes)
if (probe.Kind == CoveredKind.Function)
countFunctions++;
return countFunctions;
}
}
public List<Probe> Probes { get; set; }
public CoveredFunction(XmlNode node, string path, string sourcefile)
{
SourceFile = sourcefile;
Path = path;
Probes = new List<Probe>();
// Parse node
if (node.Name.ToLower() == "fn")
{
if (node.Attributes["name"] != null)
{
Name = node.Attributes["name"].Value;
}
if (node.Attributes != null)
{
Metrics m;
m.FunctionsTotal = 0;
if (node.Attributes["fn_total"] != null)
int.TryParse(node.Attributes["fn_total"].Value, out m.FunctionsTotal);
m.FunctionsCovered = 0;
if (node.Attributes["fn_cov"] != null)
int.TryParse(node.Attributes["fn_cov"].Value, out m.FunctionsCovered);
m.ConditionalsTotal = 0;
if (node.Attributes["cd_total"] != null)
int.TryParse(node.Attributes["cd_total"].Value, out m.ConditionalsTotal);
m.ConditionalsCovered = 0;
if (node.Attributes["cd_cov"] != null)
int.TryParse(node.Attributes["cd_cov"].Value, out m.ConditionalsCovered);
Metrics = m;
}
foreach (XmlNode child in node.ChildNodes)
{
if (child.Name.ToLower() == "probe")
{
Probe newProbe = new Probe();
if (child.Attributes["line"] != null)
int.TryParse(child.Attributes["line"].Value, out newProbe.Line);
if (child.Attributes["column"] != null)
int.TryParse(child.Attributes["column"].Value, out newProbe.Column);
if (child.Attributes["event"] != null)
{
if (child.Attributes["event"].Value.ToLower() == "full")
newProbe.Covered = CoveredCoditionalSequence.Full;
else if (child.Attributes["event"].Value.ToLower() == "false")
newProbe.Covered = CoveredCoditionalSequence.False;
else if (child.Attributes["event"].Value.ToLower() == "true")
newProbe.Covered = CoveredCoditionalSequence.True;
else
newProbe.Covered = CoveredCoditionalSequence.None;
}
if (child.Attributes["kind"] != null)
{
if (child.Attributes["kind"].Value.ToLower() == "decision")
newProbe.Kind = CoveredKind.Decision;
else if (child.Attributes["kind"].Value.ToLower() == "condition")
newProbe.Kind = CoveredKind.Condition;
else if (child.Attributes["kind"].Value.ToLower() == "function")
newProbe.Kind = CoveredKind.Function;
else if (child.Attributes["kind"].Value.ToLower() == "try")
newProbe.Kind = CoveredKind.Try;
else if (child.Attributes["kind"].Value.ToLower() == "catch")
newProbe.Kind = CoveredKind.Catch;
else if (child.Attributes["kind"].Value.ToLower() == "switch-label")
newProbe.Kind = CoveredKind.Switch;
else if (child.Attributes["kind"].Value.ToLower() == "constant")
newProbe.Kind = CoveredKind.Constant;
else
Console.WriteLine("[WARNING] Unsupported Probe Kind for Function: {0} = ({2}) [{1}]", Name, sourcefile, child.Attributes["kind"].Value.ToLower());
}
Probes.Add(newProbe);
}
}
}
}
/// <summary>
/// Append Coverage information to the Function. (Adds the metrics if the functions match)
/// </summary>
/// <param name="function"></param>
public void AppendCoverage(CoveredFunction function)
{
if ((function.Name.ToLower() == Name.ToLower()) && (function.Path.ToLower() == Path.ToLower()))
{
// Check all Probes
foreach (Probe probe in function.Probes)
{
bool found = false;
foreach (Probe probeExists in Probes)
{
if ((probeExists.Line == probe.Line) &&
(probeExists.Kind == probe.Kind) &&
(probeExists.Column == probe.Column))
{
found = true;
if (probeExists.Covered < probe.Covered)
probeExists.Covered = probe.Covered;
break;
}
}
if (!found)
Probes.Add(probe);
}
}
}
}
}
using System.Collections.Generic;
using System.Xml;
namespace Tsd.Tools.Build.TsdCover.Models
{
public class CoveredModule : CoveredObject
{
public List<CoveredFunction> Functions { get; set; }
public override int ConditionalsCovered
{
get
{
int countCovered = 0;
foreach (CoveredFunction function in Functions)
countCovered += function.ConditionalsCovered;
return countCovered;
}
}
public override int ConditionalsTotal
{
get
{
int countTotal = 0;
foreach (CoveredFunction function in Functions)
countTotal += function.ConditionalsTotal;
return countTotal;
}
}
public override int FunctionsCovered
{
get
{
int countCovered = 0;
foreach (CoveredFunction function in Functions)
countCovered += function.FunctionsCovered;
return countCovered;
}
}
public override int FunctionsTotal
{
get
{
int countTotal = 0;
foreach (CoveredFunction function in Functions)
countTotal += function.FunctionsTotal;
return countTotal;
}
}
public CoveredModule(XmlNode node, string name, string path)
{
Name = name;
Path = path;
Functions = new List<CoveredFunction>();
foreach (XmlNode srcchild in node.ChildNodes)
{
if (srcchild.Name.ToLower() == "src")
{
string sourceName = "";
if (srcchild.Attributes["name"] != null)
sourceName = srcchild.Attributes["name"].Value;
foreach (XmlNode child in srcchild.ChildNodes)
{
if (child.Name.ToLower() == "fn")
{
CoveredFunction function = new CoveredFunction(child, Path + "\\" + Name, sourceName);
AppendFunction(function);
}
}
}
}
}
/// <summary>
/// Append Coverage information to a Function in the module that matches.
/// </summary>
/// <param name="function"></param>
public void AppendFunction(CoveredFunction function)
{
foreach (CoveredFunction coveredFunction in Functions)
{
if ((function.Name.ToLower() == coveredFunction.Name.ToLower()) && (function.Path.ToLower() == coveredFunction.Path.ToLower()))
{
coveredFunction.AppendCoverage(function);
return;
}
}
Functions.Add(function);
}
}
}
namespace Tsd.Tools.Build.TsdCover.Models
{
public abstract class CoveredObject
{
public virtual int FunctionsCovered { get; set; }
public virtual int FunctionsTotal { get; set; }
public virtual int ConditionalsCovered { get; set; }
public virtual int ConditionalsTotal { get; set; }
public virtual Metrics Metrics
{
get
{
Metrics metrics;
metrics.FunctionsCovered = FunctionsCovered;
metrics.FunctionsTotal = FunctionsTotal;
metrics.ConditionalsCovered = ConditionalsCovered;
metrics.ConditionalsTotal = ConditionalsTotal;
return metrics;
}
set
{
FunctionsCovered = value.FunctionsCovered;
FunctionsTotal = value.FunctionsTotal;
ConditionalsCovered = value.ConditionalsCovered;
ConditionalsTotal = value.ConditionalsTotal;
}
}
public string Name { get; internal set; }
public virtual string Path { get; internal set; }
public virtual float FunctionsPercentCovered
{
get
{
if (FunctionsTotal == 0)
return 100;
return ((float)FunctionsCovered / (float)FunctionsTotal) * 100;
}
}
public virtual float ConditionalsPercentCovered
{
get
{
if (ConditionalsTotal == 0)
return 100;
return ((float)ConditionalsCovered / (float)ConditionalsTotal) * 100;
}
}
/// <summary>
/// Add Metrics to the Existing Metrics.
/// </summary>
/// <param name="metrics"></param>
public void AddMetrics(Metrics metrics)
{
FunctionsCovered += metrics.FunctionsCovered;
FunctionsTotal += metrics.FunctionsTotal;
ConditionalsCovered += metrics.ConditionalsCovered;
ConditionalsTotal += metrics.ConditionalsTotal;
}
}
}
using System.Collections.Generic;
using System.Xml;
namespace Tsd.Tools.Build.TsdCover.Models
{
public class CoveredProject : CoveredObject
{
public List<CoveredModule> Modules { get; set; }
public CoverageDetails Details { get; set; }
public override int FunctionsCovered
{
get
{
int count = 0;
foreach (CoveredModule module in Modules)
{
count += module.FunctionsCovered;
}
return count;
}
}
public override int FunctionsTotal
{
get
{
int count = 0;
foreach (CoveredModule o in Modules)
{
count += o.FunctionsTotal;
}
return count;
}
}
public override int ConditionalsCovered
{
get
{
int count = 0;
foreach (CoveredModule o in Modules)
{
count += o.ConditionalsCovered;
}
return count;
}
}
public override int ConditionalsTotal
{
get
{
int count = 0;
foreach (CoveredModule o in Modules)
{
count += o.ConditionalsTotal;
}
return count;
}
}
public CoveredProject(string name, CoverageDetails details)
{
Name = name;
Modules = new List<CoveredModule>();
Details = details;
}
/// <summary>
/// Add a module to the Project.
/// </summary>
/// <param name="file">Xml Data</param>
/// <param name="moduleName"></param>
public void AddModule(string file, string moduleName)
{
XmlDocument document = new XmlDocument();
document.Load(file);
AddModule(document, moduleName);
}
/// <summary>
/// Add a module to the Project.
/// </summary>
/// <param name="document"></param>
/// <param name="moduleName"></param>
public void AddModule(XmlDocument document, string moduleName)
{
XmlNode sourceNode = ReportHelper.FindSourceNode(document);
if (sourceNode != null)
{
AddModules(sourceNode, "..\\..", moduleName);
}
}
/// <summary>
/// Add multiple modules to the Project.
/// </summary>
/// <param name="node"></param>
/// <param name="parentPath"></param>
/// <param name="moduleName"></param>
private void AddModules(XmlNode node, string parentPath, string moduleName)
{
if (node == null)
return;
CoveredModule temp;
string path = parentPath;
if (path != "")
path += "\\";
if (((node.Name.ToLower() == "folder")) && (node.Attributes["name"] != null))
{
path += node.Attributes["name"].Value;
if (moduleName == node.Attributes["name"].Value)
{
temp = new CoveredModule(node, node.Attributes["name"].Value, path);
if (node.Attributes != null)
{
Metrics m;
m.FunctionsTotal = 0;
if (node.Attributes["fn_total"] != null)
int.TryParse(node.Attributes["fn_total"].Value, out m.FunctionsTotal);
m.FunctionsCovered = 0;
if (node.Attributes["fn_cov"] != null)
int.TryParse(node.Attributes["fn_cov"].Value, out m.FunctionsCovered);
m.ConditionalsTotal = 0;
if (node.Attributes["cd_total"] != null)
int.TryParse(node.Attributes["cd_total"].Value, out m.ConditionalsTotal);
m.ConditionalsCovered = 0;
if (node.Attributes["cd_cov"] != null)
int.TryParse(node.Attributes["cd_cov"].Value, out m.ConditionalsCovered);
temp.FunctionsCovered = m.FunctionsCovered;
temp.FunctionsTotal = m.FunctionsTotal;
temp.ConditionalsCovered = m.ConditionalsCovered;
temp.ConditionalsTotal = m.ConditionalsTotal;
}
if (ReportHelper.HasSourceNodes(node))
{
AppendObject(temp);
}
}
// No deeper then project
foreach (XmlNode child in node.ChildNodes)
{
if (child.Name.ToLower() == "folder")
{
AddModules(child, path, moduleName);
}
}
}
}
/// <summary>
/// Append Coverage information to an object in the project that matches.
/// </summary>
/// <param name="module"></param>
private void AppendObject(CoveredModule module)
{
// Check if the object exists (Full Path Check)
CoveredObject found = null;
foreach (CoveredModule o in Modules)
{
if (o.Path.ToLower() == module.Path.ToLower())
{
found = o;
break;
}
}
if (found == null)
{
Modules.Add(module);
}
else
{
found.AddMetrics(module.Metrics);
}
}
}
}
private static void OutputTeamCityServiceMessages(IEnumerable<ModuleCoverage> modules)
{
int methodTotal = modules.Sum(x => x.MethodTotal);
int methodCovered = modules.Sum(x => x.MethodCovered);
int branchTotal = modules.Sum(x => x.BranchTotal);
int branchCovered = modules.Sum(x => x.BranchCovered);
Console.WriteLine("##teamcity[buildStatisticValue key='CodeCoverageM' value='{0}']", methodTotal > 0 ? methodCovered * 100.0 / methodTotal : 0);
Console.WriteLine("##teamcity[buildStatisticValue key='CodeCoverageAbsMCovered' value='{0}']", methodCovered);
Console.WriteLine("##teamcity[buildStatisticValue key='CodeCoverageAbsMTotal' value='{0}']", methodTotal);
Console.WriteLine("##teamcity[buildStatisticValue key='CodeCoverageB' value='{0}']", branchTotal > 0 ? branchCovered * 100.0 / branchTotal : 0);
Console.WriteLine("##teamcity[buildStatisticValue key='CodeCoverageAbsBCovered' value='{0}']", branchCovered);
Console.WriteLine("##teamcity[buildStatisticValue key='CodeCoverageAbsBTotal' value='{0}']", branchTotal);
Console.WriteLine("##teamcity[buildStatisticValue key='CodeCoverageL' value='100']");
Console.WriteLine("##teamcity[buildStatisticValue key='CodeCoverageAbsLCovered' value='1']");
Console.WriteLine("##teamcity[buildStatisticValue key='CodeCoverageAbsLTotal' value='1']");
Console.WriteLine("##teamcity[buildStatisticValue key='CodeCoverageC' value='100']");
Console.WriteLine("##teamcity[buildStatisticValue key='CodeCoverageAbsCCovered' value='1']");
Console.WriteLine("##teamcity[buildStatisticValue key='CodeCoverageAbsCTotal' value='1']");
}
using System;
using System.Diagnostics;
using System.Text;
using System.Text.RegularExpressions;
using System.Xml;
using System.ComponentModel;
namespace Tsd.Tools.Build.TsdCover
{
public static class ReportHelper
{
public static XmlNode FindSourceNode(XmlNode root)
{
// Is this a Folder?
if (root.Name.ToLower() == "folder")
{
// Is this in a .. Folder
if ((root.Attributes["name"] != null) && (root.Attributes["name"].Value.ToLower() == ".."))
{
// Last Folder thats ..
foreach (XmlNode node in root.ChildNodes)
{
// Include Directory is considered a seperate Node.
if ((node.Attributes["name"] != null) && (node.Attributes["name"].Value.ToLower() != "..") && (node.Attributes["name"].Value.ToLower() != "include"))
{
return root;
}
}
}
}
XmlNode check;
foreach (XmlNode node in root.ChildNodes)
{
check = FindSourceNode(node);
if (check != null)
return check;
}
return null;
}
public static XmlNode FindIncludeNode(XmlNode root)
{
// Is this a Folder?
if (root.Name.ToLower() == "folder")
{
// Is this in a .. Folder
if ((root.Attributes["name"] != null) && (root.Attributes["name"].Value.ToLower() == "include"))
{
// First "include" folder
return root;
}
}
XmlNode check;
foreach (XmlNode node in root.ChildNodes)
{
check = FindIncludeNode(node);
if (check != null)
return check;
}
return null;
}
public static bool IsTestNamed(string name)
{
if (Regex.IsMatch(name, @"(UnitTests|.*?\.UnitTests|.*?_test)", RegexOptions.Singleline | RegexOptions.IgnoreCase))
{
return true;
}
return false;
}
public static bool HasSourceNodes(XmlNode node)
{
foreach (XmlNode child in node.ChildNodes)
{
if (child.Name.ToLower() == "src")
return true;
}
return false;
}
public static string CovToXml(string file)
{
Process proc = new Process();
proc.StartInfo.CreateNoWindow = true;
proc.StartInfo.FileName = "covxml";
proc.StartInfo.Arguments = "--file \"" + file + "\"";
proc.StartInfo.UseShellExecute = false;
proc.StartInfo.RedirectStandardOutput = true;
try
{
proc.Start();
StringBuilder data = new StringBuilder();
while (!proc.HasExited)
{
data.Append(proc.StandardOutput.ReadToEnd());
}
data.Append(proc.StandardOutput.ReadToEnd());
return data.ToString();
}
catch (Win32Exception ex)
{
throw new Exception("Error converting Coverage file, ensure covxml is on the PATH.");
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment