コードカヴァレッジ計測

コードカヴァレッジとは

あるプログラムコードについて実行した際に、実際にコード中の命令を何割実行したかを数値に表したものを言う。

計測手法

方法は様々である。たとえば、Linux kernel の fuzzer である Google の syzkaller を用いれば、ユーザーアプリケーションへの入力を自動で変化させつつコードカヴァレッジを確認させてくれる。また、Linux kernel のコンフィグ自体にもカヴァレッジについてのコンフィグが存在する。これは masami256 さんのブログなどが詳しい

今回は gcc に組込みの手法でカヴァレッジテストを実行する手法についてメモをする

gcov

gcov がカヴァレッジ情報を .gcov という拡張子でダンプすることでカヴァレッジを確認できる。このコマンドは gcc に附属している。

使い方を見ていく

このようなソースコードを用意し、次のようにコンパイルする。

$ gcc -fprofile-arcs -ftest-coverage -o cov_test cov_test.c

すると次のようなファイルが用意されるはずだ。

$ ls
cov_test cov_test.c cov_test.gcno

これでカヴァレッジテストの準備は完了した。では、実際にカヴァレッジテストを行なってみよう。まずは、何も引数を与えずコンパイルしたコードを実行する。

$ ./cov_test
./cov_test: you need to pass arguments at least 1
$ ls
cov_test  cov_test.c  cov_test.gcda  cov_test.gcno

.gcno に加えて .gcda が追加されている。このファイルがカヴァレッジテストの結果を格納している。ここで、gcov コマンドを用いてみよう。

$ gcov cov_test.gcda
File 'cov_test.c'
Lines executed:38.89% of 18
Creating 'cov_test.c.gcov'

cov_test.c というコード全体が 18 行で、そのうち 38.89% が実行されたということを物語っている。また、ここで新たに出力されたファイルの中身を見てみよう。

$ cat cov_test.c.gcov
        -:    0:Source:cov_test.c
        -:    0:Graph:cov_test.gcno
        -:    0:Data:cov_test.gcda
        -:    0:Runs:1
        -:    0:Programs:1
        -:    1:#include <stdio.h>
        -:    2:#include <stdlib.h>
        -:    3:
        1:    4:void usage(char *cmd)
        -:    5:{
        1:    6:    fprintf(stderr, "%s: you need to pass arguments at least 1\n", cmd);
        1:    7:    return;
        -:    8:}
        -:    9:
        1:   10:int main(int argc, char **argv)
        -:   11:{
        1:   12:    if (argc < 2) {
        1:   13:        usage(argv[0]);
        1:   14:        exit(EXIT_FAILURE);
        -:   15:    }
        -:   16:
    #####:   17:    switch (argc) {
    #####:   18:        case 2:
    #####:   19:            printf("you passed 1 argument\n");
    #####:   20:            break;
    #####:   21:        case 3:
    #####:   22:            printf("you passed 2 arguments\n");
    #####:   23:            break;
    #####:   24:        default:
    #####:   25:            printf("you passed more than 3 arguments\n");
    #####:   26:            break;
        -:   27:    }
    #####:   28:    return EXIT_SUCCESS;
        -:   29:}

見てのとおり、コード全体のうち空行やプリプロセッサの行、ブラケットのみの行などを除いた行のうち、実行された 1 でマークされており、実行されなかった行は ##### でマークされている。この両者の合計は 18 行であり、そのうち 1 でマークした行は 7 行、7/18 = 0.38.. というわけである。続けて、引数をいくつか与えてみる。

$ ./cov_test 1
you passed 1 argument
$ gcov cov_test.c.gcov
File 'cov_test.c'
Lines executed:66.67% of 18
Creating 'cov_test.c.gcov'

$ ./cov_test 1 2
you passed 2 arguments
$ gcov cov_test.c.gcov
File 'cov_test.c'
Lines executed:83.33% of 18
Creating 'cov_test.c.gcov'

どうだろうか。実行する度に引数を変えているため、当然コードパスも異なる。そして、そのぶんカヴァレッジ率が積算されている。

