Programmatic Coverage Analysis in Visual Studio 2010

本文介绍如何使用Visual Studio 2010的API进行代码覆盖率文件的程序化分析,包括引用必要的组件、设置搜索路径、创建CoverageInfo对象及获取覆盖率统计数据的方法。

As hinted upon in my last post, today’s entry will be on how to programmatically perform analysis on a Visual Studio coverage file in Visual Studio 2010.

 

The first step is to reference the coverage analysis assembly,  Microsoft.VisualStudio.Coverage.Analysis.dll, in your project.  This managed assembly can be found in “%ProgramFiles%/Microsoft Visual Studio 10.0/Common7/IDE/PrivateAssemblies” (use %ProgramFiles(x86)% on a 64-bit OS).  The project system should copy the assembly locally for your application to use.  This assembly has native x86 dependencies, so it can only be used from an x86 process.  It is therefore marked as 32-bit required and if you plan on loading it on a 64-bit OS, ensure your entry point assembly is also marked as 32-bit required (i.e. built for the “x86” platform).

 

To run your application, you will also need to copy Microsoft.VisualStudio.Coverage.Symbols.dll and dbghelp.dll to your output directory.  These are both native modules, so you won’t be able to add them as a reference.  The easiest way I’ve found to do this is to “Add Existing Item” to the project, browse to the files (both are also in PrivateAssemblies), click the little down arrow next to the “Add” button, select “Add As Link”, select the files in Solution Explorer, bring up the Properties Window, and change “Copy to Output Directory” to “Copy if newer”.  Note that Microsoft.VisualStudio.Coverage.Symbols.dll has a dependency on the Microsoft Debug Interface Access library (msdia100.dll), which is a native COM component that is registered when you install Visual Studio.  You will need to register msdia100.dll using regsvr32.exe on a machine that doesn’t have Visual Studio installed if you plan on using the coverage analysis API.

 

Next you’ll want to use the namespace containing the core implementation:

   1: using Microsoft.VisualStudio.Coverage.Analysis;

Now that we’ve setup a project to use the analysis API, let’s recall John Cunningham’s old post about using the Visual Studio 2005/2008 coverage analysis API.  There has been a minor breaking change with the old API.  The CoverageInfoManager class no longer exists.  Instead, CoverageInfo objects are created via static methods on CoverageInfo itself.  Here’s a quick comparison:

 

The old way to analyze a file:

   1: CoverageInfoManager.ExePath = "<executable_search_paths>";
   2: CoverageInfoManager.SymPath = "<symbol_search_paths>";
   3: CoverageInfo info = CoverageInfoManager.CreateInfoFromFile("<path_to_coverage_file>");
   4:  
   5: CoverageDS dataSet = info.BuildDataSet(null);
   6:  
   7: CoverageInfoManager.Shutdown();

And the new way:

   1: using (CoverageInfo info = CoverageInfo.CreateFromFile(
   2:         "<path_to_coverage_file>", 
   3:         new string[] { "<exe_path1>", "<exe_path2>" }, 
   4:         new string[] { "<sym_path1>", "<sym_path2>" }))
   5: {
   6:     CoverageDS dataSet = info.BuildDataSet();
   7: }

The executable search paths are locations where the instrumented modules can be found and the symbol search paths are where the instrumented symbols can be found.  They are optional and there is an overload of CoverageInfo.CreateFromFile that takes only the path to the coverage file.  The analysis engine will always check the same location as the coverage file to locate the instrumented modules and symbols in addition to the paths supplied.

 

The examples above use the CoverageDS type that is unchanged from Visual Studio 2008.  This type is a typed data set containing the following tables: Module, Namespace, Class, Method, Lines, and SourceFileNames.  It also has methods for exporting/importing the dataset’s data called ExportXml and ImportXml.  The exported XML is the same format that Visual Studio uses when it exports a coverage file to XML in the coverage results tool window.

 

The downside to using CoverageDS is that it will load all of the coverage data into memory at the same time.  This is simply not scalable when dealing with large numbers (i.e. many millions) of basic blocks, which usually results in 500 MB or more of data.  Therefore, the Visual Studio 2010 coverage analysis API has an alternative method for enumerating the coverage data on demand.  This also allows for easier filtering before method statistics are rolled up in the statistics of their classes, namespaces, and modules.

 

