2016年10月16日日曜日

単体テストフレームワーク CUnit(その2)

テスト仕様をテストレポートに表示できるようになったのは、つい最近の 2016年 6月。それまではテストケースのクラス名や、テストメソッドの名称でしか表現できなかった。
レポートにテスト仕様の表示が必要なことは最初のころから感じていたが、うまい実現方法が思いつかなかった。
仕様を記録するために、専用の関数を定義しようかと考えたことがある。
たとえば、事前条件や事後条件のテスト仕様を示すために、テストコード中につぎのような関数呼び出しを記述するのである。

 Specification_postcondition("valueは 100になっていること。");

技術的には何の問題も無いが、テストコードの作りやすさや保守性を考えると冗長すぎないかと思い、まだ実現していない。
テストコードそのものがテスト仕様になるはずなので、文字列で記述せずにテストコードから仕様の記述を生成できればそのほうがよいと考えている。しかしこちらの方は技術的に難しい。
いよいよ方法が見つからないようなら上記のような関数を提供することになる。

そこで当面の間、もう少し気楽に使えるできるよう、テストケースとテストメソッドに対してドキュメントコメントで概要だけを記述できるようにした。
例)

/**
 * 2つの値を加算できること。
 */
public void testAdd() {

この程度の記述でテストレポートの内容がわかり易くなるなら、書くようになるでしょう。


過去に記述したテストケースなど、ドキュメントコメントの記述が無いものを最近のフレームワークで処理すると、テストレポートに警告文が表示されるようになっている。

折角テストがOKになっても警告文はカッコ悪いですよね。これには既存のテストコードにもドキュメントを書いてくださいという思いが込められている。


将来的には、事前条件や事後条件、実行内容もレポートできるようにして、さらには Excelや Wordのテスト仕様書、テスト報告書が自動作成できるようにしたい。

2016年10月14日金曜日

単体テストフレームワーク CUnit


Javaでコードをテストするときには JUnitを使用しているが、C/C++でもそのようなのがあったら便利だと思った。
2006年ごろ、自社製(まだ会社設立前だったが)の Java VM(TCK通してないので、公式には“もどき”)の品質が悪く、ちゃんと細部までテストをしたいなというのが始まりだった。
ネット上で検索すると沢山の CUnitが見つかり、そのうちのいくつかを試してみた。
しかし、どれもあまりパッとしなかった。 使いたくないと感じたのは、
  1. プロジェクトが放置されて久しいもの
  2. 完成度があまりにも低いもの
  3. 手続きが多くて使いにくいもの
  4. ドキュメントが全く無いもの
  5. エラーが発生したときの処理が貧弱なもの
  6. レポートを見て「ワクワク」しないもの

3の使いにくいとは、リポジトリとかテストスイートなどのオブジェクトを作り、テストケースを1個ずつ追加しなければならないのは、無精者の自分には向かないと思った。
テストケースを追加したのに、テストスイートに追加を忘れると、エラーにもならずに黙ってテスト漏れになってしまう。

しばらく我慢して使っていたがあまりにも作業効率が悪いので、自分も CUnitを作ろうということになった。(VM作りに集中せず、横道に逸れてしまうのだが)

今なら Google Testを使うでしょう。 Google Testレポートはワクワクしないけど、Softiesの CUnitの目標と共通している点も多い。 ただ当時は日文のドキュメントが多くなかったので Google Testは不採用とした。(なんと無謀な…。 皆さんは真似してはいけません。時間の無駄です。 Google Testを使いましょう。)

CUnitの目標は
  1. JUnitライクとする。(Cではアノテーションができないので 3.8ベース)
  2. リポジトリやスイートはデフォルトで不要または自動とする。
  3. JUnit同様、テスト関数の結果が他のテスト関数に影響しない。(ファイルや DBを除く)
  4. テスト中にエラーが発生してプロセスが停止しても、次にテスト関数は実行できること。
  5. 赤と緑を使った刺激的でモチベーションを維持できるレポートを出力できること。

では細かな機能紹介は別にするとして、テストケースの記述例とテストレポートのスナップショットをご覧頂きましょう。
(バージョンは違うが、以前、パッケージ一覧でも例示してある。)

テストケースのサンプル

テストプログラムの冒頭


テストケースの仕様をドキュメントコメント @fileタグで記述する。

スタブ(必要なときのみ)


フィクスチャ


テストケース


テスト関数の仕様をドキュメントコメントで記述する。
Assertクラスのコメントもテストレポートに引用される。

main関数


main関数はスタティックリンク(デフォルト)する場合に必要。
CUnitクラスの testメソッドを呼ぶだけのため、覚えるのは簡単。
main関数の中で argc, argvの内容を testメソッドを呼ぶ前に書き換えることも可能。

テストレポートのスナップショット

テストケース一覧


テスト関数一覧


テスト関数の実行結果(1)


テストレポートに事前条件の表示が無いため、事後条件の検証結果に唐突感がある。
事前条件の表示は今後の課題。
printf等で標準出力に出力した結果は、"stdout"に表示される。(この例では空)
標準エラー出力に出力した結果は、"stderr"に表示される。
malloc, free, reallocは追跡され、"heap"に表示される。 この例ではメモリリークが起きているがこれはテストケースでおきたものではなく、softiesが使用している libbfdのバグによるもの。
なお、softiesが未公開の理由の1つはこの libbfdが GPLライセンスであるため。 これを排除して softiesを公開する予定であるが、まだ時間を要する。

テスト関数の実行結果(2)


テストによりテキストファイルのほか、イメージや動画、オーディオファイルが出力されると、自動的にテストレポート中に表示される。

2014年9月28日日曜日

Ubuntuに H8/300H用の gccをインストールしてみる。

Ubuntu 14.04に H8/300H用の gccをインストールしてみた。

H8/300Hは Softiesプロジェクトとは直接の関係は無い。
わざわざ直接の関係は無いと言う事は、間接の関係があると察していただけたと思う。
そもそも、Softiesプロジェクトは組み込み開発のテスト環境を提供するのが始まりだった。特に、単体テストフレームワークの CUnitがそれだ。そしてその CUnitは自社製の "ra7taj"という仮想マシンをテストするためのものであった。ra7tajの開発を 2年間一時停止して Softiesプロジェクトを進めてきた。CUnitの開発が今年の盆休みに実用域に達してひと段落したので、今月から ra7tajの開発を再開した。
それがなんで H8/300Hと関係があるのか??

ra7tajは不安定ながらも Solarisや Linux上で動作しているので、いよいよ次のステップではターゲットに組み込んで使えるようにすることになるが、そのターゲットが H8/300Hなのである。
今時なんで ARMとかじゃなくてH8/300Hかというと、…長くなるので止めとくが、CPUパワーもメモリも「そこそこ」しか無い環境で動かしてみたかったからである。
欲を言えば、8051くらいでも動作させたいが、ちょっとメモリが足りなさすぎるので見送りとなった。実を言うと、もう少し速くターゲット環境で動かしたかったが、2011年に欲を出してコンパクション可能なコレクタにしたら、それまで動作していたところがあちこちと調子悪くなってしまい、現在デバッグ中である。実際にターゲットを動かすのはいつになるやら。 ビルド環境を作るなんて気が早いかもしれない。
それに CUnitで単体テストやるのが楽しすぎて…。

CUnit作る前はどうやって ra7tajテストしていたかというと、Javaのプログラムから JNI経由で関数を呼び出していた。 Heapの状況を Swingで視覚化したりして大変便利だったのだが、ra7taj自体に JNI互換の関数を提供しはじめたら、テスト対象の ra7tajの JNIと テストドライバである JDKの JNIのシンボルが衝突して破綻してしまった。

なお、Softiesはオープンソースにしたいが、使用している一部のライブラリのライセンスの都合で未だ公開できる状況ではない。
ライセンス問題をクリアするまでもう少し掛かりそうである。

インストール

話が脱線してしまったので、元に戻そう。

インストールといっても、apt-getコマンドを使うだけである。
$sudo apt-get update

した後、
$ sudo apt-get install gcc-h8300-hms
パッケージリストを読み込んでいます... 完了
依存関係ツリーを作成しています                
状態情報を読み取っています... 完了
以下の特別パッケージがインストールされます:
  binutils-h8300-hms
提案パッケージ:
  binutils-doc gcc-doc
以下のパッケージが新たにインストールされます:
  binutils-h8300-hms gcc-h8300-hms
アップグレード: 0 個、新規インストール: 2 個、削除: 0 個、保留: 5 個。
5,554 kB のアーカイブを取得する必要があります。
この操作後に追加で 14.9 MB のディスク容量が消費されます。
続行しますか? [Y/n] y
取得:1 http://jp.archive.ubuntu.com/ubuntu/ trusty/universe binutils-h8300-hms
 amd64 2.16.1-9ubuntu1 [2,125 kB]
取得:2 http://jp.archive.ubuntu.com/ubuntu/ trusty/universe gcc-h8300-hms amd64
 1:3.4.6+dfsg2-1ubuntu1 [3,429 kB]
5,554 kB を 14秒 で取得しました (381 kB/s)                                     
以前に未選択のパッケージ binutils-h8300-hms を選択しています。
(データベースを読み込んでいます ... 現在 328030 個のファイルとディレクトリが
インストールされています。)
Preparing to unpack .../binutils-h8300-hms_2.16.1-9ubuntu1_amd64.deb ...
Unpacking binutils-h8300-hms (2.16.1-9ubuntu1) ...
以前に未選択のパッケージ gcc-h8300-hms を選択しています。
Preparing to unpack .../gcc-h8300-hms_1%3a3.4.6+dfsg2-1ubuntu1_amd64.deb ...
Unpacking gcc-h8300-hms (1:3.4.6+dfsg2-1ubuntu1) ...
Processing triggers for man-db (2.6.7.1-1) ...
binutils-h8300-hms (2.16.1-9ubuntu1) を設定しています ...
gcc-h8300-hms (1:3.4.6+dfsg2-1ubuntu1) を設定しています ...
$

gcc本体だけでなく、binutilsも一緒にインストールされた。

gccのバージョンは最近の 4.8.xとかでなくて少し古い。

コマンドが認識されるかいくつか試してみる。
$ h8300-hms-gcc --version
h8300-hms-gcc (GCC) 3.4.6
Copyright (C) 2006 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

$

$ h8300-hms-objdump --version
GNU objdump 2.16.1 Debian GNU/Linux
Copyright 2005 Free Software Foundation, Inc.
This program is free software; you may redistribute it under the terms of
the GNU General Public License.  This program has absolutely no warranty.
$

スタートアップルーチンは準備してないので、Hello, world.とかを試すのは今度にしよう。

2014年9月10日水曜日

言語Cで synchronizedを実現する(その1)

Javaの synchronized構文は便利だ。
オブジェクトをロックし、クリチカルセクションを実行したあと、確実にロックを解除するすることが言語でサポートされている。

そのような機構がない言語の場合、つぎのように記述するだろう。
lock();
// critical section
unlock();


クリチカルセクションが 1~2行程度ならよいが、長くなると何が起こるか分からない。
うっかり、この中で
if (hasError)
 return errorCode;


などとやってしまうとロックが解除されず、処理がとまってしまうか、再びロックしようとして多重ロックしてしまうかも知れない。

今回のチャレンジは、次のように関数を記述することだ。

void synchronized function(Something this) {
 // critical section
}


Javaでは synchronizedなメソッドのほかに、synchronizedブロックが記述できるが、ここでは関数だけを対象とする。

synchronizedな関数に入るときオブジェクトをロックし、出るときにアンロックする。
これを実現するための課題は、
  1. 関数に入るときと出る時に処理を行なえること。
  2. 関数に synchronized属性を持たせること。
  3. 関数が synchronizedを持って意いるか判断可能なこと。
  4. ロックするオブジェクトを認識できること。

1.関数に入るときと出る時に処理を行なう

これは以前の記事で取り上げたことのある、gcc拡張の __cyg_profile_func_enter, __cyg_profile_func_exitを使えそうである。
実験ではオブジェクトをロックせず、下記のようにトレース出力をする。
void NO_INSTRUMENT __cyg_profile_func_enter(void *function, void *caller) {
        if (isSynchronized(function))
                printf("enter synchronized function %p\n", function);
        else
                printf("enter %p\n", function);
}

void NO_INSTRUMENT __cyg_profile_func_exit(void *function, void *caller) {
        if (isSynchronized(function))
                printf("exit synchronized function %p\n", function);
        else
                printf("exit %p\n", function);
}


なお、NO_INSTRUMENTはコードを見やすくするための下記のようなマクロである。
#define NO_INSTRUMENT __attribute__((no_instrument_function))

2.関数に synchronized属性を持たせる

これはちょっと難しい。

上記の例にある、 isSynchronized(function)をどうやって実現するか。

関数に任意の属性を持たせたい。
関数とは別管理のルックアップテーブルを持たせれば簡単そうだが、別管理が面倒くさい。
関数が synchronizedかどうか人間が知りたいとき、いちいち管理テーブルを見ないとならない。
void synchronized function...のような記述できるほうが管理しやすい。
そのためには Javaのアノテーションのような機能が必要になる。
しかし、言語Cにはアノテーションは無い。 gcc拡張を探したがそれに近いものは無い。

諦め掛けていたが、トンでもないアイデアを思いついた。
synchronized用のメモリセクションを作り、そこに配置した関数を synchronizedとみなすことにしよう。

synchronizedセクションを作って関数を配置するには、gcc拡張の __attribute__構文が使える。
次のようなマクロを定義すれば、あとは関数に synchronizedを付けるだけ。
#define synchronized __attribute__((section("synchronized")))
gcc以外にもソースコード上にセクションを明記できるものがあるが、#pragma構文を使うものは残念ながら #defineすることはできない。

早速書いてみよう。 使い分けできるのが分かるよう synchronizedでない普通の関数も作っておく。

Sub.h(主要部分だけ)
extern void sub1();
extern synchronized void sub2();


Sub.c(主要部分だけ)
void sub1() {
 printf("sub1 called.\n");
}

void synchronized sub2() {
 printf("sub2 called.\n");
}


これをビルドしてできた Sub.oを逆アセンブルしてみると、ちゃんと synchronizedセクションに対応づけられている。
$ objdump -d Sub.o

Sub.o:     file format elf32-i386


Disassembly of section .text:

00000000 :
   0: 55                    push   %ebp
   1: 89 e5                 mov    %esp,%ebp
   3: 83 ec 18              sub    $0x18,%esp
   6: 8b 45 04              mov    0x4(%ebp),%eax
   9: 89 44 24 04           mov    %eax,0x4(%esp)
   d: c7 04 24 00 00 00 00  movl   $0x0,(%esp)
  14: e8 fc ff ff ff        call   15 
  19: c7 04 24 00 00 00 00  movl   $0x0,(%esp)
  20: e8 fc ff ff ff        call   21 
  25: 8b 45 04              mov    0x4(%ebp),%eax
  28: 89 44 24 04           mov    %eax,0x4(%esp)
  2c: c7 04 24 00 00 00 00  movl   $0x0,(%esp)
  33: e8 fc ff ff ff        call   34 
  38: c9                    leave  
  39: c3                    ret    

Disassembly of section synchronized:

00000000 :
   0: 55                    push   %ebp
   1: 89 e5                 mov    %esp,%ebp
   3: 83 ec 18              sub    $0x18,%esp
   6: 8b 45 04              mov    0x4(%ebp),%eax
   9: 89 44 24 04           mov    %eax,0x4(%esp)
   d: c7 04 24 00 00 00 00  movl   $0x0,(%esp)
  14: e8 fc ff ff ff        call   15 
  19: c7 04 24 0d 00 00 00  movl   $0xd,(%esp)
  20: e8 fc ff ff ff        call   21 
  25: 8b 45 04              mov    0x4(%ebp),%eax
  28: 89 44 24 04           mov    %eax,0x4(%esp)
  2c: c7 04 24 00 00 00 00  movl   $0x0,(%esp)
  33: e8 fc ff ff ff        call   34 
  38: c9                    leave  
  39: c3                    ret 

因みに synchronizedでない普通の関数は .textセクション。

3.関数が synchronizedを持って意いるか判断

そして、isSynchronized関数はつぎの通り。
int NO_INSTRUMENT isSynchronized(void* function) {
 extern char __start_synchronized;
 extern char __stop_synchronized;

 return (function >= (void*)&__start_synchronized)
  && (function < (void*)&__stop_synchronized);
}

gccでは、__start_, __stop_の後にセクション名をつけたものが、それぞれセクションの先頭と後端のアドレスを示している。 これを実行時に関数アドレスと比較することで、そのセクションの中の関数かどうか判断できるというわけである。

あとは次のようなプログラムで sub1()と sub2()を実際に呼び出してみる。 動作が確認しやすいよう、synchronizedセクションの先頭、後端アドレスと、sub1(), sub2()のアドレスも出力しておき、つづいてsub1()と sub2()を実際に呼び出す。

int NO_INSTRUMENT main(int argc, char** argv) {
 extern char __start_synchronized;
 extern char __stop_synchronized;

 printf("[synchronized section]\n");
 printf("start: %p\n", &__start_synchronized);
 printf("end: %p\n", &__stop_synchronized);
 printf("\n");

 printf("[function address]\n");
 printf("sub1: %p\n", sub1);
 printf("sub2: %p\n", sub2);
 printf("\n");

 printf("[call functions]\n");
 sub1();
 printf("\n");
 sub2();
 return 0;
}
結果は次のように、synchronizedとそうでないものがちゃんと区別できることを確認できた。
$ ./Main
[synchronized section]
start: 0x80486ec
end: 0x8048726

[function address]
sub1: 0x8048600
sub2: 0x80486ec

[call functions]
enter 0x8048600
sub1 called.
exit 0x8048600

enter synchronized function 0x80486ec
sub2 called.
exit synchronized function 0x80486ec

なお、Sub.h, Sub.cの両方で sub2()に synchronizedを記述したが、Sub.h, Sub.cのいづれか一方だけに記述しても結果は同じになる。

この手法は、synchronized以外にも使えるかも知れない。 なんとも馬鹿げたやり方だが。
※本プロジェクトの名称 softiesの先頭の sは stupidの略ですから、何でもアリです。 お許しください。

2014年7月4日金曜日

Ubuntuを 14.04にアップデート

Softiesプロジェクトでは、Open Solarisと Ubuntuを使用しているが、10-2から 12.04と 14.04にアップデートした。

ホスト OSを Windows 7 Professionalとし、VirtualBox 4.2.10の上で動かす。

ところが、OSインストール後、Guest Additionsを設定しようとすると、エラーが発生。

「Building the shared folder support module ...fail!」

これでは、ウインドウサイズが小さいままで使いにくい。

Guest Additionsの設定手順を間違えたと思い、コンソールから手動でスクリプトを起動してみたりいろいろ試してみたが何度やっても同じ。

試しに、VirtualBoxを 4.3.12にしたら、 Guest Additionsの設定が成功した。

と思ったら、今度は『仮想マシンの状態を保存』にして VirtualBoxを閉じようとすると、「Oracle VM VirtualBox Managerは動作を停止しました」のエラーが発生。

「問題が発生したため、プログラムが正しく動作しなくなりました。 プログラムは閉じられ、解決策がある場合は Windowsから通知されます。」とメッセージが表示される。(12.04, 14.04とも)

サスペンドできないのはこれまた大変不便。 VirtualBox停止させるまえに必ずシャットダウンしなければならない。

VirtualBoxのディスプレイ設定で、3Dアクセラレーションを有効化を外したら、エラーが出なくなった。

VMwareでもいろいろ問題起こすが、開発環境をアップデートするたびに何かしらの試行錯誤が強いられるのはしんどい。