$ cat cov_test.c.gcov
        -:    0:Source:cov_test.c
        -:    0:Graph:cov_test.gcno
        -:    0:Data:cov_test.gcda
        -:    0:Runs:3
        -:    0:Programs:1
        -:    1:#include <stdio.h>
        -:    2:#include <stdlib.h>
        -:    3:
        1:    4:void usage(char *cmd)
        -:    5:{
        1:    6:    fprintf(stderr, "%s: you need to pass arguments at least 1\n", cmd);
        1:    7:    return;
        -:    8:}
        -:    9:
        3:   10:int main(int argc, char **argv)
        -:   11:{
        3:   12:    if (argc < 2) {
        1:   13:        usage(argv[0]);
        1:   14:        exit(EXIT_FAILURE);
        -:   15:    }
        -:   16:
        2:   17:    switch (argc) {
        1:   18:        case 2:
        1:   19:            printf("you passed 1 argument\n");
        1:   20:            break;
        1:   21:        case 3:
        1:   22:            printf("you passed 2 arguments\n");
        1:   23:            break;
    #####:   24:        default:
    #####:   25:            printf("you passed more than 3 arguments\n");
    #####:   26:            break;
        -:   27:    }
        2:   28:    return EXIT_SUCCESS;
        -:   29:}

.gcov を確認してみると、実行しなかった行を示す ##### のマークは減っている。その変わり、新たに実行された行は 1 がマークされており、また、何度も実行された行はその回数に合わせ 23 がマークされている。

$ ./cov_test 1 2 3
you passed more than 3 arguments
$ gcov cov_test.gcda
File 'cov_test.c'
Lines executed:100.00% of 18
Creating 'cov_test.c.gcov'

cat cov_test.c.gcov
        -:    0:Source:cov_test.c
        -:    0:Graph:cov_test.gcno
        -:    0:Data:cov_test.gcda
        -:    0:Runs:4
        -:    0:Programs:1
        -:    1:#include <stdio.h>
        -:    2:#include <stdlib.h>
        -:    3:
        1:    4:void usage(char *cmd)
        -:    5:{
        1:    6:    fprintf(stderr, "%s: you need to pass arguments at least 1\n", cmd);
        1:    7:    return;
        -:    8:}
        -:    9:
        4:   10:int main(int argc, char **argv)
        -:   11:{
        4:   12:    if (argc < 2) {
        1:   13:        usage(argv[0]);
        1:   14:        exit(EXIT_FAILURE);
        -:   15:    }
        -:   16:
        3:   17:    switch (argc) {
        1:   18:        case 2:
        1:   19:            printf("you passed 1 argument\n");
        1:   20:            break;
        1:   21:        case 3:
        1:   22:            printf("you passed 2 arguments\n");
        1:   23:            break;
        1:   24:        default:
        1:   25:            printf("you passed more than 3 arguments\n");
        1:   26:            break;
        -:   27:    }
        3:   28:    return EXIT_SUCCESS;
        -:   29:}

そしてついには、全てのコードパスを実行することに成功し、100% が表示され、.gcov ファイルにおいても全ての行に数字がマークされている。また、4 回の実行のうち、どの行が必ず実行され、どの行が場合によって実行されないかもマークされた数字によってある程度把握可能になる。

lcov, genhtml

これは別個インストールが必要である。インストール後、.gcda があるディレクトリで次のように用いる。

$ lcov -c -d. -o cov.info
Capturing coverage data from .
Found gcov version: 7.3.1
Scanning . for .gcda files ...
Found 1 data files in .
Processing cov_test.gcda
TN:
SF:/home/orumin/cov/cov_test.c
FN:4,usage
FN:10,main
FNDA:1,usage
FNDA:4,main
FNF:2
FNH:2
DA:4,1
DA:6,1
DA:7,1
DA:10,4
DA:12,4
DA:13,1
DA:14,1
DA:17,3
DA:18,1
DA:19,1
DA:20,1
DA:21,1
DA:22,1
DA:23,1
DA:24,1
DA:25,1
DA:26,1
DA:28,3
LF:18
LH:18
end_of_record
Finished .info-file creation
$ genhtml cov.info
Reading data file cov.info
Found 1 entries.
Found common filename prefix "/home/orumin"
Writing .css and .png files.
Generating output.
Processing file cov/cov_test.c
Writing directory view page.
Overall coverage rate:
  lines......: 100.0% (18 of 18 lines)
  functions..: 100.0% (2 of 2 functions)

すると、次のようにファイルが出力される

$ ls
amber.png  cov  cov.info  cov_test  cov_test.c  cov_test.gcda  cov_test.gcno  emerald.png  gcov.css  glass.png  index.html  index-sort-f.html  index-sort-l.html  ruby.png  snow.png  updown.png

すなわち、gcov コマンドで plain text を出力する代わりに HTML でグラフィカルにカヴァレッジテストの結果を出力できるというわけである。