1#[cfg(test)]
2use crate::hw::qemu;
3
4#[cfg(test)]
5pub struct TestCase {
6 pub name: &'static str,
7 pub ignore: bool,
8 pub source: &'static str,
9 pub line: u32,
10 pub should_panic: bool,
11 pub test_fn: &'static dyn Fn(),
12}
13
14#[macro_export]
15macro_rules! test {
16 ($($(#[$($attr:tt)+])* fn $name:ident() $body:block)*) => {
18 $($crate::testing::test!(@meta_chain $([$($attr)+])* => [] {false, false} fn $name() $body);)*
19 };
20 ($(other:tt)*) => {
22 compile_error!("Invalid test syntax");
23 };
24 (@meta_chain
26 => [$($builtmeta:tt)*]
27 {$should_panic:expr, $ignore:expr}
28 fn $name:ident() $body:block
29 ) => {
30 $crate::testing::test!(@final [$($builtmeta)*] {$should_panic, $ignore} fn $name() $body);
31 };
32 (@meta_chain
34 [should_panic] $([$($rest:tt)+])* => [$($builtmeta:tt)*]
35 {$should_panic:expr, $ignore:expr}
36 fn $name:ident() $body:block
37 ) => {
38 $crate::testing::test!(@meta_chain $([$($rest)+])* =>
39 [
40 $($builtmeta)*
41 ]
42 {true, $ignore} fn $name() $body);
43 };
44 (@meta_chain
45 [ignore] $([$($rest:tt)+])* => [$($builtmeta:tt)*]
46 {$should_panic:expr, $ignore:expr}
47 fn $name:ident() $body:block
48 ) => {
49 $crate::testing::test!(@meta_chain $([$($rest)+])* =>
50 [
51 $($builtmeta)*
52 ]
53 {$should_panic, true} fn $name() $body);
54 };
55 (@meta_chain
57 [$($first:tt)+] $([$($rest:tt)+])* => [$($builtmeta:tt)*]
58 {$should_panic:expr, $ignore:expr}
59 fn $name:ident() $body:block
60 ) => {
61 $crate::testing::test!(@meta_chain $([$($rest)+])* =>
62 [
63 #[$($first)+]
64 $($builtmeta)*
65 ]
66 {$should_panic, $ignore} fn $name() $body);
67 };
68 (@final
70 [$($builtmeta:tt)*]
71 {$should_panic:expr, $ignore:expr}
72 fn $name:ident() $body:block
73 ) => {
74 #[cfg(test)]
75 $($builtmeta)*
76 fn $name() $body
77 #[cfg(test)]
78 #[test_case]
79 #[allow(non_upper_case_globals)]
80 const $name: $crate::testing::TestCase = $crate::testing::TestCase {
81 name: concat!(module_path!(), "::", stringify!($name)),
82 ignore: $ignore,
83 source: file!(),
84 line: line!(),
85 should_panic: $should_panic,
86 test_fn: &$name,
87 };
88 };
89}
90
91pub use test;
92
93#[cfg(test)]
94pub fn test_runner(tests: &[&TestCase]) {
95 use alloc::{string::String, vec::Vec};
96
97 use crate::{io::console, panic_handler};
98
99 println!("Running {} tests", tests.len());
100
101 let mut passed = 0;
102 let mut failed = 0;
103 let mut ignored = 0;
104
105 let mut failed_buffers = Vec::new();
106
107 for test in tests {
108 print!("test {} ... ", test.name);
109 if test.ignore {
110 println!("IGNORED");
111 ignored += 1;
112 continue;
113 }
114
115 assert!(console::start_capture().is_none());
116
117 let r = panic_handler::catch_unwind(|| (test.test_fn)());
118
119 let buffer = console::stop_capture().unwrap();
120
121 if r.is_ok() {
122 if test.should_panic {
123 failed += 1;
124 println!("FAILED (should_panic)");
125 } else {
126 passed += 1;
127 println!("OK");
128 }
129 } else if test.should_panic {
130 passed += 1;
131 println!("OK");
132 } else {
133 failed += 1;
134 println!("FAILED");
135
136 failed_buffers.push((test.name, buffer));
137 }
138 }
139
140 if !failed_buffers.is_empty() {
141 println!("\n\nfailures:\n");
142
143 for (name, panic) in failed_buffers {
144 println!("--- {name} ---\n{panic}\n");
145 }
146
147 println!();
148 }
149
150 println!("{} passed; {} failed; {} ignored", passed, failed, ignored);
151
152 if failed > 0 {
153 qemu::exit(qemu::ExitStatus::Failure);
154 } else {
155 qemu::exit(qemu::ExitStatus::Success);
156 }
157}