kernel/testing/
mod.rs

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    // The entry point
17    ($($(#[$($attr:tt)+])* fn $name:ident() $body:block)*) => {
18        $($crate::testing::test!(@meta_chain $([$($attr)+])* => [] {false, false} fn $name() $body);)*
19    };
20    // any other entrypoints are errors
21    ($(other:tt)*) => {
22        compile_error!("Invalid test syntax");
23    };
24    // The final chain, if we don't have any more `meta` attributes, we build the thing
25    (@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    // If we have meta `should_panic` or `ignore`, we modify the variable we are using
33    (@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    // Any other attributes are passed as is
56    (@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 construction
69    (@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}