gcovのログファイルをマージするツールを拾った && 手直しした

Cで書いたコードの単体テストにgcovを使っている。gcovは*.gcovなログファイルを出力するのだけど、そのログファイルにはどの行を何回実行したか/未実行の行はどこかが表示されていて、非常に便利だ。

ただ、手元で使っていて1つだけ不便な点が生じてきた。

gcovのログファイルは、

  1. テストしたいソースを含むアプリを「-fprofile-arcs -ftest-coverage」付きでgccでビルドする。
  2. ビルドしたアプリを実行する。
  3. 上記で実行した時のログ情報をgcovで取り出す。

という流れで取得する。

なので、あるソースファイルに対してテストアプリを複数作成してテストした場合、ログファイルはテストアプリごとに生成されることになる。

ログファイルが複数あると、例えば未実行のパスがないか確認する時に複数のログを見比べないといけない。手作業でやるのは面倒だし、間違いの元だ。ログファイルをマージして、各行の実行回数の合計を表示するようなツールが欲しくなってきた。

という訳でgcovのログファイルをマージするツールがないか探したら、gccのメーリングリストのアーカイブにgccr.plというものを見つけた。「gccのcontribにコミットしてくれると嬉しいな(超意訳)」的な感じでメーリングリストにポストされたみたいだけど、その後どうなったかは不明*1。ライセンスも不明*2

で、試してみた。サンプルのログファイルはこの2つ。

D:\temp\gccr>type test1\sample.c.gcov
        -:    0:Source:sample.c
        -:    0:Graph:sample.gcno
        -:    0:Data:sample.gcda
        -:    0:Runs:1
        -:    0:Programs:1
        -:    1:#include <stdio.h>
        -:    2:#include <stdlib.h>
        -:    3:
        -:    4:int main(int argc, char **argv)
function main called 1 returned 100% blocks executed 50%
        1:    5:{
        1:    6:        if (argc <= 1) {
        1:    7:                (void) puts("no arguments");
        1:    8:                return EXIT_FAILURE;
        -:    9:        } else {
    #####:   10:                int i;
        -:   11:
    #####:   12:                for (i = 1; i < argc; ++i) {
    #####:   13:                        (void) printf("%d: %s\n", i, argv[i]);
        -:   14:                }
        -:   15:        }
        -:   16:
    #####:   17:        return EXIT_SUCCESS;
        -:   18:}

D:\temp\gccr>type test2\sample.c.gcov
        -:    0:Source:sample.c
        -:    0:Graph:sample.gcno
        -:    0:Data:sample.gcda
        -:    0:Runs:1
        -:    0:Programs:1
        -:    1:#include <stdio.h>
        -:    2:#include <stdlib.h>
        -:    3:
        -:    4:int main(int argc, char **argv)
function main called 1 returned 100% blocks executed 80%
        1:    5:{
        1:    6:        if (argc <= 1) {
    #####:    7:                (void) puts("no arguments");
    #####:    8:                return EXIT_FAILURE;
        -:    9:        } else {
        1:   10:                int i;
        -:   11:
        4:   12:                for (i = 1; i < argc; ++i) {
        3:   13:                        (void) printf("%d: %s\n", i, argv[i]);
        -:   14:                }
        -:   15:        }
        -:   16:
        1:   17:        return EXIT_SUCCESS;
        -:   18:}

D:\temp\gccr>

このファイルをgccrでマージしてみる。結果は標準出力に書き出されるので、必要に応じてリダイレクトしてファイルに落とし込むことになる。

D:\temp\gccr>perl gccr.pl test1\sample.c.gcov =T1= test2\sample.c.gcov =T2=
        -:      0:Source:sample.c
        -:      0:Graph:sample.gcno
        -:      0:Data:sample.gcda
        -:      0:Runs:1
        -:      0:Programs:1
        -:      1:#include <stdio.h>
        -:      2:#include <stdlib.h>
        -:      3:
        -:      4:int main(int argc, char **argv)
Use of uninitialized value in string eq at gccr.pl line 345.
Use of uninitialized value in string eq at gccr.pl line 345.
Use of uninitialized value in string eq at gccr.pl line 345.
Use of uninitialized value in string eq at gccr.pl line 345.
Use of uninitialized value in string eq at gccr.pl line 345.
Use of uninitialized value in string eq at gccr.pl line 345.
Use of uninitialized value in string eq at gccr.pl line 345.
Use of uninitialized value in string eq at gccr.pl line 345.
                5:      {
        1:      5: =T1=
        1:      5: =T2=
                6:              if (argc <= 1) {
        1:      6: =T1=
        1:      6: =T2=
                7:                      (void) puts("no arguments");
        1:      7: =T1=
     ####:      7: =T2=
                8:                      return EXIT_FAILURE;
        1:      8: =T1=
     ####:      8: =T2=
        -:      9:      } else {
                10:                     int i;
     ####:      10: =T1=
        1:      10: =T2=
        -:      11:
                12:                     for (i = 1; i < argc; ++i) {
     ####:      12: =T1=
        4:      12: =T2=
                13:                             (void) printf("%d: %s\n", i, argv[i]);
     ####:      13: =T1=
        3:      13: =T2=
        -:      14:             }
        -:      15:     }
        -:      16:
                17:             return EXIT_SUCCESS;
     ####:      17: =T1=
        1:      17: =T2=
        -:      18:}
50.00% of 8 lines executed on target =T1=
75.00% of 8 lines executed on target =T2=
100.00% of 8 lines executed across all files

