2014年2月27日木曜日

[C++] Jenkins でカバレッジ計測結果を表示する

最近のマイブーム、カバレッジ計測です。
前回([C++] Coveralls でコードカバレッジ)、
前々回([C++] Coveralls でコードカバレッジ - その2
Travis CI + Covarlls でカバレッジ計測をしましたが、今回は Jenkins でやります。

なぜ、Jenkins かというと社内の CI ツールが Jenkins だからというだけです。
(最近は Travis CI とか Coveralls とかの方が楽で便利だなと思っているのですが、仕事では Jenkins が手放せません。)

やり方については以下の記事を参考にしました。

gcovr のインストール
まず最初に gcovr をインストールします。
公式サイトからダウンロードしても良いですが、今回は easy_install でインストールしました。
やることは簡単で、以下のコマンドを実行するだけです。
easy_install gcovr

gcovr で xml 出力
gcc でカバレッジを出力する手順は省きます。
gcovr で xml 出力するには --xml オプションをつけて実行します。
gcovr --xml --output=gcovr_result.xml ./test
--output には出力ファイル名を指定し、解析対象のフォルダパスを渡します。

Jenkins の設定
Jenkins の設定も特に難しいことはありません。
まずは、プラグインの管理で「Cobertura Plugin」をインストールします。

次に、プロジェクトの設定を開き、「ビルド後の処理の追加」から「Cobertura カバレッジ・レポートの集計」を選択します。続いて、「Cobertura XMLレポート パターン」に gcovr が出力した xml ファイルのパスを指定します。


以上で設定は終わりです。
これを実行すると、以下のようにカバレッジが表示されます。


(条件のカバレッジ率が低いですね:-<)


かなり雑に説明しましたが、とくに躓くポイントはないと思います。
視覚化すると見えてくるものもあると思うので、導入できる環境であれば是非。
(仕事で jenkins が…といいつつ実はまだ導入してない)
それでは、皆様よい開発を~。

2014年2月21日金曜日

[C++] va_list を再利用してはいけない

最近ハマったので備忘録。

C++ で可変長引数を扱うときに va_list を使うと思いますが、これって再利用してはダメなんですね。知りませんでした。

#include <stdarg.h>
#include <iostream>

void f(const char* fmt, ...)
{
    va_list va, va2;
    va_start(va, fmt);
    va_copy(va2, va);
    
    // OK
    char buf[256];
    vsprintf(buf, fmt, va);
    ::std::cout << buf;
    
    // OK
    char buf2[256];
    vsprintf(buf2, fmt, va2);
    ::std::cout << buf2;

    // NG
    char buf3[256];
    vsprintf(buf3, fmt, va);
    ::std::cout << buf3;

    va_end(va);
    va_end(va2);
}

int main(void)
{
    f("%d %d %d\r\n", 1, 2, 3);
    return 0;
}

1 2 3
1 2 3
4198192 812910400 0

[Wandbox]三へ( へ՞ਊ ՞)へ ハッハッ

va_copy というのも初めて知りました。
Visual Studio だと va_copy は 2012 以前だとないようです。2013 では使えます。

ちなみに、Visual Studio だと上記コードでも問題なく動いたので環境依存ってことなんでしょうかね。

2014年2月17日月曜日

[C++] Coveralls でコードカバレッジ - その2

Travis CI + Coveralls でカバレッジ計測を使い始めたのですが、自分が思ってた以上にカバレッジ率が低くて 恥ずかしい/// って感じだったのですが、どうやら通っているはずのコードが通っていないと判断されているようでした。

原因は gcov のバージョンが古いことのようで、Travis でデフォルトで使える gcov は version 4.6.3 なのですが、
以下のログからも分かるように、同じファイルでも相対パスの経路が違うと別ファイルとして認識されるようです。

File '../include/internal/../internal/../iutest_env.hpp'
Lines executed:85.59% of 111
../include/internal/../internal/../iutest_env.hpp:creating 'iutest_env.hpp.gcov'

File '../include/gtest/../internal/../internal/../iutest_env.hpp'
Lines executed:63.54% of 96
../include/gtest/../internal/../internal/../iutest_env.hpp:creating 'iutest_env.hpp.gcov'

そして、出力する .gcov ファイル名は同じなため上書き保存されてしまっていました。
※ Coveralls で表示されるパーセンテージと異なる場合がありますが、それは cpp-coveralls が positive failure に対応しているためです。(これに気づかず Coveralls や cpp-coveralls 側を疑ってしまい時間がかかってしまった…)

-p オプションや -l オプション使えば上書きされることはないですが、後でマージする必要があります。
マージツールもあるようですが、今回はストレートに新しいバージョンの gcov を使うことで対応しました。

.travis.yml に以下を追加。
install:
  # gcc 4.8
  - sudo add-apt-repository --yes ppa:ubuntu-toolchain-r/test
  - sudo apt-get -qq update
  - if [ "${CXX}" = 'g++' ]; then sudo apt-get -qq install g++-4.8; fi
  - if [ "${CXX}" = 'g++' ]; then sudo update-alternatives --install /usr/bin/gcc  gcc  /usr/bin/gcc-4.8 90; fi
  - if [ "${CXX}" = 'g++' ]; then sudo update-alternatives --install /usr/bin/g++  g++  /usr/bin/g++-4.8 90; fi
  - if [ "${CXX}" = 'g++' ]; then sudo update-alternatives --install /usr/bin/gcov gcov /usr/bin/gcov-4.8 90;  fi

この修正とテスト追加により、iutestカバレッジが 91% にまでなりました。
バッチがみどりになった~

そして、カバレッジ上げようとテストを見なおしていたらバグを結構潰せました。
いや~やっといてよかった~

※ 執筆時点でのカバレッジ結果なので将来的にパーセンテージが変化することがあります。
最新の情報は github へ > https://github.com/srz-zumix/iutest

2014年2月10日月曜日

[C++] Coveralls でコードカバレッジ

iutestCoveralls の利用を始めました。

導入は簡単にできたのですが、テストコードのカバレッジだけ集計されて、本来の目的であるフレームワーク側のカバレッジが集計されず、あーだこーだやったので備忘録として書き残しておきます。


導入までに関しては以下を参考にしました。
Objective-C の記事はすぐ見つかったけど c++ の記事は少ないねぇ
はじめに
はじめに iutest の構成を説明しておきます。

  • iutest
    • doc
    • include
    • projects
    • samples
    • src
    • test
    • tools

test フォルダにテストがあり、そちらでテストを実行し gcov を出力します。
カバレッジ対象は include ディレクトリ以下のソースファイルです。

root
まず、iutest/test ディレクトリで cpp-coveralls を呼び出していたため、ルートディレクトリが iutest/test になっていました。このため、iutest/include のソースファイルへのパスが "../include/iutest_***.hpp" のようになります。 cpp-coveralls では、../ で始まるパスのソースファイルは無視するようになっているため、集計されていませんでした。

修正
iutest ディレクトリ(プロジェクトのトップ)で cpp-coveralls を呼ぶように修正

UnicodeDecodeError: 'utf8' codec can't decode byte ...
ソースファイルを utf8 に変換しようとして失敗しています。
cpp-coveralls には --encoding オプションがあり、それで別の文字コードを指定することが可能です。
ただ、iutest は UTF8-BOM有り で統一するようにしたので、どこかにそうじゃないファイルがあるようです。。。

修正
Crashes in utf8 decoder · Issue #24 · eddyxu/cpp-coveralls
こちらにも書かれているように、該当のファイルパスを print するようにして(※)問題のファイルを洗い出し、文字コードの変更を行いました。


pip install cpp-coveralls
で cpp-coveralls をインストールすると lib\python2.7\site-packages\coveralls に展開されるので、そこの coverage.py を編集して print を仕込む。

テストコードの除外
cpp-coveralls で任意のファイル/ディレクトリを除外するには、--exclude (-e) オプションを使います。
当初、
coveralls -r ./ -e test
のように除外指定をしていましたが、これだと test/*.gcov ファイルの集計が行われずなにもレポートされませんでした。
やりたいことは test/*.gcov は集計するが、テストコードの *.gcov は集計しないことです。

修正
-e オプションを使用すると *.gcov ファイルの集計まで除外してしまうので、--exclude-pattern (-E) オプションを使います。-E オプションは -e オプションと違い、正規表現が使えます。また、-E オプションでの除外指定は *.gcov ファイルの集計には影響しないようです。
coveralls -r ./ -E ".*/test/.*"

これで
これで目的のカバレッジができました。

これを書いている時点で 80% と低いので、もう少し網羅率を上げて v1.8.1 リリースといきたいところですね。

2014年2月7日金曜日

[Arduino] イーサネットシールドを買いました

Arduino イーサネットシールドを買いました。

昨年 Arduino Uno R3 で LED マトリックスをチカチカさせて遊びましたが、今年はイーサネットシールドを使って遊んでみたいと思います。
もちろん、購入した目的はあるのですが、まずはツイートでもさせてみましょうかね。








何か進捗があれば、またブログにでも書きます。
今回はこれだけです。ではでは。

2014年2月3日月曜日

[Cppcheck] cfg ファイル設定

Cppcheck 1.63 がリリースされました。
といっても大分前ですが…
調べたり検証したり別のことやってたりしてたら時間がかかってしまいました。

トラブってました
1.63 を早速インストールしたところ、下記のようなエラーが…
(information) Failed to load std.cfg. Your Cppcheck installation is broken, please re-install. The Cppcheck binary was compiled without CFGDIR set. Either the std.cfg should be available in cfg or the CFGDIR should be onfigured.
cygwin から実行すると動作して、コマンドプロンプトからだと上記エラーが…
どうしたものかと調べていたら、1.63.1 がリリースされました。
こちらをインストールしたところ、問題なく実行できました。

メデタシメデタシ。ですが、転んでもただでは起きません。
.cfg ファイルは今までノータッチでしたので、少し調べてみました。

書式
<?xml version="1.0"?>
  <def>
    <function name="ZeroMemory">
      <noreturn>false</noreturn>
    </function>
  </def>
マニュアルより引用

.cfg ファイルの中身は XML になっています。
<def> ノードの中に定義を書きます。

リークチェック
Cppcheck にはメモリ/リソースのリークチェック機能があります。
ただし、この機能を使うためには確保/解放関数を Cppcheck に教えてあげる必要があります。
定義は .cfg ファイルの <memory> および <resource> ノードに書きます。
<resource>
  <alloc>CreatePen</alloc>
  <dealloc>DeleteObject</dealloc>
</resource>
マニュアルより引用

alloc ノードに確保、dealloc ノードに解放関数を定義します。
allco ノードには init 属性を付けることができ、確保されたメモリ/リソースが初期化されるかどうかを true/false で指定できます。

関数
関数は function ノードに定義します。
function ノードでは <arg nr="1"> のようにすることで、各引数に条件を持たせられます。
(nr の値は引数の順番)

未初期化チェック
<function name="CopyMemory">
  <arg nr="2">
    <not-uninit/>
  </arg>
</function>
マニュアルより引用
not-uninit サブノードを追加することで未初期化変数を引数に渡された場合に警告を出すようにできます。

nullptr チェック
<function name="CopyMemory">
  <arg nr="1">
    <not-null/>
  </arg>
</function>
マニュアルより引用
not-null サブノードを追加することで関数引数の NULL チェックができます。

範囲 チェック
<function name="do_something">
  <arg nr="1">
    <valid>0-1023</valid>
  </arg>
</function>
マニュアルより引用
valid サブノードに有効な値の範囲を記述することで範囲外チェックができます。

書式文字列
<function name="do_something">
  <arg nr="1">
    <formatstr/>
  </arg>
</function>
マニュアルより引用
formatstr を指定することで %d %x などの書式のチェックがされるようになります。

return しない関数
<function name="ZeroMemory">
  <noreturn>false</noreturn>
</function>
マニュアルより引用
noreturn に true を指定することで、exit 関数などのような制御を返さない関数であることを通知できます。
また、false にすると制御を返す関数であることを明示的にし、パス解析を助けます。

その他
他にも not-bool や leak-ignore などマニュアルに書いていないものもあるようです。


cfg の保存
.cfg ファイルは cppcheck のインストールディレクトリ直下の cfg フォルダに保存するか、
任意の場所に保存し、cppcheck のコマンドラインオプションに指定することで有効にできます。
cppcheck src --library=my.cfg

試しに書いてみた
試しに .cfg 書いてみたのですが、思ったように書くことができませんでした。

クラスメンバーにある確保・解放関数を登録しようと考えたのですが、
まずメンバー関数に対しての書き方がよくわからない。
あーだこーだやってみたが全然検出してくれず…埒が明かん!ということでソースコードチェックアウトして確認しました。

結論としては「できません」。
cppcheck はトークンごとに解析をする感じになっていて、例えばメンバ関数の呼び出し
void* p = heap.MyAlloc(10);
は、
[void][*] [p] [=][heap][.][MyAlloc][(][10][)][;]
のように分解され解析されます。
alloc 関数かどうかの判定も1つのトークンで判定されるので、この場合 MyAlloc と書くことになります。

しかし、このとき alloc 関数かどうかの判定に使われるのは「heap」という名前です。
なんでと言われようがそうなっていたので、そうなのです。
当然マッチしません。

マッチしなかった場合、トークン列が [hoge][::] や [hoge][.] などにマッチするか判断します。
マッチした場合、現在のトークンの2つ先のトークンを関数名として使用します。
上の例では、「heap」 の次の次なので 「MyAlloc」。ようやくお出まし。

そして、取得した関数名から関数定義を引いてきて、return ステートメントなどから戻り値の allocType を判定します。
戻り値から判定するときにユーザー定義の alloc も参照しますが、「MyAlloc」が alloc 関数だと判定して欲しいので期待と異なります。

というわけで、メンバー関数呼び出しによるメモリリークの検出はできなさそうでした…残念orz
※ この記事はリビジョン 10720 で書いています。最新の動作と異なる場合があります。

なんにせよ、.cfg を使ったら検出率上げられるかもしれないので、試してみてはいかかがでしょうか?
以上。