Arch Linux on WSL2 で systemd を動かす

2022/02/01 追記

genie をやめ、 Distrod へ移行しました。

github.com

Distrod は以下が良いです

  • 既存ディストロへのインストールが簡単 (シェルスクリプト実行するだけ)
  • 新規ディストロのインストールが簡単 (コマンド実行するだけで systemd 対応のディストリを WSL へ登録できる)
  • genie 等のコマンドを経由しないWSLコマンド実行 (VSCode の Remote 拡張のシェルセッションなど) も systemd 管理下プロセスとして起動される
  • (genieと比較して) .NET ランタイムや libc への依存がなく、ディストロのパッケージ更新で壊れる可能性が低い (Distrod の依存ライブラリが同梱されており、システム側ライブラリに依存しない)

昨日のエントリは systemd はいらないなどと言っていましたが、 podman コンテナ上で systemd を動かすためにはホスト側でも systemd が動いていないといけないようだったので、観念して systemd をセットアップしました。

gifnksm.hatenablog.jp

tl;dr

genie のインストール

github.com

上記リポジトリに含まれている PKGBUILD は古いバージョン用のものだったので、自分で新たに PKGBUILD を作成しました。

2021/1/18 追記: genie 1.31 へアップデートしました。 2021/1/18 追記2: GitHub へ移動しました。

github.com

Arch パッケージガイドライン - ArchWiki によると /usr/libexec は避けろとのことだったので、 /usr/lib/genie にファイルを配置しても動くよう、ソースコードを編集するような PKGBUILD になっています。 手元環境で試した限りでは問題なく動作しています。

dependsmakedepends が AUR にあり makepkg -s が使えないため、以下のようにインストール。また、 community リポジトリdotnet はバージョンが古いため、 AUR の dotnet-sdk-bin をインストールします。

$ paru -S --needed --asdeps daemonize inetutils dotnet-sdk-bin
$ makepkg -si

グループポリシーの設定

以下のスクリプトを適当なパス (今回は %userprofile%\scripts\logoff.bat) に配置します。

wsl -d Arch -- genie -u
wsl -t Arch

wsl -t Arch を実行しているのは、 genie -u 実行後、WSL を再起動することなく再度 genie -i などを実行すると何かおかしなことになるためです。 (ログオフ時には WSL も終了されると思うので明示的に行う必要はないと思いますが、念のため。)

以下記事に従い設定します。

www.nextofwindows.com

以下方法で動作確認しました。

  1. 上記を設定
  2. genie -i を実行し systemd を立ち上げる
  3. Windows をログアウト & 再ログイン
  4. genie -s でシェル立ち上げ後、 journalctl でログを確認する
  5. systemd-shutdown 等のログが出ていればOK (たぶん)

Windows Terminal の設定

自分は Windows Terminal を利用しているため、設定変更し端末を開いた時点で systemd 管理下にログインするよう設定しました。

            {
                "guid": "{a5a97cb8-8961-5535-816d-772efe0c6a3f}",
                "hidden": false,
                "name": "Arch",
                "source": "Windows.Terminal.Wsl",
                "startingDirectory": "//wsl$/Arch/home/nksm",
                "icon": "ms-appdata:///Local/archlinux.png",
                "commandline": "wsl -d Arch -- genie -s"
            },

重要なのは commandline です。

以上で systemd の設定が完了します。 また、無事 podman コンテナ内で systemd を立ち上げることもできるようになりました。

追記

ログイン時にも wsl -d Arch -- genie -i を実行する方が良いかもしれません。 初回のWindows Terminal 起動時に wsl -d Arch -- genie -s を実行すると、 systemd の起動が間に合わないからか failed to connect bus といったエラーが出ることがあるためです。

github.com

2021/1/18 追記: 上記 issue は genie 1.31 で修正されました。

追記2

systemd インストール後、全パッケージを再インストールした方が良いかもしれません。 非 systemd 環境ではパッケージインストール時に dbus 関連でエラーが出るパッケージがあり、正しく設定されていないかもしれないためです。

$ pacman -Qqn | sudo pacman -S -

追記3

PKGBUILD に間違いがあった (10-genie-envvar.sh のインストール先がおかしかった) ため、修正しました。

Arch Linux on WSL2 で podman でコンテナを動かす

自分用メモ。

