Next Generation WASM Microkernel Operating System
1// Copyright 2025 Jonas Kruckenberg
2//
3// Licensed under the Apache License, Version 2.0, <LICENSE-APACHE or
4// http://apache.org/licenses/LICENSE-2.0> or the MIT license <LICENSE-MIT or
5// http://opensource.org/licenses/MIT>, at your option. This file may not be
6// copied, modified, or distributed except according to those terms.
7
8mod args;
9mod printer;
10mod smoke;
11mod spectest;
12mod wast;
13
14use alloc::boxed::Box;
15use alloc::sync::Arc;
16use core::any::Any;
17use core::ptr::addr_of;
18use core::sync::atomic::{AtomicU64, Ordering};
19use core::{hint, slice};
20
21use futures::FutureExt;
22use futures::future::try_join_all;
23use ktest::Test;
24
25use crate::tests::args::Arguments;
26use crate::tests::printer::Printer;
27use crate::{arch, state};
28
29/// The outcome of performing a single test.
30pub enum Outcome {
31 /// The test passed.
32 Passed,
33 /// The test failed.
34 Failed(Box<dyn Any + Send + 'static>),
35 /// The test was ignored.
36 Ignored,
37}
38
39/// Conclusion of running the whole test suite
40pub struct Conclusion {
41 /// Number of tests and benchmarks that were filtered out (either by the
42 /// filter-in pattern or by `--skip` arguments).
43 pub num_filtered_out: AtomicU64,
44
45 /// Number of passed tests.
46 pub num_passed: AtomicU64,
47
48 /// Number of failed tests and benchmarks.
49 pub num_failed: AtomicU64,
50
51 /// Number of ignored tests and benchmarks.
52 pub num_ignored: AtomicU64,
53
54 /// Number of benchmarks that successfully ran.
55 pub num_measured: AtomicU64,
56}
57
58impl Conclusion {
59 /// Exits the application with error code 101 if there were any failures.
60 /// Otherwise, returns normally. **This will not run any destructors.**
61 /// Consider using [`Self::exit_code`] instead for a proper program cleanup.
62 pub fn exit_if_failed(&self) {
63 if self.has_failed() {
64 arch::exit(101)
65 }
66 }
67
68 /// Returns whether there have been any failures.
69 pub fn has_failed(&self) -> bool {
70 self.num_failed.load(Ordering::Acquire) > 0
71 }
72
73 fn empty() -> Self {
74 Self {
75 num_filtered_out: AtomicU64::new(0),
76 num_passed: AtomicU64::new(0),
77 num_failed: AtomicU64::new(0),
78 num_ignored: AtomicU64::new(0),
79 num_measured: AtomicU64::new(0),
80 }
81 }
82}
83
84pub async fn run_tests(global: &'static state::Global) -> Conclusion {
85 let chosen = global.device_tree.find_by_path("/chosen").unwrap();
86 let args = if let Some(prop) = chosen.property("bootargs") {
87 let str = prop.as_str().unwrap();
88 Arguments::from_str(str)
89 } else {
90 Arguments::default()
91 };
92
93 let tests = all_tests();
94
95 // Create printer which is used for all output.
96 let printer = Printer::new(args.format);
97
98 // If `--list` is specified, just print the list and return.
99 if args.list {
100 printer.print_list(tests, args.ignored);
101 return Conclusion::empty();
102 }
103
104 // Print number of tests
105 printer.print_title(tests.len() as u64);
106
107 let printer = Arc::new(printer);
108 let conclusion = Arc::new(Conclusion::empty());
109
110 let tests = tests.iter().map(|test| {
111 if args.is_ignored(test) {
112 printer.print_test(&test.info);
113 conclusion.num_ignored.fetch_add(1, Ordering::Release);
114 futures::future::Either::Left(core::future::ready(Ok(())))
115 } else {
116 let printer = printer.clone();
117 let conclusion = conclusion.clone();
118
119 let h = global
120 .executor
121 .try_spawn(async move {
122 // Print `test foo ...`, run the test, then print the outcome in
123 // the same line.
124 printer.print_test(&test.info);
125 (test.run)().await;
126 })
127 .unwrap()
128 .inspect(move |res| match res {
129 Ok(_) => {
130 conclusion.num_passed.fetch_add(1, Ordering::Release);
131 }
132 Err(_) => {
133 conclusion.num_failed.fetch_add(1, Ordering::Release);
134 }
135 });
136
137 futures::future::Either::Right(h)
138 }
139 });
140
141 // we handle test failures through `Conclusion` so it is safe to ignore the result here
142 let _ = try_join_all(tests).await;
143
144 printer.print_summary(&conclusion);
145
146 Arc::into_inner(conclusion).unwrap()
147}
148
149pub fn all_tests() -> &'static [Test] {
150 #[used(linker)]
151 #[unsafe(link_section = "k23_tests")]
152 static mut LINKME_PLEASE: [Test; 0] = [];
153
154 unsafe extern "C" {
155 #[allow(improper_ctypes)]
156 static __start_k23_tests: Test;
157 #[allow(improper_ctypes)]
158 static __stop_k23_tests: Test;
159 }
160
161 let start = addr_of!(__start_k23_tests);
162 let stop = addr_of!(__stop_k23_tests);
163
164 let stride = size_of::<Test>();
165 let byte_offset = stop as usize - start as usize;
166 let len = match byte_offset.checked_div(stride) {
167 Some(len) => len,
168 // The #[distributed_slice] call checks `size_of::<T>() > 0` before
169 // using the unsafe `private_new`.
170 None => unsafe { hint::unreachable_unchecked() },
171 };
172
173 unsafe { slice::from_raw_parts(start, len) }
174}