How to dump out the coverage statistics for each method:

   1: using (CoverageInfo info = CoverageInfo.CreateFromFile("foo.coverage"))
   2: {
   3:     List<BlockLineRange> lines = new List<BlockLineRange>();
   4:  
   5:     foreach (ICoverageModule module in info.Modules)
   6:     {
   7:         byte[] coverageBuffer = module.GetCoverageBuffer(null);
   8:  
   9:         using (ISymbolReader reader = module.Symbols.CreateReader())
  10:         {
  11:             uint methodId;
  12:             string methodName;
  13:             string undecoratedMethodName;
  14:             string className;
  15:             string namespaceName;
  16:  
  17:             lines.Clear();
  18:             while (reader.GetNextMethod(
  19:                 out methodId,
  20:                 out methodName,
  21:                 out undecoratedMethodName,
  22:                 out className,
  23:                 out namespaceName,
  24:                 lines))
  25:             {
  26:                 CoverageStatistics stats = CoverageInfo.GetMethodStatistics(coverageBuffer, lines);
  27:  
  28:                 Console.WriteLine("Method {0}{1}{2}{3}{4} has:",
  29:                     namespaceName == null ? "" : namespaceName,
  30:                     string.IsNullOrEmpty(namespaceName) ? "" : ".",
  31:                     className == null ? "" : className,
  32:                     string.IsNullOrEmpty(className) ? "" : ".",
  33:                     methodName
  34:                     );
  35:                 Console.WriteLine("    {0} blocks covered", stats.BlocksCovered);
  36:                 Console.WriteLine("    {0} blocks not covered", stats.BlocksNotCovered);
  37:                 Console.WriteLine("    {0} lines covered", stats.LinesCovered);
  38:                 Console.WriteLine("    {0} lines partially covered", stats.LinesPartiallyCovered);
  39:                 Console.WriteLine("    {0} lines not covered", stats.LinesNotCovered);
  40:                 lines.Clear();
  41:             }
  42:         }
  43:     }
  44: }

This example creates a CoverageInfo in the same manner as above, but instead of calling BuildDataSet, it enumerates through each module in the coverage file and then enumerates each method in the module to dump out its statistics.  You’ll notice that we call GetCoverageBuffer on the module which returns a byte[].  There is a byte in this array for each basic block in the module.  If the byte is zero, it means the basic block was not covered.  If it is non-zero, it means the basic block was covered.  So a simple method for counting the “raw” basic blocks covered/not covered would be to count the zero vs. non-zero bytes in this array.  However, the total number of basic blocks reported in coverage statistics is usually less than the total number of basic blocks in the module because certain basic blocks are discarded by GetNextMethod (usually these basic blocks are for compiler-generated code).  So keep that in mind if you want to analyze the coverage buffer directly.

 

Note that each method identifier is unique for a particular build of a module.  Each module has two properties that uniquely identify it: Signature (a Guid) and SignatureAge (a uint).  These properties actually correspond go the debug information’s signature information in the module and change whenever a module is re-linked.  For incremental links (VC++), the signature may remain the same while the signature’s age counter will increment, so these two values need to be taken together to version a particular build of a module.

 

Also, with modules built by VC++ that were linked with COMDAT folding enabled (an optimization that usually comes into play when using templates), you may see multiple functions returned by GetNextMethod that map to the same basic blocks because the functions contained duplicate code and were folded into a single copy.  To get around this, we only roll up a method’s statistics to the module’s statistics as long as we haven’t seen the starting basic block before (lines[0].BasicBlockIndex).  That way, the module’s totals are always accurate, although this means you may not always sum up the namespace numbers in a CoverageDS to arrive at the module’s numbers.  I generally recommend disabling identical COMDAT folding when collecting native code coverage data, as it gives you a better idea of what was actually executed by your tests.

 

I’ll leave filtering and doing more complicated analysis (i.e. rollup statistics) as an exercise for the reader.  Hopefully this will serve as a good starting point for those interested in programmatically analyzing code coverage data using Visual Studio 2010.

 

内容概要:本文详细介绍了基于Matlab实现的“梯级水光互补系统最大化可消纳电量期望短期优化调度模型”,属于电力系统领域高水平科研成果的复现(EI级别)。该模型聚焦于梯级水电站与光伏发电系统的协同优化调度,通过构建短期优化调度框架,旨在提升可再生能源的电量消纳能力并最大化系统综合效益。研究采用先进的数学优化方法对水光资源进行联合调度,充分考虑了光伏出力的不确定性、水资源约束、系统运行边界条件及电力平衡要求,实现了在多重约束下的电量期望最大化目标。模型不仅具备严谨的理论基础,还具有良好的工程应用前景,适用于新能源高比例渗透背景下电力系统的优化调度研究与实践。; 适合人群:具备电力系统分析、可再生能源利用或优化建模背景的研究生、科研人员及工程技术人员,特别适合致力于复现高水平学术论文(EI/顶刊)研究成果的学习者与开发者。; 使用场景及目标:① 学习并掌握梯级水电与光伏系统协同调度的建模思路与关键技术;② 熟悉基于Matlab的混合整数线性规划(MILP)或其他非线性优化方法在能源系统中的实际应用;③ 提升在新能源消纳、短期调度优化等方向的科研建模能力与代码实现水平,支持二次开发与创新研究。; 阅读建议:建议结合Matlab代码与优化理论同步研读,重点理解目标函数的设计逻辑、各类物理与运行约束的数学表达以及求解器的调用流程,推荐使用YALMIP等建模工具辅助实现,以提高模型构建效率与可读性,便于深入理解与后续拓展。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值