tl;dr

  • GitHub - yuk7/ArchWSL: ArchLinux based WSL Distribution. Supports multiple install.
    • Arch Linux on WSL2 を入れる。 scoop 使うと楽。
  • Podman - ArchWiki
    • /etc/sub{u,g}id の設定が必要。設定する値は <user>:100000:65536 などとすると分かりやすい (コンテナの uid=1000 がホストの uid=101000 になり対応関係が一目瞭然)
    • 上記設定前に podman 叩いちゃった場合は podman system migratepodman system reset を実行する必要があるみたい
  • How to run Podman on Windows with WSL2 | Enable Sysadmin
    • Red Hat の記事。最後の方の /etc/containers の設定例だけ見れば良い。
  • pacman -S shadow
    • ArchWSL インストール直後だと /usr/bin/new{u,g}idmap に capability (or setuid bit) が設定されていないため、これらのバイナリが含まれる shadow パッケージを再インストールすることで設定させる
    • 以下になっていればOK。capabilityがセットされていれば setuid bit の設定は不要。

      $ getcap /usr/bin/new{u,g}idmap
      /usr/bin/newgidmap cap_setgid=ep
      /usr/bin/newuidmap cap_setuid=ep
      $ ls -l /usr/bin/new{u,g}idmap
      -rwxr-xr-x 1 root root 41K  9月  7 22:42 /usr/bin/newgidmap
      -rwxr-xr-x 1 root root 37K  9月  7 22:42 /usr/bin/newuidmap
      

当時のツイート

飲酒正月気分で殴り書きしてるから今読むと日本語がおかしい。

その他

  • podman in podman も余裕やろと思ってやってみたら overlayfs on overlayfs (?) はできないみたいなエラーが出て挫折してしまった。 ググった感じ /var/lib/containers あたりを volume にすれば良さそうっぽいが試してない。
  • podman をググると docker と違って英語記事ばかり出てくるのは良いことなのかどうなのか。 Red Hatサブスクリプションがないと読めない記事ばっか出てくるのは辛い。 Red Hat Developerに登録 (無料) すれば読めるとのこと。Shion Tanaka さん教えて頂きありがとうございます!
  • WSL2 で systemd を動かすかどうかは悩ましいところ。 以下を使えば簡単に systemd を使えるようになるらしいが、今のところ導入していない。 github.com 導入していない理由は以下。
    • ホストマシンのシャットダウン時に systemd に対するシャットダウン操作が必要になるなど、WSL2の気軽さが損なわれてしまいそうな気がする。
    • systemd の提供する仕組みと Windows の提供する init の仕組みがコンフリクトしたりしないかが心配。ネットワーク周りとか。
    • 今のところ systemd なしでも困っていない。動かしたいサービスがあったら podman コンテナで代用できそう。 まあそのうち気が変わるかもしれない。

Rust の Index bounds check の性能影響を調べてみた

Rust の配列 (Vec<T>[T] など) では、インデックスアクセス時に境界チェック (index bounds check) が常に行われます。 常に行われる、というのはビルドの種類 (リリースビルド・デバッグビルド) に依存しないという意味です。 とにかく安全性を重視する Rust らしい仕様であると言えます。

他のプログラミング言語に目を向けて見ると、常にチェックが行われるシステムプログラミング言語というのも珍しい気がします。 すべてがプログラム作成者の責任になる C では当然何のチェックも行われませんし、 安全なシステムプログラミング言語という意味で Rust と立ち位置が近い D では、デバッグビルドの場合のみ境界チェックが行われます (リリースビルドでは行われない)。

配列の境界チェックは実行時の処理になるため、普通に考えると Rust の方がチェック処理の分だけ実行性能が落ちてしまうことになります。境界チェックが行われない配列アクセス手段が用意されている とはいえ、配列アクセスが遅いのであれば Rust で書かれる様々なプログラムの実行性能も遅くなってしまわないか心配になってしまいます。

というわけで、Rust の配列アクセス性能を測定してみました。

tl;dr

Index bounds check のコストが支配的になるケースは少なそう。

注意

以下で出てくるベンチマーク結果は、仮想マシン上の Linux で測定したものなので実マシン上とは異なる結果になっている可能性があります。 また、筆者はアセンブリが読めず、CPUの気持ちも分からないため、コンパイル結果に関する見解は多くの誤りが含まれている可能性があります。 もし変な記述があれば、コメント等頂けると大変助かります。

