コード網羅率を観察する

コード網羅ツールは、コードのどの部分が実際に実行され、どの部分が一回も呼ばれなかったかを、プログラマが判断できるようにする。GHCには計器付きコードを生成するオプションがあり、生成されたコードはHaskell Program Coverage(HPC)ツールキット(GHCに付属している)の一員としてコード網羅率を記録する。このコード網羅率情報は、HPCツールを使って人間が理解できる形式に変換することができる。

正しい計器付きコードは二種類の網羅率情報を供給する。ソース網羅率と真偽値制御網羅率である。ソース網羅率は、プログラムがどれくらい隅々まで使われたかの度合いで、三つの水準、すなわち宣言(最上位のものと局所的なものの両方)、分岐(複数の等式やcaseの枝からの選択)、式(あらゆる深さのもの)で測られる。真偽値網羅率は、構文的に真偽値が要求される全ての場所(ガード、条件、qualifier(訳者: qualifierって何?))について、どれくらいTrueとFalseの両方が得られたかである。

HPCは、この両方の種類の情報を、二つの主要な方法で表示する。一つは、統計要約の付いたテキスト形式の報告書(hpc報告)であり、もう一つはソースの色付きマークアップ(hpcマークアップ)である。真偽値網羅については、それぞれのガード、条件、qualifierについて、四つの結果があり得る。TrueとFalseの両方が起こった、Trueだけ、Falseだけ、一回も評価されなかった、である。hpcマークアップの出力において、黄色の背景による強調はプログラムのその部分が一回も評価されなかったことを示し、緑の背景は常にTrueだったことを、赤い背景は常にFalseだったことを示す。

小さな例: 逆数をとる

例として、Recip.hsという、逆数の正確な十進表現を計算するプログラムを使おう。小数の循環部分は括弧に入れて示すことにする。

reciprocal :: Int -> (String, Int)
reciprocal n | n > 1 = ('0' : '.' : digits, recur)
             | otherwise = error
	          "attempting to compute reciprocal of number <= 1"
  where
  (digits, recur) = divide n 1 []
divide :: Int -> Int -> [Int] -> (String, Int)
divide n c cs | c `elem` cs = ([], position c cs)
              | r == 0      = (show q, 0)
              | r /= 0      = (show q ++ digits, recur)
  where
  (q, r) = (c*10) `quotRem` n
  (digits, recur) = divide n r (c:cs)

position :: Int -> [Int] -> Int
position n (x:xs) | n==x      = 1
                  | otherwise = 1 + position n xs

showRecip :: Int -> String
showRecip n =
  "1/" ++ show n ++ " = " ++
  if r==0 then d else take p d ++ "(" ++ drop p d ++ ")"
  where
  p = length d - r
  (d, r) = reciprocal n

main = do
  number <- readLn
  putStrLn (showRecip number)
  main

HPCの計器付与は-fhpcフラグで有効になる。

$ ghc -fhpc Recip.hs --make 

HPC index(.mix)ファイルはサブディレクトリ.hpcに置かれる。これはHPCにとっての.hiファイルのようなものだと考えられる。

$ ./Recip
1/3
= 0.(3)

以下のようにして、網羅率のテキスト要約を生成できる。

$ hpc report Recip
 80% expressions used (81/101)
 12% boolean coverage (1/8)
      14% guards (1/7), 3 always True, 
                        1 always False, 
                        2 unevaluated
       0% 'if' conditions (0/1), 1 always False
     100% qualifiers (0/0)
 55% alternatives used (5/9)
100% local declarations used (9/9)
100% top-level declarations used (5/5)

マークアップされたソースを生成することもできる。

$ hpc markup Recip
writing Recip.hs.html

これによって、Haskellモジュール一つにつき一つのファイルと、四つのインデックスファイル(hpc_index.html、hpc_index_alt.html、hpc_index_exp.html、hpc_index_fun.html)が生成される。

網羅率の測定器を付与するオプション

網羅率を有効にするのは簡単で、-fhpcフラグを使えばよい。計器付きのものと計器付きでないものは自由に混ぜて構わない。Mainモジュールをコンパイルするとき、hpcコンパイルされたモジュールがあれば、GHCが自動的にそれを検出して、正しい初期化コードを追加する。

hpcツールキット

hpcツールキットはcvs/svn/darcs的なインタフェースを持っている。つまり、一つのバイナリに沢山の機能単位が含まれている。

$ hpc 
Usage: hpc COMMAND ...

Commands:
  help        Display help for hpc or a single command
Reporting Coverage:
  report      Output textual report about program coverage
  markup      Markup Haskell source with program coverage
Processing Coverage files:
  sum         Sum multiple .tix files in a single .tix file
  combine     Combine two .tix files in a single .tix file
  map         Map a function over a single .tix file
Coverage Overlays:
  overlay     Generate a .tix file from an overlay file
  draft       Generate draft overlay that provides 100% coverage
Others:
  show        Show .tix file in readable, verbose format
  version     Display version for hpc

一般的に言って、これらのオプションは、計器付きバイナリが生成した後の.tixファイルに対して動作する。hpcは、生の.tixファイルと、生成される詳細な報告書の間の仲介として働く。

hpcツールは、アプリケーションをビルドした場所の最上位ディレクトリに居て、その同じ最上位ディレクトリに.tixファイルがあることを前提とする。別のディレクトリに対してhpcを使うには--srcdirフラグを使うことができる。また、複数の場所でコンパイルされたプログラム(パッケージでは普通のことである)を分析する場合は、--srcdirを複数回使えばよい。

