年末年始ムードも落ち着き、今年も年頃の男女が共にそわそわする一大イベントのシーズンがやってきました。その結果に一喜一憂し、人によっては人生を左右することもあるイベント……そうテスト(入試や学年末試験など)ですね。そんなわけで今回はパッケージのテストについてお話しします。
Debian形式のパッケージとそのテストツール
UbuntuはDebianをベースにしたディストリビューションであり、パッケージ管理システムもパッケージ形式もDebianと同じものがそのまま使われています。ユーザーがソフトウェアセンターやapt-getコマンドからダウンロードするものの実態は、このDebian形式の「バイナリパッケージ」と呼ばれるものなのです。
あるソフトウェアのバイナリパッケージはおもに次のようなルートで生成されます。
ソフトウェアの開発元(アップストリーム)からソースコードのアーカイブを取得する
ソースコードをDebianのルールに従ってビルド、インストールするためのスクリプト群(debian/ディレクトリ)を追加する
アーカイブとdebian/ディレクトリを元に「ソースパッケージ」を作成する
ソースパッケージをビルドし、バイナリパッケージを作成する
ほとんどの工程は半自動化されており、パッケージング用のいくつかのスクリプトを駆使することで実現します。そのためDebianやUbuntuの開発者が最も時間と頭を使うのは、ユーザーからの不具合報告を元にユーザーと一緒に原因を追求したうえで2. のdebian/ディレクトリの中を調整したり、その結果を必要に応じてアップストリームに報告したりする部分になります。
アップストリームの新規リリースに追随したり、何らかの不具合を修正すれば新しいパッケージがリリースされます。これが「ちゃんとビルドできるのか」「 ちゃんとインストールやアップグレードできるのか」「 ちゃんと動くのか」をテストするのも開発者の作業の1つです。
ビルドテスト
パッケージを修正したらまず行うことはビルドできるかどうかの確認です。しかもただローカル環境でビルドするのではなく、パッケージのビルドに必要なものだけが揃ったよりクリーンな環境でのビルドを確認することが重要です。そんなクリーンな環境を準備し、自動的にビルドに必要なパッケージを揃えて、ビルドを行ってくれるツールとして、pbuilderやcowbuilderなどが使われます。
ある時点でパッケージをビルドできたとしても、ツールチェインの更新や依存ライブラリのバージョンアップ、パッケージ名の変遷などが原因で、ビルドできなくなること、「 FTBFS(Fails To Build From Source) 」と呼ばれる問題がしばしば発生しているのです。Ubuntuでも定期的にリポジトリ全体をリビルドすることでFTBFSの早期発見 を心がけています[1] 。
パッケージのポリシー
Debianのパッケージはただソフトウェアをインストールするだけでなく、より「お行儀良く」インストールできることが求められます。そうしないとDebianの膨大なパッケージ資産がお互いに干渉することなく1つのシステムで共存したり、ソフトウェアごとのアップグレードに追随することが難しくなるからです。そこでDebianではたとえば「Debianポリシーマニュアル 」を策定しており、DebianをベースにしたUbuntuでもこのポリシーに従うことが求められています。
と言ってもこの膨大なポリシーすべてに従っているかどうかを手作業で確認するのは至難の業です。機械でできることは機械に任せる、ということでDebianには「lintian」と呼ばれるパッケージの静的解析ツールが存在します。lintianコマンドにソースパッケージやバイナリパッケージを渡すことで、インストールされるファイルが「Filesystem Hierarchy Standard 」に従っているか、アーキテクチャごとに適切なバイナリが生成されているか、依存関係に明らかな間違いは存在しないかといった基本的なことから、実行ファイルごとにマニュアルは揃っているか、ドキュメントのスペルに間違いはないかといったパッケージの品質に関わる部分まで、いろいろなことを確認してくれるのです。
Debianの場合、各パッケージについてlintianを実行した結果は、Lintian Reports にまとまっています。
インストールやアップグレードのテスト
ビルドも問題なく、とくにポリシーにも違反していないとなれば次に確認するのは、そのバイナリパッケージが正しくインストールされるかどうか、です。さらにはリポジトリにある既存のバージョンをインストールした状態からアップグレードできるかどうか、アンインストールできるかどうか、その際に余計なデータを残していないかどうか、なども確認したほうが良いでしょう。
これを自動的に行ってくれるのがpiupartsと言うコマンド です。piupartsを使えば、pbuilderのときと同様にクリーンな環境を用意したうえで、インストール、アップグレード、アンインストールのいくつかの組み合わせや、それぞれの作業前後のディレクトリツリーの状態のチェックを行ってくれます。
Debianでは、各リリースごとやリリース間ごとの各パッケージのpiupartsの結果が公開されています 。ちなみに残念ながらUbuntuは1年ぐらい前までそもそもベースシステムすらpiupartsがパスしない状態になっているなど、まだpiupartsが使える状況にはありませんでした。最近はだいぶ改善してきたので、今後は各パッケージでもpiupartsを積極的に使っていくことになると思います。
動作確認
もちろんインストールするだけでなく、インストールした後にソフトウェアの動作確認も行います。最低でも、不具合が修正されたかどうかの確認は行うことになるでしょう。パッケージによってはリグレッションが発生していないかの確認や何らかのテストツールを使うことになるかもしれません。
そんな様々なテストを行うことで、より品質の高いパッケージがアーカイブで公開されることになるのです。
autopkgtest
いろいろなフェーズのテストが自動化される中、動作確認だけは依然としてほぼ手動な状態です。たしかに不具合によって確認すべきことがばらばらですし、ソフトウェアによってはユーザーのグラフィカルな操作も必要ですので、そう簡単には自動化できないものではあります。
それでも可能な限り自動化できないか、と考えるのが人間の性というもの。たとえばライブラリパッケージなどは、そのライブラリをリンクするプログラムを作ってビルドして出力結果を確認するスクリプトを作れば自動化可能です。そんな考えのもとに提案されたのが「autopkgtest 」という仕組みです。
ようやくここからが本題です。
autopkgtestの仕組み
autpkgtestはバイナリパッケージに対して、debian/tests/以下に記載されたテストを実行する仕組みです。状況やテスト内容に従って、対象のバイナリパッケージやテストに必要なパッケージがインストールされますし、複数のテストを記載すればそれを順番に実行してくれます。
最近のソフトウェアには、最初からテストプログラムが付随しているものも多く存在します。これらはソースパッケージからバイナリパッケージへビルドするときに自動的に実行されますし、テストが失敗すればビルド自体がエラーになることが一般的です。ただし、これらはあくまでソフトウェアをビルドした結果を対象としたテストです。それをバイナリパッケージとして構築し、システムにインストールしたあとも正しく動くかどうかというとそれはまた別の話です。autopkgtestは、パッケージインストール後にソースパッケージに含まれているテストを実行する仕組みも存在します。
Debianでは現在、このautopkgtestはDEP 8 としてDRAFT段階にあります。
Ubuntuでは既にいくつかのパッケージはautopkgtestを導入しています。導入されたパッケージについては、QAチームのJenkins上でテストが実行され 、その結果が表示されます。
サンプルで試してみる
では実際に試してみましょう。まずはテストしたい環境でautopkgtestパッケージをインストールしておきます。
$ sudo apt-get update
$ sudo apt-get install autopkgtest
いきなりテストを書くのではなく、既にあるテストを確認してみましょう。シンプルな例としてlibxml2が存在します。
$ mkdir /tmp/pkgtest-libxml2; cd /tmp/pkgtest-libxml2
$ apt-get source libxml2
$ cd libxml2-2.9.1+dfsg1/
$ ls debian/tests
build control python
$ cat debian/tests/control
Tests: build python
Depends: libxml2-dev, python-libxml2, build-essential, pkg-config
このうちcontrol がテストの設定を行うファイルです。Testsフィールドで「build」と「python」という名前のテストを実行することを宣言し、Dependsフィールドでテストを行うには「libxml2-dev、python-libxml2、build-essential、pkg-config」パッケージが必要なことを指定しています。
テストごとに同名のファイルを作って、そこにテストを記述します。たとえばbuild はlibxml2ライブラリを使うC言語のプログラムをビルドし、実行しています。リンク先を見てもらうとわかるとおり、これはただのシェルスクリプトです。python のほうもlibxml2のPythonバインディングを使ったテストになっています。
このテストを実行するにはadt-runコマンドを使用します。
$ sudo adt-run --no-built-binaries --built-tree=. --- adt-virt-null
adt-run: $ vserver: adt-virt-null
adt-run: $ genkey: sh -ec <SCRIPT> x /home/shibata/.autopkgtest/gpg
+ gpg --homedir=/home/shibata/.autopkgtest/gpg --batch --gen-key key-gen-params
(中略)
adt-run: & apt0t-build: [----------------------------------------
adt-run: & apt0t-build: ----------------------------------------]
adt-run: & apt0t-build: - - - - - - - - - - results - - - - - - - - - -
apt0t-build PASS
adt-run: & apt0t-build: - - - - - - - - - - stdout - - - - - - - - - -
build: OK
run: OK
(中略)
adt-run: & tree0t-python: [----------------------------------------
adt-run: & tree0t-python: ----------------------------------------]
adt-run: & tree0t-python: - - - - - - - - - - results - - - - - - - - - -
tree0t-python PASS
adt-run: @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ tests done.
「build」と「python」テストがそれぞれ実行され、「 PASS」と表示されていることがわかります。
ちなみに上記を実行すると「手元の環境に」libxml2とdebian/tests/controlファイルのDependsに指定したパッケージがインストールされるので注意してください。
「--no-build-binaries」は指定したソースパッケージをビルドしないことを意味します。バイナリパッケージが必要になる場合は適宜アーカイブからインストールします。
「--built-tree」はソースパッケージのビルドツリーを指定します。要するにソースパッケージを展開した先です。これ以外にも「--source」でソースパッケージそのものを指定できます。
たとえば次のようにパッケージ名そのものを指定した場合は、/tmpにソースパッケージを展開する所から自動で行ってくれます。
$ sudo adt-run libxml2 --- adt-virt-null
「---」以降は「virt server」の設定になります。テストはできるだけクリーンな環境で行うべきです。パッケージをインストールして動作をテストするなら何らかの仮想環境で行うことになります。autopkgtestでは「virt server」を選ぶことで、複数の種類の仮想環境を切り替えて使うことができます。
「adt-virt-null」はダミーの仮想環境で、ホストをそのまま仮想環境であると想定して使用します。よって先程述べたように、ホストにテスト対象のパッケージや依存パッケージがインストールされることになります。
adt-virt-lxcを使う
autopkgtestではLXCも仮想環境として使用できます 。これはautopkgtestの2.4以降で追加された機能なので、Debianならjessie以降、UbuntuならTrustyが必要です。
まずLXCをインストールした上で、ベースとなるLXCコンテナーを作成しておきます。
$ sudo apt-get install lxc
$ sudo lxc-create -t ubuntu-cloud -n trusty-cloud -- -r trusty
ubuntu-cloudはUbuntu Cloud Image を使用するテンプレートです。EC2などと同様に、cloud-config用のUserDataを渡すこともできます。詳しくは「lxc-create -t ubuntu-cloud -h」を参照してください。もちろん、通常のubuntuテンプレートでもかまいません。「 -n trusty-cloud」はコンテナーの名前で、「 -r trusty」は作成するコンテナーのリリースを指定しています。
実際に作成されたコンテナーは次のコマンドで確認できます。
$ sudo lxc-ls --fancy
NAME STATE IPV4 IPV6 AUTOSTART
--------------------------------------------
trusty-cloud STOPPED - - NO
次に、autopkgtestを今度はadt-virt-lxcを使って実行しましょう。
$ sudo adt-run --no-built-binaries --built-tree=. --- adt-virt-lxc --ephemeral trusty-cloud
adt-run: $ vserver: adt-virt-lxc --ephemeral trusty-cloud
adt-run: @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ builds ...
adt-run: @@@@@@@@@@@@@@@@@@@@ tree tree0
adt-run: @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ builds done.
adt-run: @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ tests ...
adt-run: @@@@@@@@@@@@@@@@@@@@ test tree tree0
adt-run: @@@@@@@@@@ run_tests ...
adt-run: & tree0t-build: preparing
(中略)
adt-run: & tree0t-python: [----------------------------------------
adt-run: & tree0t-python: ----------------------------------------]
adt-run: & tree0t-python: - - - - - - - - - - results - - - - - - - - - -
tree0t-python PASS
adt-run: @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ tests done.
「adt-virt-lxc」に渡している「--ephemeral」は、テスト用のコンテナーを複製する代わりに、lxc-start-ephemeralコマンドによりoverlayfsを使った一時的なコピーを作成してそれを使用します。よってtrusty-cloudコンテナーそのものには影響を与えません。
たとえばテスト中にlxc-lsを実行すれば、新しいコンテナーが作成され、起動していることがわかるでしょう。
$ sudo lxc-ls --fancy
NAME STATE IPV4 IPV6 AUTOSTART
---------------------------------------------------------
adt-virt-lxc-tcgnqo RUNNING 10.0.3.247 - NO
trusty-cloud STOPPED - - NO
ちなみにtrusty-cloudコンテナーを今後もテスト環境のベースコンテナーとして使い続けるなら、定期的にパッケージリストを更新する必要があります。これは普通にコンテナーを起動して、アップグレードしてください。今後はadt-virt-lxc側でアップグレードできる仕組みが追加されるはずです。
$ sudo lxc-start -n trusty-cloud
ユーザー名:ubuntu、パスワード:ubuntuでログイン
$ sudo apt-get update && sudo apt-get dist-upgrade
$ sudo halt
まとめ
現在どのパッケージがautopkgtestに対応しているかは次のコマンドで確認できます。
$ sudo apt-get install dctrl-tools
$ grep-dctrl -F Testsuite autopkgtest -s Package /var/lib/apt/lists/*Sources
Debianのsidだと254個、UbuntuのTrustyだと(Ubuntu独自パッケージの分嵩上げされて)312個表示されます。とくにUbuntu側は、Ubuntu Touchにおいてテストの自動化を積極的に推進している都合上、Ubuntu Touch関連のライブラリとコアパッケージの対応が急務となっています。
よってautopkgtestによるテストコードは、今後さまざまなパッケージに追加されることでしょう。