Visual Studio 2013でカバレッジ測定する – Jenkins Advent Calendar 2013

jenkinsこの記事は、Jenkins Advent Calendar 2013の21日目の記事です。

とてもマイナーなネタと思われます。また、私自身はJenkinsの管理者ではない(ド素人)ので、外した事を書いているかもしれません。誰かの役に立つかなぁ?困っている人は居そうですが…

能書きは良いので、ツールを使いたいって人は、CodePlexで公開しているのでどうぞ。


バージョン0:事の始まり

タイトル通り、Visual Studio 2013でカバレッジ測定します。それだけであればVSで完結してしまうのですが、カバレッジ測定をJenkinsで行って、結果をEmmaプラグインで表示したい、というネタです。

経緯:とある開発プロジェクトが開始され、私がプロジェクトに参加した時点で、以下の事が決まっていました。

  • プロジェクト管理はALMinium、CIはJenkins、ソース管理はSubversionで行う。
  • クライアント側はWindows、サーバー側はJavaで開発する。

・・・いや、Java側は多分快適なんでしょうが、Windows側は色々苦しいんですよ、それだと(ちなみに私はWindows側のアーキテクトとして作業する予定で参加)。とは言っても、決まってしまっているので、Java側主導でWindows側が放置っぽくなってしまわないようにしないとなぁ…と思いました。

# 全部Windowsなら、間違いなくTFSでサクッとやるところなんですが。あ、すいません、Jenkinsの話でした(汗

で、早速CIビルド環境の構築で、Windowsのエージェントを動かす環境を整えたり、Visual Studioの環境をちまちまと整えなければならないなど、早くもメンドクサイ感が(名誉のために補足:環境構築してくれた方は前向きに取り組んでいます)。

そして、MSTestでのテスト実行もOKとなったので、次の段階としてカバレッジ測定結果を表示させようとしたのです。

この辺りとか:[hudson]hudsonでMSTestのテスト結果とカバレッジをレポートする設定をしてみた。
この辺りとか:Jenkins で MSTest の結果とコードカバレッジを表示する方法

うを、またしてもメンドクサイ事態に。専任でもない人にこういうのを頼み続けると、本当に必要な時に動いてくれなくなってしまうので、私の方で何とかしようと。
(その代わり、業務時間外でやることで、この部分は自分のネタにさせてもらいました)

TooDifficultProcessJenkinsの知識ほぼ0であったので、カバレッジデータの変換プロセスを把握する事から開始。上のブログ記事によると、MSTestで出力されるカバレッジデータ(*.coverage)はバイナリーの独自形式で、これをMSBuildのカスタムタスクでXML形式(内容不明)に変換、その後XMLスタイルシートでEmma形式に変換させる。Emma形式になれば、Emmaプラグインで表示する事が出来る、という流れを把握。

ようするに、これを自動化して、単一のツールにしてしまえば扱いが大幅に楽になる。別のJenkins環境への展開も容易になるはずだ。


バージョン1:いきなり問題発覚 orz

バイナリのカバレッジデータを変換するMSBuildタスクのプラグイン「VS Coverage to XML Converter MSBuild task」は、VS2008に対応したコードだ。調査を続けると、どうもカバレッジデータを読み取るDLLが、Visual Studioのバージョン毎に違っている。おまけに、VS2010からは大幅に仕様が変更されており、ファイルが全く認識されない。

つまり、だ。カバレッジデータを生成したVisual Studioのバージョンを使って解析しないと、結果が怪しいか又はエラーが発生するということだ。

プラグインの配布ファイルには、何故かカバレッジ測定結果を変換するライブラリ「Microsoft.VisualStudio.Coverage.Analysis.dll」が添付されている。本来、このファイルはVisual Studioに添付されているものであり、含まれていないのが筋だ。MSが配布しているのだから、ライセンス的には問題ない(?)のだろうが、何れにしてもこれはVS2008向けだ。他のバージョンでは使用しないほうが良い。
(ちなみにこのプラグインは、今に至るまで全く更新されていない)

そもそも、カバレッジデータを収集する場合は、カバレッジ測定可能なVisual Studioがビルド環境にインストールされているはずだ。だから、初めからそれを使えばいいのだ。

「VS Coverage to XML Converter MSBuild task」のソースコードを見ると、ライブラリを呼び出してバイナリデータをXMLに変換している。何のことは無い、バイナリデータがDataSet(インメモリのテーブルコレクション)に変換されるので、DataSet.WriteXmlでデータセット形式のXMLで書き出しているだけだ。

そして、このXMLをjenkins.orgで配布されているXMLスタイルシートを使って、Emma形式に変換する。どうせC#で書いた方が速いしシンプルなので、Command Line Transformation Utilityには頼らない。

変換は全てインメモリでやれば、テンポラリファイルにまつわる問題を排除できるし、XMLスタイルシートもEXE内に埋め込めば、ファイルが読めなくて刺さるとかいう問題もなくなるので、この辺りはツールとしてシンプルに磨き上げる。

