From 851a70c6716c26db07baa3128d258266cadd83f9 Mon Sep 17 00:00:00 2001 From: Laura Abbott Date: Wed, 30 Aug 2023 12:25:21 -0400 Subject: [PATCH] Re-work test suite The existing test suite relied on ITM/ETM for getting test information. We've since built up other tooling that doesn't require those features. Switch to a hiffy based test design to allow for control of tests to happen off target. --- Cargo.lock | 4 + build/xtask/src/config.rs | 1 + task/hiffy/Cargo.toml | 2 + task/hiffy/src/common.rs | 1 + task/hiffy/src/main.rs | 3 + task/hiffy/src/tests.rs | 117 +++++ test/test-api/Cargo.toml | 1 + test/test-api/src/lib.rs | 29 +- test/test-assist/src/main.rs | 1 - test/test-runner/Cargo.toml | 1 + test/test-runner/src/main.rs | 455 ++++-------------- test/test-suite/Cargo.toml | 2 +- test/test-suite/src/main.rs | 48 +- test/tests-gemini-bu/app.toml | 11 +- test/tests-gimletlet/app.toml | 11 +- test/tests-lpc55xpresso/app.toml | 17 +- test/tests-lpc55xpresso/src/main.rs | 1 + test/tests-psc/app.toml | 11 +- .../Cargo.toml | 0 .../app.toml | 17 +- test/tests-stm32fx/app-f3.toml | 11 +- test/tests-stm32fx/app.toml | 11 +- test/tests-stm32g0/README.mkdn | 14 - test/tests-stm32g0/app-g070.toml | 20 +- test/tests-stm32h7/app-h743.toml | 11 +- test/tests-stm32h7/app-h753.toml | 11 +- 26 files changed, 401 insertions(+), 410 deletions(-) create mode 100644 task/hiffy/src/tests.rs rename test/{tests-gemini-bu-rot => tests-rot-carrier}/Cargo.toml (100%) rename test/{tests-gemini-bu-rot => tests-rot-carrier}/app.toml (71%) delete mode 100644 test/tests-stm32g0/README.mkdn diff --git a/Cargo.lock b/Cargo.lock index 9adec6ce9..2f9a7f87a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4092,6 +4092,7 @@ dependencies = [ "ringbuf", "serde", "static-cell", + "test-api", "userlib", "zerocopy", ] @@ -4679,6 +4680,7 @@ dependencies = [ "build-util", "num-traits", "userlib", + "zerocopy", ] [[package]] @@ -4735,6 +4737,7 @@ dependencies = [ "cortex-m-semihosting", "hubris-num-tasks", "num-traits", + "ringbuf", "test-api", "userlib", "zerocopy", @@ -4752,6 +4755,7 @@ dependencies = [ "drv-i2c-devices", "hubris-num-tasks", "num-traits", + "ringbuf", "task-config", "test-api", "test-idol-api", diff --git a/build/xtask/src/config.rs b/build/xtask/src/config.rs index e943b9e67..9bdc3c656 100644 --- a/build/xtask/src/config.rs +++ b/build/xtask/src/config.rs @@ -628,6 +628,7 @@ impl BuildConfig<'_> { "asm_const", "naked_functions", "named-profiles", + "used_with_arg", ]); // nightly features that our dependencies use: nightly_features.extend([ diff --git a/task/hiffy/Cargo.toml b/task/hiffy/Cargo.toml index bd36b581d..3d3b7da02 100644 --- a/task/hiffy/Cargo.toml +++ b/task/hiffy/Cargo.toml @@ -19,6 +19,7 @@ hubris-num-tasks = { path = "../../sys/num-tasks", features = ["task-enum"] } ringbuf = { path = "../../lib/ringbuf" } static-cell = { path = "../../lib/static-cell" } userlib = { path = "../../sys/userlib" } +test-api = { path = "../../test/test-api", optional = true} byteorder.workspace = true cfg-if.workspace = true @@ -36,6 +37,7 @@ anyhow.workspace = true cfg-if.workspace = true [features] +testsuite = [ "test-api" ] itm = [ "userlib/log-itm" ] semihosting = [ "userlib/log-semihosting" ] i2c = [] diff --git a/task/hiffy/src/common.rs b/task/hiffy/src/common.rs index 77676e8d4..a5bb985f8 100644 --- a/task/hiffy/src/common.rs +++ b/task/hiffy/src/common.rs @@ -27,6 +27,7 @@ where /// on the Hubris side. (The purpose of this function is to allow for /// device-mandated sleeps to in turn for allow for bulk device operations.) /// +#[allow(dead_code)] pub(crate) fn sleep( stack: &[Option], _data: &[u8], diff --git a/task/hiffy/src/main.rs b/task/hiffy/src/main.rs index 468743d84..3f09801b6 100644 --- a/task/hiffy/src/main.rs +++ b/task/hiffy/src/main.rs @@ -35,6 +35,9 @@ cfg_if::cfg_if! { } else if #[cfg(feature = "stm32g0")] { pub mod stm32g0; use crate::stm32g0::*; + } else if #[cfg(feature = "testsuite")] { + pub mod tests; + use crate::tests::*; } else { pub mod generic; use crate::generic::*; diff --git a/task/hiffy/src/tests.rs b/task/hiffy/src/tests.rs new file mode 100644 index 000000000..358522232 --- /dev/null +++ b/task/hiffy/src/tests.rs @@ -0,0 +1,117 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. + +use byteorder::ByteOrder; +use hif::*; +use ringbuf::*; +use test_api::*; +#[allow(unused_imports)] +use userlib::{sys_send, task_slot}; +use zerocopy::AsBytes; + +task_slot!(TEST_TASK, suite); +task_slot!(RUNNER, runner); + +// arg0: test id number +pub(crate) fn run_a_test( + stack: &[Option], + _data: &[u8], + rval: &mut [u8], +) -> Result { + if stack.len() < 1 { + return Err(Failure::Fault(Fault::MissingParameters)); + } + + let fp = stack.len() - 1; + + let id = match stack[fp + 0] { + Some(id) => id, + None => { + return Err(Failure::Fault(Fault::EmptyParameter(0))); + } + }; + + userlib::kipc::restart_task(TEST_TASK.get_task_index().into(), true); + + ringbuf_entry!(Trace::RunTest(id)); + let (rc, _len) = sys_send( + TEST_TASK.get_task_id(), + SuiteOp::RunCase as u16, + &id.as_bytes(), + &mut [], + &[], + ); + + if rc != 0 { + return Err(Failure::FunctionError(rc)); + } + + let mut result: u32 = TestResult::NotDone as u32; + + loop { + let (rc, _len) = sys_send( + RUNNER.get_task_id(), + RunnerOp::TestResult as u16, + &[], + &mut result.as_bytes_mut(), + &[], + ); + + if rc != 0 { + return Err(Failure::FunctionError(rc)); + } + + match TestResult::try_from(result) { + Ok(x) => match x { + TestResult::Success => { + byteorder::LittleEndian::write_u32(rval, 1); + return Ok(core::mem::size_of::()); + } + TestResult::Failure => { + byteorder::LittleEndian::write_u32(rval, 0); + return Ok(core::mem::size_of::()); + } + TestResult::NotDone => (), + }, + Err(x) => return Err(Failure::FunctionError(x)), + } + } +} + +#[derive(Copy, Clone, PartialEq)] +enum Trace { + Execute((usize, hif::Op)), + Failure(Failure), + Success, + RunTest(u32), + None, +} + +ringbuf!(Trace, 64, Trace::None); + +pub enum Functions { + RunTest(usize, bool), +} + +pub(crate) static HIFFY_FUNCS: &[Function] = &[run_a_test]; + +// +// This definition forces the compiler to emit the DWARF needed for debuggers +// to be able to know function indices, arguments and return values. +// +#[no_mangle] +#[used] +static HIFFY_FUNCTIONS: Option<&Functions> = None; + +pub(crate) fn trace_execute(offset: usize, op: hif::Op) { + ringbuf_entry!(Trace::Execute((offset, op))); +} + +pub(crate) fn trace_success() { + ringbuf_entry!(Trace::Success); +} + +pub(crate) fn trace_failure(f: hif::Failure) { + ringbuf_entry!(Trace::Failure(f)); +} diff --git a/test/test-api/Cargo.toml b/test/test-api/Cargo.toml index 3e886bd9d..f54df9cbb 100644 --- a/test/test-api/Cargo.toml +++ b/test/test-api/Cargo.toml @@ -6,6 +6,7 @@ edition = "2021" [dependencies] userlib = { path = "../../sys/userlib" } num-traits = { workspace = true } +zerocopy = { workspace = true } [build-dependencies] build-util = { path = "../../build/util" } diff --git a/test/test-api/src/lib.rs b/test/test-api/src/lib.rs index e88c94504..a5e332d05 100644 --- a/test/test-api/src/lib.rs +++ b/test/test-api/src/lib.rs @@ -41,10 +41,6 @@ pub enum AssistOp { /// Operations that are performed by the test-suite #[derive(FromPrimitive)] pub enum SuiteOp { - /// Get the number of test cases (`() -> usize`). - GetCaseCount = 1, - /// Get the name of a case (`usize -> [u8]`). - GetCaseName = 2, /// Run a case, replying before it starts (`usize -> ()`). RunCase = 3, } @@ -57,5 +53,28 @@ pub enum RunnerOp { ReadAndClearNotes = 0, /// Signals that a test is complete, and that the runner is switching back /// to passive mode (`() -> ()`). - TestComplete = 0xFFFF, + TestComplete = 0xfffe, + /// Returns the result of the last test if it completed + TestResult = 0xffff, +} + +#[derive(FromPrimitive)] +#[repr(u32)] +pub enum TestResult { + Failure = 0, + Success = 1, + NotDone = 3, +} + +impl TryFrom for TestResult { + type Error = u32; + + fn try_from(value: u32) -> Result { + match value { + 0 => Ok(TestResult::Failure), + 1 => Ok(TestResult::Success), + 3 => Ok(TestResult::NotDone), + x => Err(x), + } + } } diff --git a/test/test-assist/src/main.rs b/test/test-assist/src/main.rs index 00c05cd8f..eea2fd081 100644 --- a/test/test-assist/src/main.rs +++ b/test/test-assist/src/main.rs @@ -144,7 +144,6 @@ fn eat_some_pi(highregs: bool) { #[export_name = "main"] fn main() -> ! { - sys_log!("assistant starting"); let mut buffer = [0; 4]; let mut last_reply = 0u32; let mut stored_value = 0; diff --git a/test/test-runner/Cargo.toml b/test/test-runner/Cargo.toml index 21e167df0..387b87a22 100644 --- a/test/test-runner/Cargo.toml +++ b/test/test-runner/Cargo.toml @@ -9,6 +9,7 @@ cortex-m = { workspace = true } cortex-m-semihosting = { workspace = true, optional = true } num-traits = { workspace = true } zerocopy = { workspace = true } +ringbuf = { path = "../../lib/ringbuf" } armv6m-atomic-hack = { path = "../../lib/armv6m-atomic-hack" } hubris-num-tasks = { path = "../../sys/num-tasks" } diff --git a/test/test-runner/src/main.rs b/test/test-runner/src/main.rs index 3afe1f02d..3d0153c68 100644 --- a/test/test-runner/src/main.rs +++ b/test/test-runner/src/main.rs @@ -7,394 +7,149 @@ //! # Architecture //! //! This task is intended to play the "supervisor" role in test images. It -//! controls execution of another task, the "testsuite," which contains the -//! actual tests. (A test image can also contain other tasks as needed.) +//! receives notification of test status from another task which runs the +//! actual tests. The actual triggering of the tests comes from another +//! entity (currently hiffy) //! //! This task should be index 0, while the testsuite should be index 1. //! -//! A test _suite_ consists of one or more test _cases_, which are run -//! individually and can fail separately. -//! -//! The test protocol assumes that the testsuite is message-driven. The -//! interaction between the two tasks is as follows: -//! -//! ```text -//! runner testsuite -//! | * <-- blocks in RECV -//! | : -//! | test metadata request : \ -//! +---------------------------->+ | repeats for each -//! : test metadata response | | test case in suite -//! +<----------------------------+ / -//! | -//! | run test case N -//! +---------------------------->+ -//! : ok will do | -//! +<----------------------------+ -//! | | running -//! * <-- blocks in RECV | test -//! : service request | code... -//! +<----------------------------+ \ -//! | service response : | test can make 0 or more service calls -//! +---------------------------->+ / -//! | | -//! * <-- blocks in RECV | more test code... -//! : test case complete | -//! +<----------------------------+ \ -//! | acknowledge : | until it reports the test is done. -//! +---------------------------->+ / -//! | | -//! | * <-- blocks in RECV -//! | -//! and so on -//! ``` -//! -//! The key detail in the diagram above: the runner and the testsuite *switch -//! roles* in terms of who calls who. -//! -//! - Between tests, the runner does the calling, to get metadata and eventually -//! ask for a test to start. -//! - While the test is running, the runner listens for messages. The testsuite -//! may call the runner at this point to request services (like checking fault -//! reporting), or to signal that the test is done. -//! - At that point the roles reverse again. -//! -//! # Output -//! -//! Output is produced on ITM stimulus port 8. Output is in a line-oriented -//! human-readable format modeled after report formats like TAP, but avoiding -//! some issues. -//! -//! A test report consists of the following lines: -//! -//! - `meta` - marks the beginning of test suite metainformation -//! - `expect N` - indicates that N (decimal integer) test cases are to follow. -//! - N repeats of: -//! - `case NAME` - provides the NAME (UTF-8 string not containing newlines) of -//! the next test case. -//! - `run` - marks the beginning of test suite execution -//! - N repeats of: -//! - `start NAME` - indicates that test suite NAME (UTF-8 string not -//! containing newlines) is starting, and any hangs should be blamed on it. -//! - `finish STATUS NAME` - indicates that test suite NAME has completed with -//! STATUS (which is `ok` or `FAIL`). -//! - `done STATUS` - signals the end of the test suite. STATUS is `ok` if all -//! tests passed, `FAIL` if any failed. +//!```text +//!Test Suite Test Requester Test supervisor +//! (currently hiffy) + +//! + | +//! | | +//! | | +//! Run test N | | +//! <-------------------------++ | +//! | | +-------> | +//! | | + | +//! | Ok will do | Waits for notifications | +//! +------------------------->+ and faults etc. | +//! | | | +//! | | | +//! | <--+ Does test things | | +//! | | Done yet? | +//! | | | +//! | +-------------------------->+ +//! | | | +//! | | | +//! | | | +//! | All done! | | +//! +----------------------+ | +--------------------+ +//! | | | | +//! ++-----------+ | +//! | | +//! | | +//! | | +//! | Here is the status ++ +//! ^---------------------------+ +//!``` #![no_std] #![no_main] -use core::sync::atomic::{AtomicU32, Ordering}; +use ringbuf::*; use test_api::*; use userlib::*; -use zerocopy::AsBytes; #[cfg(armv6m)] use armv6m_atomic_hack::*; -cfg_if::cfg_if! { - if #[cfg(armv6m)] { - /// Helper macro for producing output by semihosting :-( - macro_rules! test_output { - ($s:expr) => { - cortex_m_semihosting::hprintln!($s); - }; - ($s:expr, $($tt:tt)*) => { - cortex_m_semihosting::hprintln!($s, $($tt)*); - }; - } - } else { - /// Helper macro for producing output on stimulus port 8. - macro_rules! test_output { - ($s:expr) => { - unsafe { - let stim = &mut (*cortex_m::peripheral::ITM::PTR).stim[8]; - cortex_m::iprintln!(stim, $s); - } - }; - ($s:expr, $($tt:tt)*) => { - unsafe { - let stim = &mut (*cortex_m::peripheral::ITM::PTR).stim[8]; - cortex_m::iprintln!(stim, $s, $($tt)*); - } - }; - } - } -} +/// We are sensitive to all notifications, to catch unexpected ones in test. +const ALL_NOTIFICATIONS: u32 = !0; /// This runner is written such that the task under test must be task index 1. /// (And the runner must be zero.) const TEST_TASK: usize = 1; -#[no_mangle] -static TEST_KICK: AtomicU32 = AtomicU32::new(0); -static TEST_RUNS: AtomicU32 = AtomicU32::new(0); - -/// We are sensitive to all notifications, to catch unexpected ones in test. -const ALL_NOTIFICATIONS: u32 = !0; - -fn test_run() { - // Get things rolling by restarting the test task. This ensures that it's - // running, so that we don't depend on the `start` key in `app.toml` for - // correctness. - restart_tester(); +#[derive(Copy, Clone, PartialEq)] +enum Trace { + Notification, + TestComplete(TaskId), + TestResult(TaskId), + None, +} - // Begin by interrogating the task to understand the shape of the test - // suite, and produce the `meta` section. - test_output!("meta"); - let case_count = get_case_count(); - test_output!("expect {}", case_count); +ringbuf!(Trace, 64, Trace::None); - // Read and print the name of each test case. - for i in 0..case_count { - output_name("case", i); +#[export_name = "main"] +fn main() -> ! { + struct MonitorState { + received_notes: u32, + test_status: Option, } - // Transition to running tests. - test_output!("run"); - let mut failures = 0; - - for i in 0..case_count { - // Restart every time to ensure state is clear. - restart_tester(); + let mut state = MonitorState { + received_notes: 0, + test_status: None, + }; - // Read the name, again. Yes, this means the test suite could change - // test names on us. Oh well. It's easier than storing the names. - output_name("start", i); - - // Ask the test to start running. It's *supposed* to immediately reply - // and then call us back when it finishes. - start_test(i); - - // We now start playing the receiver, monitoring messages from both the - // kernel and the testsuite. - - // TODO this is where we need to set a timer, but to do that, we need to - // be able to read the current time. - - struct MonitorState { - received_notes: u32, - test_status: Option, - } - - let mut state = MonitorState { - received_notes: 0, - test_status: None, - }; - - // Continue monitoring messages until (1) the test has been reported as - // complete, or (2) we get notice from the kernel that the testsuite has - // crashed. - while state.test_status.is_none() { - hl::recv( - &mut [], - ALL_NOTIFICATIONS, - &mut state, - |state, bits| { - // Record all received notification bits. - state.received_notes |= bits; - - if bits & 1 != 0 { - // Uh-oh, somebody faulted. - if find_and_report_fault() { - // It was the test. - state.test_status = Some(false); - } + loop { + hl::recv( + &mut [], + ALL_NOTIFICATIONS, + &mut state, + |state, bits| { + ringbuf_entry!(Trace::Notification); + + // Record all received notification bits. + state.received_notes |= bits; + + if bits & 1 != 0 { + // Uh-oh, somebody faulted. + if find_and_report_fault() { + // It was the test. + state.test_status = Some(false); } - }, - |state, op: RunnerOp, msg| -> Result<(), u32> { - match op { - RunnerOp::ReadAndClearNotes => { - let (_, caller) = - msg.fixed::<(), u32>().ok_or(2u32)?; - caller.reply(state.received_notes); - state.received_notes = 0; - } - RunnerOp::TestComplete => { - let (_, caller) = - msg.fixed::<(), ()>().ok_or(2u32)?; - caller.reply(()); - state.test_status = Some(true); + } + }, + |state, op: RunnerOp, msg| -> Result<(), u32> { + match op { + RunnerOp::ReadAndClearNotes => { + let (_, caller) = msg.fixed::<(), u32>().ok_or(2u32)?; + caller.reply(state.received_notes); + state.received_notes = 0; + } + RunnerOp::TestComplete => { + let (_, caller) = msg.fixed::<(), ()>().ok_or(2u32)?; + ringbuf_entry!(Trace::TestComplete(caller.task_id())); + caller.reply(()); + state.test_status = Some(true); + } + RunnerOp::TestResult => { + let (_, caller) = msg.fixed::<(), u32>().ok_or(2u32)?; + ringbuf_entry!(Trace::TestResult(caller.task_id())); + match state.test_status { + Some(r) => { + if r { + caller.reply(TestResult::Success as u32); + } else { + caller.reply(TestResult::Failure as u32); + } + state.test_status = None; + } + None => caller.reply(TestResult::NotDone as u32), } } - Ok(()) - }, - ); - } - - // Indicate final state of this case. - let status_str = if state.test_status.unwrap() { - "finish ok" - } else { - failures += 1; - "finish FAIL" - }; - - output_name(status_str, i); - } - - // Indicate final state of the suite. - if failures == 0 { - test_output!("done pass"); - } else { - test_output!("done FAIL"); - } -} - -#[export_name = "main"] -fn main() -> ! { - loop { - test_run(); - TEST_RUNS.fetch_add(1, Ordering::SeqCst); - - while TEST_KICK.load(Ordering::SeqCst) == 0 { - continue; - } - - TEST_KICK.fetch_sub(1, Ordering::SeqCst); - } -} - -/// Contacts the test suite to retrieve the name of test case `index`, and then -/// prints it after `context`. -/// -/// If the name is invalid UTF-8, substitutes a decimal representation of -/// `index`. -fn output_name(context: &str, index: usize) { - let mut name = [0; 64]; - let name_slice = get_case_name(index, &mut name); - if let Ok(name_str) = core::str::from_utf8(name_slice) { - test_output!("{} {}", context, name_str.trim()); - } else { - // If any tests are not valid UTF-8, replace their name with their - // index. - test_output!("{} {}", context, index); - } -} - -/// Asks the kernel to restart the testsuite task and updates our expected -/// generation. -fn restart_tester() { - kipc::restart_task(TEST_TASK, true); -} - -/// Gets a `TaskId` to the testsuite in its current generation. -fn tester_task_id() -> TaskId { - sys_refresh_task_id(TaskId::for_index_and_gen( - TEST_TASK, - Generation::default(), - )) -} - -/// Contacts the test suite to get the number of defined cases. -fn get_case_count() -> usize { - let tid = tester_task_id(); - let mut response = 0; - let op = SuiteOp::GetCaseCount as u16; - let (rc, len) = sys_send(tid, op, &[], response.as_bytes_mut(), &[]); - assert_eq!(rc, 0); - assert_eq!(len, 4); - response -} - -/// Contacts the test suite to extract the name of case `id` into `buf`. Returns -/// the prefix of `buf` that contains the returned name (which may be padded -/// with spaces). -fn get_case_name(id: usize, buf: &mut [u8]) -> &[u8] { - let tid = tester_task_id(); - let op = SuiteOp::GetCaseName as u16; - let (rc, len) = sys_send(tid, op, &id.as_bytes(), buf, &[]); - assert_eq!(rc, 0); - &buf[..len.min(buf.len())] -} - -/// Contacts the testsuite to ask to start case `id`. -fn start_test(id: usize) { - let tid = tester_task_id(); - let op = SuiteOp::RunCase as u16; - let (rc, len) = sys_send(tid, op, &id.as_bytes(), &mut [], &[]); - assert_eq!(rc, 0); - assert_eq!(len, 0); -} - -fn log_fault(t: usize, fault: &FaultInfo) { - match fault { - FaultInfo::MemoryAccess { address, .. } => match address { - Some(a) => { - sys_log!("Task #{} Memory fault at address {:#x}", t, a); - } - - None => { - sys_log!("Task #{} Memory fault at unknown address", t); - } - }, - - FaultInfo::BusError { address, .. } => match address { - Some(a) => { - sys_log!("Task #{} Bus error at address {:#x}", t, a); - } - - None => { - sys_log!("Task #{} Bus error at unknown address", t); - } - }, - - FaultInfo::StackOverflow { address, .. } => { - sys_log!("Task #{} Stack overflow at address {:#x}", t, address); - } - - FaultInfo::DivideByZero => { - sys_log!("Task #{} Divide-by-zero", t); - } - - FaultInfo::IllegalText => { - sys_log!("Task #{} Illegal text", t); - } - - FaultInfo::IllegalInstruction => { - sys_log!("Task #{} Illegal instruction", t); - } - - FaultInfo::InvalidOperation(details) => { - sys_log!("Task #{} Invalid operation: {:#010x}", t, details); - } - - FaultInfo::SyscallUsage(e) => { - sys_log!("Task #{} Bad Syscall Usage {:?}", t, e); - } - - FaultInfo::Panic => { - sys_log!("Task #{} Panic!", t); - } - - FaultInfo::Injected(who) => { - sys_log!("Task #{} Fault injected by task #{}", t, who.index()); - } - FaultInfo::FromServer(who, what) => { - sys_log!( - "Task #{} fault from server #{}: {:?}", - t, - who.index(), - what - ); - } + } + Ok(()) + }, + ); } } /// Scans the kernel's task table looking for a task that has fallen over. /// Prints any that are found. /// -/// If the testsuite is found to have fallen over, it is restarted, and this -/// function returns `true`. +/// If the testsuite is found to have fallen over, this function returns true. +/// The test suite is _not_ restarted to give a chance to collect task state fn find_and_report_fault() -> bool { let mut tester_faulted = false; for i in 0..hubris_num_tasks::NUM_TASKS { let s = kipc::read_task_status(i); - if let TaskState::Faulted { fault, .. } = s { - log_fault(i, &fault); + if let TaskState::Faulted { .. } = s { if i == TEST_TASK { tester_faulted = true; - restart_tester(); } } } diff --git a/test/test-suite/Cargo.toml b/test/test-suite/Cargo.toml index e22cb5735..ebfd6d65f 100644 --- a/test/test-suite/Cargo.toml +++ b/test/test-suite/Cargo.toml @@ -14,7 +14,7 @@ task-config = { path = "../../lib/task-config" } test-api = { path = "../test-api" } test-idol-api = { path = "../test-idol-api" } userlib = { path = "../../sys/userlib", features = ["panic-messages"] } - +ringbuf = { path = "../../lib/ringbuf" } # Some tests require talking to I2C devices on the target board drv-i2c-api = { path = "../../drv/i2c-api", optional = true } drv-i2c-devices = { path = "../../drv/i2c-devices", optional = true } diff --git a/test/test-suite/src/main.rs b/test/test-suite/src/main.rs index 9bd2c3233..b4aab8e1e 100644 --- a/test/test-suite/src/main.rs +++ b/test/test-suite/src/main.rs @@ -4,7 +4,7 @@ //! Test suite. //! -//! This task is driven by the `runner` to run test cases (defined below). +//! This task is driven by another entity (currently hiffy) //! //! Any test case that fails should indicate this by `panic!` (or equivalent, //! like failing an `assert!`). @@ -18,23 +18,38 @@ //! //! The Idol server, `test-idol-server`, tests Idol-mediated IPC. It must be //! included in the image with the name `idol`, but its ID is immaterial. - +#![feature(used_with_arg)] #![no_std] #![no_main] use hubris_num_tasks::NUM_TASKS; +use ringbuf::*; use test_api::*; use userlib::*; use zerocopy::AsBytes; +#[derive(Copy, Clone, PartialEq)] +enum Trace { + TestStart, + TestFinish, + None, +} + +ringbuf!(Trace, 64, Trace::None); + /// A constant that is known to be in a region we can't access to induce a /// memory fault. (Note that if TZ is enabled, this will in fact induce a /// secure fault, and a different constant will be required.) const BAD_ADDRESS: u32 = 0x0; /// Helper macro for building a list of functions with their names. +/// We use the humility debug processing to get the name of each +/// test case and the total number of tests. The #[used(linker)] +/// is to ensure that this actually gets emitted as a symbol. macro_rules! test_cases { ($($(#[$attr:meta])* $name:path,)*) => { + #[no_mangle] + #[used(linker)] static TESTS: &[(&str, &(dyn Fn() + Send + Sync))] = &[ $( $(#[$attr])* @@ -1462,34 +1477,25 @@ fn main() -> ! { &mut buffer, |op, msg| -> Result<(), u32> { match op { - SuiteOp::GetCaseCount => { - let (_, caller) = - msg.fixed::<(), usize>().ok_or(2u32)?; - caller.reply(TESTS.len()); - } - SuiteOp::GetCaseName => { - let (&idx, caller) = - msg.fixed::().ok_or(2u32)?; - let mut name_buf = [b' '; 64]; - let name = TESTS[idx].0; - let name_len = name.len().min(64); - name_buf[..name_len] - .copy_from_slice(&name.as_bytes()[..name_len]); - caller.reply(name_buf); - } SuiteOp::RunCase => { let (&idx, caller) = msg.fixed::().ok_or(2u32)?; - let caller_tid = caller.task_id(); caller.reply(()); + ringbuf_entry!(Trace::TestStart); TESTS[idx].1(); let op = RunnerOp::TestComplete as u16; - // Call back with status. - let (rc, len) = - sys_send(caller_tid, op, &[], &mut [], &[]); + ringbuf_entry!(Trace::TestFinish); + // Report back to the runner (aka our monitor) + let (rc, len) = sys_send( + RUNNER.get_task_id(), + op, + &[], + &mut [], + &[], + ); assert_eq!(rc, 0); assert_eq!(len, 0); } diff --git a/test/tests-gemini-bu/app.toml b/test/tests-gemini-bu/app.toml index facce1e04..18e71acee 100644 --- a/test/tests-gemini-bu/app.toml +++ b/test/tests-gemini-bu/app.toml @@ -44,9 +44,18 @@ max-sizes = {flash = 4096, ram = 1024} stacksize = 1024 start = true +[tasks.hiffy] +name = "task-hiffy" +priority = 3 +features = ["testsuite"] +max-sizes = {flash = 32768, ram = 16384 } +stacksize = 2048 +start = true +task-slots = ["suite", "runner"] + [tasks.idle] name = "task-idle" -priority = 3 +priority = 4 max-sizes = {flash = 256, ram = 256} stacksize = 256 start = true diff --git a/test/tests-gimletlet/app.toml b/test/tests-gimletlet/app.toml index da8393fd1..fa7b04e69 100644 --- a/test/tests-gimletlet/app.toml +++ b/test/tests-gimletlet/app.toml @@ -44,9 +44,18 @@ max-sizes = {flash = 4096, ram = 1024} stacksize = 1024 start = true +[tasks.hiffy] +name = "task-hiffy" +priority = 3 +features = ["testsuite"] +max-sizes = {flash = 32768, ram = 32768 } +stacksize = 2048 +start = true +task-slots = ["suite", "runner"] + [tasks.idle] name = "task-idle" -priority = 3 +priority = 4 max-sizes = {flash = 256, ram = 256} stacksize = 256 start = true diff --git a/test/tests-lpc55xpresso/app.toml b/test/tests-lpc55xpresso/app.toml index 6f435e02b..aee00b302 100644 --- a/test/tests-lpc55xpresso/app.toml +++ b/test/tests-lpc55xpresso/app.toml @@ -46,9 +46,24 @@ max-sizes = {flash = 4096, ram = 1024} stacksize = 1024 start = true +[tasks.hiffy] +name = "task-hiffy" +priority = 3 +features = ["testsuite"] +max-sizes = {flash = 32768, ram = 16384 } +stacksize = 2048 +start = true +task-slots = ["suite", "runner"] + [tasks.idle] name = "task-idle" -priority = 3 +priority = 4 max-sizes = {flash = 256, ram = 256} stacksize = 256 start = true + +[signing.certs] +signing-certs = ["../../support/fake_certs/fake_certificate.der.crt"] +root-certs = ["../../support/fake_certs/fake_certificate.der.crt"] +private-key = "../../support/fake_certs/fake_private_key.pem" + diff --git a/test/tests-lpc55xpresso/src/main.rs b/test/tests-lpc55xpresso/src/main.rs index 17ecc8fe9..79402e088 100644 --- a/test/tests-lpc55xpresso/src/main.rs +++ b/test/tests-lpc55xpresso/src/main.rs @@ -2,6 +2,7 @@ // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at https://mozilla.org/MPL/2.0/. +#![feature(used_with_arg)] #![no_std] #![no_main] diff --git a/test/tests-psc/app.toml b/test/tests-psc/app.toml index 7581a5f49..fa37795fd 100644 --- a/test/tests-psc/app.toml +++ b/test/tests-psc/app.toml @@ -70,9 +70,18 @@ task-slots = ["sys"] "i2c4.event" = 0b0000_1000 "i2c4.error" = 0b0000_1000 +[tasks.hiffy] +name = "task-hiffy" +priority = 3 +features = ["testsuite"] +max-sizes = {flash = 32768, ram = 32768 } +stacksize = 2048 +start = true +task-slots = ["suite", "runner"] + [tasks.idle] name = "task-idle" -priority = 3 +priority = 4 max-sizes = {flash = 128, ram = 256} stacksize = 256 start = true diff --git a/test/tests-gemini-bu-rot/Cargo.toml b/test/tests-rot-carrier/Cargo.toml similarity index 100% rename from test/tests-gemini-bu-rot/Cargo.toml rename to test/tests-rot-carrier/Cargo.toml diff --git a/test/tests-gemini-bu-rot/app.toml b/test/tests-rot-carrier/app.toml similarity index 71% rename from test/tests-gemini-bu-rot/app.toml rename to test/tests-rot-carrier/app.toml index 10875e5eb..040403ce5 100644 --- a/test/tests-gemini-bu-rot/app.toml +++ b/test/tests-rot-carrier/app.toml @@ -14,7 +14,6 @@ name = "test-runner" priority = 0 max-sizes = {flash = 16384, ram = 4096} start = true -features = ["itm"] [tasks.suite] name = "test-suite" @@ -36,7 +35,6 @@ name = "test-assist" priority = 1 max-sizes = {flash = 16384, ram = 4096} start = true -features = ["itm"] [tasks.idol] name = "test-idol-server" @@ -45,10 +43,23 @@ max-sizes = {flash = 4096, ram = 1024} stacksize = 1024 start = true +[tasks.hiffy] +name = "task-hiffy" +priority = 3 +features = ["testsuite"] +max-sizes = {flash = 32768, ram = 16384 } +stacksize = 2048 +start = true +task-slots = ["suite", "runner"] + [tasks.idle] name = "task-idle" -priority = 3 +priority = 4 max-sizes = {flash = 256, ram = 256} stacksize = 256 start = true +[signing.certs] +signing-certs = ["../../support/fake_certs/fake_certificate.der.crt"] +root-certs = ["../../support/fake_certs/fake_certificate.der.crt"] +private-key = "../../support/fake_certs/fake_private_key.pem" diff --git a/test/tests-stm32fx/app-f3.toml b/test/tests-stm32fx/app-f3.toml index 55bd3abb9..6acff2ef8 100644 --- a/test/tests-stm32fx/app-f3.toml +++ b/test/tests-stm32fx/app-f3.toml @@ -44,9 +44,18 @@ max-sizes = {flash = 4096, ram = 1024} stacksize = 1024 start = true +[tasks.hiffy] +name = "task-hiffy" +priority = 3 +features = ["testsuite"] +max-sizes = {flash = 32768, ram = 32768 } +stacksize = 2048 +start = true +task-slots = ["suite", "runner"] + [tasks.idle] name = "task-idle" -priority = 3 +priority = 4 max-sizes = {flash = 256, ram = 256} stacksize = 256 start = true diff --git a/test/tests-stm32fx/app.toml b/test/tests-stm32fx/app.toml index 523c9f84e..7a6f74386 100644 --- a/test/tests-stm32fx/app.toml +++ b/test/tests-stm32fx/app.toml @@ -45,9 +45,18 @@ max-sizes = {flash = 4096, ram = 1024} stacksize = 1024 start = true +[tasks.hiffy] +name = "task-hiffy" +priority = 3 +features = ["testsuite"] +max-sizes = {flash = 32768, ram = 32768 } +stacksize = 2048 +start = true +task-slots = ["suite", "runner"] + [tasks.idle] name = "task-idle" -priority = 3 +priority = 4 max-sizes = {flash = 256, ram = 256} stacksize = 256 start = true diff --git a/test/tests-stm32g0/README.mkdn b/test/tests-stm32g0/README.mkdn deleted file mode 100644 index c0de574f7..000000000 --- a/test/tests-stm32g0/README.mkdn +++ /dev/null @@ -1,14 +0,0 @@ -# STM32G0 System Tests - -This tests the kernel (not drivers!) on the STM32G0, to exercise Cortex-M0 -support. - -Cortex-M0 parts don't have ITM, so the conventional `humility test` won't work. -Instead, you want to run this under OpenOCD with semihosting enabled. The -easiest way to do this is by - -- Run `cargo xtask humility test/tests-stm32g0/app-g070.toml -- openocd` -- In a separate terminal, run `cargo xtask gdb test/tests-stm32g0/app-g070.toml` -- Delete breakpoints in GDB: `d` -- Run: `c` -- The output should appear in the OpenOCD terminal diff --git a/test/tests-stm32g0/app-g070.toml b/test/tests-stm32g0/app-g070.toml index d1b32f476..e4a048767 100644 --- a/test/tests-stm32g0/app-g070.toml +++ b/test/tests-stm32g0/app-g070.toml @@ -6,24 +6,22 @@ memory = "memory-g070.toml" [kernel] name = "demo-stm32g0-nucleo" -requires = {flash = 17344, ram = 2808} +requires = {flash = 17544, ram = 2828} features = ["g070"] stacksize = 2048 [tasks.runner] name = "test-runner" priority = 0 -max-sizes = {flash = 16384, ram = 2048} +max-sizes = {flash = 16384, ram = 4096} start = true -features = ["semihosting"] stacksize = 1904 [tasks.suite] name = "test-suite" priority = 2 -max-sizes = {flash = 65536, ram = 2048} +max-sizes = {flash = 65536, ram = 4096} start = true -features = ["semihosting"] task-slots = ["assist", "idol", "suite", "runner"] stacksize = 1504 @@ -39,7 +37,6 @@ name = "test-assist" priority = 1 max-sizes = {flash = 16384, ram = 2048} start = true -features = ["semihosting"] stacksize = 1504 [tasks.idol] @@ -49,9 +46,18 @@ max-sizes = {flash = 4096, ram = 1024} stacksize = 1024 start = true +[tasks.hiffy] +name = "task-hiffy" +priority = 3 +features = ["testsuite"] +max-sizes = {flash = 32768, ram = 32768 } +stacksize = 2048 +start = true +task-slots = ["suite", "runner"] + [tasks.idle] name = "task-idle" -priority = 3 +priority = 4 max-sizes = {flash = 128, ram = 64} stacksize = 64 start = true diff --git a/test/tests-stm32h7/app-h743.toml b/test/tests-stm32h7/app-h743.toml index 44743e9fb..dc5b7e733 100644 --- a/test/tests-stm32h7/app-h743.toml +++ b/test/tests-stm32h7/app-h743.toml @@ -45,9 +45,18 @@ max-sizes = {flash = 4096, ram = 1024} stacksize = 1024 start = true +[tasks.hiffy] +name = "task-hiffy" +priority = 3 +features = ["testsuite"] +max-sizes = {flash = 32768, ram = 32768 } +stacksize = 2048 +start = true +task-slots = ["suite", "runner"] + [tasks.idle] name = "task-idle" -priority = 3 +priority = 4 max-sizes = {flash = 256, ram = 256} stacksize = 256 start = true diff --git a/test/tests-stm32h7/app-h753.toml b/test/tests-stm32h7/app-h753.toml index 212edacdf..070c74d09 100644 --- a/test/tests-stm32h7/app-h753.toml +++ b/test/tests-stm32h7/app-h753.toml @@ -45,9 +45,18 @@ max-sizes = {flash = 4096, ram = 1024} stacksize = 1024 start = true +[tasks.hiffy] +name = "task-hiffy" +priority = 3 +features = ["testsuite"] +max-sizes = {flash = 32768, ram = 32768 } +stacksize = 2048 +start = true +task-slots = ["suite", "runner"] + [tasks.idle] name = "task-idle" -priority = 3 +priority = 4 max-sizes = {flash = 256, ram = 256} stacksize = 256 start = true