Merge #144 144: Improve output of the libtock test suite r=Woyten a=Woyten This PR adds clear assertions to the integration test suite. It also tries to remove some confusion about the intent of the integration tests by renaming them from `hardware_test.rs` to `libtock_test.rs`. Co-authored-by: Woyten <woyten.tielesch@online.de>
diff --git a/CHANGELOG.md b/CHANGELOG.md index 9325962..7af6694 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md
@@ -40,6 +40,8 @@ - `./run_example.sh` has been deleted - Instead, use `PLATFORM=<platform> cargo r<arch> <your_app>`. This will build the app for your CPU architecture and platform-specific memory layout and flash it via J-Link to your board - Targets without support for atomics can be built +- The `TockAllocator` is no longer included by default and needs to to be opted-in via `--features=alloc` +- `hardware_test.rs` is now called `libtock_test.rs` to make clear that the intent is to test the correctness of `libtock-rs`, not the hardware or the kernel ## a8bb4fa9be504517d5533511fd8e607ea61f1750 (0.1.0)
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 26f3490..68c463d 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md
@@ -35,18 +35,19 @@ - connect your device to your computer - open a console, e.g. `tockloader listen` -- run the tests: `PLATFORM=nrf52 cargo rtv7em hardware_test --features=alloc` +- run the tests: `PLATFORM=nrf52 cargo rtv7em libtock_test --features=alloc` The expected output on the UART console will be as follows. ``` -[test-results] -heap_test = "Heap works." -formatting = works -should_be_one = 1 -gpio_works = true -trait_obj_value_usize = 1 -trait_obj_value_string = string -callbacks_work = true -all_tests_run = true +[ OK ] Console +[ OK ] static mut +[ OK ] Dynamic dispatch +[ OK ] Formatting +[ OK ] Heap +[ OK ] Callbacks +[ OK ] GPIO initialization +[ OK ] GPIO activation +[ OK ] GPIO read/write +[ OK ] Test suite finished with state SUCCESS ```
diff --git a/Cargo.toml b/Cargo.toml index 2122490..c94d15f 100644 --- a/Cargo.toml +++ b/Cargo.toml
@@ -31,8 +31,8 @@ required-features = ["alloc"] [[example]] -name = "hardware_test" -path = "examples-alloc/hardware_test.rs" +name = "libtock_test" +path = "examples-alloc/libtock_test.rs" required-features = ["alloc"] [[example]]
diff --git a/README.md b/README.md index e58223e..18a04b1 100644 --- a/README.md +++ b/README.md
@@ -14,7 +14,7 @@ and that there can only be one application written in rust at a time and it must be installed as the first application on the board, unless you want to play games with linker scripts. -There are some `*_layout.ld` files provided that allow to run the +There are some `layout_*.ld` files provided that allow to run the examples on common boards. Due to MPU region alignment issues they may not work for applications that use a lot of RAM, in that case you may have to change the SRAM @@ -83,12 +83,12 @@ extern crate alloc; ``` -to the preamble. +to the preamble and store your example in the `examples-alloc` folder. To run on the code on your board you can use ```bash -PLATFORM=<platform> cargo r<arch> <your_app> +PLATFORM=<platform> cargo r<arch> <your_app> [--features=alloc] ``` This script does the following steps for you:
diff --git a/examples-alloc/hardware_test.rs b/examples-alloc/hardware_test.rs deleted file mode 100644 index 2f7028c..0000000 --- a/examples-alloc/hardware_test.rs +++ /dev/null
@@ -1,139 +0,0 @@ -#![no_std] - -/// Hardware regression tests. -/// Need P0.03 and P0.04 to be connected (on a nrf52-dk). -extern crate alloc; - -use alloc::string::String; -use core::fmt::Write; -use futures::future; -use libtock::console::Console; -use libtock::gpio::GpioRead; -use libtock::gpio::GpioState; -use libtock::gpio::GpioWrite; -use libtock::gpio::ResistorMode; -use libtock::result::TockResult; -use libtock::timer::DriverContext; -use libtock::timer::Duration; - -static mut STATIC: usize = 0; - -trait MyTrait { - fn do_something_with_a_console(&self, console: &mut Console); -} - -impl MyTrait for usize { - fn do_something_with_a_console(&self, console: &mut Console) { - writeln!(console, "trait_obj_value_usize = {}", &self).unwrap(); - } -} - -impl MyTrait for String { - fn do_something_with_a_console(&self, console: &mut Console) { - writeln!(console, "trait_obj_value_string = {}", &self).unwrap(); - } -} - -#[libtock::main] -async fn main() -> TockResult<()> { - let mut drivers = libtock::retrieve_drivers()?; - - let mut gpio_driver = drivers.gpio.init_driver()?; - let mut console = drivers.console.create_console(); - - let mut gpios = gpio_driver.gpios(); - let mut pin_in = gpios.next().unwrap(); - let pin_in = pin_in.enable_input(ResistorMode::PullDown)?; - let mut pin_out = gpios.next().unwrap(); - let mut pin_out = pin_out.enable_output()?; - - writeln!(console, "[test-results]")?; - - test_heap(&mut console); - test_formatting(&mut console); - test_static_mut(&mut console); - - test_gpio(&mut console, &pin_in, &mut pin_out); - - test_trait_objects(&mut console, &pin_in, &mut pin_out)?; - - test_callbacks_and_wait_forever(&mut console, &mut drivers.timer).await -} - -fn test_heap(console: &mut Console) { - let mut string = String::from("heap_test = \"Heap "); - string.push_str("works.\""); - writeln!(console, "{}", string).unwrap(); -} - -fn test_formatting(console: &mut Console) { - writeln!(console, "formatting = {}", String::from("works")).unwrap(); -} - -/// needs P0.03 and P0.04 to be connected -/// Output order should be: -/// trait_obj_value_usize = 1 -/// trait_obj_value_string = string -fn test_trait_objects( - console: &mut Console, - pin_in: &GpioRead, - pin_out: &mut GpioWrite, -) -> TockResult<()> { - pin_out.set_high()?; - - let string = String::from("string"); - - let x = match pin_in.read()? { - GpioState::Low => &string as &dyn MyTrait, - GpioState::High => &1usize as &dyn MyTrait, - }; - - let y = match pin_in.read()? { - GpioState::Low => &1usize as &dyn MyTrait, - GpioState::High => &string as &dyn MyTrait, - }; - - x.do_something_with_a_console(console); - y.do_something_with_a_console(console); - - Ok(()) -} - -fn test_static_mut(console: &mut Console) { - increment_static_mut(); - - writeln!(console, "should_be_one = {}", unsafe { STATIC }).unwrap(); -} - -/// needs P0.03 and P0.04 to be connected -fn test_gpio(console: &mut Console, pin_in: &GpioRead, pin_out: &mut GpioWrite) { - pin_out.set_high().ok().unwrap(); - - writeln!( - console, - "gpio_works = {}", - pin_in.read().ok() == Some(GpioState::High) - ) - .unwrap(); -} - -async fn test_callbacks_and_wait_forever( - console: &mut Console, - timer_context: &mut DriverContext, -) -> TockResult<()> { - let mut with_callback = timer_context.with_callback(|_, _| { - writeln!(console, "callbacks_work = true").unwrap(); - writeln!(console, "all_tests_run = true").unwrap(); - }); - - let mut timer = with_callback.init()?; - - timer.set_alarm(Duration::from_ms(500))?; - - future::pending().await -} - -#[inline(never)] -fn increment_static_mut() { - unsafe { STATIC += 1 }; -}
diff --git a/examples-alloc/libtock_test.rs b/examples-alloc/libtock_test.rs new file mode 100644 index 0000000..d764af3 --- /dev/null +++ b/examples-alloc/libtock_test.rs
@@ -0,0 +1,209 @@ +// Libtock regression tests to be used with real hardware. +// Requires P0.03 and P0.04 to be connected (on a nRF52 DK). + +#![no_std] + +extern crate alloc; + +use alloc::string::String; +use core::fmt::Write; +use core::future::Future; +use core::mem; +use core::pin::Pin; +use core::task::Context; +use core::task::Poll; +use libtock::console::Console; +use libtock::console::ConsoleDriver; +use libtock::gpio::GpioDriverFactory; +use libtock::gpio::GpioState; +use libtock::gpio::ResistorMode; +use libtock::result::TockResult; +use libtock::timer::DriverContext; +use libtock::timer::Duration; + +#[libtock::main] +async fn main() -> TockResult<()> { + let mut drivers = libtock::retrieve_drivers()?; + + let mut test = LibtockTest::initialize(drivers.console); + + let test_result = libtock_test(&mut test, &mut drivers.timer, &mut drivers.gpio).await; + + if test_result.is_ok() && test.is_success() { + test.log_success("Test suite finished with state SUCCESS") + } else { + test.log_failure("Test suite finished with state FAILURE") + } +} + +async fn libtock_test( + test: &mut LibtockTest, + timer: &mut DriverContext, + gpio: &mut GpioDriverFactory, +) -> TockResult<()> { + test.console()?; + test.static_mut()?; + test.dynamic_dispatch()?; + test.formatting()?; + test.heap()?; + test.callbacks(timer).await?; + test.gpio(gpio)?; + Ok(()) +} + +struct LibtockTest { + console: Console, + success: bool, +} + +impl LibtockTest { + fn initialize(console: ConsoleDriver) -> Self { + Self { + console: console.create_console(), + success: true, + } + } + + fn console(&mut self) -> TockResult<()> { + self.log_success("Console") + } + + fn static_mut(&mut self) -> TockResult<()> { + self.check_if_true(increment_static_mut() == 1, "static mut") + } + + fn dynamic_dispatch(&mut self) -> TockResult<()> { + let (x, y) = if foo() == "foo" { + (&'0' as &dyn MyTrait, &0usize as &dyn MyTrait) + } else { + (&0usize as &dyn MyTrait, &'0' as &dyn MyTrait) + }; + + self.check_if_true( + (x.dispatch(), y.dispatch()) == ("str", "usize"), + "Dynamic dispatch", + ) + } + + fn formatting(&mut self) -> TockResult<()> { + let mut string = String::new(); + write!(string, "{}bar", foo())?; + + self.check_if_true(string == "foobar", "Formatting") + } + + fn heap(&mut self) -> TockResult<()> { + let mut string = String::new(); + string.push_str(foo()); + string.push_str("bar"); + + self.check_if_true(string == "foobar", "Heap") + } + + async fn callbacks(&mut self, timer_context: &mut DriverContext) -> TockResult<()> { + let mut callback_hit = false; + let mut with_callback = timer_context.with_callback(|_, _| callback_hit = true); + let mut timer = with_callback.init()?; + + timer.set_alarm(Duration::from_ms(50))?; + + AlternatingFuture { yielded: false }.await; + + mem::drop(timer); + + self.check_if_true(callback_hit, "Callbacks") + } + + fn gpio(&mut self, gpio: &mut GpioDriverFactory) -> TockResult<()> { + let mut gpio_driver = gpio.init_driver().ok().unwrap(); + + self.log_success("GPIO initialization")?; + + let mut gpios = gpio_driver.gpios(); + let mut pin_in = gpios.next().unwrap(); + let pin_in = pin_in.enable_input(ResistorMode::PullDown).ok().unwrap(); + let mut pin_out = gpios.next().unwrap(); + let pin_out = pin_out.enable_output().ok().unwrap(); + + self.log_success("GPIO activation")?; + + pin_out.set_high().ok().unwrap(); + + self.check_if_true( + pin_in.read().ok() == Some(GpioState::High), + "GPIO read/write", + ) + } + + fn is_success(&self) -> bool { + self.success + } + + fn check_if_true(&mut self, condition: bool, message: &str) -> TockResult<()> { + if condition { + self.log_success(message) + } else { + self.log_failure(message) + } + } + + fn log_success(&mut self, message: &str) -> TockResult<()> { + writeln!(&mut self.console, "[ OK ] {}", message)?; + Ok(()) + } + + fn log_failure(&mut self, message: &str) -> TockResult<()> { + writeln!(&mut self.console, "[ FAILURE ] {}", message)?; + self.success = false; + Ok(()) + } +} + +#[inline(never)] +// Do not inline this to prevent compiler optimizations +fn foo() -> &'static str { + "foo" +} + +#[inline(never)] +fn increment_static_mut() -> usize { + static mut STATIC: usize = 0; + + unsafe { + STATIC += 1; + STATIC + } +} + +trait MyTrait { + fn dispatch(&self) -> &'static str; +} + +impl MyTrait for usize { + fn dispatch(&self) -> &'static str { + "usize" + } +} + +impl MyTrait for char { + fn dispatch(&self) -> &'static str { + "str" + } +} + +struct AlternatingFuture { + yielded: bool, +} + +impl Future for AlternatingFuture { + type Output = (); + + fn poll(mut self: Pin<&mut Self>, _cx: &mut Context) -> Poll<Self::Output> { + self.yielded = !self.yielded; + if self.yielded { + Poll::Pending + } else { + Poll::Ready(()) + } + } +}
diff --git a/src/lang_items.rs b/src/lang_items.rs index bb4bd77..b8731a2 100644 --- a/src/lang_items.rs +++ b/src/lang_items.rs
@@ -31,18 +31,32 @@ where T: Termination, { - main(); + main().check_result(); } #[lang = "termination"] -pub trait Termination {} +pub trait Termination { + fn check_result(self); +} -impl Termination for () {} +impl Termination for () { + fn check_result(self) {} +} -impl Termination for crate::result::TockResult<()> {} +impl Termination for TockResult<()> { + fn check_result(self) { + if self.is_err() { + unsafe { report_panic() }; + } + } +} #[panic_handler] unsafe fn panic_handler(_info: &PanicInfo) -> ! { + report_panic() +} + +unsafe fn report_panic() -> ! { // Signal a panic using the LowLevelDebug capsule (if available). super::debug::low_level_status_code(1);