次に、VS2010以降のライブラリとの互換性の問題。そもそも変換ライブラリのクラスがちょっと違ってる。ILSpyを使ってごにょごにょし、ちょっとテストコードを書いて、使用方法を把握。ただ、ツールのコマンドラインオプションで対象のVisual Studioを切り替えられるようにしたかったため、共通のインターフェイスを独立したDLLに定義し、それぞれのバージョンに対応するライブラリとして、プラグインのような形で動的にロードするように実装。これで、バージョン指定で呼び換えが可能になった。

vscoveragetoemma異なるバージョンの、同一の名称を持つDLLの読み込みを可能にするため、サイドバイサイドアセンブリ構成という機能を使う。バージョン毎にサブフォルダを作り(VS2005, VS2008, VS2010と言った具合に)、その中にインストール済みのカバレッジ変換DLLを手動コピーして配置してもらう。この部分が導入で面倒なところだ。後で何とかしたい。

最後にツールの体裁を整え、CodePlexにサインアップして公開した。合わせて、この時点のソースコードをCodePlexにチェックインした。

このツールを客先で使ってもらった。Jenkinsへの登録は、コマンドラインツールとして起動するだけだ。引数も出来るだけ省略可能にしてあるし、ファイルを明示的に指定しなくて、フォルダ指定だけで勝手に探索して変換してくれる。おまけに並列実行させているので、大量のカバレッジファイルがあっても高速に処理できる。

めでたしめでたし…


バージョン2:何かがおかしい

数日間、カバレッジの結果をJenkins上で眺めていたが、どうも変だ。やけに数値が少ない。ブロック数や行数など、想定よりも少なすぎる。Visual Studio上でのカバレッジブラウザと結果を見比べているうちに、何となく原因が見えてきた。

