この記事はNetBSD Advent Calendar 2015の20日目として書かれました。
#はじめに
NetBSDにはATFというテストフレームワークが用意されており、ユーザランドプログラムやカーネルのためのテストが定期的に実行されされ、コード変更によってリグレッションを起こしていないかチェックされています。
またNetBSDにはrump kernelという仕組みがあり、ユーザプロセスとしてNetBSDカーネルを実行できます。ATFテストにはrump kernelを使ったテストがたくさんあります。
本記事では、特にNetBSDカーネルのネットワーク機能のATFテストをrump kernelを使って書く方法について説明します。
#ATFテストの書き方
基本的なATFテストの書き方はCreating atf-based tests for NetBSD srcに書いてあります。ATFテストはC言語とシェルスクリプトで書く方法がありますが、rump kernelを使ったテストは今ではシェルスクリプトで書くことが推奨されているので1、そちらの書き方の説明をします。
##シェルスクリプトでATFテストを書く
簡単なテストを書いてみます。
atf_test_case test1 cleanup
atf_test_case test2 cleanup
test1_head()
{
atf_set "descr" "Tests for something of test1"
atf_set "require.progs" "touch" "test"
}
test2_head()
{
atf_set "descr" "Tests for something of test2"
}
test1_body()
{
atf_check -s exit:0 touch testfile
atf_check -s exit:0 test -f testfile
}
test2_body()
{
// do some tests...
}
test1_cleanup()
{
rm -f testfile
}
test2_cleanup()
{
// cleanup
}
atf_init_test_cases()
{
atf_add_test_case test1
atf_add_test_case test2
}
ここではtest1とtest2という2つのテストを書きました。
まずatf_test_case
とatf_init_test_cases
とatf_add_test_case
を使って、ATFにtest1とtest2の2つのテストを追加することを宣言します2。atf_test_case
の第二引数のcleanupはこのテストにcleanup部があることを宣言しています3。
テスト自体はheadとbodyとcleanupの3つで構成されています。headはテストのメタデータを書く場所で、テストの概要やテストに必要なコマンドを列挙します4。bodyはテスト本体です。テスト環境をセットアップしたり、atf_check
等のassertionコマンドを用いてテストを記述します。cleanupはテストで作成したファイルを消したり、プロセスをkillしたり、デバッグ用の出力を行ったりする場所です。bodyの後に実行されます。上の例ではbodyで作ったファイルtestfile
を消しています。
##atf_check
bodyで使われているatf_check
コマンドについて説明します。atf_check
はコマンドラインを引数に取り、コマンドを実行して期待する結果になっているかをチェックします。-s exit:0
は指定したコマンドが成功する(exist statusが0)ことをassertionしています。逆に失敗することを期待する場合は、-s not-exit:0
のように書きます。
atf_check
は他にも標準出力や標準エラー出力のチェックもできます。-s exit:0 -o match:"hoge"
でコマンドが成功して、かつ標準出力にhogeという文字列が含まれているかをチェックします。-s not-exit:0 -e match:"fuga"
でコマンドが失敗して、かつ標準エラー出力にfugaという文字列が含まれているかをチェックします。出力を無視したい時は-o ignore
や-e ignore
と書きます。
コマンドラインでパイプ等を使いたい場合は、atf_check ... -x "ls |grep hoge"
のように書きます。
ほとんどの場合はatf_check
で事足りますが、単に文字列の一致をチェックをするatf_equal
などを使う場合もあります。
#Rump kernelを使ったATFテストの書き方
rump kernelを使って、NetBSDの通信機能が正しく動作するかpingを使ってチェックする、簡単なテスト(ping_test
)を書いてみます。上記説明と重複する部分は省略しています。
##Rump kernelを使ったATFテスト例
inetserver="rump_server -lrumpnet -lrumpnet_net -lrumpnet_netinet"
inetserver="$inetserver -lrumpnet_netinet6 -lrumpnet_shmif"
SOCK1=unix://commsock1
SOCK2=unix://commsock2
BUS=bus
IP1=10.0.0.1
IP2=10.0.0.2
TIMEOUT=1
setup_rump_server
{
local sock=$1
local ip=$2
local bus=$3
atf_check -s exit:0 ${inetserver} $sock
export RUMP_SERVER=$sock
atf_check -s exit:0 rump.ifconfig shmif0 create
atf_check -s exit:0 rump.ifconfig shmif0 linkstr $bus
atf_check -s exit:0 rump.ifconfig shmif0 $ip
atf_check -s exit:0 rump.ifconfig shmif0 up
atf_check -s exit:0 rump.ifconfig -w 10
}
ping_test_body
{
setup_rump_server $SOCK1 $IP1 $BUS
setup_rump_server $SOCK2 $IP2 $BUS
export RUMP_SERVER=$SOCK1
atf_check -s exit:0 -o ignore rump.ping -n -c 1 -w $TIMEOUT $IP2
export RUMP_SERVER=$SOCK2
atf_check -s exit:0 -o ignore rump.ping -n -c 1 -w $TIMEOUT $IP1
}
ping_test_cleanup
{
env RUMP_SERVER=$SOCK1 rump.halt
env RUMP_SERVER=$SOCK2 rump.halt
}
このテストでやっていることは、2つのrump kernelプロセスを立ち上げネットワークインタフェースとIPを設定(setup_rump_server
)、双方からpingが成功するかチェック、最後にcleanupでrump kernelプロセスを停止しています。
##rump_server
Rump kernelプロセスはrump_server
コマンドで立ち上げます。Rump kernelはモジュール化(共有ライブラリ化)されており、必要な機能だけをロードして使います。この例では、ネットワーク機能とrump kernel用ネットワークインタフェース(shmif
)をロードしています。
rump_server
の第二引数には、rump_server
プロセスと他のユーザプロセスが通信するためのソケットを指定します。ATFテストではローカルで閉じているのでUnixドメインソケットを使っています。以降、当該rump_server
に対して何かコマンドを実行したい場合は、RUMP_SERVER
環境変数にソケットを指定して、rump.XXX
というコマンドを使って実行します。この例ではrump.ifconfig
やrump.ping
コマンドを使っています5。
##shmif
shmif
はrump kernel用のネットワークインタフェースです。ファイルを介してEthernetフレームをrump kernelプロセス間でやりとりします。rump.ifconfig ... linkstr $bus
でファイル名を指定しています。
Rump kernel間で送受信されたEthernetフレームは、すべてファイルに保存されているので中身を見ることが簡単にできます。shmif_dumpbus -p - $bus 2>/dev/null| tcpdump -n -e -r -
とやるとtcpdump
でフレームを表示させることができます。
#おわりに
簡単ですが、rump kernelを使ってATFテストを書く方法を説明してみました。
カーネルはコード規模が大きいので、既存の動作を壊さずコードを変更することが難しいです。Rump kernelでネットワーク機能をはじめとするカーネル機能のテストを書いておくことで、開発のコストが大幅に少なくなります。
カーネルのコードを改良することだけがカーネル開発ではありません。カーネルのためのATFテストを追加することもカーネル開発においてはとしても重要です。この記事がそのための一助になれば幸いです。
-
...とrump kernelの作者の人が言ってました。 ↩
-
重複しているように見えますが、
atf_test_case
とatf_init_test_cases
(atf_add_test_case
)は両方とも必要です。 ↩ -
cleanupを書き忘れるとcleanup部が実行されないので忘れずに書きましょう。 ↩
-
例のためtouchとtestコマンドを
require.progs
に書いていますが、baseに常に入っているコマンドは書かなくても構いません。 ↩ -
Rump kernelにはrump化されたコマンドを使う方法の他に、librumphijack.soというシステムコールをハイジャックする共有ライブラリを使って、rump化されていないユーザプログラムを使う方法もあります。 ↩