| Crates.io | ktest |
| lib.rs | ktest |
| version | 0.1.6 |
| created_at | 2025-10-12 22:44:30.096327+00 |
| updated_at | 2025-11-07 02:44:35.53535+00 |
| description | A custom test framework for Rust-based operating system kernels |
| homepage | |
| repository | https://github.com/philogroves/ktest |
| max_upload_size | |
| id | 1879813 |
| size | 52,721 |
A custom test framework with features that are relevant to Rust-based operating system kernels.
x86_64 is the only currently supported architecture
Single Crate: https://github.com/philo-groves/example-kernel-kboot-ktest
Workspace: https://github.com/philo-groves/example-kernel-kboot-ktest-multicrate
#[ktest] macro for test functions#[ignore] and #[should_panic] tagsklib!("test_group"); macro for test setup:
-debugcon deviceallocator)An allocator is NOT required for this library to function correctly. This library uses heapless structures without dynamic allocation.
The main.rs test setup is slightly more complex than lib.rs tests. In your main.rs, add the following #![cfg_attr(test, ...)] and #[cfg(test)] attributes/sections:
#![cfg_attr(test, feature(custom_test_frameworks))] // enable custom test frameworks
#![cfg_attr(test, test_runner(ktest::runner))] // assign ktest as the runner
#![cfg_attr(test, reexport_test_harness_main = "test_main")] // always use "test_main", expected by ktest/kboot
fn kernel_main(boot_info: &'static mut bootloader_api::BootInfo) -> ! {
// initialize the kernel
// IMPORTANT: run tests if we are in test mode; this is the primary inclusion for main.rs
#[cfg(test)]
{
ktest::init_harness("binary");
test_main(); // should match reexport_test_harness_main
}
// continue running the kernel
}
For lib.rs packages, there exists as klib!("test group"); macro which will inject the proper source code for:
allocator feature (default: false)See the relatively small source file: https://github.com/philo-groves/ktest/blob/main/src/macros/klib.rs
// the following 3 lines are the exact same from the main.rs example; both files require the setup
#![cfg_attr(test, feature(custom_test_frameworks))] // enable custom test frameworks
#![cfg_attr(test, test_runner(ktest::runner))] // assign ktest as the runner
#![cfg_attr(test, reexport_test_harness_main = "test_main")] // always use "test_main", expected by ktest/kboot
// "basic_crate" will act as a label for this test group; make sure this is test-only
#[cfg(test)]
ktest::klib!("basic_crate");
The klib!("test group"); macro can be expanded with two optional arguments: klib_config and boot_config:
klib_config: Configurations for the test runner; currently, this holds function references to run before or after tests.boot_config: A direct reference to the bootloader configuration#[cfg(test)] // klib_config and boot_config are optional
ktest::klib!("extended_crate", klib_config = &KLIB_CONFIG, boot_config = &BOOTLOADER_CONFIG);
#[cfg(test)] // this config is optional
pub const KLIB_CONFIG: ktest::KlibConfig = ktest::KlibConfigBuilder::new_default()
.before_tests(|boot_info| init(boot_info))
.after_tests(|| teardown())
.build();
#[cfg(test)] // this config is optional
pub const BOOTLOADER_CONFIG: bootloader_api::BootloaderConfig = {
const HIGHER_HALF_START: u64 = 0xffff_8000_0000_0000;
const PHYSICAL_MEMORY_OFFSET: u64 = 0x0000_0880_0000_0000;
let mut config = bootloader_api::BootloaderConfig::new_default();
config.mappings.physical_memory = Some(bootloader_api::config::Mapping::FixedAddress(PHYSICAL_MEMORY_OFFSET));
config.mappings.dynamic_range_start = Some(HIGHER_HALF_START);
config
};
#[cfg(test)] // this function is optional
pub fn init(_boot_info: &'static bootloader_api::BootInfo) {
// Setup code to run before tests, e.g. kernel initialization
}
#[cfg(test)] // this function is optional
pub fn teardown() {
// Teardown code to run after tests
}
kboot and ktest both support Limine as an optional bootloader. Both of these programs must have their limine feature enabled.
In your kboot runner, usually in .cargo/config.toml, use the --limine flag:
kboot --limine
In your kernel's ktest dependency inclusion, enable the limine feature:
[dev-dependencies]
ktest = { version = "0.1.6", features = ["limine"] }
################################################################
# Running 2 library tests for module: kernel::tests
----------------------------------------------------------------
lib_assertion [pass]
lib_assertion_2 [fail] @ src\lib.rs:119: Make sure tests fail correctly
If you are using this library WITHOUT kboot, your JSON output will be line-delimited and look like this:
{"test_count":2,"test_group":"library"}
{"tests":["kernel::tests::lib_assertion","kernel::tests::lib_assertion_2"]}
{"cycle_count":866,"result":"pass","test":"kernel::tests::lib_assertion"}
{"cycle_count":0,"location":"src\\lib.rs:119","message":"Make sure tests fail correctly","result":"fail","test":"kernel::tests::lib_assertion_2"}
If you are using this library WITH kboot, the tool will reformat your line-delimited JSON output automatically and it will look like this:
{
"test_group": "library",
"summary": {
"total": 2,
"passed": 1,
"failed": 1,
"ignore": 0,
"duration": 6266
},
"modules": [
{
"module": "kernel::tests",
"tests": [
{
"test": "lib_assertion",
"result": "pass",
"cycle_count": 1142
},
{
"test": "lib_assertion_2",
"result": "fail",
"cycle_count": 0,
"location": "src\\lib.rs:119",
"message": "Make sure tests fail correctly"
}
]
}
]
}