テストに利用したソースコード一式は、以下Gistからどうぞ。

https://gist.github.com/gifnksm/9a792feff8567067b2d272c86258c2d4

利用したコンパイラは、nightly (2016/5/30) です。

$ rustc --version
rustc 1.11.0-nightly (a967611d8 2016-05-30)

テスト1: 配列先頭からシーケンシャルアクセスする

まずは、シンプルなケースから。いくつかの関数について性能を比較します。

1. インデックスアクセスその1 (D index)

#[inline(never)]
pub fn direct_index(arr: &[u64]) -> u64 {
    let mut sum = 0;
    for i in 0..arr.len() {
        sum += arr[i];
    }
    sum
}

libcoreのソース によると、arr[i] へのアクセスで arr.len() との比較により index bounds check が行われます (assert! はリリースモードでも消えずに残ります)

arr.len() との比較は Range::next() でも行われている ため、コンパイラの最適化により arr[i] の index bounds check は除去されるかもしれません

2. インデックスアクセスその2 (D index_len)

#[inline(never)]
pub fn direct_index_len(arr: &[u64], len: usize) -> u64 {
    let mut sum = 0;
    for i in 0..len {
        sum += arr[i];
    }
    sum
}

i の範囲を引数で渡すように変更したものです。arr[i] の index bounds check が除去されずに残ることが期待値です。

3. イテレータでのアクセス (D iter)

#[inline(never)]
pub fn direct_iter(arr: &[u64]) -> u64 {
    let mut sum = 0;
    for &n in arr {
        sum += n;
    }
    sum
}

std::slice::Iter::next の実装によると、イテレータを使った場合はインデックスアクセスの場合と異なり、Cと同等の性能になることが期待されます。

4. C プログラム (D c)

uint64_t direct_c(const uint64_t *arr, size_t len)
{
    uint64_t sum =  0;
    for (size_t i = 0; i < len; i++) {
        sum += arr[i];
    }
    return sum;
}

テスト1の測定結果

$ cargo bench
test tests::D_c         ... bench:      26,372 ns/iter (+/- 362)
test tests::D_index     ... bench:      26,389 ns/iter (+/- 373)
test tests::D_index_len ... bench:      59,863 ns/iter (+/- 602)
test tests::D_iter      ... bench:      26,386 ns/iter (+/- 465)

1, 3, 4 (index, iter, c) はほぼ性能は同じです。objdump -d の結果を見ると 1, 4 (index, c) はほぼ同じアセンブリが出力されていましたが、 3 (iter) は少し長めのアセンブリでした。

1, 2 の結果を比較すると、1の場合は想定通り、 arr[i] の index bounds check は除去されているように思えます。

テスト1のまとめ

配列をシーケンシャルに先頭からアクセスしていく場合、 index bounds check のコストは無視して良いと思います。

先頭からアクセスする場合、多くのケースではイテレータを使うことが多いでしょう。また、スライスを渡す場合でも、スライスと len をペアで渡すよりも、&[..len] のように slice を slicing して渡すような書き方をする方が、引き渡す引数の数を減らすことができ、インタフェースがシンプルになるなどのメリットがあるためです。 slicing した場合、1と同じような性能になることが予想されます。

テスト2: 外部から指定された順序でインデックスアクセスする

配列先頭からシーケンシャルアクセスするのではなく、ランダムな順番など関数外から渡される順番に基づいて間接的 (?) にアクセスするケースについて測定します。

1. index bounds check ありの間接アクセス (I iter, R iter)

#[inline(never)]
fn indirect_iter(arr: &[u64], idx: &[usize]) -> u64 {
    let mut sum = 0;
    for &i in idx {
        sum += arr[i];
    }
    sum
}

2. index bounds check なしの間接アクセス (I iter_nc, R iter_nc)

#[inline(never)]
unsafe fn indirect_iter_uc(arr: &[u64], idx: &[usize]) -> u64 {
    let mut sum = 0;
    for &i in idx {
        sum += *arr.get_unchecked(i);
    }
    sum
}

1 の index bounds check を行わない版です。

3. C プログラム (I c, R c)