ここからは、hpcのメジャーモードについて詳しく説明する。

hpc report

hpc reportは、網羅率のテキスト形式の報告書を与える。デフォルトで、includeやexcludeが使われない限り、報告書を生成する上ですべてのモジュールとパッケージが考慮に入る。--per-moduleフラグが使われない限り、報告書は要約である。--xml-outputオプションは、hpcを使って網羅率を収集するツールのためにある。

$ hpc help report
Usage: hpc report [OPTION] .. <TIX_FILE> [<MODULE> [<MODULE> ..]]

Options:

    --per-module                  show module level detail
    --decl-list                   show unused decls
    --exclude=[PACKAGE:][MODULE]  exclude MODULE and/or PACKAGE
    --include=[PACKAGE:][MODULE]  include MODULE and/or PACKAGE
    --srcdir=DIR                  path to source directory of .hs files
                                  multi-use of srcdir possible
    --hpcdir=DIR                  sub-directory that contains .mix files
                                  default .hpc [rarely used]
    --xml-output                  show output in XML

hpc markup

hpc markupはソースファイルをマークアップして色付きhtmlにする。

$ hpc help markup
Usage: hpc markup [OPTION] .. <TIX_FILE> [<MODULE> [<MODULE> ..]]

Options:

    --exclude=[PACKAGE:][MODULE]  exclude MODULE and/or PACKAGE
    --include=[PACKAGE:][MODULE]  include MODULE and/or PACKAGE
    --srcdir=DIR                  path to source directory of .hs files
                                  multi-use of srcdir possible
    --hpcdir=DIR                  sub-directory that contains .mix files
                                  default .hpc [rarely used]
    --fun-entry-count             show top-level function entry counts
    --highlight-covered           highlight covered code, rather that code gaps
    --destdir=DIR                 path to write output to

hpc sum

hpc sumは任意の個数の.tixファイルを足し合わせて、単一の.tixファイルにする。hpc sumは元の.tixファイルを変更せず、新しい.tixファイルを作る。

$ hpc help sum
Usage: hpc sum [OPTION] .. <TIX_FILE> [<TIX_FILE> [<TIX_FILE> ..]]
Sum multiple .tix files in a single .tix file

Options:

    --exclude=[PACKAGE:][MODULE]  exclude MODULE and/or PACKAGE
    --include=[PACKAGE:][MODULE]  include MODULE and/or PACKAGE
    --output=FILE                 output FILE
    --union                       use the union of the module namespace (default is intersection)

hpc combine

hpc combineはhpcのスイスアーミーナイフである。.tixファイルの差(difference)をとること、ある.tixファイルから別の.tixファイルを引く(subtract)こと、二つの.tixファイルを足す(add)ことができる。hpc combineは元の.tixファイルを変更せず、新しい.tixファイルを生成する。

$ hpc help combine
Usage: hpc combine [OPTION] .. <TIX_FILE> <TIX_FILE>
Combine two .tix files in a single .tix file

Options:

    --exclude=[PACKAGE:][MODULE]  exclude MODULE and/or PACKAGE
    --include=[PACKAGE:][MODULE]  include MODULE and/or PACKAGE
    --output=FILE                 output FILE
    --function=FUNCTION           combine .tix files with join function, default = ADD
                                  FUNCTION = ADD | DIFF | SUB
    --union                       use the union of the module namespace (default is intersection)

hpc map

hpc mapは.tixファイルを反転(invert)したり零化(zero)したりする。hpc mapは元の.tixファイルを変更せず、新しい.tixファイルを生成する。

$ hpc help map
Usage: hpc map [OPTION] .. <TIX_FILE> 
Map a function over a single .tix file

Options:

    --exclude=[PACKAGE:][MODULE]  exclude MODULE and/or PACKAGE
    --include=[PACKAGE:][MODULE]  include MODULE and/or PACKAGE
    --output=FILE                 output FILE
    --function=FUNCTION           apply function to .tix files, default = ID
                                  FUNCTION = ID | INV | ZERO
    --union                       use the union of the module namespace (default is intersection)

hpc overlayとhpc draft

overlayはHPCの実験的機能で、網羅率のテキスト記述である。hpc draftは.tixファイルからdraft overlayを作るのに使われ、hpc overlayはoverlayから.tixファイルを生成する。

% hpc help overlay
Usage: hpc overlay [OPTION] .. <OVERLAY_FILE> [<OVERLAY_FILE> [...]]

Options:

    --srcdir=DIR   path to source directory of .hs files
                   multi-use of srcdir possible
    --hpcdir=DIR   sub-directory that contains .mix files
                   default .hpc [rarely used]
    --output=FILE  output FILE
% hpc help draft  
Usage: hpc draft [OPTION] .. <TIX_FILE>

Options:

    --exclude=[PACKAGE:][MODULE]  exclude MODULE and/or PACKAGE
    --include=[PACKAGE:][MODULE]  include MODULE and/or PACKAGE
    --srcdir=DIR                  path to source directory of .hs files
                                  multi-use of srcdir possible
    --hpcdir=DIR                  sub-directory that contains .mix files
                                  default .hpc [rarely used]
    --output=FILE                 output FILE

Haskell Program Coverageの注意点と短所

HPCは.tixファイルをロックしようとしないので、同じディレクトリでバイナリを並行に実行すると競合条件が現れるだろう。バイナリの名前を変更する以外に、生成される.tixファイルの名前を変えるすべはない。HPCはGHCiでは動作しない。