blob: a938f46708b6c2ecd7bd6fcf714a3341fcaf980a [file] [log] [blame]
// 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(())
}
}
}