uint64_t indirect_c(const uint64_t *arr, const size_t *idx, size_t idx_len)
{
    uint64_t sum =  0;
    for (size_t i = 0; i < idx_len; i++) {
        sum += arr[idx[i]];
    }
    return sum;
}

テスト1の測定結果

$ cargo bench
test tests::I_c         ... bench:      60,888 ns/iter (+/- 1,113)
test tests::I_iter      ... bench:      69,746 ns/iter (+/- 1,124)
test tests::I_iter_uc   ... bench:      69,355 ns/iter (+/- 633)
test tests::R_c         ... bench:     162,968 ns/iter (+/- 14,711)
test tests::R_iter      ... bench:     169,162 ns/iter (+/- 1,470)
test tests::R_iter_uc   ... bench:     163,944 ns/iter (+/- 1,850)

I_XXX はシーケンシャルにアクセスする場合、R_XXX はランダムアクセスする場合です。 どちらのケースでも c, iter_uc, iter の順で高速です。 _iter_iter_uc で実行速度に差はあるのですが、Cと差が生じているのは別の理由によるのかもしれません。

テスト2のまとめ

この例のように配列のインデックスを間接アクセスするケースは、配列の要素をランダムアクセスすることが多いと思いますが、その場合、 index bounds check ではない別の原因で大きく性能が落ちてしまう (キャッシュ関連?) ようです。 ですので、index bounds check だけをことさら取り上げる必要もないのかなぁ、と思います。

まとめ

考察が雑。

Xperia Z5の画面が割れてタッチに反応しなくなった時にやったこと

機種変更して1ヶ月くらいのXperia Z5の画面を割ってしまう事故を起こしてしまった時にやったことを記録がてらメモ。 同じような事故を起こしてしまった人の助けとなれば。。。

前提

  • 機種: Xperia Z5 (au)
  • 故障内容: 画面のガラスが割れ、タッチ操作が行えなくなる。通信や充電は正常に行える

電源を切る

携帯電話を目覚まし時計代わりに使っている方は多いと思いますが、タッチ操作が効かなくなってしまった場合、鳴り始めたアラームをとめることが出来ません。 特にスヌーズ機能を有効にしている場合、アラームはいつまでも鳴り続けるようです。 (朝、家を出るときにアラームをとめられず、諦めて家に放置した携帯電話が、夜帰宅して鳴り続けているのを見たときの絶望感ときたら。。。)

アラームをとめるために電源を切ろうとしても、電源ボタンを長押しするだけでは電源オフ、再起動等の選択肢が表示されるだけで、タッチ操作が要求されてしまいます、が、以下のいずれかの方法で、タッチ操作なしで電源を切ることが出来ます。

電源+ボリューム増ボタン長押しの方が、カバーを開ける必要がなく楽です。

マウスを接続

タッチ操作が効かなくなった携帯の操作方法として、マウスを接続する方法が有名ですが、 Xperia Z5 の場合、電源オン状態でマウスを接続するだけではマウスを使う事が出来ません (通常時はUSBポートに通電されておらず、機器接続のためにいくつか操作する必要があるそうです)。

しかし、マウスを接続したまま端末を再起動すると、なぜか機器が認識され、ロック画面にマウスカーソルが表示されます。 タッチ操作が効かなくなった場合の救済措置でしょうか。

USB type A 接続のマウスはそのままではつなぐことは出来ないため、 USB type A メス <-> マイクロ"B" オス の変換コネクタが必要です。 マイクロUSBにもAとBのタイプがあるので、お間違えなく。 わたしは、間違えて無駄な変換コネクタを買ってしまいました。。。

機種破損時の交換サービスを利用する

わたしは au ユーザで、au 安心携帯サポートというサービスに入っていたため、以下から申し込みをすることで、実費負担2000円でリフレッシュ品の機種と交換することができました。

日付が変わる頃に申し込みをしたのですが、その二日後には交換用端末が届きました。申し込みとほぼ同時に発送してくれるようです。 壊れてしまった機種の交換もポストに投函するだけと非常に簡単でした。 もしこのサービスに加入されているのなら使わない手はないと思います。

なんかauの宣伝みたいですね。

データを移行する

ここまでの作業で新しい端末も手に入りますので、後は壊れてしまった端末からデータを移すだけです。 Xperia Transfer Mobile というアプリを使うと、Xperia間のアプリケーションのデータやホーム画面のアイコン配置までそっくりそのままコピーしてくれました。移行には結構時間がかかったのですが、環境がほとんどそのまま移動されるので便利です (一部アプリは移行できていなかったようですが)。

