Guides#

pw_kernel: A Rust-based kernel for embedded systems

This guide covers real-world usage topics such as unit testing and panic detecting.

Unit testing#

pw_kernel includes a lightweight unit testing framework designed for both bare-metal and kernel-aware tests.

Writing a test#

Tests are written as standard Rust functions annotated with the #[test] attribute from the unittest crate. The test function should return a unittest::Result<()>.

Bare-metal test example:

// in your_module/lib.rs or your_module/tests.rs
#[cfg(test)]
mod tests {
    use unittest::test;

    fn add(a: u32, b: u32) -> u32 {
        a + b
    }

    #[test]
    fn test_addition() -> unittest::Result<()> {
        unittest::assert_eq!(add(2, 2), 4);
        unittest::assert_ne!(add(2, 2), 5);
        unittest::assert_true!(add(1,1) == 2);
        Ok(())
    }
}

Assertions#

The unittest crate provides several assertion macros:

  • unittest::assert_eq!(a, b): Asserts that a is equal to b.

  • unittest::assert_ne!(a, b): Asserts that a is not equal to b.

  • unittest::assert_true!(expr): Asserts that expr evaluates to true.

  • unittest::assert_false!(expr): Asserts that expr evaluates to false.

Kernel-aware tests#

For tests that require kernel services (e.g., testing scheduler behavior, mutexes, or timers), you can mark them as needing the kernel by passing the needs_kernel argument to the #[test] attribute:

use kernel::sync::mutex::Mutex;
use unittest::test;

static MY_MUTEX: Mutex<u32> = Mutex::new(0);

#[test(needs_kernel)]
fn test_mutex_locking() -> unittest::Result<()> {
    let guard = MY_MUTEX.lock();
    unittest::assert_eq!(*guard, 0);
    // guard is dropped here, unlocking the mutex
    Ok(())
}

Panic detector#

panic_detector scans a Rust binary and uses static analysis to display a list of all panic call sites.

Currently only rv32 ELF files are supported.

Setup:

  1. Add a panic handler to the Rust binary which will be used by panic_detector to find the panic call sites.

    #[panic_handler]
    fn panic_handler(info: &core::panic::PanicInfo) -> ! {
        if let Some(location) = info.location() {
            panic_is_possible(location.file().as_ptr(), location.file().len(), location.line(), location.column());
        } else {
            panic_is_possible(core::ptr::null(), 0, 0, 0);
        }
    }
    
    #[unsafe(no_mangle)]
    #[inline(never)]
    extern "C" fn panic_is_possible(filename: *const u8, filename_len: usize, line: u32, col: u32) -> !{
        // The arguments to this function are reverse-engineereddocs.html
        // from the machine code by static analysis to
        // display a list of all the panic call-sites to the
        // user in the mutask_no_panic_test() error message.
        // See welder/src/check_panic.rs for more details.
    
        // If this symbol exists in the binary, panics are
        // possible. Presubmit tests can ensure that this symbol
        // does not exist in the final binary.  Do not rename or
        // remove this function.
        core::hint::black_box(filename);
        core::hint::black_box(filename_len);
        core::hint::black_box(line);
        core::hint::black_box(col);
        loop {}
    }
    
  2. Add a test target to Bazel for the binary to validate.

    rust_binary(
        name = "example",
        srcs = ["main.rs"],
    )
    
    load("//pw_kernel/tooling/panic_detector:rust_binary_no_panics_test.bzl", "rust_binary_no_panics_test")
    
    rust_binary_no_panics_test(
        name = "example_no_panic_test",
        binary = ":example",
    )
    
  3. Run the bazel test. If any panics are detected , panic_detector prints the locations to stdout.

    $ bazel test //:example_no_panic_test
    
    Found panic ufmt-0.2.0/src/helpers.rs line 58 column 13. Branch trace:
      00103896 lui      a0,0x104           (task0_entry)
      0010389e jal      ra,-1734           (task0_entry)
      0010320a jalr     ra,ra,-82          (panic_const_add_overflow)
      001031d2 jalr     ra,ra,-78          (core::panicking::panic_fmt)
      00103192 jal      ra,2               (rust_begin_unwind)
      00103194 addi     sp,sp,-16          (panic_is_possible)