これは Rust Advent Calendar 2016 (2) の 14日目の記事です。
lcovが出力するレポートのパーサー&マージャーをRustと実装しました。
lcovはgcovの拡張版みたいなもので、gcovの出力ファイルから、HTML形式のようなコードカバレッジレポートを出力するツールです。
lcovは出力の過程で、処理しやすいようにgcovの出力ファイルから独自のレポート形式に変換します。
そのレポートをパース&マージできるようにしたものが、今回実装したライブラリになります。
レポートのパース
lcovのレポートはデータをレコードで表現した単純なテキスト形式のファイルです。
各レコードは次のようになっています。
原文はこちらを参考してください。
No. | レコード | 説明 |
---|---|---|
1 | TN:<test name> | テスト名 |
2 | SF:<absolute path to the source file> | ソースファイルのパス(絶対パス) |
3 | DA:<line number>,<execution count>[,<checksum>] | 左から行番号、実行された回数、該当行のコードのMD5値 |
4 | FN:<line number of function> | 関数の開始行 |
5 | FNDA:<execution count>,<function name> | 左から関数の実行された回数、関数名 |
6 | FNF:<number of functions found> | 見つかった関数の数 |
7 | FNH:<number of function hit> | 実行された関数の数 |
8 | LH:<number of lines with an execution> | 見つかった行の数 |
9 | LF:<number of instrumented lines> | 実行された行の数 |
10 | BRDA:<line number>,<block number>,<branch number>,<taken> | 左から行番号、ブロック番号、分岐番号、実行された回数 |
11 | BRF:<number of branches found> | 見つかった分岐の数 |
12 | BRH:<number of branches hit> | 実行された分岐の数 |
13 | end_of_record | 終了を示すレコード、ソースコード単位で出力される |
レポートをパースするには、次のようにファイルからパーサーを生成して、1レコードずつパースします。
extern crate lcov_parser; use lcov_parser:: { LCOVParser, LCOVRecord, FromFile }; fn main() { let mut parser = LCOVParser::from_file("../../../fixture/report.lcov").unwrap(); loop { match parser.next().expect("parse the report") { None => { break; }, Some(record) => match record { LCOVRecord::SourceFile(file_name) => println!("File: {}", file_name), LCOVRecord::EndOfRecord => println!("Finish"), _ => { continue; } } } } }
レポートのマージ
さて本題のレポートマージです。
レポートをマージする際に、気をつけなければならいない点はマージするレポートのバージョンです。
マージしたいレポートが同じバージョンのソフトウェアのレポート場合、マージすることは可能ですが、違う場合は基本的にマージすることができません。
なぜマージできないかというと、どのバージョンの時、コードカバレッジはどの程度だったか?が知りたいわけで、異なるバージョンのレポートをマージすると、正しいレポートにならないためです。
式 | 結果 |
---|---|
v1+ v1 | v1 |
v1+ v2 | マージできない(バージョンが違う) |
この為、マージする際にレポートのチェックサムを利用します。
チェックサムは、DAレコードに情報として含まれています。(lcovのchecksumオプションを使用した場合)
このチェックサム値はソースコードの特定の行のMD5値になっており、同じバージョンのソフトウェアの場合、 値が同値になります。
下記の例だと、fixture.cの6行目のMD5値がPF4Rz2r7RTliO9u6bZ7h6gという意味になります。
SF:/Users/holyshared/Documents/projects/lcov-parser/tests/fixtures/merge/fixture.c ...... ...... ...... ...... DA:6,2,PF4Rz2r7RTliO9u6bZ7h6g
このようなチェックを行いつつ、マージしてくれる関数も実装しています。
save_asを使用することで、出力ファイルを指定できます。
extern crate lcov_parser; use lcov_parser:: { merge_files }; fn main() { let trace_files = [ "../../../tests/fixtures/fixture1.info", "../../../tests/fixtures/fixture2.info" ]; let _ = match merge_files(&trace_files) { Ok(report) => report.save_as("/tmp/merged_report.info"), Err(err) => panic!(err) }; }
まとめ
最初は、パーサーだけ実装する予定でしたが、lcovのPerlのコードを読んでいて、ついでだからマージもできるようにするかと思い実装してみました。 既存のプロダクトのコードの移植していく作業は、普段つかっているものの理解力があがるので、勉強になりますね。