Qiita Teams that are logged in
You are not logged in to any team

Log in to Qiita Team
Community
OrganizationAdvent CalendarQiitadon (β)
Service
Qiita JobsQiita ZineQiita Blog
5
Help us understand the problem. What is going on with this article?
@ozaki-r

Rump kernelを使ってATFテストを書く

More than 5 years have passed since last update.

この記事は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_caseatf_init_test_casesatf_add_test_caseを使って、ATFにtest1とtest2の2つのテストを追加することを宣言します2atf_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.ifconfigrump.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テストを追加することもカーネル開発においてはとしても重要です。この記事がそのための一助になれば幸いです。


  1. ...とrump kernelの作者の人が言ってました。 

  2. 重複しているように見えますが、atf_test_caseatf_init_test_cases(atf_add_test_case)は両方とも必要です。 

  3. cleanupを書き忘れるとcleanup部が実行されないので忘れずに書きましょう。 

  4. 例のためtouchとtestコマンドをrequire.progsに書いていますが、baseに常に入っているコマンドは書かなくても構いません。 

  5. Rump kernelにはrump化されたコマンドを使う方法の他に、librumphijack.soというシステムコールをハイジャックする共有ライブラリを使って、rump化されていないユーザプログラムを使う方法もあります。 

5
Help us understand the problem. What is going on with this article?
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away

Comments

No comments
Sign up for free and join this conversation.
Sign Up
If you already have a Qiita account Login
5
Help us understand the problem. What is going on with this article?