まとめ

携帯の破損でいろいろと苦労しましたが、最終的には問題解決して、元と同じ環境に戻すことができました。 戻せるとは言え、携帯を壊してしまうと面倒な作業が発生しますし、こんな作業はやらないで済むに越したことはないですね。。。

EmscriptenでRustをJavaScriptへコンパイルする

以前のエントリではうまくいかなかった、RustのJavaScriptへのコンパイルですが、

gifnksm.hatenablog.jp

先日フォーラムに投稿されていたやり方をまねるだけで簡単にできるようになっていました。

internals.rust-lang.org

Rust本体にEmscripten対応が取り込まれたのですが、動かすためには専用のLLVMが必要などいくつか制限があります。 制限があるとはいえ、以前よりも簡単にEmscriptenでのコンパイルが試せるようになったのは朗報ですね!

追記

リンクしか内容がない記事というのもアレなので、Emscripten を使ったコンパイル方法を簡単に書いておきます。

  1. Emscripten環境変数を設定する

     $ source <path to emsdk>/emsdk_portable/emsdk_env.sh
    
  2. rustc--target オプションつきで呼び出す

     $ <path to rust root>/x86_64-unknown-linux-gnu/stage2/bin/rustc --target asmjs-unknown-emscripten ./hello.rs -O
    

以上。簡単ですね。

以下のプログラムをコンパイルしてみたところ、1MB程度の巨大なJavaScriptのコードが生成されました。ただの Hello world にしてはすごい量ですが、Emscriptenのランタイム (?) のソースコードの量が多いようです。

fn main() {
  println!("Hello emscripten world!");
}

-O や、 -C lto 等の最適化オプションをつけることもできます。 最適化あり・なしの結果をGistに貼り付けてみました。

main関数はここから始まるようです。 なにがなんやら、、、といった感じですね。。。。

LTO有効だからといって、劇的にサイズが減るわけでもないようです。

EmscriptenでRustをJSへコンパイルする (コンパイルできてない)

2016/3/8追記

うまくいきました。エントリ書きました。

gifnksm.hatenablog.jp


RustでEmscriptenを使う方法を調べたのでメモ。2015年12月28日時点でなんとか動作させるための手順です。今後オフィシャルにEmscriptenサポートが入るなどすると、もっと簡単なやり方になるかと思います。

Emscripten SDK をインストール

Emscriptenのサイト から Emscripten SDK をダウンロードしてきて、インストールします。

$ wget https://s3.amazonaws.com/mozilla-games/emscripten/releases/emsdk-portable.tar.gz
$ tar xvf emsdk-portable.tar.gz
$ cd emsdk_portable
$ ./emsdk update
$ ./emsdk install latest
$ ./emsdk activate latest

Emscripten 対応の rust をコンパイル

brson/rustemscripten ブランチをクローン。

$ git clone git@github.com:brson/rust.git -b emscripten

なお、12/28時点でのコミットのハッシュは c42f32e です。

当該ブランチの src/llvm submodule は rust-lang のものが読み込まれていますが、ハッシュ値emscriptenLLVM fork (emscripten-fastcomp) のものとなっていますので、以下の変更を加えます (.gitmodules のコミット漏れ?)

diff --git a/.gitmodules b/.gitmodules
index 83be070..5f92864 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -1,6 +1,6 @@
 [submodule "src/llvm"]
    path = src/llvm
-  url = https://github.com/rust-lang/llvm.git
+   url = https://github.com/kripken/emscripten-fastcomp.git
    branch = master
 [submodule "src/compiler-rt"]
    path = src/compiler-rt

ここまで準備できたらいよいよ configure & make です。以下の作業が必要なようです。

$ source <path to SDK>/emsdk_env.sh
$ ./configure --target=asmjs-unknown-emscripten
$ make -j4

なお、手元の OSX 環境では configure に失敗するため、EmscriptenのIssueコメントを参考にし、 CC=gcc CXX=g++環境変数を指定して configure したところ、うまく動作しました (デフォルトコンパイラが clang の環境でEmscripten 側の clang が使われてしまうとだめなのかもしれません)。

configure: error: C preprocessor "/lib/cpp" fails sanity check

