Merge #136 136: Create syscall recorder for tests r=Woyten a=torfmaster # Content of the PR This PR contains an "event recorder" which enables to write unit tests which invoke syscalls. The idea is to capture all syscalls and record them to assert which syscalls occur. The return value for a syscall can also be set. # Help needed Comments are very welcome. The mock syscall implementation is very crude because it is good enough to cover the only existing test case. Ideas for meaningful tests are very welcome so I can elaborate which infrastructure is needed to create these tests. Ideas would be * test the set alarm retry mechanism of the alarm implementation * test non-trivial callbacks (i.e. temperature) Co-authored-by: torfmaster <briefe@kebes.de>
diff --git a/src/leds.rs b/src/leds.rs index e0f420c..c505cf9 100644 --- a/src/leds.rs +++ b/src/leds.rs
@@ -127,3 +127,35 @@ } } } + +#[cfg(test)] +mod test { + use super::command_nr; + use super::DRIVER_NUMBER; + use crate::result::TockResult; + use crate::syscalls; + use crate::syscalls::raw::Event; + + #[test] + pub fn single_led_can_be_enabled() { + let events = syscalls::raw::run_recording_events::<TockResult<()>, _>(|next_return| { + let mut drivers = unsafe { crate::drivers::retrieve_drivers_unsafe() }; + + next_return.set(1); + + let leds_driver = drivers.leds.init_driver()?; + next_return.set(0); + + let led = leds_driver.get(0)?; + led.on()?; + Ok(()) + }); + assert_eq!( + events, + vec![ + Event::Command(DRIVER_NUMBER, command_nr::COUNT, 0, 0), + Event::Command(DRIVER_NUMBER, command_nr::ON, 0, 0), + ] + ); + } +}
diff --git a/src/syscalls/platform_mock.rs b/src/syscalls/platform_mock.rs index 30c528f..66e79f0 100644 --- a/src/syscalls/platform_mock.rs +++ b/src/syscalls/platform_mock.rs
@@ -1,49 +1,103 @@ +use core::cell::Cell; +use core::cell::RefCell; +use std::vec::Vec; + /// yield for a callback fired by the kernel /// # Safety /// Yielding inside a callback conflicts with Rust's safety guarantees. For example, /// a FnMut closure could be triggered multiple times making a &mut a shared reference. -pub unsafe fn yieldk() {} +pub unsafe fn yieldk() { + EVENTS.with(|e| e.borrow_mut().push(Event::YieldK)); +} /// Subscribe a callback to the kernel /// # Safety /// Unsafe as passed callback is dereferenced and called. pub unsafe fn subscribe( - _: usize, - _: usize, - _: *const unsafe extern "C" fn(usize, usize, usize, usize), - _: usize, + arg1: usize, + arg2: usize, + arg3: *const unsafe extern "C" fn(usize, usize, usize, usize), + arg4: usize, ) -> isize { - unimplemented() + EVENTS.with(|e| { + e.borrow_mut() + .push(Event::Subscribe(arg1, arg2, arg3, arg4)) + }); + NEXT_OUTPUT.with(|e| e.get()) } /// Send a command to the tock kernel /// # Safety /// This function usually involves assembly calls which are unsafe. -pub unsafe fn command(_: usize, _: usize, _: usize, _: usize) -> isize { - unimplemented() +pub unsafe fn command(arg1: usize, arg2: usize, arg3: usize, arg4: usize) -> isize { + EVENTS.with(|e| e.borrow_mut().push(Event::Command(arg1, arg2, arg3, arg4))); + NEXT_OUTPUT.with(|e| e.get()) } /// Call a command only taking into accoun the first argument /// # Safety /// Unsafe as ignored arguments cause leaking of registers to the kernel -pub unsafe fn command1(_: usize, _: usize, _: usize) -> isize { - unimplemented() +pub unsafe fn command1(arg1: usize, arg2: usize, arg3: usize) -> isize { + EVENTS.with(|e| e.borrow_mut().push(Event::Command1(arg1, arg2, arg3))); + NEXT_OUTPUT.with(|e| e.get()) } /// Share a memory region with the kernel /// # Safety /// Unsafe as the pointer to the shared buffer is potentially dereferenced by the kernel. -pub unsafe fn allow(_: usize, _: usize, _: *mut u8, _: usize) -> isize { - unimplemented() +pub unsafe fn allow(arg1: usize, arg2: usize, arg3: *mut u8, arg4: usize) -> isize { + EVENTS.with(|e| e.borrow_mut().push(Event::Allow(arg1, arg2, arg3, arg4))); + NEXT_OUTPUT.with(|e| e.get()) } /// Generic operations on the app's memory as requesting more memory /// # Safety /// Allows the kernel to do generic operations on the app's memory which can cause memory corruption. -pub unsafe fn memop(_: u32, _: usize) -> isize { - unimplemented() +pub unsafe fn memop(arg1: u32, arg2: usize) -> isize { + EVENTS.with(|e| e.borrow_mut().push(Event::Memop(arg1, arg2))); + NEXT_OUTPUT.with(|e| e.get()) } -fn unimplemented() -> ! { - unimplemented!("Unimplemented for tests"); +/// For tests: Run the closure recording the syscalls which are invoked in during the run of the closure. +pub fn run_recording_events<R, C: FnMut(&NextReturn) -> R>(mut f: C) -> Vec<Event> { + NEXT_OUTPUT.with(|n| n.set(0)); + NEXT_OUTPUT.with(|n| f(n)); + let mut output = Vec::new(); + EVENTS.with(|e| output.append(&mut e.borrow_mut())); + output +} + +thread_local!(static EVENTS: RefCell<Vec<Event>> = RefCell::new(Vec::new())); +thread_local!(static NEXT_OUTPUT: NextReturn = NextReturn { next_return: Cell::new(0) }); + +#[derive(Clone, Debug, PartialEq)] +/// For tests: syscall event +pub enum Event { + YieldK, + Subscribe( + usize, + usize, + *const unsafe extern "C" fn(usize, usize, usize, usize), + usize, + ), + Command(usize, usize, usize, usize), + Command1(usize, usize, usize), + Allow(usize, usize, *mut u8, usize), + Memop(u32, usize), +} + +/// For tests: controls the next return value of any syscall +pub struct NextReturn { + next_return: Cell<isize>, +} + +impl NextReturn { + /// Set the next return value + pub fn set(&self, value: isize) { + self.next_return.set(value); + } + + fn get(&self) -> isize { + self.next_return.get() + } }