So this approach is basically just useful to test for codegen, struct layout and maybe endianness bugs. After all, if your code doesnât interact with the microcontroller hardware, you may as well just test it (its logic) on the host (cargo test --target $HOST).
I see what youâre saying here but I donât think itâs really accurate. To give one specific example: letâs say that Iâm writing an RTOS for a Cortex-M. I need to test and debug my context-switching code. This definitely requires target-specific code, but as itâs only interacting with registers and memory that is emulated by QEMU, itâs totally testable. Another example of useful tests to run in QEMU might be benchmarking when doing performance optimization. And even if it were true that this was only good for codegen bugs, thatâs still a pretty big win in the meta-problem of making it easier to bring Rust up on new platforms. So even if we did restrict testing of embedded platforms to just QEMU, I think it would be worth it.
But I feel like doing that would be missing the point a bit.
Basically you reimplement #[test] (as a compiler plugin)
The reason that I like the idea of compiling âtest librariesâ for each test function so much is that it doesnât require this. It makes the testing story so much more flexible to be able to link test structures into arbitrary test harnesses. Iâm no expert in software testing, but Iâd imagine that this would be useful even outside the embedded realm - maybe you want to hook your test functions up to a fuzzer, for example (Iâm not sure this would work in the current scheme since test functions canât take arguments but itâs just a thought).
Just to make sure that I am communicating effectively here, this is a sketch of what I imagine:
src/main.rs:
#[test]
fn foo() {
// test some stuff
}
#[test]
fn bar() {
// test more stuff
}
After running rustc --test src/main.rs:
$ ls target/debug
...
libmycrate_test_foo.rlib
libmycrate_test_bar.rlib
...
$ objdump -t target/debug/libmycrate_test_foo.rlib
__horrible_mangled___TEST_ENTRY_POINT_foo
__horrible_mangled___other_function_required_by_foo
$ cargo test
$ ls target/debug/
...
mycrate_test-somehash
...
We move the test harness generation into cargo test, workflow for 99+% of users is unchanged, but now we can write our own cargo test-hil much more easily without having to do crazy things with compiler-internal crates.
The downside is that I doubt this can reuse much of the test crate code / rustc --test infrastructure and that this probably will require some sort of shim / glue code for every device that the framework wants to support (each device has different register maps, number of peripherals, etc.)
This is kind of inevitable, to my mind, at least in the short term. If traits for (e.g.) I2C peripherals are standardized, as I think is kicked around higher in this thread, maybe we can do better someday, but for now Iâll take âworks but needs a shimâ over âdoesnât work unless you do unsavory things with librustcâ.