| // Libtock regression tests to be used with real hardware. |
| // Requires P0.03 and P0.04 to be connected (on a nRF52 DK) and |
| // P0.01 and P0.03 to be connected (on a nRF52840dk). |
| |
| #![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::ptr::read_volatile; |
| use core::task::Context; |
| use core::task::Poll; |
| use libtock::console::ConsoleDriver; |
| use libtock::gpio::GpioDriverFactory; |
| use libtock::gpio::GpioState; |
| use libtock::gpio::ResistorMode; |
| use libtock::println; |
| 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") |
| } |
| } |
| |
| #[cfg_attr( |
| feature = "__internal_disable_gpio_in_integration_test", |
| allow(unused_variables) |
| )] |
| async fn libtock_test( |
| test: &mut LibtockTest, |
| timer: &mut DriverContext, |
| gpio: &mut GpioDriverFactory, |
| ) -> TockResult<()> { |
| test.console()?; |
| test.sdata()?; |
| test.static_mut()?; |
| test.dynamic_dispatch()?; |
| test.formatting()?; |
| test.heap()?; |
| test.drivers_only_instantiable_once()?; |
| #[cfg(not(feature = "__internal_disable_timer_in_integration_test"))] |
| test.callbacks(timer).await?; |
| #[cfg(not(feature = "__internal_disable_gpio_in_integration_test"))] |
| test.gpio(gpio)?; |
| Ok(()) |
| } |
| |
| struct LibtockTest { |
| success: bool, |
| } |
| |
| impl LibtockTest { |
| fn initialize(console: ConsoleDriver) -> Self { |
| console.create_console(); |
| Self { success: true } |
| } |
| |
| fn console(&mut self) -> TockResult<()> { |
| self.log_success("Console") |
| } |
| |
| // RISC-V has the .sdata section, which is ostensibly for commonly-accessed |
| // read-write global variables. In practice, the Rust compiler tends to put |
| // small globals (<= 8 bytes) into it. This test verifies that small |
| // data symbols are accessible. |
| fn sdata(&mut self) -> TockResult<()> { |
| static mut SDATA: [u8; 4] = [1, 2, 3, 4]; |
| // Use volatile reads to prevent the compiler from constant-folding |
| // everything away. |
| let sdata_ptr = unsafe { &SDATA } as *const u8; |
| let sum = (0..4) |
| .map(|i| unsafe { read_volatile(sdata_ptr.offset(i)) }) |
| .sum::<u8>(); |
| self.check_if_true(sum == 10, "sdata") |
| } |
| |
| 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") |
| } |
| |
| fn drivers_only_instantiable_once(&mut self) -> TockResult<()> { |
| self.check_if_true( |
| libtock::retrieve_drivers().is_err(), |
| "Drivers only instantiable once", |
| ) |
| } |
| |
| #[cfg_attr( |
| feature = "__internal_disable_timer_in_integration_test", |
| allow(dead_code) |
| )] |
| 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(1000))?; |
| |
| AlternatingFuture { yielded: false }.await; |
| |
| mem::drop(timer); |
| |
| self.check_if_true(callback_hit, "Callbacks") |
| } |
| |
| #[cfg_attr( |
| feature = "__internal_disable_gpio_in_integration_test", |
| allow(dead_code) |
| )] |
| 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<()> { |
| println!("[ OK ] {}", message); |
| Ok(()) |
| } |
| |
| fn log_failure(&mut self, message: &str) -> TockResult<()> { |
| println!("[ 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(()) |
| } |
| } |
| } |