Recently I had a project where I was using Microsoft Visual Studio 2008 Team System for development and CruiseControl.NET for doing continuous integration. I VisualStudioLogousually care about code coverage when running unit tests, so I decided to integrate the code profiling tool included in Visual Studio Team System as part of my build process, in order to produce a code coverage report with each build.

I figured that wouldn’t be too hard, all I had to do in my build script was to invoke Visual Studio’s test runner’s executable (usually found in C:\Program Files\Microsoft Visual Studio 9.0\Common7\IDE\MSTest.exe) passing an option to enable code coverage profiling, and grab the output in a file that CruiseControl.NET would later use to produce the build report.

However, I quickly found out that Visual Studio’s test runner produces code coverage output in a binary proprietary format while CruiseControl.NET uses XML in order to generate its reports. Ouch!

Luckily, Microsoft distributes a .NET API that can be used to convert the content of code coverage files produced by Visual Studio into XML. Pheeew!

The library is contained in the Microsoft.VisualStudio.Coverage.Analysis.dll assembly, which can be found in the Visual Studio 2008 Team System installation folder (usually in C:\Program Files\Microsoft Visual Studio 9.0\Common7\IDE\PrivateAssemblies). So all I had to do was to add an extra step in the build process to invoke that library and do the conversion.

Since I am using MSBuild to run the build, I encapsulated the code in an MSBuild task which you can find over here at the MSDN Code Gallery. There isn’t really much to it, the actual conversion is easily done in a couple of lines of code:

// You need to specify the directory containing
// the binaries that have been profiled by MSTest
CoverageInfoManager.SymPath = symbolsDirPath;
CoverageInfoManager.ExePath = symbolsDirPath;

// The input file is the binary output produced by MSTest
CoverageInfo info = CoverageInfoManager.CreateInfoFromFile(inputFilePath);
CoverageDS dataSet = info.BuildDataSet(null);
dataSet.WriteXml(outputFilePath);

Then, the task can be invoked from the MSBuild script with:

<!-- Imports the task from the assembly -->
<UsingTask TaskName="ConvertVSCoverageToXml" AssemblyFile="CI.MSBuild.Tasks.dll" />

<!-- The values of the 'OutputPath' and 'TestConfigName' variables
     must be the same as the arguments passed to MSTest.exe
     with the /resultsfile and /runconfig options -->
<ConvertVSCoverageToXml 
    CoverageFiles="$(OutputPath)\$(TestConfigName)\In\$(ComputerName)\data.coverage"
    SymbolsDirectory="$(OutputPath)\$(TestConfigName)\Out"
    OutputDirectory="$(OutputPath)" />

This could easily be achieved in much the same way with an NAnt task, if that’s your build tool of choice.

/Enrico

6 Responses to “Visual Studio Code Coverage reports with MSBuild”

  1. Rotte2 Says:

    Nice!

  2. megakemp Says:

    Thanks :-)

  3. Bryan Says:

    This is prefect, but I am having a problem in TFS 2008 SP1.

    With:
    CoverageFiles=”$(OutputPath)\$(TestConfigName)\In\$(ComputerName)\data.coverage”

    The:
    $(OutputPath)\$(TestConfigName)

    part seems to be resolving to nothing. I have found:
    $(TestResultsRoot)

    gets pretty close, but not quite there. There is a GUID that is used for the directory before the In directory. How do I get that as a MSBUILD property?


    • I haven’t used the TFS Build Agent to run the builds, so I’m not sure about what path the $(TestResultsRoot) variable gets set to at runtime.
      I manually wrote our MSBuild script which is run by CruiseControl.NET, and in that I explicitly set the output directory for the test results by invoking MSTest.exe with the /resultsfile:$(OutputPath)\mstest.log option.
      The name of the test output directory relative to the $(OutputPath) path can be then defined in the Test Run Configuration File (*.testrunconfig). You could set it to a fixed value, for example “TestWithCoverage”, which will make the result in the path “$(OutputPath)\TestWithCoverage\In\$(ComputerName)\data.coverage”.

      Hope this helps.

      /Enrico

      • Bryan Says:

        I edited the task to get the GUID off of the .trx file. It wasn’t too difficult, I just thought there would be a MSBUILD property that would get me there without that extra work. Thanks for your help.


  4. I’m glad you found a solution :-)


Leave a Reply