blob: ed22e2833d87431c6018c5630afbbb3504cc1f47 [file] [log] [blame]
// Copyright 2025 The Pigweed Authors
//
// Licensed under the Apache License, Version 2.0 (the "License"); you may not
// use this file except in compliance with the License. You may obtain a copy of
// the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations under
// the License.
#![no_std]
use core::ptr::addr_of_mut;
use intrusive_collections::{intrusive_adapter, LinkedList, LinkedListLink};
pub use pw_bytes;
intrusive_adapter!(pub TestDescAndFnAdapter<'a> = &'a TestDescAndFn: TestDescAndFn { link: LinkedListLink });
static mut TEST_LIST: Option<LinkedList<TestDescAndFnAdapter>> = None;
// All accesses to test list go through this function. This gives us a
// single point of ownership of TEST_LIST and keeps us from leaking references
// to it.
fn access_test_list<F>(callback: F)
where
F: FnOnce(&mut LinkedList<TestDescAndFnAdapter>),
{
// Safety: Tests are single threaded for now. This assumption needs to be
// revisited.
let test_list: &mut Option<LinkedList<TestDescAndFnAdapter>> =
unsafe { addr_of_mut!(TEST_LIST).as_mut().unwrap_unchecked() };
let list = test_list.get_or_insert_with(|| LinkedList::new(TestDescAndFnAdapter::new()));
callback(list)
}
pub fn add_test(test: &'static mut TestDescAndFn) {
access_test_list(|test_list| test_list.push_back(test))
}
pub fn for_each_test<F>(mut callback: F)
where
F: FnMut(&TestDescAndFn),
{
access_test_list(|test_list| {
for test in test_list.iter() {
callback(test);
}
});
}
pub struct TestError {
pub file: &'static str,
pub line: u32,
pub message: &'static str,
}
pub type Result<T> = core::result::Result<T, TestError>;
pub enum TestFn {
StaticTestFn(fn() -> Result<()>),
}
pub struct TestDesc {
pub name: &'static str,
}
pub struct TestDescAndFn {
pub desc: TestDesc,
pub test_fn: TestFn,
pub link: LinkedListLink,
}
impl TestDescAndFn {
pub const fn new(desc: TestDesc, test_fn: TestFn) -> Self {
Self {
desc,
test_fn,
link: LinkedListLink::new(),
}
}
}
// We're marking these as send and sync so that we can declare statics with.
// them. They're not actually Send and Sync because they contain linked list
// pointers but in practice tests are single threaded and these are never sent
// between threads.
//
// A better pattern here must be worked out with intrusive lists of static data
// (for statically declared threads for instance) so we'll revisit this later.
unsafe impl Send for TestDescAndFn {}
unsafe impl Sync for TestDescAndFn {}
#[macro_export]
macro_rules! assert_eq {
($a:expr, $b:expr) => {
if $a != $b {
return Err(unittest::TestError {
file: file!(),
line: line!(),
message: unittest::pw_bytes::concat_static_strs!(
"assert_eq!(",
stringify!($a),
", ",
stringify!($b),
") failed"
),
});
}
};
}
#[macro_export]
macro_rules! assert_true {
($a:expr) => {
if !$a {
return Err(unittest::TestError {
file: file!(),
line: line!(),
message: unittest::pw_bytes::concat_static_strs!(
"assert_true!(",
stringify!($a),
") failed"
),
});
}
};
}
#[macro_export]
macro_rules! assert_false {
($a:expr) => {
if $a {
return Err(unittest::TestError {
file: file!(),
line: line!(),
message: unittest::pw_bytes::concat_static_strs!(
"assert_false!(",
stringify!($a),
") failed"
),
});
}
};
}