💤

RustでQEMU上からテストする

2023/03/11に公開

目的

現在RustでMikanOSに挑戦しています。 RustもOSも、まだまだ理解できていことだらけですが、なんとかday06aまで実装できました。

https://github.com/elm-register/mikanos-rs

ここまではカーネルのクレートとライブラリのクレートを分離して、テストはライブラリ側でしていました。

workspace
    ├── kernel
    └── kernel-lib

しかしながら、day06bでマウスドライバを実装する必要があるため、QEMU上でもテストをできるようにしたほうがいいと思い
今回の対応に至りました。

動きのイメージはWriting an OS in Rustのような感じです。
https://os.phil-opp.com/ja/testing/

カスタムテストフレームワーム

stdのテストは使えないため、カスタムテストフレームワークを使用します。
ちなみに、上記のページと同じやり方です。

カーネルのメインファイルに以下3行を追加しました。

// lib
#![feature(custom_test_frameworks)] // カスタムフレームワークの使用を宣言
#![test_runner(my_runner)] // テストランナーとなる関数名を指定
#![reexport_test_harness_main = "test_main"] // テストのエントリーポイントとなる関数名を指定

さらにランナーとテストケースを追加
(関係ありませんが、関数にもトレイト実装できるのスゴイ)

pub trait Testable {
    fn run(&self) -> ();
}

impl<T> Testable for T
    where
        T: Fn(),
{
    fn run(&self) {
        println!("test name={}", core::any::type_name::<T>());
        self();
        println!("[ok]");
        println!();
    }
}

#[cfg(test)]
pub fn my_runner(tests: &[&dyn Testable]) {
    println!("start test! num={}", tests.len());
    for t in tests {
        t.run();
    }
    common_lib::assembly::hlt_forever();
}

#[test_case]
fn it_should() {
    assert_eq!(0, 0)
}

テストビルド

ビルドしたバイナリ単体で動かすわけではなく、一度ディスクイメージに書き出す必要があります。そのため、testフラグをtrueにした状態で
ビルドだけできる方法がないか調べました。

調べてみた感じだと2通りのやり方があるっぽい?みたいですが、両者の違いがわからない...。

# 1 
cargo build --tests
# 2 こちらを採用
cargo test --no-run

しかし、以下のようなエラーが発生

language item required, but not found: `eh_personality`

パニック発生時に回復させるかなどの挙動を定義させるためのようですが、
workspaceのcargo.tomlにpanic=abortをすでに指定しているため、なぜこのエラーが出るのかわからない...

ターゲットを指定しているkernel.jsonに以下を追加し、cargo.tomlのpanic=abortを削除したところ
ビルドできるようになりました!

https://doc.rust-lang.org/rustc/tests/index.html

// kernel.json
"panic-strategy": "abort",

テストの実行

通常のビルド時はtarget/kernel/debug直下にELFファイルが作成されていますが、
テストコンパイルされたファイルはtarget/kernel/debug/deps/にあり、ファイル名も変わっていました。

// 通常ビルド時
target/kernel/debug/kernel.elf
// テストビルド時
target/kernel/debug/deps/kernel-51e4073052a851eb.elf

ディスクイメージを作成する際にカーネルファイルのパスを指定する必要があるため、テストビルド時には以下のコマンドで検索するようにしました。

find target/kernel/debug/deps/ -name '*.elf'

動作するようにはなりましたが、テスト時には毎回cargo cleanをする必要があるため、時間がかかるのがネックです...。
(見えにくいですが、黄色い文字がテスト時の出力です。)

TODO

OS in RustではQemuの終了処理などを追加していましたが、余力があるときでいいかな...。

参考文献

https://doc.rust-lang.org/rustc/tests/index.html

https://doc.rust-lang.org/beta/unstable-book/language-features/custom-test-frameworks.html

https://os.phil-opp.com/ja/testing/

Discussion