Reputation: 92
Does anyone have any experience importing Visual Studio Enterprise 2017 code coverage results (either *.coverage or *.coveragexml) into TeamCity? We're testing a C++ project, so we cannot use TeamCitys built in coverage report tools.
The help page (https://confluence.jetbrains.com/display/TCD10/Code+Coverage) hints that MSTest/VSTest may be supported, but I have failed to find anything (either here or on Google) that gives instructions, or even states that it can be done.
Upvotes: 3
Views: 383
Reputation: 126
This is much more complex than it should be but here is how I did it.
Step one is to use this dll Microsoft.VisualStudio.Coverage.Analysis to access the types CoverageInfo and CoverageDS. You can then do something like:
var infoFiles = new List<CoverageInfo>();
try
{
var paths = Directory.GetFiles(args[0], "*.coverage", SearchOption.AllDirectories);
infoFiles.AddRange(paths.Select(path => CoverageInfo.CreateFromFile(path, new string[] {path}, new string[] { })));
}
catch (Exception e)
{
Console.WriteLine("Error opening coverage data: {0}", e.Message);
return 1;
}
var coverageData = new List<CoverageDS>(infoFiles.Select(coverageInfo => coverageInfo.BuildDataSet()));
var data = coverageData.Aggregate(new CoverageDS(), CoverageDS.Join);
Which gives you a CoverageDS type representing any coverage files it finds. You can then manually parse this to get coverage info and use Teamcity Service messages to write out coverage info. i.e. write something like this to the console:
where x is the percentage of blocks covered
A full list of server messages can be found here: custom chart
I ended up using an fSharp xml type provider to parse the coverageinfo to provide me with a block coverage value.
namespace CoverageXMLParser
open FSharp.Data
type coverageXML = XmlProvider<"sample.xml">
type coverageStats = {coveredLines : int; totalLines : int}
module Parser =
let TeamcityStatAbsLinesCoveredString = "CodeCoverageAbsLCovered"
let TeamcityStatAbsTotalString = "CodeCoverageAbsLTotal"
let TeamcityStatCoveredBlocksString = "CodeCoverageB"
let (TeamcityServiceMessageString : Printf.TextWriterFormat<_>)= "##teamcity[buildStatisticValue key='%s' value='%f']"
let filterXML (xml: string) filter =
let coverage = coverageXML.Parse(xml)
let filtered = coverage.Modules |> Array.filter(fun x -> x.ModuleName.Contains(filter))
coverageXML.CoverageDsPriv(filtered, coverage.SourceFileNames).XElement.ToString()
let getModules (xml : string) =
coverageXML.Parse(xml).Modules
let filterModules (xml: string) filter =
getModules xml |> Array.filter(fun x -> x.ModuleName.Contains(filter))
let getCoveredBlocks modules =
modules |> Array.fold( fun acc (elem : coverageXML.Module) -> acc + elem.BlocksCovered ) 0
let getUnCoveredBlocks modules =
modules |> Array.fold( fun acc (elem : coverageXML.Module) -> acc + elem.BlocksNotCovered ) 0
let getCoveredLines modules =
modules |> Array.fold( fun acc (elem : coverageXML.Module) -> acc + elem.LinesCovered ) 0
let getUncoveredLines modules=
modules |> Array.fold( fun acc (elem : coverageXML.Module) -> acc + elem.LinesNotCovered) 0
let getPartialCoveredLines modules =
modules |> Array.fold( fun acc (elem : coverageXML.Module) -> acc + elem.LinesPartiallyCovered ) 0
let getCoverageLineStats modules =
let totalLines = getCoveredLines modules + getUncoveredLines modules + getPartialCoveredLines modules
{coveredLines = getCoveredLines modules; totalLines = totalLines}
let getCoveredBlocksPercent modules =
let covered = getCoveredBlocks modules
let uncovered = getUnCoveredBlocks modules
let percent = float covered / float (uncovered + covered)
percent * 100.0
let writeTeamcityCoverageMessageFromXml xml =
let filteredModules = filterModules xml "test"
printfn TeamcityServiceMessageString TeamcityStatCoveredBlocksString (getCoveredBlocksPercent filteredModules)
All you will need to get that working is to make a new fsharp project, install the brilliant fSharp.Data nuget package and provide a sample.xml in the project directory. The sample.xml can be obtained by calling the getXml() method on the CoverageInfo and writing to a file. The cool thing about this is I am able to filter out any external code by using the filterModules function.
I use it by having a tool that runs this code build on teamcity, link it as an artifact dependency to the build I care about. Then I run the unit tests call the tool on the created coverage file. Phew, that was easy wasn't it.......
Upvotes: 2