hello world! のコンパイル

謎のエラーが出て通らず。。。

$ LD_LIBRARY_PATH=../rust-emscripten-c42f32e/x86_64-unknown-linux-gnu/stage2/lib/ ../rust-emscripten-c42f32e/x86_64-unknown-linux-gnu/stage2/bin/rustc ./main.rs --target asmjs-unknown-emscripten
error: linking with `emcc` failed: exit code: 1
note: "emcc" "-L" "/home/nksm/tmp/rust-emscripten-c42f32e/x86_64-unknown-linux-gnu/stage2/lib/rustlib/asmjs-unknown-emscripten/lib" "main.0.o" "-o" "main.js" "-Wl,--gc-sections" "-nodefaultlibs" "-L" "/home/nksm/tmp/rust-emscripten-c42f32e/x86_64-unknown-linux-gnu/stage2/lib/rustlib/asmjs-unknown-emscripten/lib" "-Wl,-Bstatic" "-Wl,-Bdynamic" "/home/nksm/tmp/rust-emscripten-c42f32e/x86_64-unknown-linux-gnu/stage2/lib/rustlib/asmjs-unknown-emscripten/lib/libstd-ca1c970e.rlib" "/home/nksm/tmp/rust-emscripten-c42f32e/x86_64-unknown-linux-gnu/stage2/lib/rustlib/asmjs-unknown-emscripten/lib/libcollections-ca1c970e.rlib" "/home/nksm/tmp/rust-emscripten-c42f32e/x86_64-unknown-linux-gnu/stage2/lib/rustlib/asmjs-unknown-emscripten/lib/librustc_unicode-ca1c970e.rlib" "/home/nksm/tmp/rust-emscripten-c42f32e/x86_64-unknown-linux-gnu/stage2/lib/rustlib/asmjs-unknown-emscripten/lib/librand-ca1c970e.rlib" "/home/nksm/tmp/rust-emscripten-c42f32e/x86_64-unknown-linux-gnu/stage2/lib/rustlib/asmjs-unknown-emscripten/lib/liballoc-ca1c970e.rlib" "/home/nksm/tmp/rust-emscripten-c42f32e/x86_64-unknown-linux-gnu/stage2/lib/rustlib/asmjs-unknown-emscripten/lib/liballoc_system-ca1c970e.rlib" "/home/nksm/tmp/rust-emscripten-c42f32e/x86_64-unknown-linux-gnu/stage2/lib/rustlib/asmjs-unknown-emscripten/lib/liblibc-ca1c970e.rlib" "/home/nksm/tmp/rust-emscripten-c42f32e/x86_64-unknown-linux-gnu/stage2/lib/rustlib/asmjs-unknown-emscripten/lib/libcore-ca1c970e.rlib" "-l" "c"
note: error: Unknown attribute kind (45)
error: Unknown attribute kind (45)
error: Unknown attribute kind (45)
error: Unknown attribute kind (45)
error: Unknown attribute kind (45)
error: Unknown attribute kind (45)
error: Unknown attribute kind (45)
error: Unknown attribute kind (45)
error: Unknown attribute kind (45)
error: Unknown attribute kind (45)
error: Unknown attribute kind (45)
error: Unknown attribute kind (45)
error: Unknown attribute kind (45)
error: Unknown attribute kind (45)
error: Unknown attribute kind (45)
error: Unknown attribute kind (45)
error: Unknown attribute kind (45)
error: Unknown attribute kind (45)
error: Unknown attribute kind (45)
Traceback (most recent call last):
  File "/home/nksm/dev/emsdk_portable/emscripten/master/emcc", line 1309, in <module>
    final = shared.Building.llvm_opt(final, link_opts, DEFAULT_FINAL)
  File "/home/nksm/dev/emsdk_portable/emscripten/master/tools/shared.py", line 1471, in llvm_opt
    assert os.path.exists(target), Failed to run llvm optimizations:  + output
AssertionError: Failed to run llvm optimizations:

error: aborting due to previous error

うーん、なんでしょうこれは。

kcovを使ってRustプログラムのカバレッジを測定する

本記事は Rust Advent Calendar 2015 13日目の記事です。

前置き

Rust 1.5リリースおめでとうございます!!! cargo install の登場により、Rust製ツール群のインストールが飛躍的に楽になりました。最近のアップデートに追従できていなかったという方も、是非是非ダウンロードしてみてください。

