Add ktest framework

This commit is contained in:
Zhang Junyang
2023-11-04 16:41:30 +08:00
committed by Tate, Hongliang Tian
parent d7cc52c615
commit b8818bb740
28 changed files with 373 additions and 297 deletions

View File

@ -6,7 +6,6 @@ runner = "cargo run --package jinux-runner --"
kcheck = "check --target x86_64-custom.json -Zbuild-std=core,alloc,compiler_builtins -Zbuild-std-features=compiler-builtins-mem" kcheck = "check --target x86_64-custom.json -Zbuild-std=core,alloc,compiler_builtins -Zbuild-std-features=compiler-builtins-mem"
kbuild = "build --target x86_64-custom.json -Zbuild-std=core,alloc,compiler_builtins -Zbuild-std-features=compiler-builtins-mem" kbuild = "build --target x86_64-custom.json -Zbuild-std=core,alloc,compiler_builtins -Zbuild-std-features=compiler-builtins-mem"
krun = "run --target x86_64-custom.json -Zbuild-std=core,alloc,compiler_builtins -Zbuild-std-features=compiler-builtins-mem" krun = "run --target x86_64-custom.json -Zbuild-std=core,alloc,compiler_builtins -Zbuild-std-features=compiler-builtins-mem"
ktest = "test --target x86_64-custom.json -Zbuild-std=core,alloc,compiler_builtins -Zbuild-std-features=compiler-builtins-mem"
kclippy = "clippy --target x86_64-custom.json -Zbuild-std=core,alloc,compiler_builtins -Zbuild-std-features=compiler-builtins-mem" kclippy = "clippy --target x86_64-custom.json -Zbuild-std=core,alloc,compiler_builtins -Zbuild-std-features=compiler-builtins-mem"
component-check = "component check --target x86_64-custom.json -Zbuild-std=core,alloc,compiler_builtins -Zbuild-std-features=compiler-builtins-mem" component-check = "component check --target x86_64-custom.json -Zbuild-std=core,alloc,compiler_builtins -Zbuild-std-features=compiler-builtins-mem"

View File

