Writing device drivers for embedded Rust using the traits from embedded-hal or embedded-hal-async is a breeze.
Example
For a simple SPI driver, our driver only needs to accept a generic SpiDevice instead of the concrete type.
Testing
However, once we want to start to test the async logic (in our case, the read_byte function), we'll run into troubles.
Unlike other async runtimes, like tokio or smol, embassy is designed to be run on no_std targets, using ARM Cortex (or similar) microprocessors. Therefore, it lacks the equivalent to tokio::test, which is used to create an async runtime for testing.
// Assert that our driver did actually read a single byte.
driver.destroy.done;
}
Solution
To solve this, we need to grab a few crates that can provide us with an async runtime that can drive our tests.
[]
# for the mock of the SpiDevice
= "0.11"
# provides a blocking executor for futures
= "0.3"
# required if we're using any mutex from `embassy-sync`
= { = "1.2", = ["std"] }
With futures-test we can replace our #[test] annotation to declare an async function that uses a blocking runtime to drive our futures:
async
With that, we can now test our async logic.
However, when testing more complex code that depends on the embassy ecosystem, we might require either more mocks or actually run some of the embassy logic on our host device.
For example, we might depend on timers from embassy-time. These can be driven on std targets with:
= { = "0.5", = ["generic-queue-64", "std"] }
Conclusion
With a little bit of creativity we managed to test our async embedded Rust driver using the executor from futures-test.
