diff --git a/Cargo.lock b/Cargo.lock index 844fa608e..19c96e690 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -842,6 +842,14 @@ version = "0.1.0" [[package]] name = "ktest" version = "0.1.0" +dependencies = [ + "ktest-proc-macro", + "owo-colors", +] + +[[package]] +name = "ktest-proc-macro" +version = "0.1.0" dependencies = [ "proc-macro2", "quote", @@ -1018,6 +1026,12 @@ version = "1.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" +[[package]] +name = "owo-colors" +version = "3.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1b04fb49957986fdce4d6ee7a65027d55d4b6d2265e5848bbb507b58ccfdb6f" + [[package]] name = "paste" version = "1.0.14" diff --git a/README.md b/README.md index c5b38d888..770d666c9 100644 --- a/README.md +++ b/README.md @@ -58,7 +58,7 @@ make run ### Unit Test -#### User mode unit test +#### User mode unit tests Many of our crates does not require running on bare metal environment and can be tested through the standard Cargo testing framework. A specific list of which crates can be tested with `cargo test` is listed in the `[workspace.metadata.usermode_testable]` entry in the `Cargo.toml` file of the root workspace. @@ -69,7 +69,7 @@ make test Nevertheless, you could enter the directory of a specific crate and invoke `cargo test` to perform user mode unit tests and doctests. -#### Kernel mode unit test +#### Kernel mode unit tests We can run unit tests in kernel mode for crates like `jinux-frame` or `jinux-std`. This is powered by our [ktest](framework/libs/ktest) framework. ```bash diff --git a/framework/jinux-frame/src/arch/x86/qemu.rs b/framework/jinux-frame/src/arch/x86/qemu.rs index 609bd0f84..5d8fd7bfe 100644 --- a/framework/jinux-frame/src/arch/x86/qemu.rs +++ b/framework/jinux-frame/src/arch/x86/qemu.rs @@ -1,4 +1,4 @@ -//! QEMU isa debug device. +//! Providing the ability to exit QEMU and return a value as debug result. /// The exit code of x86 QEMU isa debug device. In `qemu-system-x86_64` the /// exit code will be `(code << 1) | 1`. So you could never let QEMU invoke @@ -12,11 +12,18 @@ pub enum QemuExitCode { Failed = 0x20, } +/// Exit QEMU with the given exit code. +/// +/// This function assumes that the kernel is run in QEMU with the following +/// QEMU command line arguments that specifies the ISA debug exit device: +/// `-device isa-debug-exit,iobase=0xf4,iosize=0x04`. pub fn exit_qemu(exit_code: QemuExitCode) -> ! { use x86_64::instructions::port::Port; + let mut port = Port::new(0xf4); + // Safety: The write to the ISA debug exit port is safe and `0xf4` should + // be the port number. unsafe { - let mut port = Port::new(0xf4); port.write(exit_code as u32); } unreachable!() diff --git a/framework/jinux-frame/src/boot/mod.rs b/framework/jinux-frame/src/boot/mod.rs index 8c766d039..674c7dd4a 100644 --- a/framework/jinux-frame/src/boot/mod.rs +++ b/framework/jinux-frame/src/boot/mod.rs @@ -3,9 +3,10 @@ //! pub mod kcmdline; +pub mod memory_region; + use kcmdline::KCmdlineArg; -pub mod memory_region; use self::memory_region::MemoryRegion; use alloc::{string::String, vec::Vec}; @@ -116,7 +117,16 @@ pub fn call_jinux_main() -> ! { } #[cfg(ktest)] { + use crate::arch::qemu::{exit_qemu, QemuExitCode}; + use alloc::boxed::Box; + use core::any::Any; crate::init(); - ktest::do_ktests!(); + let fn_catch_unwind = &(unwinding::panic::catch_unwind::<(), fn()> + as fn(fn()) -> Result<(), Box<(dyn Any + Send + 'static)>>); + use ktest::runner::{run_ktests, KtestResult}; + match run_ktests(crate::console::print, fn_catch_unwind) { + KtestResult::Ok => exit_qemu(QemuExitCode::Success), + KtestResult::Failed => exit_qemu(QemuExitCode::Failed), + } } } diff --git a/framework/jinux-frame/src/lib.rs b/framework/jinux-frame/src/lib.rs index 8a32caed6..7174b2a07 100644 --- a/framework/jinux-frame/src/lib.rs +++ b/framework/jinux-frame/src/lib.rs @@ -82,7 +82,7 @@ fn invoke_ffi_init_funcs() { } } -/// Unit test for the ktest framework and functions of the frame. +/// Simple unit tests for the ktest framework. #[if_cfg_ktest] mod test { #[ktest] @@ -95,4 +95,10 @@ mod test { fn failing_assertion() { assert_eq!(0, 1); } + + #[ktest] + #[should_panic(expected = "expected panic message")] + fn expect_panic() { + panic!("expected panic message"); + } } diff --git a/framework/jinux-frame/src/panicking.rs b/framework/jinux-frame/src/panicking.rs index 527c7db26..ec4148278 100644 --- a/framework/jinux-frame/src/panicking.rs +++ b/framework/jinux-frame/src/panicking.rs @@ -1,29 +1,13 @@ -//! Panic support in Jinux Frame. +//! Panic support. -use alloc::boxed::Box; -use alloc::string::{String, ToString}; +use alloc::{boxed::Box, string::ToString}; use crate::arch::qemu::{exit_qemu, QemuExitCode}; use crate::println; -#[derive(Clone, Debug)] -pub struct PanicInfo { - pub message: String, - pub file: String, - pub line: usize, - pub col: usize, -} - -impl core::fmt::Display for PanicInfo { - fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { - writeln!(f, "Panicked at {}:{}:{}", self.file, self.line, self.col)?; - writeln!(f, "{}", self.message) - } -} - #[panic_handler] -pub fn panic_handler(info: &core::panic::PanicInfo) -> ! { - let throw_info = PanicInfo { +fn panic_handler(info: &core::panic::PanicInfo) -> ! { + let throw_info = ktest::PanicInfo { message: info.message().unwrap().to_string(), file: info.location().unwrap().file().to_string(), line: info.location().unwrap().line() as usize, diff --git a/framework/libs/ktest-proc-macro/Cargo.toml b/framework/libs/ktest-proc-macro/Cargo.toml new file mode 100644 index 000000000..91204c934 --- /dev/null +++ b/framework/libs/ktest-proc-macro/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "ktest-proc-macro" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[lib] +proc-macro = true + +[dependencies] +proc-macro2 = "1.0.69" +quote = "1.0.33" +rand = "0.8.5" +syn = { version = "2.0.29", features = ["full"] } diff --git a/framework/libs/ktest-proc-macro/src/lib.rs b/framework/libs/ktest-proc-macro/src/lib.rs new file mode 100644 index 000000000..30fb919e9 --- /dev/null +++ b/framework/libs/ktest-proc-macro/src/lib.rs @@ -0,0 +1,128 @@ +#![feature(proc_macro_span)] + +extern crate proc_macro2; + +use proc_macro::TokenStream; +use quote::quote; +use rand::{distributions::Alphanumeric, Rng}; +use syn::{parse_macro_input, Expr, Ident, ItemFn, ItemMod}; + +/// The conditional compilation attribute macro to control the compilation of test +/// modules. +#[proc_macro_attribute] +pub fn if_cfg_ktest(_attr: TokenStream, item: TokenStream) -> TokenStream { + // Assuming that the item is a module declearation, otherwise panics. + let input = parse_macro_input!(item as ItemMod); + + let crate_name = std::env::var("CARGO_PKG_NAME").unwrap(); + + let output = quote! { + #[cfg(all(ktest, any(ktest = "all", ktest = #crate_name)))] + #input + }; + + TokenStream::from(output) +} + +/// The test attribute macro to mark a test function. +#[proc_macro_attribute] +pub fn ktest(_attr: TokenStream, item: TokenStream) -> TokenStream { + // Assuming that the item has type `fn() -> ()`, otherwise panics. + let input = parse_macro_input!(item as ItemFn); + assert!( + input.sig.inputs.is_empty(), + "ktest function should have no arguments" + ); + assert!( + matches!(input.sig.output, syn::ReturnType::Default), + "ktest function should return `()`" + ); + + // Generate a random identifier to avoid name conflicts. + let fn_id: String = rand::thread_rng() + .sample_iter(&Alphanumeric) + .take(8) + .map(char::from) + .collect(); + + let fn_name = &input.sig.ident; + let fn_ktest_item_name = Ident::new( + &format!("{}_ktest_item_{}", &input.sig.ident, &fn_id), + proc_macro2::Span::call_site(), + ); + + let is_should_panic_attr = |attr: &&syn::Attribute| { + attr.path() + .segments + .iter() + .any(|segment| segment.ident == "should_panic") + }; + let mut attr_iter = input.attrs.iter(); + let should_panic = attr_iter.find(is_should_panic_attr); + let (should_panic, expectation) = match should_panic { + Some(attr) => { + assert!( + !attr_iter.any(|attr: &syn::Attribute| is_should_panic_attr(&attr)), + "multiple `should_panic` attributes" + ); + match &attr.meta { + syn::Meta::List(l) => { + let arg_err_message = "`should_panic` attribute should only have zero or one `expected` argument, with the format of `expected = \"\"`"; + let expected_assign = + syn::parse2::(l.tokens.clone()).expect(arg_err_message); + let Expr::Lit(s) = *expected_assign.right else { + panic!("{}", arg_err_message); + }; + let syn::Lit::Str(expectation) = s.lit else { + panic!("{}", arg_err_message); + }; + (true, Some(expectation)) + } + _ => (true, None), + } + } + None => (false, None), + }; + let expectation_tokens = if let Some(s) = expectation { + quote! { + Some(#s) + } + } else { + quote! { + None + } + }; + + let package_name = std::env::var("CARGO_PKG_NAME").unwrap(); + let span = proc_macro::Span::call_site(); + let source = span.source_file().path(); + let source = source.to_str().unwrap(); + let line = span.line(); + let col = span.column(); + + let register_ktest_item = quote! { + #[cfg(ktest)] + #[used] + #[link_section = ".ktest_array"] + static #fn_ktest_item_name: ktest::KtestItem = ktest::KtestItem::new( + #fn_name, + (#should_panic, #expectation_tokens), + ktest::KtestItemInfo { + module_path: module_path!(), + fn_name: stringify!(#fn_name), + package: #package_name, + source: #source, + line: #line, + col: #col, + }, + ); + }; + + let output = quote! { + #input + + #register_ktest_item + }; + + TokenStream::from(output) +} diff --git a/framework/libs/ktest/Cargo.toml b/framework/libs/ktest/Cargo.toml index f14cfac80..44e5ffb54 100644 --- a/framework/libs/ktest/Cargo.toml +++ b/framework/libs/ktest/Cargo.toml @@ -5,11 +5,6 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html -[lib] -proc-macro = true - [dependencies] -proc-macro2 = "1.0.69" -quote = "1.0.33" -rand = "0.8.5" -syn = { version = "2.0.29", features = ["full"] } +owo-colors = "3.5.0" +ktest-proc-macro = { path = "../ktest-proc-macro" } diff --git a/framework/libs/ktest/src/lib.rs b/framework/libs/ktest/src/lib.rs index 47ac74303..17f7011ea 100644 --- a/framework/libs/ktest/src/lib.rs +++ b/framework/libs/ktest/src/lib.rs @@ -1,12 +1,17 @@ //! # The kernel mode testing framework of Jinux. //! //! `ktest` stands for kernel-mode testing framework. Its goal is to provide a -//! `cargo test`-like experience for any crates that depends on jinux-frame. +//! `cargo test`-like experience for any `#![no_std]` bare metal crates. //! -//! All the tests written in the source tree of the crates will be run using the -//! `do_ktests!()` macro immediately after the initialization of jinux-frame. -//! Thus you can use any feature provided by the frame including the heap -//! allocator, etc. +//! In Jinux, all the tests written in the source tree of the crates will be run +//! immediately after the initialization of jinux-frame. Thus you can use any +//! feature provided by the frame including the heap allocator, etc. +//! +//! By all means, ktest is an individule crate that only requires: +//! - a custom linker script section `.ktest_array`, +//! - and an alloc implementation. +//! to work. And the frame happens to provide both of them. Thus, any crates depending +//! on the frame can use ktest without any extra dependency. //! //! ## Usage //! @@ -26,6 +31,11 @@ //! fn failing_assertion() { //! assert_eq!(0, 1); //! } +//! #[ktest] +//! #[should_panic(expected = "expected panic message")] +//! fn expect_panic() { +//! panic!("expected panic message"); +//! } //! } //! ``` //! @@ -59,198 +69,153 @@ //! a default conditional compilation setting: //! `#[cfg(all(ktest, any(ktest = "all", ktest = #crate_name)))]` //! -//! We do not support `#[should_panic]` attribute, but the implementation is quite -//! slow currently. Use it with cautious. +//! We support the `#[should_panic]` attribute just in the same way as the standard +//! library do, but the implementation is quite slow currently. Use it with cautious. //! //! Doctest is not taken into consideration yet, and the interface is subject to //! change. //! -//! ## How it works -//! -//! The `ktest` framework is implemented using the procedural macro feature of Rust. -//! The `ktest` attribute macro will generate a static fn pointer variable linked in -//! the `.ktest_array` section. The `do_ktests!()` macro will iterate over all the -//! static variables in the section and run the tests. -//! -#![feature(proc_macro_span)] +#![no_std] +#![feature(panic_info_message)] -extern crate proc_macro2; +pub mod runner; -use proc_macro::TokenStream; -use quote::quote; -use rand::{distributions::Alphanumeric, Rng}; -use syn::{parse_macro_input, Ident, ItemFn, ItemMod}; +extern crate alloc; +use alloc::{boxed::Box, string::String}; +use core::result::Result; -/// The conditional compilation attribute macro to control the compilation of test -/// modules. -#[proc_macro_attribute] -pub fn if_cfg_ktest(_attr: TokenStream, item: TokenStream) -> TokenStream { - // Assuming that the item is a module declearation, otherwise panics. - let input = parse_macro_input!(item as ItemMod); +pub use ktest_proc_macro::{if_cfg_ktest, ktest}; - let crate_name = std::env::var("CARGO_PKG_NAME").unwrap(); - - let output = quote! { - #[cfg(all(ktest, any(ktest = "all", ktest = #crate_name)))] - #input - }; - - TokenStream::from(output) +#[derive(Clone, Debug)] +pub struct PanicInfo { + pub message: String, + pub file: String, + pub line: usize, + pub col: usize, } -/// The test attribute macro to mark a test function. -#[proc_macro_attribute] -pub fn ktest(_attr: TokenStream, item: TokenStream) -> TokenStream { - // Assuming that the item has type `fn() -> ()`, otherwise panics. - let input = parse_macro_input!(item as ItemFn); - assert!( - input.sig.inputs.is_empty(), - "ktest function should have no arguments" - ); - assert!( - matches!(input.sig.output, syn::ReturnType::Default), - "ktest function should return `()`" - ); +impl core::fmt::Display for PanicInfo { + fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { + writeln!(f, "Panicked at {}:{}:{}", self.file, self.line, self.col)?; + writeln!(f, "{}", self.message) + } +} - // Generate a random identifier to avoid name conflicts. - let fn_id: String = rand::thread_rng() - .sample_iter(&Alphanumeric) - .take(8) - .map(char::from) - .collect(); +#[derive(Clone)] +pub enum KtestError { + Panic(Box), + ShouldPanicButNoPanic, + ExpectedPanicNotMatch(&'static str, Box), + Unknown, +} - let fn_name = &input.sig.ident; - let fn_ktest_item_name = Ident::new( - &format!("{}_ktest_item_{}", &input.sig.ident, &fn_id), - proc_macro2::Span::call_site(), - ); +#[derive(Clone)] +pub struct KtestItemInfo { + pub module_path: &'static str, + pub fn_name: &'static str, + pub package: &'static str, + pub source: &'static str, + pub line: usize, + pub col: usize, +} - // Since Rust does not support unamed structures, we have to generate a - // unique name for each test item structure. - let ktest_item_struct = Ident::new( - &format!("KtestItem{}", &fn_id), - proc_macro2::Span::call_site(), - ); +#[derive(Clone)] +pub struct KtestItem { + fn_: fn() -> (), + should_panic: (bool, Option<&'static str>), + info: KtestItemInfo, +} - let should_panic = input.attrs.iter().any(|attr| { - attr.path() - .segments - .iter() - .any(|segment| segment.ident == "should_panic") - }); +type CatchUnwindImpl = fn(f: fn() -> ()) -> Result<(), Box>; - let package_name = std::env::var("CARGO_PKG_NAME").unwrap(); - let span = proc_macro::Span::call_site(); - let source = span.source_file().path(); - let source = source.to_str().unwrap(); - let line = span.line(); - let col = span.column(); - - let register = quote! { - struct #ktest_item_struct { - fn_: fn() -> (), - should_panic: bool, - module_path: &'static str, - fn_name: &'static str, - package: &'static str, - source: &'static str, - line: usize, - col: usize, +impl KtestItem { + pub const fn new( + fn_: fn() -> (), + should_panic: (bool, Option<&'static str>), + info: KtestItemInfo, + ) -> Self { + Self { + fn_, + should_panic, + info, } - #[cfg(ktest)] - #[used] - #[link_section = ".ktest_array"] - static #fn_ktest_item_name: #ktest_item_struct = #ktest_item_struct { - fn_: #fn_name, - should_panic: #should_panic, - module_path: module_path!(), - fn_name: stringify!(#fn_name), - package: #package_name, - source: #source, - line: #line, - col: #col, - }; - }; + } - let output = quote! { - #input + pub fn info(&self) -> &KtestItemInfo { + &self.info + } - #register - }; - - TokenStream::from(output) + /// Run the test with a given catch_unwind implementation. + pub fn run(&self, catch_unwind_impl: &CatchUnwindImpl) -> Result<(), KtestError> { + let test_result = catch_unwind_impl(self.fn_); + if !self.should_panic.0 { + // Should not panic. + match test_result { + Ok(()) => Ok(()), + Err(e) => match e.downcast::() { + Ok(s) => Err(KtestError::Panic(s)), + Err(_payload) => Err(KtestError::Unknown), + }, + } + } else { + // Should panic. + match test_result { + Ok(()) => Err(KtestError::ShouldPanicButNoPanic), + Err(e) => match e.downcast::() { + Ok(s) => { + if let Some(expected) = self.should_panic.1 { + if s.message == expected { + Ok(()) + } else { + Err(KtestError::ExpectedPanicNotMatch(expected, s)) + } + } else { + Ok(()) + } + } + Err(_payload) => Err(KtestError::Unknown), + }, + } + } + } } -/// The procedural macro to run all the tests. -#[proc_macro] -pub fn do_ktests(_item: TokenStream) -> TokenStream { - let body = quote! { - use crate::arch::qemu::{exit_qemu, QemuExitCode}; - - struct KtestItem { - fn_: fn() -> (), - should_panic: bool, - module_path: &'static str, - fn_name: &'static str, - package: &'static str, - source: &'static str, - line: usize, - col: usize, - }; +macro_rules! ktest_array { + () => {{ extern "C" { fn __ktest_array(); fn __ktest_array_end(); - }; - let item_size = core::mem::size_of::() as u64; - let l = (__ktest_array_end as u64 - __ktest_array as u64) / item_size; - crate::println!("Running {} tests", l); - for i in 0..l { - unsafe { - let item_ptr = (__ktest_array as u64 + item_size * i) as *const u64; - let item = item_ptr as *const KtestItem; - crate::print!("[{}] test {}::{} ...", (*item).package, (*item).module_path, (*item).fn_name); - let test_result = unwinding::panic::catch_unwind((*item).fn_); - let print_failure_heading = || { - crate::println!("\nfailures:\n"); - crate::println!("---- {}:{}:{} - {} ----", (*item).source, (*item).line, (*item).col, (*item).fn_name); - }; - if !(*item).should_panic { - match test_result { - Ok(()) => { - crate::println!(" ok"); - }, - Err(e) => { - crate::println!(" FAILED"); - print_failure_heading(); - match e.downcast::() { - Ok(s) => { - crate::println!("[caught panic] {}", s); - }, - Err(payload) => { - crate::println!("[caught panic] unknown panic payload: {:#?}", payload); - }, - } - exit_qemu(QemuExitCode::Failed); - }, - } - } else { - match test_result { - Ok(()) => { - crate::println!(" FAILED"); - print_failure_heading(); - crate::println!("test did not panic as expected"); - exit_qemu(QemuExitCode::Failed); - }, - Err(_) => { - crate::println!(" ok"); - }, - } - } - } } - exit_qemu(QemuExitCode::Success); - }; - - TokenStream::from(body) + let item_size = core::mem::size_of::(); + let l = (__ktest_array_end as usize - __ktest_array as usize) / item_size; + // Safety: __ktest_array is a static section consisting of KtestItem. + unsafe { core::slice::from_raw_parts(__ktest_array as *const KtestItem, l) } + }}; +} + +pub struct KtestIter { + index: usize, +} + +impl KtestIter { + fn new() -> Self { + Self { index: 0 } + } +} + +impl core::iter::Iterator for KtestIter { + type Item = KtestItem; + + fn next(&mut self) -> Option { + let Some(ktest_item) = ktest_array!().get(self.index) else { + return None; + }; + self.index += 1; + Some(ktest_item.clone()) + } +} + +fn get_ktest_tests() -> (usize, KtestIter) { + (ktest_array!().len(), KtestIter::new()) } diff --git a/framework/libs/ktest/src/runner.rs b/framework/libs/ktest/src/runner.rs new file mode 100644 index 000000000..1490bf181 --- /dev/null +++ b/framework/libs/ktest/src/runner.rs @@ -0,0 +1,88 @@ +use crate::{CatchUnwindImpl, KtestError, KtestItem}; + +use alloc::vec::Vec; +use core::format_args; + +use owo_colors::OwoColorize; + +pub enum KtestResult { + Ok, + Failed, +} + +/// Run all the tests registered by `#[ktest]` in the `.ktest_array` section. +/// +/// Need to provide a print function to print the test result, and a `catch_unwind` +/// implementation to catch the panic. +/// +/// Returns the test result interpreted as `ok` or `FAILED`. +pub fn run_ktests(print: PrintFn, catch_unwind: &CatchUnwindImpl) -> KtestResult +where + PrintFn: Fn(core::fmt::Arguments), +{ + macro_rules! print { + ($fmt: literal $(, $($arg: tt)+)?) => { + print(format_args!($fmt $(, $($arg)+)?)) + } + } + + let (n, ktests) = crate::get_ktest_tests(); + print!("\nrunning {} tests\n\n", n); + let mut passed: usize = 0; + let mut failed_tests: Vec<(KtestItem, KtestError)> = Vec::new(); + for test in ktests { + print!( + "[{}] test {}::{} ...", + test.info().package, + test.info().module_path, + test.info().fn_name + ); + match test.run(catch_unwind) { + Ok(()) => { + print!(" {}\n", "ok".green()); + passed += 1; + } + Err(e) => { + print!(" {}\n", "FAILED".red()); + failed_tests.push((test.clone(), e.clone())); + } + } + } + let failed = failed_tests.len(); + if failed == 0 { + print!("\ntest result: {}.", "ok".green()); + } else { + print!("\ntest result: {}.", "FAILED".red()); + } + print!(" {} passed; {} failed.\n", passed, failed); + if failed > 0 { + print!("\nfailures:\n\n"); + for (t, e) in failed_tests { + print!( + "---- {}:{}:{} - {} ----\n\n", + t.info().source, + t.info().line, + t.info().col, + t.info().fn_name + ); + match e { + KtestError::Panic(s) => { + print!("[caught panic] {}\n", s); + } + KtestError::ShouldPanicButNoPanic => { + print!("test did not panic as expected\n"); + } + KtestError::ExpectedPanicNotMatch(expected, s) => { + print!("[caught panic] expected panic not match\n"); + print!("expected: {}\n", expected); + print!("caught: {}\n", s); + } + KtestError::Unknown => { + print!("[caught panic] unknown panic payload! (fatal panic handling error in ktest)\n"); + } + } + } + return KtestResult::Failed; + } + KtestResult::Ok +}