1 // Copyright 2025 The Pigweed Authors
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License"); you may not
4 // use this file except in compliance with the License. You may obtain a copy of
5 // the License at
6 //
7 // https://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11 // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12 // License for the specific language governing permissions and limitations under
13 // the License.
14
15 #![no_std]
16
17 use core::ptr::addr_of_mut;
18 use intrusive_collections::{intrusive_adapter, LinkedList, LinkedListLink};
19
20 pub use pw_bytes;
21
22 intrusive_adapter!(pub TestDescAndFnAdapter<'a> = &'a TestDescAndFn: TestDescAndFn { link: LinkedListLink });
23
24 static mut TEST_LIST: Option<LinkedList<TestDescAndFnAdapter>> = None;
25
26 // All accesses to test list go through this function. This gives us a
27 // single point of ownership of TEST_LIST and keeps us from leaking references
28 // to it.
access_test_list<F>(callback: F) where F: FnOnce(&mut LinkedList<TestDescAndFnAdapter>),29 fn access_test_list<F>(callback: F)
30 where
31 F: FnOnce(&mut LinkedList<TestDescAndFnAdapter>),
32 {
33 // Safety: Tests are single threaded for now. This assumption needs to be
34 // revisited.
35 let test_list: &mut Option<LinkedList<TestDescAndFnAdapter>> =
36 unsafe { addr_of_mut!(TEST_LIST).as_mut().unwrap_unchecked() };
37 let list = test_list.get_or_insert_with(|| LinkedList::new(TestDescAndFnAdapter::new()));
38 callback(list)
39 }
40
add_test(test: &'static mut TestDescAndFn)41 pub fn add_test(test: &'static mut TestDescAndFn) {
42 access_test_list(|test_list| test_list.push_back(test))
43 }
44
for_each_test<F>(mut callback: F) where F: FnMut(&TestDescAndFn),45 pub fn for_each_test<F>(mut callback: F)
46 where
47 F: FnMut(&TestDescAndFn),
48 {
49 access_test_list(|test_list| {
50 for test in test_list.iter() {
51 callback(test);
52 }
53 });
54 }
55
56 pub struct TestError {
57 pub file: &'static str,
58 pub line: u32,
59 pub message: &'static str,
60 }
61
62 pub type Result<T> = core::result::Result<T, TestError>;
63
64 pub enum TestFn {
65 StaticTestFn(fn() -> Result<()>),
66 }
67
68 pub struct TestDesc {
69 pub name: &'static str,
70 }
71
72 pub struct TestDescAndFn {
73 pub desc: TestDesc,
74 pub test_fn: TestFn,
75 pub link: LinkedListLink,
76 }
77
78 impl TestDescAndFn {
new(desc: TestDesc, test_fn: TestFn) -> Self79 pub const fn new(desc: TestDesc, test_fn: TestFn) -> Self {
80 Self {
81 desc,
82 test_fn,
83 link: LinkedListLink::new(),
84 }
85 }
86 }
87
88 // We're marking these as send and sync so that we can declare statics with.
89 // them. They're not actually Send and Sync because they contain linked list
90 // pointers but in practice tests are single threaded and these are never sent
91 // between threads.
92 //
93 // A better pattern here must be worked out with intrusive lists of static data
94 // (for statically declared threads for instance) so we'll revisit this later.
95 unsafe impl Send for TestDescAndFn {}
96 unsafe impl Sync for TestDescAndFn {}
97
98 #[macro_export]
99 macro_rules! assert_eq {
100 ($a:expr, $b:expr) => {
101 if $a != $b {
102 return Err(unittest::TestError {
103 file: file!(),
104 line: line!(),
105 message: unittest::pw_bytes::concat_static_strs!(
106 "assert_eq!(",
107 stringify!($a),
108 ", ",
109 stringify!($b),
110 ") failed"
111 ),
112 });
113 }
114 };
115 }
116
117 #[macro_export]
118 macro_rules! assert_ne {
119 ($a:expr, $b:expr) => {
120 if $a == $b {
121 return Err(unittest::TestError {
122 file: file!(),
123 line: line!(),
124 message: unittest::pw_bytes::concat_static_strs!(
125 "assert_ne!(",
126 stringify!($a),
127 ", ",
128 stringify!($b),
129 ") failed"
130 ),
131 });
132 }
133 };
134 }
135
136 #[macro_export]
137 macro_rules! assert_true {
138 ($a:expr) => {
139 if !$a {
140 return Err(unittest::TestError {
141 file: file!(),
142 line: line!(),
143 message: unittest::pw_bytes::concat_static_strs!(
144 "assert_true!(",
145 stringify!($a),
146 ") failed"
147 ),
148 });
149 }
150 };
151 }
152
153 #[macro_export]
154 macro_rules! assert_false {
155 ($a:expr) => {
156 if $a {
157 return Err(unittest::TestError {
158 file: file!(),
159 line: line!(),
160 message: unittest::pw_bytes::concat_static_strs!(
161 "assert_false!(",
162 stringify!($a),
163 ") failed"
164 ),
165 });
166 }
167 };
168 }
169