9. Testing Facades

This chapter describes how to use a testing facade to validate the operation of a bare metal target.

Bare metal targets do not have an operating system (OS). This means they do not link to libc or have access to std functionality like println!("").

Alongside qualified bare metal targets, additional facade targets, which act as a facade over the respective bare metal target, can be installed . These targets provide access to std for purposes such as testing and profiling.

9.1. Prerequisites

Note

Testing Facades are only supported on the Armv8-A Linux (glibc) and x86-64 Linux (glibc) host platforms at this time.

Using a facade requires qemu and binfmt to be configured correctly for the target architecture. Setup instructions differ based on operating system and distribution. While developing Ferrocene, we use the instructions found in Testing Other Targets.

For coverage, you’ll also need to install rustfilt, as well as rust-profdata (from cargo-binutils).

cargo install rustfilt
cargo install cargo-binutils

9.2. Using Facades

Facades enable profiling support, as well as access to std, compared to their bare metal targets. Behind the scenes, a facade provides the equivalent of a Linux libc to the binary.

The following configurations can be used to flag code to only run on the facade:

#[cfg(target_os = "linux")]
fn only_on_facade() { /* ... */ }

#[cfg_attr(target_os = "linux", derive(Debug))]
struct OnlyHasDebugOnFacade { /* ... */ }

fn only_prints_on_facade(value: u32) {
   let x = value;
   // ...

   #[cfg(target_os = "linux")]
   {
      println!("Received {x}");
   }

   // ...
   x
}

This is useful for things like test harnesses, extra instrumentation, and debug assertions.

The following configures code to not be run on facades (so, only on bare metal):

#[cfg(not(target_os = "linux"))]
fn only_on_bare_metal() {}

#[cfg_attr(not(target_os = "linux"), must_use)]
struct OnlyHasMustUseOnBareMetal {}

fn only_does_thing_on_bare_metal() {
   #[cfg(not(target_os = "linux"))]
   {
      // Initialize on-board LED that the facade can't use
      // ...
   }

   // Run calculations (facade compatible)
   // ...
}

This is useful when your code would normally bring up hardware not present on the facade.

9.2.1. Testing

Note

Currently cargo is not a qualified component of Ferrocene. Its test harness (used during cargo test) is also unqualified.

If your organization needs a qualified test harness, or wishes to use cargo in a qualified way, please contact sales.

Since facades include a std, it’s possible to use assert!() with them. This means that you can do basic logic testing without any hardware in the loop.

Let’s assume we have a simple function to test:

#[no_std]

use core::ffi::c_int;

#[no_mangle]
extern "C" fn return_a_number(even: bool) -> c_int {
   if even {
      2
   } else {
      1
   }
}

First, let’s make sure this builds:

rustc --edition 2021 --target thumbv7em-none-eabihf --crate-type staticlib src/thing.rs --out-dir artifacts

Now, we’ll add support for the facade:

#![cfg_attr(not(target_os = "linux"), no_std)]

use core::ffi::c_int;

#[unsafe(no_mangle)]
extern "C" fn return_a_number(even: bool) -> c_int {
   if even {
      return 2;
   } else {
      return 1;
   }
}

Next, we’ll write a simple test suite using assertions that runs only on the facade:

#[test]
fn it_works() {
   assert_eq!(return_a_number(false), 1);
   assert_eq!(return_a_number(true), 2);
}

Build with facade target:

rustc --edition 2021 --target thumbv7em-ferrocene.facade-eabihf --test -Z panic-abort-tests -C instrument-coverage src/thing.rs --out-dir artifacts

Then run it:

$ ./artifacts/thing

running 1 test
test it_works ... ok

test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.03s

Try changing one of the assertions to be wrong then observe the failure:

$ ./artifacts/thing

running 1 test
test it_works ... FAILED

failures:

---- it_works stdout ----
---- it_works stderr ----

thread 'main' panicked at src/thing.rs:20:5:
assertion `left == right` failed
left: 2
right: 3
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
qemu: uncaught target signal 6 (Aborted) - core dumped


failures:
   it_works

test result: FAILED. 0 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.52s

9.2.2. Code Coverage

We are going to write a simple library that can be both built as a library on a bare metal target and as an executable on a facade.

// src/thing.rs

#![cfg_attr(not(target_os = "linux"), no_std)]

use core::ffi::c_int;

#[unsafe(no_mangle)]
extern "C" fn return_a_number(even: bool) -> c_int {
   if even {
      return 2;
   } else {
      return 1;
   }
}

#[test]
fn it_works() {
   assert_eq!(return_a_number(false), 1);
   // assert_eq!(return_a_number(true), 2);
}


#[cfg(not(target_os = "linux"))]
#[panic_handler]
fn panic(_info: &core::panic::PanicInfo) -> ! {
   loop {}
}

We can build for a bare metal target (Armv7E-M bare-metal (hard-float)) by running:

rustc --edition 2021 --target thumbv7em-none-eabihf src/thing.rs --out-dir artifacts

We can build for the equivalent facade, with instrumentation, by running:

rustc --edition 2021 --target thumbv7em-ferrocene.facade-eabihf --test -Z panic-abort-tests -C instrument-coverage src/thing.rs --out-dir artifacts

To create the profraw file:

LLVM_PROFILE_FILE="profiling/thing-%p-%m.profraw" artifacts/thing

Then create the profdata:

rust-profdata merge --sparse profiling/thing-*.profraw -o profiling/thing.profdata

Then create the coverage report:

rust-cov report -Xdemangler=rustfilt artifacts/thing \
   --instr-profile=profiling/thing.profdata \
   --show-instantiation-summary

That should output something like the following:

Filename              Regions    Missed Regions     Cover   Functions  Missed Functions  Executed  Instantiations   Missed Insts.  Executed       Lines      Missed Lines     Cover    Branches   Missed Branches     Cover
----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
$CWD/src/thing.rs           9                 1    88.89%           2                 0   100.00%               2               0   100.00%           8                 1    87.50%           0                 0         -
----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
TOTAL                       9                 1    88.89%           2                 0   100.00%               2               0   100.00%           8                 1    87.50%           0                 0         -

Not bad, but the coverage could be improved! Uncomment the assert_eq!(return_a_number(true), 2); line and run it again. Coverage should now be 100%.