背景

Rustには、 言語組み込みのユニットテスト機能 や、cargo test のインテグレーションテストサポートなど、テストを実行させる方法は充実しているのですが、gcov相当のカバレッジ採取機能については、 2015年12月時点では残念ながらサポートされていません (RFCリポジトリにIssueはあります)。しかし、Rust にはプログラムのDWARF情報 (デバッグ情報) を生成する機能があるため、 kcov というプログラムを利用することでカバレッジを採取することが可能です。

本記事では、 kcov を利用して Rust プログラムのカバレッジを測定する方法について説明します。

kcov のインストール

ディストリビューションでバイナリが配付されていない場合、ソースコードからビルドすることになります。 以下に載っているコマンドラインを参考にしてみてください。

kcov を使う

cargo test --no-runデバッグシンボルのついたテスト用実行ファイルを作成し、kcov 経由で実行します。

$ cargo test --no-run
$ kcov target/cov target/debug/$TEST_EXECUTABLE

上記実行すると、 target/cov ディレクトリ配下にカバレッジ測定結果を格納したHTMLファイルが生成されます。 デフォルトでは、 ~/.cargo 配下のファイル (Cargo.toml で依存関係として指定したライブラリ) についてもカバレッジ測定対象となってしまうため、邪魔であれば以下のように --exclude-pattern オプションを指定することで、カバレッジ測定対象を絞ることが出来ます。

$ kcov --exclude-pattern=/.cargo target/cov target/debug/$TEST_EXECUTABLE
# multirust を使っている場合、以下
$ kcov --exclude-pattern=/.multirust target/cov target/debug/$TEST_EXECUTABLE

また、kcov複数回実行した場合、カバレッジ情報が蓄積されていきます。引数などの条件を変えて何度もコマンドを実行し、それらの結果を合算することなどが可能です。

$ kcov --exclude-pattern=/.cargo target/cov target/debug/$TEST_EXECUTABLE
$ kcov --exclude-pattern=/.cargo target/cov target/debug/$TEST_EXECUTABLE --ignored

Coveralls との連携

Coveralls とは、 GitHub (BitBucketも?) で開発しているソースコードのテストカバレッジを表示してくれるサービスです。 Travis CI と連携して利用します。

kcov には測定したカバレッジCoveralls へと送信してくれる機能があり、.travis.yml 上で kcov を以下のように呼び出すよう設定するだけで Coveralls を利用できます。

$ kcov --coveralls-id=${TRAVIS_JOB_ID} target/cov target/debug/$TEST_EXECUTABLE

ただし、Travis CI にはデフォルトで kcov はインストールされていないため、 .travis.yml 中で kcov をインストールしてやる必要があります。

language: rust
sudo: false
addons:
  apt:
    packages:
      - libcurl4-openssl-dev
      - libelf-dev
      - libdw-dev
before_script:
  - |
      if [ "${TRAVIS_OS_NAME}" = "linux" ]; then
        export KCOV="${TRAVIS_BUILD_DIR}/kcov/build/src/kcov --exclude-pattern=/.cargo --coveralls-id=${TRAVIS_JOB_ID} ${TRAVIS_BUILD_DIR}/coverage"
        wget https://github.com/SimonKagstrom/kcov/archive/master.zip
        unzip master.zip
        mv kcov-master kcov
        mkdir kcov/build
        cd kcov/build
        cmake ..
        make
        cd ${TRAVIS_BUILD_DIR}
      fi
script:
  - ${KCOV} target/debug/$TEST_EXECUTABLE

すこしめんどくさいですね。

travis-cargo を使う

すべての Rust プロジェクトの .travis.yml に kcov のインストール手順を書くのは面倒だというあなたのために、 travis-cargo という便利ツールが公開されています。 rustdoc の生成物を github.io にアップロードするなど、カバレッジ生成採取の便利な機能も搭載されています。

一部、travis-cargo では実現できないこともあるのですが (cargo test で生成されるバイナリ以外のカバレッジ採取する場合など)、多くの場合、 travis-cargo を使うのが便利だと思います。

最後に

筆者が公開している以下リポジトリでは、ビルド時にカバレッジを採取し、バッジとして README に表示しています。.travis.yml の書き方の参考にどうぞ。