petardfsでアプリケーションをテストする

 petardファイルシステム(petardfs)は、エラーだけを生成するように設計されたファイルシステムである。ただし、エラーを生成する条件と期待されるエラーの姿を任意に指定できる。このため、petardfsはシステムテストと単体テストに便利である。たとえば、ファイルを開くのに失敗した場合やファイルを5000バイト読み込んだところで読み込みエラーが起きた場合に、アプリケーションから正しいエラーメッセージが表示されることを確認できる。

 petardfsでは、セットアップの簡単なFUSE(Filesystem in Userspace)を使うので、カーネルの再コンパイルや新しいカーネルモジュールの追加は必要ない。標準の設定で“ベースファイルシステム”とマウントポイントを指定する。たとえば、/home/ben/fooがベースファイルシステムで、ファイルシステムを/home/ben/petard-fooにマウントするとしよう。これ以外の設定を行わなくても、foo下にあるファイルはpetard-fooでそのまま使用できる。petardfsでは、XML設定ファイルを使って、エラーを報告するファイルと使用するエラーコードを設定する。たとえば、foo.txtがバイト34~37の位置でEIOエラーを発生する、などの設定が可能だ。

 petardfsのビルドとインストールは、通常のconfiguremakemake installの手順で行う。petardfsはfuselagefsファイルシステムを基盤とするが、これはFUSEのC++ラッパーであり、ベースファイルシステムをマウントポイントにマッピングする機能を提供する。そのおかげで、開発者は他のFUSEファイルシステムをすばやく作成し、これを若干の機能が追加されたベースファイルシステムとして提供することができる。

 petardfsとfuselagefsは、SourceForge.netからダウンロードできる。

petardfs上での実行

 petardfsの最重要オプションは、–error-definitions(-e)と–url(-u)である。前者はエラー定義が格納されたXMLファイルを指定し、後者はベースファイルシステムを指定する。ベースファイルシステムは、常に絶対パスで指定する。また、petardfsファイルシステムをマウントするパスを最後の引数として指定する。前掲の例で言えば、ベースファイルシステムを指定するオプションは、-u /home/ben/fooになる。コマンドラインの最後の引数には、petardfsを使用する場所、つまり/home/ben/petard-fooを指定する。

 petardfsコマンドラインオプションのなかで、FUSEに指示を渡すものは名前が--fuse-で始まる。通常、FUSEファイルシステムがfusermount -dでアンマウントされるまで、petardfsはバックグラウンドで動作する。FUSEオプションの「–fuse-forground」を使うと、端末でfusermountコマンドを実行するかCtrl+Cを押すまで、petardfsは端末のフォアグラウンドに留まる。「–fuse-forground」オプションを使うのは、petardfs自体にバグの疑いがある場合が主である。petardfsをフォアグラウンドで実行すると、stderrメッセージを使用できる。petardfsをGNUデバッガから直接呼び出すこともできる。

設定ファイル

 petardfsでは、ファイルシステムに関連する主要な関数にエラーのリストを指定できる。設定ファイルのトップレベルの要素は「petardfs-config」である。この要素に1つの「errors」要素が含まれる。各関数は「errors」下に要素として現れる。関数ごとにエラー条件のリストを指定できる。

 すべてのファイルパスは、ベースファイルシステムからの相対パスで指定する。ファイルシステムパスのルートは、ベースファイルシステムでもある。たとえば、ベースファイルシステムが/home/ben/fooで、マウント先が/home/ben/petard-fooの場合、次のエラー条件は/home/ben/petard-foo/file2.txtの読み込み時にEIOエラーを生成する。

<petardfs-config>
    <errors>

      <read>
	<error path="/file2.txt">
	  <n start-offset="4096" end-offset="4196" error-code="&EIO;"/>
	</error>
      <read>

    <errors>
<petardfs-config>

 さらに細かいエラー条件を指定するには、EINTRとEAGAINを使う。どちらも、アプリケーションが同じI/O関数呼び出しを再試行することを意味する。たとえば、次の設定では、file3.txtファイルの読み込みは、アプリケーションがバイト20000の読み込みを3回試みた後で、続行を許可される。

