Adam Bennett
Adam Bennett

Reputation: 92

Visual Studio Enterprise Code Coverage report in TeamCity

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

Answers (1)

user2327063
user2327063

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:

teamcity[buildStatisticValue key='CodeCoverageB' value='x']

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

Related Questions