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