これはEmmaプラグインの問題だ。というと怒られるので、見解の相違といったところか(+XMLスタイルシートの問題でもある)。Emmaプラグインの入力となるXMLファイルは、「Javaのカバレッジ測定結果」が前提なのだ。Javaと.NET CLSの型システムは似ている所は多いが、根本的に思想が異なるので、無理やり同一視すると今回のような問題が発生する。

  • JavaTypeSystemJavaでは、パッケージと物理的なソースコードの配置は1対1で対応させることが求められる(多分?)。そのためか、Emmaの解析階層構造は、「パッケージ」「ソースファイル」「クラス」「メソッド」の順で並ぶ(実際にはパッケージが固められたjarが、暗黙に先頭に存在する)。
    しかし、.NET CLSはパッケージに対応する「名前空間」と、ソースファイルの物理的な対応付けは存在しない。それどころか、jarに対応する「アセンブリ(DLL)」は、名前空間と直接対応しない。例えば、とある名前空間が別々のアセンブリに同居していても何ら問題はない。名前空間とは、構造を定義しない単なる名前でしかない。
    ややこしいと思うかもしれないが、物理的な特性と型の論理構造が分離されているので、これらは別々に設計出来るのが.NET CLS型システムの利点なのだ。
  • dotNETTypeSystemJavaでは、一つのクラスは一つのソースコードに対応する。そのため、Emmaの階層構造で「ソースファイル」が「クラス」の前に存在しても問題はない。しかし.NET CLS(C#)では、partial classが存在する。partial classは、単一のクラスに含まれるメンバを、複数のソースコードに記述する事が出来る機能だ。そのため、Emmaの階層構造では順序を逆にしないと、正しく構造を反映した事にならない。

従って、Emmaの階層構造をそのまま当てはめるには無理がある。XMLスタイルシートで変換されたXMLを眺めると、同一のパッケージ配下に、同じソースファイルのエレメントが複数存在したり、同じクラスのエレメントが複数存在したりしている。その結果、Emma側ではどれか一つだけが読み取られて表示されているようだ。だから、件数が少なくなる。

もっとも、XMLスタイルシートの集計が十分に賢ければ、このようなエレメントを同一とみなして、それっぽく集計できたのかもしれない。原因が分かった当初、このスタイルシートを修正しようと思ったのだが、あまりに複雑で理解不能であったため、早々に放棄。自分で一から集計コードを書く事にした。

どうせ自分で集計するなら、もうXMLに頼る必要もない。DataSetから直接値を取得して集計すればいい。そういうわけで、いったんXMLをMemoryStreamに書いていたコードもバッサリと切り捨て、コードをシンプルにすることが出来た。ただ、集計計算を行う部分は、「Javaがこうだから.NETではこうなって…」みたいな事を考えるたびに混乱し、正しい集計アルゴリズムに落ち着くまでが苦痛だった。

こんな作業が待っていることがはじめから分かっていたら、作ってなかったかもしれない…

SimpleProcess最後に、懸念事項であった、インストールされているカバレッジ変換DLLを手動コピーしなければならない作業を排除した。どのVisual Studioがインストールされているか、どこにインストールされているかは、レジストリを見ればわかる。なので、レジストリを見て、自動的にDLLのパスを把握し、自力でそこからロードするようにした。DLLを動的に読み込むため、サイドバイサイドアセンブリ構成は不要になったので削除。更に各DLLのクラスをリフレクションで参照するようにして、DLLの依存関係も排除。これで、ツールをダウンロードしたらzipファイルから展開して、すぐに使う事が出来るようになった。


ふりかえり

こうして、最新版「Visual Studio Coverage file to Emma converter」はリリースされ、晴れて正しいカバレッジ集計をJenkinsで確認できるようになりました。

一点だけ心残りなのは、やはりJavaと.NET CLSでの型システムの違いによる集計の考え方でしょうか。結局、「名前空間」を「パッケージ」にマッピングする方法で解決したのですが、閲覧者が求めている集計とちょっと違う気がします。無理やりにでも「アセンブリ(DLL)」を「パッケージ」にマッピングするように集計すべきだったかもしれない、あるいはオプションで切り替えれるようにした方が良かったかもしれない、という気がしています。

あとは、物好きな人が改良して下さい。私は疲れたので一休みしますww

Visual Studio Coverage file to Emma converter

CodePlex now!
And 1.0.1.0 released!!

Visual Studio Coverage file to Emma converter : CodePlex


Visual Studio Coverage file to Emma converter.
Simple solution, can apply only one tool to five Visual Studio versions.
Fast multicore processing.

Included:

  • Command line executable.
  • MSBuild custom task.

Thank you choosing this tool, probably use with jenkins.
(I’m not jenkins professional ops, may not operate this tool…)

Require:
.NET Framework 4.5.1 Runtime environment.
Visual Studio 2005, 2008, 2010, 2012 and/or 2013 versions (Require code coverage option)

Setup:
1. Copy VSCoverageToEmma.exe, VSCoverageToEmma.dll to your tool folder.
2. Copy VS
folders (ex:VS2013) to your tool folder.
3. Copy your Visual Studio’s “Microsoft.VisualStudio.Coverage..dll”
(In %VSROOTFOLDER%Common7IDEPrivateAssemblies) into VS
folders.

Tool folder example:

  Tool
    +--- VSCoverageToEmma.exe
    +--- VSCoverageToEmma.Interfaces.dll
    +--- VSCoverageToEmma.Core.dll
    +--- VSCoverageToEmma.VS2005Converter.dll
    +--- VSCoverageToEmma.VS2008Converter.dll
    +--- VSCoverageToEmma.VS2010Converter.dll
    +--- VSCoverageToEmma.VS2012Converter.dll
    +--- VSCoverageToEmma.VS2013Converter.dll
    +--- VS2005
    |       +--- Microsoft.VisualStudio.Coverage.Analysis.dll (From Visual Studio 2005, if use)
    +--- VS2008
    |       +--- Microsoft.VisualStudio.Coverage.Analysis.dll (From Visual Studio 2008, if use)
    +--- VS2010
    |       +--- Microsoft.VisualStudio.Coverage.Analysis.dll (From Visual Studio 2010, if use)
    |       +--- Microsoft.VisualStudio.Coverage.Interop.dll  (From Visual Studio 2010, if use)
    |       +--- Microsoft.VisualStudio.Coverage.Symbols.dll  (From Visual Studio 2010, if use)
    +--- VS2012
    |       +--- Microsoft.VisualStudio.Coverage.Analysis.dll (From Visual Studio 2012, if use)
    |       +--- Microsoft.VisualStudio.Coverage.Interop.dll  (From Visual Studio 2012, if use)
    |       +--- Microsoft.VisualStudio.Coverage.Symbols.dll  (From Visual Studio 2012, if use)
    +--- VS2013
            +--- Microsoft.VisualStudio.Coverage.Analysis.dll (From Visual Studio 2013, if use)
            +--- Microsoft.VisualStudio.Coverage.Interop.dll  (From Visual Studio 2013, if use)
            +--- Microsoft.VisualStudio.Coverage.Symbols.dll  (From Visual Studio 2013, if use)

Command line usage (VS2013):

  C:TEMPVSCoverageToEmmaDebug>VSCoverageToEmma.exe VS2013 "C:TEMPCoverageTargetTestResults" "C:TEMPCoverageTargetbinDebug" "C:TEMPCoverageTargetbinDebug" "C:TEMPCoverageTargetTestResults"

If use VS2010/VS2012/VS2013 converter, automatically recursive search binary/symbol files in nested folders.

MSBuild usage:
Target assembly is “VSCoverageToEmma.Core.dll”, task name is “VSCoverageToEmma”.
ConverterName: required (ex: “VS2005”)
VSCoverageFolderPath: required (path)
BinariesFolderPath: optional (path)
SymbolsFolderPath: optional (path)
EmmaFolderPath: optional (path)
VSCoverageFiles: output (path list)
EmmaFiles: output (path list)
Methods: output (number)

MSBuild limitation: Tool execution failed in 64bit process. (VS Coverage library required 32bit mode)

Download from SkyDrive: VSCoverageToEmma-1.0.zip