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);