6 releases

0.4.0 May 31, 2024
0.3.0 Feb 5, 2024
0.2.3 Jan 26, 2024
0.2.2 Dec 27, 2023
0.1.0 Dec 6, 2023

#165 in Embedded development

Download history 20/week @ 2024-03-01 25/week @ 2024-03-08 26/week @ 2024-03-15 19/week @ 2024-03-29 7/week @ 2024-04-05 1/week @ 2024-04-26 4/week @ 2024-05-10 21/week @ 2024-05-17 6/week @ 2024-05-24 286/week @ 2024-05-31 186/week @ 2024-06-07 544/week @ 2024-06-14

1,033 downloads per month
Used in sam3-hal


319 lines

Embedded Test

Crates.io Crates.io

The embedded-test library provides a test harness for embedded systems (riscv, arm and xtensa). Use this library on the target together with probe-rs on the host to run integration tests on your embedded device.

probe-rs together with embedded-test provide a (libtest compatible) test runner, which will:

  1. Flash all the tests to the device in one go (via the probe-rs run command)
  2. Request information about all tests from the device (via semihosting SYS_GET_CMDLINE)
  3. In turn for each testcase:
    • Reset the device
    • Signal to the device (via semihosting SYS_GET_CMDLINE) which test to run
    • Wait for the device to signal that the test completed successfully or with error (via semihosting SYS_EXIT)
  4. Report the results

Since the test runner (probe-rs run) is libtest compatible ( using libtest-mimic), you can use intellij or vscode to run individual tests with the click of a button.


  • Runs each test case individually, and resets the device between each test case
  • Supports an init function which will be called before each test case and can pass state to the test cases
  • Supports async test and init functions (needs feature embassy)
  • Support #[should_panic], #[ignore] and #[timeout(<seconds>)] attributes for each test case


Add the following to your Cargo.toml:

embedded-test = { version = "0.4.0", features = ["init-log"] }

name = "example_test"
harness = false

Install the runner on your system:

cargo install probe-rs-tools

Add the following to your .cargo/config.toml:

runner = "probe-rs run --chip esp32c6"
# `probe-rs run` will autodetect whether the elf to flash is a normal firmware or a test binary

Add the following to your build.rs file:

fn main() {

Then you can run your tests with cargo test --test example_test or use the button in vscode/intellij.

Having trouble setting up? Checkout out the FAQ and common Errors Wiki page.

Example Test

Example repo
More Detailed Cargo.toml
Async Test Example

Example for tests/example_test.rs


mod tests {
    use esp_hal::{clock::ClockControl, delay::Delay, peripherals::Peripherals, prelude::*};

    // Optional: A init function which is called before every test
    fn init() -> Delay {
        let peripherals = Peripherals::take();
        let system = peripherals.SYSTEM.split();
        let clocks = ClockControl::max(system.clock_control).freeze();
        let delay = Delay::new(&clocks);

        // The init function can return some state, which can be consumed by the testcases

    // A test which takes the state returned by the init function (optional)
    fn takes_state(_state: Delay) {

    // Example for a test which is conditionally enabled
    #[cfg(feature = "log")]
    fn log() {
        log::info!("Hello, log!"); // Prints via esp-println to rtt

    // Another example for a conditionally enabled test
    #[cfg(feature = "defmt")]
    fn defmt() {
        use defmt_rtt as _;
        defmt::info!("Hello, defmt!"); // Prints via defmt-rtt to rtt

    // A test which is cfg'ed out
    fn it_works_disabled() {

    // Tests can be ignored with the #[ignore] attribute
    fn it_works_ignored() {

    // A test that fails with a panic
    fn it_fails1() {

    // A test that fails with a returned Err(&str)
    fn it_fails2() -> Result<(), &'static str> {
        Err("It failed because ...")

    // Tests can be annotated with #[should_panic] if they are expected to panic
    fn it_passes() {

    // This test should panic, but doesn't => it fails
    fn it_fails3() {}

    // Tests can be annotated with #[timeout(<secs>)] to change the default timeout of 60s
    fn it_timeouts() {
        loop {} // should run into the 10s timeout

Configuration features

Feature Default? Description
panic-handler Yes Defines a panic-handler which will invoke semihosting::process::abort() on panic
defmt No Prints testcase exit result to defmt. You'll need to setup your defmt #[global_logger] yourself.
log No Prints testcase exit result to log. You'll need to setup your logging sink yourself (or combine with the init-log feature).
init-rtt No Calls rtt_target::rtt_init_print!() before starting any tests
init-log No Calls rtt_log::init(); before starting any tests.
embassy No Enables async test and init functions. Note: You need to enable at least one executor feature on the embassy-executor crate unless you are using the external-executor feature.
external-executor No Allows you to bring your own embassy executor which you need to pass to the #[tests] macro (e.g. #[embedded_test::tests(executor = esp_hal::embassy::executor::thread::Executor::new())])
xtensa-semihosting No Enables semihosting for xtensa targets.


Licensed under either of:

at your option.


Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions.