Upgrade to Pro — share decks privately, control downloads, hide ads and more …

マイコンでもRustのtestがしたい その2/KernelVM Tokyo 18

マイコンでもRustのtestがしたい その2/KernelVM Tokyo 18

Avatar for Toshifumi NISHINAGA

Toshifumi NISHINAGA

August 09, 2025
Tweet

More Decks by Toshifumi NISHINAGA

Other Decks in Programming

Transcript

  1. 前回のおさらい • マイコン(no_std)でもRustのテストをしたいなら、embedded- testを使うと良い • probe-rs + embedded-test • https://crates.io/crates/embedded-test

    • いいところ • std環境のtestとほぼ同じ書き方ができる • panicするテスト(should panic)が書ける 2025/05/11 2
  2. 今日の目的 • 大目的 • no_std環境でもstd環境のcargo test並のテストがしたい • 中目的 • std環境のcargo

    testがどのような仕組みで動いているかを知る • no_std環境では今最もstd環境に近いtestが実現できるprobe-rs+embedded- testがどうやってその機能を実現しているかを深堀りする • 小目的 • cargo testはどうやってテストの成否を判定しているのか • probe-rsとembedded-testはそれぞれ何をやっているか • なぜshould_panicテストができるのか • custom test harness では使えないのになぜ使えるの? 2025/05/11 3
  3. std Rustのテスト • unit test • ソースコード内に testコードを追記 するだけ •

    integration test • tests/*.rs にテスト を書く • doc test • コードのドキュメ ント内にテストが かける(!?) • 今回は省略 参考: https://doc.rust-lang.org/rust-by- example/testing/integration_testing.html unit test integration test 2025/05/11 6 コード: https://github.com/tnishinaga/20250511_kernelvm_kansai_sample/tree/main/no01_std_tests
  4. cargo testを実行すると何が起こるのか • テスト用バイナリを生成する • rustcにtest用オプション(--test)を渡してビルド(※) • テスト用バイナリを実行する • 個別のテストの成否はここで集計

    • report形式の変更等もテスト用バイナリの担当 • テスト用バイナリの終了コードを見てTestの成否を判断 • cargo testは各ユニットテストの実行や集計等には関与していない • テスト用バイナリ(正確にはtest crate) の機能 • 前回のK/VMで紹介した内容は誤り 2025/05/11 9 ※: cargo build --tests --lib -vvv するとコンパイルオプションが取れる
  5. cargo testのコード確認 1. run_tests内で run_unit_testが呼ばれる 2. run_unit_test内ではtest 用バイナリをビルドし てexecしているだけ 2025/05/11

    10 https://github.com/rust- lang/cargo/blob/c24e1064277fe51ab7 2011e2612e556ac56addf7/src/cargo/o ps/cargo_test.rs#L76 ① ②
  6. cargo testの動作確認 • 目的 • cargo testがテストの実行と終了コードの確認しかしてないことを確認 する • 方法

    • runnerにexit 0を実行するshell scriptを登録してcargo testを実行する • cargoにはrunnerがセットされている場合はrunnerを使ってバイナリを実行する仕 組みがあるので、これを使う • 例: cargo run時にprobe-rsを使ってプログラムを書き込んで実行させるとか • cargo testの実装から読み取った挙動 • 何もテストが実行されていなくてもTestにPassしたと出てくるはず • exit 1に書き換えればFailするはず 2025/05/11 11
  7. 動作確認結果 • exit 0を返すだけの shell scriptを動かす とtestが成功する • exit 1を返すと失敗に

    なる • よって、cargo testは 終了コードしか見て いない 2025/05/11 12
  8. テスト用バイナリの生成機序 • cargo testがrustcに `--test` オプションを渡してビルドしている • rustcは `--test` オプションを渡されると以下を行う

    • cfg(test)を有効化する • https://github.com/rust- lang/rust/blob/6b00bc3880198600130e1cf62b8f8a93494488cc/compiler/rustc_session /src/config/cfg.rs#L295-L298 • testを実行するmain関数を展開 • 多分このあたりでやっている • https://github.com/rust- lang/rust/blob/master/compiler/rustc_builtin_macros/src/test_harness.rs#L287 • cfg(test)よりmod testsが有効化される • mod tests内の `#[test]` マクロが展開され、test実行用の定義が追加 される 2025/05/11 14
  9. 展開されたコードとtest crateがやること • 展開されたコード • 各unit test用関数に対応したTestDescAndFn型の定義を作る • HarnessはTestDescAndFnのsliceをtest_main_static関数に渡して実行する •

    libtest側 • 受け取ったsliceを元にunit testの関数を呼び出して順次実行する • 実態はこのあたり • https://github.com/rust- lang/rust/blob/e05ab47e6c418fb2b9faa2eae9a7e70c65c98eaa/library/test/src/lib.rs#L284 • 引数が与えられると各種機能を提供する • 各unit testの一覧表示とか、出力形式の変更とか • IDE側のテストの単体実行などを実現する際に使われている • この機能を実装すると、各種IDE側との親和性が高まる(= libtest compatibleになる) 2025/05/11 16
  10. 前回のおさらい • no_std環境ではstd環境のテストができない • test時に生成されるハーネスがstdに依存しているため • custom test frameworksを使えばno_std環境でもテストはできる が、一部機能が使えない

    • panicするテストとか • probe-rs+embedded-testを使えばno_std環境でもstd環境並みの テストができる • panicするテストも書ける • なぜ? 2025/05/11 19
  11. probe-rsを使ったcargo testのOverview 2025/05/11 20 cargo test (1) probe-rs run \

    <test options> probe-rs (test mode) マイコン (2) イメージ書き込み (3) 実行 & 結果(json) (4) 成否判定 集計 表示 (5) exit codeを返す (0)テストコードのビルド probe-rsとマイコン間の文字入出力や終了コードのやり取りにはsemihostingを用いる
  12. probe-rsを使ったcargo testのOverview 2025/05/11 21 cargo test (1) probe-rs run \

    <test options> probe-rs (test mode) マイコン (2) イメージ書き込み (3) 実行 & 結果(json) (4) 成否判定 集計 表示 (5) exit codeを返す (0)テストコードのビルド probe-rsとマイコン間の文字入出力や終了コードのやり取りにはsemihostingを用いる
  13. embedded-test crateの機能 • ビルド時 • マクロ展開をしてno_stdで動くテスト用バイナリを作る • 説明省略 • テストの実行は

    embedded_test::export::run_testsで行う • 動作時(embedded_test::export::run_testsの機能) • 引数に応じて以下の2機能を提供する • list • 各テストの一覧をsemihosting経由で出力する • run <テスト名> • 指定されたテストを実行し、成否をsemihostingで返す • 各コマンド実行後は終了する • 複数のテストを実行する機能はない。そこは外部のプログラムだより。 2025/05/11 23
  14. マクロ展開 2025/05/11 24 • #[embedded_test::tests] マクロが以下の展開を行 う • #[init] •

    #[test] • #[should_panic] • #[ignore] • #[timeout] • 展開コードはこのあたり • https://github.com/probe- rs/embedded- test/blob/81857b62554196 724ea81d0a9d1020e7bc60 41a0/macros/src/lib.rs#L1 18-L138
  15. マクロ展開されたコードがやっているこ と • 以下の定義を生成 • テスト用main関数 • __embedded_test_entry と、そこから呼ばれる __embedded_test_start

    • おもしろポイント • リンカスクリプトに定義されたシンボル経由で__embedded_test_startを呼ぶこと でembedded-test.xの不足をコンパイル時に検出している • 最後に embedded_test::export::run_testsでテストを実行する • 各種テスト用情報の入ったheapless::vec::Vec • 関数名とか関数ポインタとか 2025/05/11 25
  16. embedded_test::export::run_testsの機能 • semihosting経由で引数を受け取って出力を返す • 実行結果(成功・失敗)をsemihosting経由で伝える • 成功時はexit(0)を返す • https://github.com/probe-rs/embedded- test/blob/81857b62554196724ea81d0a9d1020e7bc6041a0/src/export.rs#L84C12-

    L84C30 • 失敗時または異常終了時はabortが返る • 失敗時(各テストは check_outcome関数経由で実行され、結果が返る) • https://github.com/probe-rs/embedded- test/blob/81857b62554196724ea81d0a9d1020e7bc6041a0/src/export.rs#L105-L113 • 異常終了時(panic) • https://github.com/probe-rs/embedded- test/blob/81857b62554196724ea81d0a9d1020e7bc6041a0/src/lib.rs#L18 2025/05/11 26
  17. probe-rsを使ったcargo testのOverview 2025/05/11 27 cargo test (1) probe-rs run \

    <test options> probe-rs (test mode) マイコン (2) イメージ書き込み (3) 実行 & 結果(json) (4) 成否判定 集計 表示 (5) exit codeを返す (0)テストコードのビルド probe-rsとマイコン間の文字入出力や終了コードのやり取りにはsemihostingを用いる
  18. probe-rs run(test mode)の機能 • probe-rs runにtest用オプションを与えるとtest modeで動作する • https://github.com/probe-rs/probe- rs/blob/c6dae5f3e0db1af0e159f6e6aff12df33d8ab447/probe-rs-

    tools/src/bin/probe-rs/cmd/run.rs#L285 • test modeでは以下の機能を提供する • テストの実行・制御 • テスト結果の収集・表示 2025/05/11 28
  19. テストモードのoverview 1. マイコンへのプログラムの書き込み & 実行 2. マイコン側からテストの一覧を取得 1. semihosting経由で list

    コマンドを実行してテスト関数一覧を取得 • SessionInterface::list_testsとlist_tests_impl関数が担当 3. テスト一覧に対し、以下を繰り返し行う(foreach) 1. マイコンをリセット • https://github.com/probe-rs/probe-rs/blob/71d09fd6807353c49fddd98f28966e0d7e94b67e/probe-rs- tools/src/bin/probe-rs/rpc/functions/test.rs#L229 2. semihosting経由で run <テスト名> を実行 • https://github.com/probe-rs/probe-rs/blob/71d09fd6807353c49fddd98f28966e0d7e94b67e/probe-rs- tools/src/bin/probe-rs/rpc/functions/test.rs#L260 3. 実行結果を取得し、成否を画面に表示 4. 実行結果をexit codeとして返す • 成功時は0, 失敗時はそれ以外 • cargo testはこの値を元に成否を判定 2025/05/11 29
  20. テストの実行と画面表示の方法 • probe-rsはlibtest_mimicを用いて cargo test とほぼ同じ出力を提供し ている • 右図参照 •

    probe-rsのやっていること • テストの処理をclosureにしてTrial型の リストを作成 • https://github.com/probe-rs/probe- rs/blob/c6dae5f3e0db1af0e159f6e6aff12 df33d8ab447/probe-rs- tools/src/bin/probe-rs/util/cli.rs#L591- L623 • libtest_mimic::runで全テストを実行・ 結果表示 • https://github.com/probe-rs/probe- rs/blob/c6dae5f3e0db1af0e159f6e6aff12 df33d8ab447/probe-rs- tools/src/bin/probe-rs/util/cli.rs#L540 2025/05/11 31
  21. panicするテスト(should_panic)の実現方法 • 通常はマイコン側がpanicしたらprobe-rsはテスト失敗 判定をする • マイコン側はsemihosting経由でabortを返す • probe-rsは以下の機序でpanic時にテストが失敗したと判定 • handle_halt関数はabortを受け取ったらTestOutcome::Panicを返す

    • https://github.com/probe-rs/probe- rs/blob/c6dae5f3e0db1af0e159f6e6aff12df33d8ab447/probe-rs- tools/src/bin/probe-rs/rpc/functions/test.rs#L395-L397 • outcomeが期待通り(outcome == expected_outcome(Pass))でないな ら失敗 • https://github.com/probe-rs/probe- rs/blob/c6dae5f3e0db1af0e159f6e6aff12df33d8ab447/probe-rs- tools/src/bin/probe-rs/rpc/functions/test.rs#L265-L268 • マイコン側の関数に#[should_panic]がついている場合は 成功にする • should_panicがtrueの場合はexpected_outcomeがPanicにな る • (outcome == expected_outcome) が true になるので成功とな る 2025/05/11 32 probe-rsのテスト成否判定部分 outcomeが期待通りの場合は成功
  22. probe-rs+embedded-testのテストの仕組み • マイコン側(embedded-test)は以下の2機能のみを提供する • テストの一覧表示 • 指定されたテストの実行 • probe-rs(とlibtest_mimic)がその他の部分をすべて担当する •

    テスト一覧(と詳細情報)取得 • 各テストの実行 • 実行結果取得 • 画面表示(libtest_mimicの機能) • 終了コード出力 • マイコン・probe-rs間の通信にsemihostingを用いる 2025/05/11 34
  23. 今回のまとめ • cargo testは以下しか行っていない • テスト用バイナリのビルド • 上記バイナリの実行と終了コードのチェック • 各テストの実行制御や結果の表示・集計はtest

    crate側で行っている • probe-rs + embedded-testは協力してstd環境のcargo test相当の 機能を提供している • embedded-test • マイコン側でテストリストの出力、指定されたテストの実行を行う • probe-rs • テストの実行・実行後のリセット・結果の集計等を行う 2025/05/11 35
  24. probe-rs + embedded_testが使えない環境 でこの機能を実現するには? • x86_64環境でのOS開発ではprobe-rs+embedded_testが使えない • semihosting機能がないため • x86_64+qemuならexit

    deviceを使って模擬できるかも • マイコン側の機能はcustom test flameworksで対応できるかも • probe-rs側の機能は新規開発が必要そう • コストとメリットを天秤にかけて要検討 • コスト • 様々な開発が必要 • メリット • should_panicテスト等ができる • テスト結果の表示がきれいになる 2025/05/11 37
  25. probe-rs + embedded_testが使えない環境 でこの機能を実現するには? 2 • 実機のx86_64マシンで自作OS等をテストするには? • 以下の仕組みを用意して、制御ソフトを作ればできるかも? •

    リセット機能 • IoTコンセントとか? • プログラムのロード機能 • uefiのhttp bootとか? • 入出力 • UARTとか? (UART使えるお手頃x86マシンって今もあるのだろうか?) 2025/05/11 38