<error path="/file1.txt">
  <n start-offset="10000" end-offset="10000" error-code="&EINTR;" times="10"/>
</error>
<error path="/file3.txt">
  <n start-offset="20000" end-offset="20000" error-code="&EAGAIN;" times="3"/>
</error>

 この例で使用するすべてのエラーコードは、XMLエンティティである。好き勝手に決めた整数エラーコードがXMLファイルのあちこちに散らばるのを避けるため、エラーコードはXMLファイル本体のヘッダーに定義される。petardfsは、特定のエラー条件に関連付けられた数値エラーコードをただ返すだけである。

<!DOCTYPE petardfs-config [

<!ENTITY	EPERM		 "1">
<!ENTITY	ENOENT		 "2">
<!ENTITY	ESRCH		 "3">
...

 petardfsの現在のリリースでは、エラー条件を関連付けられる有効な関数は、read、write、fsync、mkdir、symlink、unlink、rmdir、rename、link、chmod、chown、ftruncate、utime、openである。

petardfsの実行

 完全なXML設定ファイルは、petardfsディストリビューションtarballにtestsuite/sampledata/config-simple-filesystem-simple-error-in-read-file1.xmlとして収められている。simple-filesystem.tarには、file1.txt、file2.txtなどの名前の一連のファイルを持つ小さなファイルシステムが含まれる。設定ファイルには、これらのサンプルファイルで発生する各種のエラーが定義されている。

 以下の一連のコマンドでは、petardfsをマウントし、このエラー生成ファイルシステムに簡単な指示を送っている。トップレベルへの「…」パスの部分は、petardfsのtarballディストリビューション内のファイルへのパスで置き換えることができる。petardfsでfile1.txtにdiffコマンドを実行すると、“Interrupted system call”のエラーが起きることに注意してほしい。catコマンドではこのエラーが起きても適切に再試行が行われるので、この問題を回避するため、catを使ってファイルのコピーを作成してからdiffをこのコピーに対して実行する。file2.txtのエラーは、4096番目のバイトで起きる単純なEIOエラーである。catは、このエラーをstderr経由でレポートし、終了する。

$ mkdir /tmp/petard
$ cd /tmp/petard
$ cp .../simple-filesystem.tar .
$ cp .../config-simple-filesystem-simple-error-in-read-file1.xml
$ mkdir -p foo petard-foo
$ cd foo
$ tar xvf ../simple-filesystem.tar
$ cd ..
$ petardfs -e config-simple-filesystem-simple-error-in-read-file1.xml \
   -u `pwd`/foo  petard-foo
$ ls -l petard-foo/
total 512
-rw-rw---- 1 ben ben 50000 2007-03-14 16:15 barB.txt
-rw-rw---- 1 ben ben 50000 2007-03-14 16:15 barC.txt
-rw-rw---- 1 ben ben 50000 2007-03-14 16:15 file1.txt
-rw-rw---- 1 ben ben 50000 2007-03-14 16:15 file2.txt
-rw-rw---- 1 ben ben 50000 2007-03-14 16:15 file3.txt
-rw-rw---- 1 ben ben 50000 2007-03-14 16:15 file4.txt
-rw-rw---- 1 ben ben 50000 2007-03-14 16:15 file5.txt
-rw-rw---- 1 ben ben 50000 2007-03-14 16:15 fooA.txt
-rw-rw---- 1 ben ben 50000 2007-03-14 16:15 simplefile.txt
drwxrwx--- 2 ben ben  4096 2007-03-14 16:16 sub1
drwxrwx--- 2 ben ben  4096 2007-03-14 16:16 sub2

$ diff -Nuar petard-foo/file1.txt foo/file1.txt
diff: petard-foo/file1.txt: Interrupted system call
$ cat petard-foo/file1.txt >|/tmp/file1.txt
$ diff -Nuar /tmp/file1.txt foo/file1.txt

$ cat petard-foo/file2.txt >|/tmp/file2.txt
cat: petard-foo/file2.txt: Input/output error
$ diff -Nuar petard-foo/file2.txt foo/file2.txt
diff: petard-foo/file2.txt: Input/output error

Dejagnu

 petardfsディストリビューションは、petardfsをアプリケーションのテストスイートに統合する方法のサンプルとしても使える。petardfsには、petardfsの関数が期待どおりに動作することをテストするためにDejaGnuを使用する、限定的なテストスイートが含まれている。トップレベルのMakefile.amでは、DejaGnuはAUTOMAKE_OPTIONSに含まれる。サブディレクトリtestsuite/petardfs.test下のpetardfs.expには、入門レベルのpetardfs用TCL/expect DejaGnuテストスイートが含まれる。

 petardfs.expによって実行されるテストは、petardfsそのものが期待どおりに動作するかどうかを確認する。ただし、petardfsをテストするコマンドと、petardfs上で動作するアプリケーションをテストするコマンドは、非常に似ている。たとえば、さまざまな設定オプションを使ってpetardfsを実行することや、proc unmount_petardfs {}のような関数を使ってpetardfsファイルシステムを再度アンマウントする必要があることは、両者に共通する。

 テストスイートの中心部は、petardfs.expファイルの最後にあるrun_all関数にある。

proc run_all {} {

    verbose "Running all existing tests... this will take a long time..."
    run_noerror_petardfs_test_suite
    run_simple_petardfs_test_suite
}

run_all

 run_simple_petardfs_test_suite関数は、指定されたエラー設定を使ってpetardfsファイルシステムをマウントし、指定された一連のテストケースをpetardfsに対して実行し、期待どおりの結果かどうかをチェックする。petardfs.expのテストケースでは、さまざまなコマンドをpetardfsに対して実行することにより、特定のエラーメッセージが生成されることが期待される。最後に、run_simple_petardfs_test_suiteは、petardfsをアンマウントして、テストスイート内の他のテスト関数が別の設定でpetardfsを同じマウントポイントにマウントできるようにし、またテストスイートの完了後に動作中のpetardfsのインスタンスが残らないようにする。

 petardfs.expには、いくつかのユーティリティ関数が用意され、利用される。rt_expected{ testname expected cmd }関数は、診断結果または成功をレポートするためにtestnameを使ってコマンド(cmd)を実行する。expectedの文字列は、テストの成功時にコマンドの出力に表示される。verify_output_identical { cmdA cmdB diffflags }関数は、2つのコマンドを実行し、diff -Nsuarとdiffに渡される追加フラグdiffflagsを使って、両者の出力を比較する。このようなユーティリティ関数があることで、DejaGnuテストスイートケースをpetardfs用に作成する作業はかなり簡単になる。ユーティリティ関数では、アプリケーションのタイムアウトエラーとspawn/expectペアが処理される。

proc run_simple_petardfs_test_suite {} {

    global PTT
    global UTBASE
    global SDATA
    global verbose
    global PETARDFS

    set ERRORDESC "$SDATA/config-simple-filesystem-simple-error-in-read-file1.xml"
    set BASEDIR $PTT
    setup_basedir_and_input;

    cd $BASEDIR
    system $PETARDFS -e "$ERRORDESC" -u "$BASEDIR/input" "$BASEDIR/fusefs"
    if { $verbose > 1 } {
	send_user "Mounted petardfs with specified errors:$ERRORDESC...\n"
    }

    set testname [rt_version "io-error-at-4096" ]
    rt_expected $testname  "Input/output error" "cat $BASEDIR/fusefs/file2.txt"

    # file1.txt has some EINTR returns
    verify_output_identical \
	"cat $BASEDIR/input/file1.txt" \
	"cat $BASEDIR/fusefs/file1.txt" "-Nuar"

    ....

    unmount_petardfs;
}

まとめ

 petardfsを使うことで、アプリケーションに厳しい目を向け、エラー条件に適切に反応するかどうかを確認できる。テストスイートを通してアプリケーションをpetardfs上で実行することにより、プロジェクトのエラー処理コード内にエラーを検出し、修正できる。起動時にファイルを開くことに失敗した場合など、多くのエラー状況においてアプリケーションは単に終了するだけでよいが、ログファイルの切り詰めに失敗したような場合は、エラーの発生をメッセージで報告するだけで、動作を継続する必要がある。petardfsを使うと、読み込みエラーのような一般的なエラーとtruncate(2)エラーのような珍しいエラーの両方をテストすることができる。

Linux.com 原文