@ -5,7 +5,6 @@ on:
push: push:
branches: branches:
- main - main
- releases/*
jobs: jobs:
test: test:

25
.github/workflows/unit_test.yml vendored Normal file
View File

@ -0,0 +1,25 @@
name: Unit test
on:
pull_request:
push:
branches:
- main
jobs:
test:
runs-on: ubuntu-latest
timeout-minutes: 15
container: jinuxdev/jinux:0.2.1
steps:
- run: echo "Running in jinuxdev/jinux:0.2.1"
- uses: actions/checkout@v3
- name: Ktest Unit Test
id: ktest_unit_test
run: make run KTEST=all ENABLE_KVM=0 RELEASE_MODE=1
# TODO: include the unit tests for the crates that supports cargo test.
# TODO: add component check.

34
Cargo.lock generated
View File

@ -216,7 +216,7 @@ dependencies = [
"heck", "heck",
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.28", "syn 2.0.38",
] ]
[[package]] [[package]]
@ -368,7 +368,7 @@ dependencies = [
"proc-macro-error", "proc-macro-error",
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.28", "syn 2.0.38",
] ]
[[package]] [[package]]
@ -475,7 +475,7 @@ checksum = "ba330b70a5341d3bc730b8e205aaee97ddab5d9c448c4f51a7c2d924266fa8f9"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.28", "syn 2.0.38",
] ]
[[package]] [[package]]
@ -567,7 +567,7 @@ version = "0.1.0"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.28", "syn 2.0.38",
] ]
[[package]] [[package]]
@ -637,6 +637,7 @@ dependencies = [
"inherit-methods-macro", "inherit-methods-macro",
"int-to-c-enum", "int-to-c-enum",
"intrusive-collections", "intrusive-collections",
"ktest",
"lazy_static", "lazy_static",
"log", "log",
"multiboot2", "multiboot2",
@ -757,6 +758,7 @@ dependencies = [
"jinux-util", "jinux-util",
"jinux-virtio", "jinux-virtio",
"keyable-arc", "keyable-arc",
"ktest",
"lazy_static", "lazy_static",
"lending-iterator", "lending-iterator",
"libflate", "libflate",
@ -830,6 +832,16 @@ checksum = "078e285eafdfb6c4b434e0d31e8cfcb5115b651496faca5749b88fafd4f23bfd"
name = "keyable-arc" name = "keyable-arc"
version = "0.1.0" version = "0.1.0"
[[package]]
name = "ktest"
version = "0.1.0"
dependencies = [
"proc-macro2",
"quote",
"rand",
"syn 2.0.38",
]
[[package]] [[package]]
name = "lazy_static" name = "lazy_static"
version = "1.4.0" version = "1.4.0"
@ -1061,9 +1073,9 @@ dependencies = [
[[package]] [[package]]
name = "proc-macro2" name = "proc-macro2"
version = "1.0.66" version = "1.0.69"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "18fb31db3f9bddb2ea821cde30a9f70117e3f119938b5ee630b7403aa6e2ead9" checksum = "134c189feb4956b20f6f547d2cf727d4c0fe06722b20a0eec87ed445a97f92da"
dependencies = [ dependencies = [
"unicode-ident", "unicode-ident",
] ]
@ -1090,9 +1102,9 @@ dependencies = [
[[package]] [[package]]
name = "quote" name = "quote"
version = "1.0.32" version = "1.0.33"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "50f3b39ccfb720540debaa0164757101c08ecb8d326b15358ce76a62c7e85965" checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
] ]
@ -1278,9 +1290,9 @@ dependencies = [
[[package]] [[package]]
name = "syn" name = "syn"
version = "2.0.28" version = "2.0.38"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "04361975b3f5e348b2189d8dc55bc942f278b2d482a6a0365de5bdd62d351567" checksum = "e96b79aaa137db8f61e26363a0c9b47d8b4ec75da28b7d1d614c2303e232408b"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@ -1320,7 +1332,7 @@ checksum = "090198534930841fab3a5d1bb637cde49e339654e606195f8d9c76eeb081dc96"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.28", "syn 2.0.38",
] ]
[[package]] [[package]]

View File

@ -8,6 +8,7 @@ ENABLE_KVM ?= 1
GDB_CLIENT ?= 0 GDB_CLIENT ?= 0
GDB_SERVER ?= 0 GDB_SERVER ?= 0
INTEL_TDX ?= 0 INTEL_TDX ?= 0
KTEST ?= none
SKIP_GRUB_MENU ?= 1 SKIP_GRUB_MENU ?= 1
RELEASE_MODE ?= 0 RELEASE_MODE ?= 0
# End of setting up Make varaiables # End of setting up Make varaiables
@ -22,8 +23,8 @@ KERNEL_CMDLINE += -c exit 0
endif endif
CARGO_KBUILD_ARGS := CARGO_KBUILD_ARGS :=
CARGO_KRUN_ARGS := CARGO_KRUN_ARGS :=
GLOBAL_RUSTC_FLAGS :=
ifeq ($(RELEASE_MODE), 1) ifeq ($(RELEASE_MODE), 1)
CARGO_KBUILD_ARGS += --release CARGO_KBUILD_ARGS += --release
@ -60,6 +61,11 @@ CARGO_KBUILD_ARGS += --features intel_tdx
CARGO_KRUN_ARGS += --features intel_tdx CARGO_KRUN_ARGS += --features intel_tdx
endif endif
ifneq ($(KTEST), none)
comma := ,
GLOBAL_RUSTC_FLAGS += --cfg ktest --cfg ktest=\"$(subst $(comma),\" --cfg ktest=\",$(KTEST))\"
endif
ifeq ($(SKIP_GRUB_MENU), 1) ifeq ($(SKIP_GRUB_MENU), 1)
CARGO_KRUN_ARGS += --skip-grub-menu CARGO_KRUN_ARGS += --skip-grub-menu
endif endif
@ -82,16 +88,16 @@ setup:
build: build:
@make --no-print-directory -C regression @make --no-print-directory -C regression
@cargo kbuild $(CARGO_KBUILD_ARGS) @RUSTFLAGS="$(GLOBAL_RUSTC_FLAGS)" cargo kbuild $(CARGO_KBUILD_ARGS)
tools: tools:
@cd services/libs/comp-sys && cargo install --path cargo-component @cd services/libs/comp-sys && cargo install --path cargo-component
run: build run: build
@cargo krun $(CARGO_KRUN_ARGS) @RUSTFLAGS="$(GLOBAL_RUSTC_FLAGS)" cargo krun $(CARGO_KRUN_ARGS)
test: build test:
@cargo ktest @python3 ./tools/test/run_tests.py
docs: docs:
@cargo doc # Build Rust docs @cargo doc # Build Rust docs

View File

@ -56,11 +56,16 @@ make build
make run make run
``` ```
### Test ### Unit Test
We can run unit tests and integration tests if building succeeds. We can run unit tests if building succeeds. This is powered by our [ktest](framework/libs/ktest) framework.
```bash ```bash
make test make run KTEST=all
```
You could also specify tests in a crate or a subset of tests to run, as long as you defined them well using cfg.
```bash
make run KTEST=jinux-frame,jinux-std
``` ```
If we want to check access control policy among components, install some standalone tools (e.g., `cargo-component`). If we want to check access control policy among components, install some standalone tools (e.g., `cargo-component`).
@ -73,7 +78,7 @@ Then we can use the tool to check access control policy.
cargo component-check cargo component-check
``` ```
### Syscall Test ### Integration Test
This command will build the syscall test binary and automatically run Jinux with the tests using QEMU. This command will build the syscall test binary and automatically run Jinux with the tests using QEMU.
```bash ```bash

View File

@ -13,6 +13,7 @@ volatile = { version = "0.4.5", features = ["unstable"] }
buddy_system_allocator = "0.9.0" buddy_system_allocator = "0.9.0"
pod = { git = "https://github.com/jinzhao-dev/pod", rev = "d7dba56" } pod = { git = "https://github.com/jinzhao-dev/pod", rev = "d7dba56" }
align_ext = { path = "../libs/align_ext" } align_ext = { path = "../libs/align_ext" }
ktest = { path = "../libs/ktest" }
intrusive-collections = "0.9.5" intrusive-collections = "0.9.5"
log = "0.4" log = "0.4"
lazy_static = { version = "1.0", features = ["spin_no_std"] } lazy_static = { version = "1.0", features = ["spin_no_std"] }

View File

@ -24,24 +24,6 @@ SECTIONS
.text : AT(ADDR(.text) - KERNEL_VMA) { *(.text .text.*) } .text : AT(ADDR(.text) - KERNEL_VMA) { *(.text .text.*) }
.rodata : AT(ADDR(.rodata) - KERNEL_VMA) { *(.rodata .rodata.*) } .rodata : AT(ADDR(.rodata) - KERNEL_VMA) { *(.rodata .rodata.*) }
.data : AT(ADDR(.data) - KERNEL_VMA) { *(.data .data.*) }
.bss : AT(ADDR(.bss) - KERNEL_VMA) {
__bss = .;
*(.bss .bss.*) *(COMMON)
__bss_end = .;
}
.tdata : AT(ADDR(.tdata) - KERNEL_VMA) { *(.tdata .tdata.*) }
.tbss : AT(ADDR(.tbss) - KERNEL_VMA) { *(.tbss .tbss.*) }
.init_array : AT(ADDR(.init_array) - KERNEL_VMA) {
__sinit_array = .;
*(.init_array .init_array.*)
__einit_array = .;
}
.data.rel.ro : AT(ADDR(.data.rel.ro) - KERNEL_VMA) { *(.data.rel.ro .data.rel.ro.*) }
.dynamic : AT(ADDR(.dynamic) - KERNEL_VMA) { *(.dynamic) }
.eh_frame_hdr : AT(ADDR(.eh_frame_hdr) - KERNEL_VMA) { .eh_frame_hdr : AT(ADDR(.eh_frame_hdr) - KERNEL_VMA) {
__eh_frame_hdr = .; __eh_frame_hdr = .;
KEEP(*(.eh_frame_hdr)) KEEP(*(.eh_frame_hdr))
@ -53,8 +35,37 @@ SECTIONS
__eh_frame_end = .; __eh_frame_end = .;
} }
# The notes section are used to mark the PVH boot entry point, useful for QEMU and Xen .data.rel.ro : AT(ADDR(.data.rel.ro) - KERNEL_VMA) { *(.data.rel.ro .data.rel.ro.*) }
.notes : { *(.notes) } .dynamic : AT(ADDR(.dynamic) - KERNEL_VMA) { *(.dynamic) }
.init_array : AT(ADDR(.init_array) - KERNEL_VMA) {
__sinit_array = .;
KEEP(*(SORT(.init_array .init_array.*)))
__einit_array = .;
}
.got : AT(ADDR(.got) - KERNEL_VMA) { *(.got .got.*) }
.got.plt : AT(ADDR(.got.plt) - KERNEL_VMA) { *(.got.plt .got.plt.*) }
. = DATA_SEGMENT_RELRO_END(0, .);
.data : AT(ADDR(.data) - KERNEL_VMA) { *(.data .data.*) }
.bss : AT(ADDR(.bss) - KERNEL_VMA) {
__bss = .;
*(.bss .bss.*) *(COMMON)
__bss_end = .;
}
.ktest_array : AT(ADDR(.ktest_array) - KERNEL_VMA) {
__ktest_array = .;
KEEP(*(SORT(.ktest_array)))
__ktest_array_end = .;
}
.tdata : AT(ADDR(.tdata) - KERNEL_VMA) { *(.tdata .tdata.*) }
.tbss : AT(ADDR(.tbss) - KERNEL_VMA) { *(.tbss .tbss.*) }
. = DATA_SEGMENT_END(.);
__kernel_end = . - KERNEL_VMA; __kernel_end = . - KERNEL_VMA;
} }

View File

@ -124,12 +124,6 @@ fn init_memory_regions(memory_regions: &'static Once<Vec<MemoryRegion>>) {
memory_regions.call_once(|| regions); memory_regions.call_once(|| regions);
} }
// The entry point of kernel code, which should be defined by the package that
// uses jinux-frame.
extern "Rust" {
fn jinux_main() -> !;
}
/// The entry point of Rust code called by the Linux 64-bit boot compatible bootloader. /// The entry point of Rust code called by the Linux 64-bit boot compatible bootloader.
#[no_mangle] #[no_mangle]
unsafe extern "sysv64" fn __linux64_boot(params_ptr: *const boot_params::BootParams) -> ! { unsafe extern "sysv64" fn __linux64_boot(params_ptr: *const boot_params::BootParams) -> ! {
@ -144,5 +138,5 @@ unsafe extern "sysv64" fn __linux64_boot(params_ptr: *const boot_params::BootPar
init_framebuffer_info, init_framebuffer_info,
init_memory_regions, init_memory_regions,
); );
jinux_main(); crate::boot::call_jinux_main();
} }

View File

@ -332,12 +332,6 @@ struct MemoryEntry {
memory_type: MemoryAreaType, memory_type: MemoryAreaType,
} }
// The entry point of kernel code, which should be defined by the package that
// uses jinux-frame.
extern "Rust" {
fn jinux_main() -> !;
}
static MB1_INFO: Once<&'static MultibootLegacyInfo> = Once::new(); static MB1_INFO: Once<&'static MultibootLegacyInfo> = Once::new();
/// The entry point of Rust code called by inline asm. /// The entry point of Rust code called by inline asm.
@ -353,5 +347,5 @@ unsafe extern "sysv64" fn __multiboot_entry(boot_magic: u32, boot_params: u64) -
init_framebuffer_info, init_framebuffer_info,
init_memory_regions, init_memory_regions,
); );
jinux_main(); crate::boot::call_jinux_main();
} }

View File

@ -163,12 +163,6 @@ fn init_memory_regions(memory_regions: &'static Once<Vec<MemoryRegion>>) {
memory_regions.call_once(move || non_overlapping_regions_from(regions.as_ref())); memory_regions.call_once(move || non_overlapping_regions_from(regions.as_ref()));
} }
// The entry point of kernel code, which should be defined by the package that
// uses jinux-frame.
extern "Rust" {
fn jinux_main() -> !;
}
/// The entry point of Rust code called by inline asm. /// The entry point of Rust code called by inline asm.
#[no_mangle] #[no_mangle]
unsafe extern "sysv64" fn __multiboot2_entry(boot_magic: u32, boot_params: u64) -> ! { unsafe extern "sysv64" fn __multiboot2_entry(boot_magic: u32, boot_params: u64) -> ! {
@ -184,5 +178,5 @@ unsafe extern "sysv64" fn __multiboot2_entry(boot_magic: u32, boot_params: u64)
init_framebuffer_info, init_framebuffer_info,
init_memory_regions, init_memory_regions,
); );
jinux_main(); crate::boot::call_jinux_main();
} }

View File

@ -99,3 +99,24 @@ define_global_static_boot_arguments!(
pub fn init() { pub fn init() {
call_all_boot_init_callbacks(); call_all_boot_init_callbacks();
} }
/// Call the framework-user defined entrypoint of the actual kernel.
///
/// Any kernel that uses the jinux-frame crate should define a function named
/// `jinux_main` as the entrypoint.
pub fn call_jinux_main() -> ! {
#[cfg(not(ktest))]
unsafe {
// The entry point of kernel code, which should be defined by the package that
// uses jinux-frame.
extern "Rust" {
fn jinux_main() -> !;
}
jinux_main();
}
#[cfg(ktest)]
{
crate::init();
ktest::do_ktests!();
}
}

View File

@ -19,6 +19,8 @@
extern crate alloc; extern crate alloc;
#[macro_use] #[macro_use]
extern crate ktest;
#[macro_use]
extern crate static_assertions; extern crate static_assertions;
pub mod arch; pub mod arch;
@ -44,7 +46,7 @@ pub use self::error::Error;
pub use self::prelude::Result; pub use self::prelude::Result;
use alloc::vec::Vec; use alloc::vec::Vec;
use arch::irq::{IrqCallbackHandle, IrqLine}; use arch::irq::{IrqCallbackHandle, IrqLine};
use core::{mem, panic::PanicInfo}; use core::mem;
#[cfg(feature = "intel_tdx")] #[cfg(feature = "intel_tdx")]
use tdx_guest::init_tdx; use tdx_guest::init_tdx;
use trapframe::TrapFrame; use trapframe::TrapFrame;
@ -108,35 +110,6 @@ pub(crate) const fn zero<T>() -> T {
unsafe { mem::MaybeUninit::zeroed().assume_init() } unsafe { mem::MaybeUninit::zeroed().assume_init() }
} }
pub trait Testable {
fn run(&self);
}
impl<T> Testable for T
where
T: Fn(),
{
fn run(&self) {
print!("{}...\n", core::any::type_name::<T>());
self();
println!("[ok]");
}
}
pub fn test_runner(tests: &[&dyn Testable]) {
println!("Running {} tests", tests.len());
for test in tests {
test.run();
}
exit_qemu(QemuExitCode::Success);
}
pub fn test_panic_handler(info: &PanicInfo) -> ! {
println!("[failed]");
println!("Error: {}", info);
exit_qemu(QemuExitCode::Failed);
}
pub fn panic_handler() { pub fn panic_handler() {
// let mut fp: usize; // let mut fp: usize;
// let stop = unsafe{ // let stop = unsafe{
@ -181,3 +154,11 @@ pub fn exit_qemu(exit_code: QemuExitCode) -> ! {
} }
unreachable!() unreachable!()
} }
#[if_cfg_ktest]
mod test {
#[ktest]
fn trivial_assertion() {
assert_eq!(0, 0);
}
}

View File

@ -284,11 +284,11 @@ impl fmt::Debug for AtomicBits {
} }
} }
#[cfg(test)] #[if_cfg_ktest]
mod test { mod test {
use super::*; use super::*;
#[test] #[ktest]
fn new() { fn new() {
let bits = AtomicBits::new_zeroes(1); let bits = AtomicBits::new_zeroes(1);
assert!(bits.len() == 1); assert!(bits.len() == 1);
@ -303,7 +303,7 @@ mod test {
assert!(bits.len() == 65); assert!(bits.len() == 65);
} }
#[test] #[ktest]
fn set_get() { fn set_get() {
let bits = AtomicBits::new_zeroes(128); let bits = AtomicBits::new_zeroes(128);
for i in 0..bits.len() { for i in 0..bits.len() {
@ -328,7 +328,7 @@ mod test {
} }
} }
#[test] #[ktest]
fn iter_ones() { fn iter_ones() {
let bits = AtomicBits::new_zeroes(1); let bits = AtomicBits::new_zeroes(1);
assert!(bits.iter_ones().count() == 0); assert!(bits.iter_ones().count() == 0);
@ -353,7 +353,7 @@ mod test {
assert!(bits.iter_ones().count() == 3); assert!(bits.iter_ones().count() == 3);
} }
#[test] #[ktest]
fn iter_zeroes() { fn iter_zeroes() {
let bits = AtomicBits::new_ones(1); let bits = AtomicBits::new_ones(1);
assert!(bits.iter_zeroes().count() == 0); assert!(bits.iter_zeroes().count() == 0);
@ -380,7 +380,7 @@ mod test {
assert!(bits.iter_zeroes().count() == 5); assert!(bits.iter_zeroes().count() == 5);
} }
#[test] #[ktest]
fn iter() { fn iter() {
let bits = AtomicBits::new_zeroes(7); let bits = AtomicBits::new_zeroes(7);
assert!(bits.iter().all(|bit| bit == false)); assert!(bits.iter().all(|bit| bit == false));

View File

@ -0,0 +1,15 @@
[package]
name = "ktest"
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"] }

View File

@ -0,0 +1,192 @@
//! # 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.
//!
//! 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.
//!
//! ## Usage
//!
//! To write a unit test for any crates, it is recommended to create a new test
//! module, e.g.:
//!
//! ```rust
//! use ktest::{ktest, if_cfg_ktest};
//! #[if_cfg_ktest]
//! mod test {
//! #[ktest]
//! fn trivial_assertion() {
//! assert_eq!(0, 0);
//! }
//! }
//! ```
//!
//! And also, any crates using the ktest framework should be linked with jinux-frame
//! and import the `ktest` crate:
//!
//! ```toml
//! # Cargo.toml
//! [dependencies]
//! ktest = { path = "relative/path/to/ktest" }
//! ```
//!
//! By the way, `#[ktest]` attribute along also works, but it hinders test control
//! using cfgs since plain attribute marked test will be executed in all test runs
//! no matter what cfgs are passed to the compiler. More importantly, using `#[ktest]`
//! without cfgs occupies binary real estate since the `.ktest_array` section is not
//! explicitly stripped in normal builds.
//!
//! Rust cfg is used to control the compilation of the test module. In cooperation
//! with the `ktest` framework, the Makefile will set the `RUSTFLAGS` environment
//! variable to pass the cfgs to all rustc invocations. To run the tests, you need
//! to pass a list of cfgs to the Makefile, e.g.:
//!
//! ```bash
//! make run KTEST=jinux-frame,jinux-std,align_ext,tdx-guest
//! ```
//!
//! It is flexible to specify the cfgs for running the tests. The cfg value is not
//! limited to crate names, enabling your imagination to configure running any subsets
//! of tests in any crates. And to ease development, `#[if_cfg_ktest]` is expanded to
//! a default conditional compilation setting:
//! `#[cfg(all(ktest, any(ktest = "all", ktest = #crate_name)))]`
//!
//! Currently we do not support `#[should_panic]` attribute, and this feature will
//! be added in the future.
//!
//! 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)]
extern crate proc_macro2;
use proc_macro::TokenStream;
use quote::quote;
use rand::{distributions::Alphanumeric, Rng};
use syn::{parse_macro_input, 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(),
);
// 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(),
);
let span = proc_macro::Span::call_site();
let source = span.source_file();
let crate_name = std::env::var("CARGO_PKG_NAME").unwrap();
let hint_str = format!(
"[{}] {}: {}()",
crate_name,
source.path().to_str().unwrap(),
fn_name
);
let register = quote! {
struct #ktest_item_struct {
fn_: fn() -> (),
hint: &'static str,
}
#[cfg(ktest)]
#[used]
#[link_section = ".ktest_array"]
static #fn_ktest_item_name: #ktest_item_struct = #ktest_item_struct {
fn_: #fn_name,
hint: #hint_str,
};
};
let output = quote! {
#input
#register
};
TokenStream::from(output)
}
/// The procedural macro to run all the tests.
#[proc_macro]
pub fn do_ktests(_item: TokenStream) -> TokenStream {
let body = quote! {
struct KtestItem {
fn_: fn() -> (),
hint: &'static str,
};
extern "C" {
fn __ktest_array();
fn __ktest_array_end();
}
let item_size = core::mem::size_of::<KtestItem>() 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 address = (__ktest_array as u64 + item_size * i) as *const u64;
let item = address as *const KtestItem;
crate::print!("{} ...", (*item).hint);
((*item).fn_)();
}
crate::println!(" Ok!");
}
crate::exit_qemu(crate::QemuExitCode::Success);
};
TokenStream::from(body)
}

View File

@ -1,11 +1,8 @@
#![no_std] #![no_std]
#![no_main] #![no_main]
#![feature(custom_test_frameworks)]
// The no_mangle macro need to remove the `forbid(unsafe_code)` macro. The bootloader needs the _start function // The no_mangle macro need to remove the `forbid(unsafe_code)` macro. The bootloader needs the _start function
// to be no mangle so that it can jump into the entry point. // to be no mangle so that it can jump into the entry point.
// #![forbid(unsafe_code)] // #![forbid(unsafe_code)]
#![test_runner(jinux_frame::test_runner)]
#![reexport_test_harness_main = "test_main"]
extern crate jinux_frame; extern crate jinux_frame;
use core::panic::PanicInfo; use core::panic::PanicInfo;
@ -13,8 +10,6 @@ use jinux_frame::println;
#[no_mangle] #[no_mangle]
pub fn jinux_main() -> ! { pub fn jinux_main() -> ! {
#[cfg(test)]
test_main();
jinux_frame::init(); jinux_frame::init();
println!("[kernel] finish init jinux_frame"); println!("[kernel] finish init jinux_frame");
component::init_all(component::parse_metadata!()).unwrap(); component::init_all(component::parse_metadata!()).unwrap();
@ -22,7 +17,6 @@ pub fn jinux_main() -> ! {
jinux_std::run_first_process(); jinux_std::run_first_process();
} }
#[cfg(not(test))]
#[panic_handler] #[panic_handler]
fn panic(info: &PanicInfo) -> ! { fn panic(info: &PanicInfo) -> ! {
use jinux_frame::{exit_qemu, QemuExitCode}; use jinux_frame::{exit_qemu, QemuExitCode};
@ -31,14 +25,3 @@ fn panic(info: &PanicInfo) -> ! {
jinux_frame::panic_handler(); jinux_frame::panic_handler();
exit_qemu(QemuExitCode::Failed); exit_qemu(QemuExitCode::Failed);
} }
#[cfg(test)]
#[panic_handler]
fn panic(info: &PanicInfo) -> ! {
jinux_frame::test_panic_handler(info);
}
#[test_case]
fn trivial_assertion() {
assert_eq!(1, 1);
}

View File

@ -35,9 +35,7 @@ pub(crate) fn init() {
let page_size = size / PAGE_SIZE; let page_size = size / PAGE_SIZE;
let start_paddr = framebuffer.address; let start_paddr = framebuffer.address;
let io_mem = let io_mem = todo!("IoMem is private for components now, should fix it.");
IoMem::new(start_paddr..(start_paddr + jinux_frame::config::PAGE_SIZE * page_size))
.unwrap();
let mut buffer: Vec<u8> = Vec::with_capacity(size); let mut buffer: Vec<u8> = Vec::with_capacity(size);
for _ in 0..size { for _ in 0..size {

View File

@ -40,6 +40,7 @@ smoltcp = { version = "0.9.1", default-features = false, features = [
"socket-raw", "socket-raw",
"socket-dhcpv4", "socket-dhcpv4",
] } ] }
ktest = { path = "../../../framework/libs/ktest" }
tdx-guest = { path = "../../../framework/libs/tdx-guest", optional = true } tdx-guest = { path = "../../../framework/libs/tdx-guest", optional = true }
# parse elf file # parse elf file

View File

@ -34,6 +34,8 @@ extern crate alloc;
extern crate lru; extern crate lru;
#[macro_use] #[macro_use]
extern crate controlled; extern crate controlled;
#[macro_use]
extern crate ktest;
pub mod device; pub mod device;
pub mod driver; pub mod driver;

View File

@ -132,7 +132,7 @@ impl<R> VmarChildOptions<R> {
} }
} }
#[cfg(test)] #[if_cfg_ktest]
mod test { mod test {
use super::*; use super::*;
use crate::vm::page_fault_handler::PageFaultHandler; use crate::vm::page_fault_handler::PageFaultHandler;
@ -142,13 +142,13 @@ mod test {
use jinux_frame::vm::VmIo; use jinux_frame::vm::VmIo;
use jinux_rights::Full; use jinux_rights::Full;
#[test] #[ktest]
fn root_vmar() { fn root_vmar() {
let vmar = Vmar::<Full>::new_root(); let vmar = Vmar::<Full>::new_root();
assert!(vmar.size() == ROOT_VMAR_HIGHEST_ADDR); assert!(vmar.size() == ROOT_VMAR_HIGHEST_ADDR);
} }
#[test] #[ktest]
fn child_vmar() { fn child_vmar() {
let root_vmar = Vmar::<Full>::new_root(); let root_vmar = Vmar::<Full>::new_root();
let root_vmar_dup = root_vmar.dup().unwrap(); let root_vmar_dup = root_vmar.dup().unwrap();
@ -167,7 +167,7 @@ mod test {
.is_err()); .is_err());
} }
#[test] #[ktest]
fn map_vmo() { fn map_vmo() {
let root_vmar = Vmar::<Full>::new_root(); let root_vmar = Vmar::<Full>::new_root();
let vmo = VmoOptions::<Full>::new(PAGE_SIZE).alloc().unwrap().to_dyn(); let vmo = VmoOptions::<Full>::new(PAGE_SIZE).alloc().unwrap().to_dyn();
@ -193,7 +193,7 @@ mod test {
assert!(root_vmar.read_val::<u8>(another_map_offset).unwrap() == 100); assert!(root_vmar.read_val::<u8>(another_map_offset).unwrap() == 100);
} }
#[test] #[ktest]
fn handle_page_fault() { fn handle_page_fault() {
const OFFSET: usize = 0x1000_0000; const OFFSET: usize = 0x1000_0000;
let root_vmar = Vmar::<Full>::new_root(); let root_vmar = Vmar::<Full>::new_root();

View File

@ -516,13 +516,13 @@ impl VmoChildType for VmoSliceChild {}
pub struct VmoCowChild; pub struct VmoCowChild;
impl VmoChildType for VmoCowChild {} impl VmoChildType for VmoCowChild {}
#[cfg(test)] #[if_cfg_ktest]
mod test { mod test {
use super::*; use super::*;
use jinux_frame::vm::VmIo; use jinux_frame::vm::VmIo;
use jinux_rights::Full; use jinux_rights::Full;
#[test] #[ktest]
fn alloc_vmo() { fn alloc_vmo() {
let vmo = VmoOptions::<Full>::new(PAGE_SIZE).alloc().unwrap(); let vmo = VmoOptions::<Full>::new(PAGE_SIZE).alloc().unwrap();
assert!(vmo.size() == PAGE_SIZE); assert!(vmo.size() == PAGE_SIZE);
@ -530,7 +530,8 @@ mod test {
assert!(vmo.read_val::<usize>(0).unwrap() == 0); assert!(vmo.read_val::<usize>(0).unwrap() == 0);
} }
#[test] #[ktest]
// FIXME: should_panic doesn't work with ktest, two negative makes a positive...
#[should_panic] #[should_panic]
/// FIXME: alloc continuous frames is not supported now /// FIXME: alloc continuous frames is not supported now
fn alloc_continuous_vmo() { fn alloc_continuous_vmo() {
@ -541,7 +542,7 @@ mod test {
assert!(vmo.size() == 10 * PAGE_SIZE); assert!(vmo.size() == 10 * PAGE_SIZE);
} }
#[test] #[ktest]
fn write_and_read() { fn write_and_read() {
let vmo = VmoOptions::<Full>::new(PAGE_SIZE).alloc().unwrap(); let vmo = VmoOptions::<Full>::new(PAGE_SIZE).alloc().unwrap();
let val = 42u8; let val = 42u8;
@ -555,7 +556,7 @@ mod test {
assert!(read_val == 0x78563412) assert!(read_val == 0x78563412)
} }
#[test] #[ktest]
fn slice_child() { fn slice_child() {
let parent = VmoOptions::<Full>::new(2 * PAGE_SIZE).alloc().unwrap(); let parent = VmoOptions::<Full>::new(2 * PAGE_SIZE).alloc().unwrap();
let parent_dup = parent.dup().unwrap(); let parent_dup = parent.dup().unwrap();
@ -570,7 +571,7 @@ mod test {
assert!(parent.read_val::<u32>(99).unwrap() == 0x1234); assert!(parent.read_val::<u32>(99).unwrap() == 0x1234);
} }
#[test] #[ktest]
fn cow_child() { fn cow_child() {
let parent = VmoOptions::<Full>::new(2 * PAGE_SIZE).alloc().unwrap(); let parent = VmoOptions::<Full>::new(2 * PAGE_SIZE).alloc().unwrap();
let parent_dup = parent.dup().unwrap(); let parent_dup = parent.dup().unwrap();
@ -596,7 +597,7 @@ mod test {
assert!(cow_child.read_val::<u32>(PAGE_SIZE + 10).unwrap() == 12345); assert!(cow_child.read_val::<u32>(PAGE_SIZE + 10).unwrap() == 12345);
} }
#[test] #[ktest]
fn resize() { fn resize() {
let vmo = VmoOptions::<Full>::new(PAGE_SIZE) let vmo = VmoOptions::<Full>::new(PAGE_SIZE)
.flags(VmoFlags::RESIZABLE) .flags(VmoFlags::RESIZABLE)

View File

@ -1,43 +0,0 @@
#![no_std]
#![no_main]
#![feature(custom_test_frameworks)]
#![test_runner(jinux_frame::test_runner)]
#![reexport_test_harness_main = "test_main"]
extern crate alloc;
use core::panic::PanicInfo;
use jinux_frame::println;
static mut INPUT_VALUE: u8 = 0;
#[no_mangle]
pub fn jinux_main() -> ! {
jinux_frame::init();
test_main();
loop {}
}
#[panic_handler]
fn panic(info: &PanicInfo) -> ! {
jinux_frame::test_panic_handler(info)
}
#[test_case]
fn test_input() {
x86_64::instructions::interrupts::enable();
println!("please input value into console to pass this test");
// FIXME: Where is tty?
// jinux_std::driver::tty::register_serial_input_callback(input_callback);
unsafe {
while INPUT_VALUE == 0 {
x86_64::instructions::hlt();
}
// println!("input value:{}", INPUT_VALUE);
}
}
pub fn input_callback(input: u8) {
println!("input value:{}", input);
unsafe {
INPUT_VALUE = input;
}
}

View File

@ -1,27 +0,0 @@
#![no_std]
#![no_main]
#![feature(custom_test_frameworks)]
#![test_runner(jinux_frame::test_runner)]
#![reexport_test_harness_main = "test_main"]
extern crate alloc;
use core::panic::PanicInfo;
#[no_mangle]
pub fn jinux_main() -> ! {
jinux_frame::init();
component::init_all(component::parse_metadata!()).unwrap();
test_main();
loop {}
}
#[panic_handler]
fn panic(info: &PanicInfo) -> ! {
jinux_frame::test_panic_handler(info)
}
#[test_case]
fn test_framebuffer() {
for _i in 0..30 {
jinux_framebuffer::println!("test_println!");
}
}

View File

@ -1,26 +0,0 @@
#![no_std]
#![no_main]
#![feature(custom_test_frameworks)]
#![test_runner(jinux_frame::test_runner)]
#![reexport_test_harness_main = "test_main"]
extern crate alloc;
use core::panic::PanicInfo;
use jinux_frame::println;
#[no_mangle]
pub fn jinux_main() -> ! {
jinux_frame::init();
component::init_all(component::parse_metadata!()).unwrap();
test_main();
loop {}
}
#[panic_handler]
fn panic(info: &PanicInfo) -> ! {
jinux_frame::test_panic_handler(info)
}
#[test_case]
fn test_rtc() {
println!("real time:{:?}", jinux_time::get_real_time());
}

View File

@ -1,23 +0,0 @@
#![no_std]
#![no_main]
#![feature(custom_test_frameworks)]
#![test_runner(jinux_frame::test_runner)]
#![reexport_test_harness_main = "test_main"]
use core::panic::PanicInfo;
#[no_mangle]
pub fn jinux_main() -> ! {
jinux_frame::init();
test_main();
loop {}
}
#[panic_handler]
fn panic(info: &PanicInfo) -> ! {
jinux_frame::test_panic_handler(info)
}
#[test_case]
fn test_println() {
jinux_frame::println!("test_println output");
}

View File

@ -1,43 +0,0 @@
#![no_std]
#![no_main]
#![feature(custom_test_frameworks)]
#![test_runner(jinux_frame::test_runner)]
#![reexport_test_harness_main = "test_main"]
use jinux_frame::timer::Timer;
extern crate alloc;
use alloc::sync::Arc;
use core::panic::PanicInfo;
use core::time::Duration;
use jinux_frame::println;
static mut TICK: usize = 0;
#[no_mangle]
pub fn jinux_main() -> ! {
jinux_frame::init();
test_main();
loop {}
}
#[panic_handler]
fn panic(info: &PanicInfo) -> ! {
jinux_frame::test_panic_handler(info)
}
#[test_case]
fn test_timer() {
x86_64::instructions::interrupts::enable();
unsafe {
let timer = Timer::new(timer_callback).unwrap();
timer.set(Duration::from_secs(1));
while TICK < 5 {}
}
}
pub fn timer_callback(timer: Arc<Timer>) {
unsafe {
TICK += 1;
println!("TICK:{}", TICK);
timer.set(Duration::from_secs(1));
}
}

View File

@ -5,11 +5,13 @@
# Update Cargo style versions (`version = "{version}"`) in file $1 # Update Cargo style versions (`version = "{version}"`) in file $1
update_cargo_versions() { update_cargo_versions() {
echo "Updating file $1"
sed -i "s/^version = \"[[:digit:]]\+\.[[:digit:]]\+\.[[:digit:]]\+\"$/version = \"${new_version}\"/g" $1 sed -i "s/^version = \"[[:digit:]]\+\.[[:digit:]]\+\.[[:digit:]]\+\"$/version = \"${new_version}\"/g" $1
} }
# Update Docker image versions (`jinuxdev/jinux:{version}`) in file $1 # Update Docker image versions (`jinuxdev/jinux:{version}`) in file $1
update_image_versions() { update_image_versions() {
echo "Updating file $1"
sed -i "s/jinuxdev\/jinux:[[:digit:]]\+\.[[:digit:]]\+\.[[:digit:]]\+/jinuxdev\/jinux:${new_version}/g" $1 sed -i "s/jinuxdev\/jinux:[[:digit:]]\+\.[[:digit:]]\+\.[[:digit:]]\+/jinuxdev\/jinux:${new_version}/g" $1
} }
@ -37,8 +39,10 @@ update_image_versions ${JINUX_SRC_DIR}/README.md
update_image_versions ${SCRIPT_DIR}/docker/README.md update_image_versions ${SCRIPT_DIR}/docker/README.md
# Update Docker image versions in workflows # Update Docker image versions in workflows
update_image_versions ${JINUX_SRC_DIR}/.github/workflows/syscall_test.yml WORKFLOWS=$(find "${JINUX_SRC_DIR}/.github/workflows/" -type f -name "*.yml")
update_image_versions ${JINUX_SRC_DIR}/.github/workflows/cargo_check.yml for workflow in $WORKFLOWS; do
update_image_versions $workflow
done
# Create or update VERSION # Create or update VERSION
echo "${new_version}" > ${VERSION_PATH} echo "${new_version}" > ${VERSION_PATH}