D:\temp\gccr>perl gccr.pl -c test1\sample.c.gcov =T1= test2\sample.c.gcov =T2=
        -:      0:Source:sample.c
        -:      0:Graph:sample.gcno
        -:      0:Data:sample.gcda
        -:      0:Runs:1
        -:      0:Programs:1
        -:      1:#include <stdio.h>
        -:      2:#include <stdlib.h>
        -:      3:
        -:      4:int main(int argc, char **argv)
Use of uninitialized value in string eq at gccr.pl line 345.
Use of uninitialized value in string eq at gccr.pl line 345.
Use of uninitialized value in string eq at gccr.pl line 345.
Use of uninitialized value in string eq at gccr.pl line 345.
Use of uninitialized value in string eq at gccr.pl line 345.
Use of uninitialized value in string eq at gccr.pl line 345.
Use of uninitialized value in string eq at gccr.pl line 345.
Use of uninitialized value in string eq at gccr.pl line 345.
Use of uninitialized value in string eq at gccr.pl line 429.
Use of uninitialized value in string eq at gccr.pl line 429.
Use of uninitialized value in string eq at gccr.pl line 429.
Use of uninitialized value in string eq at gccr.pl line 429.
        2:      5:      {
        2:      6:              if (argc <= 1) {
        1:      7:                      (void) puts("no arguments");
        1:      8:                      return EXIT_FAILURE;
        -:      9:      } else {
        1:      10:                     int i;
        -:      11:
        4:      12:                     for (i = 1; i < argc; ++i) {
        3:      13:                             (void) printf("%d: %s\n", i, argv[i]);
        -:      14:             }
        -:      15:     }
        -:      16:
        1:      17:             return EXIT_SUCCESS;
        -:      18:}
50.00% of 8 lines executed on target =T1=
75.00% of 8 lines executed on target =T2=
100.00% of 8 lines executed across all files

D:\temp\gccr>

確かにマージされるけど、何かがおかしい。

  • gcov的に実行可能と判断している行を出力する時に、水平タブ1個分だけ余分にインデントされてしまう。
  • 行の左から2番目に出力される行番号が左揃え。行番号の桁数が増えると、右側のソースコードのインデントに干渉する可能性がある。というか元ソースが半角空白でインデントしてある場合、多分行番号の桁が増えるとインデントが崩れる。
  • 行番号の左側の空白部分が水平タブ。ソースコードのインデントにタブを使っている場合、(タブの大きさによるけど)ソースコードののインデントに干渉する可能性がある。
  • warningが煩い。

ということで、やっつけ仕事で直した。

D:\temp\gccr>perl gccr.pl test1\sample.c.gcov =T1= test2\sample.c.gcov =T2=
        -:    0:Source:sample.c
        -:    0:Graph:sample.gcno
        -:    0:Data:sample.gcda
        -:    0:Runs:1
        -:    0:Programs:1
        -:    1:#include <stdio.h>
        -:    2:#include <stdlib.h>
        -:    3:
        -:    4:int main(int argc, char **argv)
              5:{
        1:    5: =T1=
        1:    5: =T2=
              6:        if (argc <= 1) {
        1:    6: =T1=
        1:    6: =T2=
              7:                (void) puts("no arguments");
        1:    7: =T1=
     ####:    7: =T2=
              8:                return EXIT_FAILURE;
        1:    8: =T1=
     ####:    8: =T2=
        -:    9:        } else {
             10:                int i;
     ####:   10: =T1=
        1:   10: =T2=
        -:   11:
             12:                for (i = 1; i < argc; ++i) {
     ####:   12: =T1=
        4:   12: =T2=
             13:                        (void) printf("0: \n", i, argv[i]);
     ####:   13: =T1=
        3:   13: =T2=
        -:   14:                }
        -:   15:        }
        -:   16:
             17:        return EXIT_SUCCESS;
     ####:   17: =T1=
        1:   17: =T2=
        -:   18:}
50.00% of 8 lines executed on target =T1=
75.00% of 8 lines executed on target =T2=
100.00% of 8 lines executed across all files

D:\temp\gccr>perl gccr.pl -c test1\sample.c.gcov =T1= test2\sample.c.gcov =T2=
        -:    0:Source:sample.c
        -:    0:Graph:sample.gcno
        -:    0:Data:sample.gcda
        -:    0:Runs:1
        -:    0:Programs:1
        -:    1:#include <stdio.h>
        -:    2:#include <stdlib.h>
        -:    3:
        -:    4:int main(int argc, char **argv)
        2:    5:{
        2:    6:        if (argc <= 1) {
        1:    7:                (void) puts("no arguments");
        1:    8:                return EXIT_FAILURE;
        -:    9:        } else {
        1:   10:                int i;
        -:   11:
        4:   12:                for (i = 1; i < argc; ++i) {
        3:   13:                        (void) printf("0: \n", i, argv[i]);
        -:   14:                }
        -:   15:        }
        -:   16:
        1:   17:        return EXIT_SUCCESS;
        -:   18:}
50.00% of 8 lines executed on target =T1=
75.00% of 8 lines executed on target =T2=
100.00% of 8 lines executed across all files

D:\temp\gccr>

warningは暫定的に「no warnings 'uninitialized';」で逃げてみた、というぐらいにやっつけ。Perlは使わないのでよく分からんとです。

*1:gcc-4.3.3のソース一式を落としてきて中身を見たけど、それらしいものは含まれてない模様。

*2:GPLなのか? ちょっと判断できない。