Programmatic Code Coverage Data Merging in Visual Studio 2010

In my last post, I demonstrated how you could use the coverage analysis assembly to analyze coverage files.  Today I will quickly cover how to programmatically merge multiple coverage files together.  This replicates the functionality that the code coverage results tool window has for merging coverage data from multiple test runs together.  The following assumes you’ve read the instructions in the previous post to reference the analysis assembly and use the Microsoft.VisualStudio.Coverage.Analysis namespace.

This function joins coverage files together into a single CoverageInfo using the Visual Studio 2010 API:

    1: static CoverageInfo JoinCoverageFiles(IEnumerable<string> files)
    2: {
    3:     if (files == null)
    4:         throw new ArgumentNullException("files");
    6:     // This will represent the joined coverage files
    7:     CoverageInfo result = null;
    9:     try
   10:     {
   11:         foreach (string file in files)
   12:         {
   13:             // Create from the current file
   14:             CoverageInfo current = CoverageInfo.CreateFromFile(file);
   15:             if (result == null)
   16:             {
   17:                 // First time through, assign to result
   18:                 result = current;
   19:                 continue;
   20:             }
   22:             // Not the first time through, join the result with the current
   23:             CoverageInfo joined = null;
   24:             try
   25:             {
   26:                 joined = CoverageInfo.Join(result, current);
   27:             }
   28:             finally
   29:             {
   30:                 // Dispose current and result
   31:                 current.Dispose();
   32:                 current = null;
   33:                 result.Dispose();
   34:                 result = null;
   35:             }
   37:             result = joined;
   38:         }
   39:     }
   40:     catch (Exception)
   41:     {
   42:         if (result != null)
   43:         {
   44:             result.Dispose();
   45:         }
   46:         throw;
   47:     }
   49:     return result;
   50: }

This function could be called like this:

    1: try
    2: {
    3:     using (CoverageInfo info = JoinCoverageFiles(files))
    4:     {
    5:         // Dump out the info like you would if it weren't joined
    6:     }
    7: }
    8: catch (CoverageAnalysisException ex)
    9: {
   10:     Console.WriteLine(ex);
   11: }

One thing I left out in my previous post’s examples is CoverageAnalysisException.  This is the base type for exceptions thrown by the analysis engine, so it will be one you will want to catch when using the analysis API.  Analysis exceptions are usually the result of a missing instrumented module (ImageNotFoundException) or .pdb file (SymbolsNotFoundException) .

How does joining two coverage files work under the covers?  It’s rather simple, actually.  When joining two coverage files together, we first union the modules together in the two coverage files.  Remember the ICoverageModule.Signature and ICoverageModule.SignatureAge properties?  These two properties are the unique identity of the module.  If the module is found in both of the coverage files we simply bitwise OR their coverage buffers together to arrive at the merged coverage data.  If a module is only found in one coverage file, we use the coverage buffer that was collected for it as is.

As you might notice from this scheme, we will only merge module data together for modules that are identical.  When you instrument a module, we record the original file’s signature data in the instrumented module.  This means you can instrument an original module multiple times and merge the data between different instrumented copies together, provided that each instrumentation was performed the same way.  However, once the original module is rebuilt, you can no longer merge coverage data collected for the old version with data collected from the newer version.  When you attempt to do so, you’ll get two modules of the same name in the resulting coverage data distinguished by the time they were built.  In the future we hope to do a better job when merging coverage data between different builds of the same module.

Please note that what I’ve described here is specific to Visual Studio 2010.  Earlier versions used a weaker heuristic to determine if module data can be merged together, sometimes resulting in undesirable behavior.  With 2010, you get a consistent merge every time, provided the coverage data came from the same original module.

Well, this is it for my code coverage blog posts for a while.  My next series of posts will be a walk through of the test impact features in Visual Studio 2010, including a walk through of how to use it from Team Foundation Build and the new Microsoft Test and Lab Management product.