LCOV/GCOVを使用して、プロジェクトのテストカバレッジを作成しています。最近、ブランチカバレッジをさらに有効にしようとしました。しかし、どうやら、これは開発者の高レベルのビューから期待する結果をもたらさないようです。
C++でブランチカバレッジを使用すると、レポートが至る所にブランチで広がります。 (問題の検索が示すように)ほとんどの場合、例外処理コードがこれらの「隠されたブランチ」を作成すると思われます。また、GCOV/LCOVはこれらをスキップしないようです。
問題を示す小さなテストプロジェクトを作成しました: https://github.com/ghandmann/lcov-branch-coverage-weirdness
現在、Ubuntu 16.04を使用しています。で:
本番コードは、c ++ 11を有効にして構築されています。最小限の例はc ++ 11を有効にして構築されていませんが、さまざまなオプション(c ++標準、最適化、-fno-exceptions
)私たちはまずまずの結果を思いつきませんでした。
誰かアイデアがありますか?ヒント?私たちは何か間違った方法で使用していますか?これは-他のどこかで述べたように-本当に期待される動作ですか?
更新:
gcc-helpメーリングリスト でも指摘されているように、これらの「隠されたブランチ」は例外処理のために発生します。したがって、-fno-exceptions
gccに切り替えると、「単純な」プログラムに対して100%の分岐カバレッジが生成されます。しかし、例外が無効になっている場合、gccは実際に例外を使用するコード(try-catch、throwなど)のコンパイルを拒否します。したがって、実際の製品コードでは、これはオプションではありません。どうやら、この場合は新しい100%になるように〜50%のカバレッジを宣言する必要があります。 ;)
問題は、GCCは、スローされた例外が原因でスコープが終了する可能性がある各行のブランチ情報も記録することです(たとえば、GCC 6.3.1およびlcov 1.12を使用したFedora 25上)。
この情報の価値は限られています。ブランチカバレッジデータの主な使用例は、次のような複数節の論理式を持つ複雑なif文です。
if (foo < 1 && (bar > x || y == 0))
テストスイートがbar > x
のケースもカバーしているかどうか、またはy == 0
のテストケースだけがあるかどうかを確認したいとします。
これには、ブランチカバレッジデータの収集とlcovのgenhtmlによる視覚化が役立ちます。次のような単純なifステートメントの場合
if (p == nullptr) {
return false;
}
return true;
次の行のカバレッジを調べることで、ブランチが取得されたかどうかがわかるため、ブランチカバレッジデータは必要ありません。
genhtml
によって生成されるlcov
の入力は、比較的単純なテキスト形式です( geninfo(1)
を参照)。したがって、BRDA:
で始まりif文に属さないすべての行が削除されるように、後処理できます。たとえば、このアプローチを実装する filterbr.py
を参照してください。他のlcov/genhtml処理ステップについては gen-coverage.py
を、参照してください 結果のトレースファイルがcodecovにアップロードされるプロジェクト例 (codecovはgenhtml
を使用しませんlcovトレースファイルをインポートして、ブランチカバレッジデータを表示できます)。
-O1 -fno-omit-frame-pointer -fno-optimize-sibling-calls
のようなものでコンパイルすると、記録されたブランチカバレッジデータの数はいくらか減りますが、それほど多くはありません。-fprofile-instr-generate -fcoverage-mapping
でコンパイルし、llvm-profdata
およびllvm-cov
でポストプロセス)と呼ばれる別のアプローチも実装します。ただし、そのツールチェーンはブランチカバレッジデータをサポートしていません(2017-05-01現在)。--rc lcov_branch_coverage=0
および--no-branch-coverage
)GCCは、多数の例外処理を追加します。特に関数呼び出しを行うとき。
これを修正するには、ビルドに-fno-exceptions -fno-inline
を追加します。
追加する必要があります。おそらく、テストのためにこれらのフラグをオンにするだけです。だからこのようなもの:
g++ -O0 --coverage -fno-exceptions -fno-inline main.cpp -o test-coverage
g++ -O3 --coverage main.cpp -o testcov
を試すことができます。私はあなたのファイルでg ++-5.4でこれを試しましたが、正常に動作します。つまり、標準のprintfと文字列呼び出しで例外が破棄されます。
実際、O0
以外の最適化フラグを使用すると、gcovはCPPファイル内の単純な標準ライブラリ呼び出しに対して生成された例外を無視します。通常の例外も最適化されるかどうかはわかりません(そうは思いませんが、まだ試していません)。
しかし、プロジェクトでO1
、O2
、O3
、さらにはOs
ではなく、O0のみをコードで使用する必要があるかどうかはわかりません。 。
私はちょうど同じ問題に出くわしたので、例外のためにこれらのカバーされていないブランチを取り除きたいです。私に適した解決策を見つけました。
コードで「例外をスロー」を使用することは避けます。代わりに例外をスローするメソッドを提供するクラスを設計しました。例外クラスはそれほど複雑ではないため、カバレッジについてはあまり気にしません。そのため、LCOV_EXCL_STARTおよびLCOV_EXCL_STOPですべてを除外します。または、その例外クラスのブランチカバレッジのみをオフにすることもできます。
私は認めますが、それは簡単な解決策ではありませんが、私の目的のためには、他の理由のためにも完璧です(その例外クラスが柔軟である必要がありますので、異なる実装を提供できるようになります:そうしないと)。