From b8818bb740ac6ed0b81813a842c70e0e499ff09d Mon Sep 17 00:00:00 2001 From: Zhang Junyang Date: Sat, 4 Nov 2023 16:41:30 +0800 Subject: [PATCH] Add ktest framework --- .cargo/config.toml | 1 - .github/workflows/cargo_check.yml | 1 - .github/workflows/unit_test.yml | 25 +++ Cargo.lock | 34 +++- Makefile | 18 +- README.md | 13 +- framework/jinux-frame/Cargo.toml | 1 + .../jinux-frame/src/arch/x86/boot/linker.ld | 53 +++-- .../src/arch/x86/boot/linux_boot/mod.rs | 8 +- .../src/arch/x86/boot/multiboot/mod.rs | 8 +- .../src/arch/x86/boot/multiboot2/mod.rs | 8 +- framework/jinux-frame/src/boot/mod.rs | 21 ++ framework/jinux-frame/src/lib.rs | 41 +--- framework/jinux-frame/src/sync/atomic_bits.rs | 12 +- framework/libs/ktest/Cargo.toml | 15 ++ framework/libs/ktest/src/lib.rs | 192 ++++++++++++++++++ kernel/main.rs | 17 -- services/comps/framebuffer/src/lib.rs | 4 +- services/libs/jinux-std/Cargo.toml | 1 + services/libs/jinux-std/src/lib.rs | 2 + .../libs/jinux-std/src/vm/vmar/options.rs | 10 +- services/libs/jinux-std/src/vm/vmo/options.rs | 15 +- tests/console_input.rs | 43 ---- tests/framebuffer.rs | 27 --- tests/rtc.rs | 26 --- tests/test_example.rs | 23 --- tests/timer_test.rs | 43 ---- tools/bump_version.sh | 8 +- 28 files changed, 373 insertions(+), 297 deletions(-) create mode 100644 .github/workflows/unit_test.yml create mode 100644 framework/libs/ktest/Cargo.toml create mode 100644 framework/libs/ktest/src/lib.rs delete mode 100644 tests/console_input.rs delete mode 100644 tests/framebuffer.rs delete mode 100644 tests/rtc.rs delete mode 100644 tests/test_example.rs delete mode 100644 tests/timer_test.rs diff --git a/.cargo/config.toml b/.cargo/config.toml index 2986574a0..c353f9505 100644 --- a/.cargo/config.toml +++ b/.cargo/config.toml @@ -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" 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" -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" component-check = "component check --target x86_64-custom.json -Zbuild-std=core,alloc,compiler_builtins -Zbuild-std-features=compiler-builtins-mem" diff --git a/.github/workflows/cargo_check.yml b/.github/workflows/cargo_check.yml index e25de41a6..24945b479 100644 --- a/.github/workflows/cargo_check.yml +++ b/.github/workflows/cargo_check.yml @@ -5,7 +5,6 @@ on: push: branches: - main - - releases/* jobs: test: diff --git a/.github/workflows/unit_test.yml b/.github/workflows/unit_test.yml new file mode 100644 index 000000000..3f30963d8 --- /dev/null +++ b/.github/workflows/unit_test.yml @@ -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. diff --git a/Cargo.lock b/Cargo.lock index 18178fd33..5643b1780 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -216,7 +216,7 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.28", + "syn 2.0.38", ] [[package]] @@ -368,7 +368,7 @@ dependencies = [ "proc-macro-error", "proc-macro2", "quote", - "syn 2.0.28", + "syn 2.0.38", ] [[package]] @@ -475,7 +475,7 @@ checksum = "ba330b70a5341d3bc730b8e205aaee97ddab5d9c448c4f51a7c2d924266fa8f9" dependencies = [ "proc-macro2", "quote", - "syn 2.0.28", + "syn 2.0.38", ] [[package]] @@ -567,7 +567,7 @@ version = "0.1.0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.28", + "syn 2.0.38", ] [[package]] @@ -637,6 +637,7 @@ dependencies = [ "inherit-methods-macro", "int-to-c-enum", "intrusive-collections", + "ktest", "lazy_static", "log", "multiboot2", @@ -757,6 +758,7 @@ dependencies = [ "jinux-util", "jinux-virtio", "keyable-arc", + "ktest", "lazy_static", "lending-iterator", "libflate", @@ -830,6 +832,16 @@ checksum = "078e285eafdfb6c4b434e0d31e8cfcb5115b651496faca5749b88fafd4f23bfd" name = "keyable-arc" version = "0.1.0" +[[package]] +name = "ktest" +version = "0.1.0" +dependencies = [ + "proc-macro2", + "quote", + "rand", + "syn 2.0.38", +] + [[package]] name = "lazy_static" version = "1.4.0" @@ -1061,9 +1073,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.66" +version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18fb31db3f9bddb2ea821cde30a9f70117e3f119938b5ee630b7403aa6e2ead9" +checksum = "134c189feb4956b20f6f547d2cf727d4c0fe06722b20a0eec87ed445a97f92da" dependencies = [ "unicode-ident", ] @@ -1090,9 +1102,9 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.32" +version = "1.0.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50f3b39ccfb720540debaa0164757101c08ecb8d326b15358ce76a62c7e85965" +checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" dependencies = [ "proc-macro2", ] @@ -1278,9 +1290,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.28" +version = "2.0.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04361975b3f5e348b2189d8dc55bc942f278b2d482a6a0365de5bdd62d351567" +checksum = "e96b79aaa137db8f61e26363a0c9b47d8b4ec75da28b7d1d614c2303e232408b" dependencies = [ "proc-macro2", "quote", @@ -1320,7 +1332,7 @@ checksum = "090198534930841fab3a5d1bb637cde49e339654e606195f8d9c76eeb081dc96" dependencies = [ "proc-macro2", "quote", - "syn 2.0.28", + "syn 2.0.38", ] [[package]] diff --git a/Makefile b/Makefile index 7ab0a9812..f2caac167 100644 --- a/Makefile +++ b/Makefile @@ -8,6 +8,7 @@ ENABLE_KVM ?= 1 GDB_CLIENT ?= 0 GDB_SERVER ?= 0 INTEL_TDX ?= 0 +KTEST ?= none SKIP_GRUB_MENU ?= 1 RELEASE_MODE ?= 0 # End of setting up Make varaiables @@ -22,8 +23,8 @@ KERNEL_CMDLINE += -c exit 0 endif CARGO_KBUILD_ARGS := - -CARGO_KRUN_ARGS := +CARGO_KRUN_ARGS := +GLOBAL_RUSTC_FLAGS := ifeq ($(RELEASE_MODE), 1) CARGO_KBUILD_ARGS += --release @@ -60,6 +61,11 @@ CARGO_KBUILD_ARGS += --features intel_tdx CARGO_KRUN_ARGS += --features intel_tdx endif +ifneq ($(KTEST), none) +comma := , +GLOBAL_RUSTC_FLAGS += --cfg ktest --cfg ktest=\"$(subst $(comma),\" --cfg ktest=\",$(KTEST))\" +endif + ifeq ($(SKIP_GRUB_MENU), 1) CARGO_KRUN_ARGS += --skip-grub-menu endif @@ -82,16 +88,16 @@ setup: build: @make --no-print-directory -C regression - @cargo kbuild $(CARGO_KBUILD_ARGS) + @RUSTFLAGS="$(GLOBAL_RUSTC_FLAGS)" cargo kbuild $(CARGO_KBUILD_ARGS) tools: @cd services/libs/comp-sys && cargo install --path cargo-component run: build - @cargo krun $(CARGO_KRUN_ARGS) + @RUSTFLAGS="$(GLOBAL_RUSTC_FLAGS)" cargo krun $(CARGO_KRUN_ARGS) -test: build - @cargo ktest +test: + @python3 ./tools/test/run_tests.py docs: @cargo doc # Build Rust docs diff --git a/README.md b/README.md index f90825f8e..b65fcc195 100644 --- a/README.md +++ b/README.md @@ -56,11 +56,16 @@ make build 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 -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`). @@ -73,7 +78,7 @@ Then we can use the tool to check access control policy. cargo component-check ``` -### Syscall Test +### Integration Test This command will build the syscall test binary and automatically run Jinux with the tests using QEMU. ```bash diff --git a/framework/jinux-frame/Cargo.toml b/framework/jinux-frame/Cargo.toml index 7ae8c3c16..182084521 100644 --- a/framework/jinux-frame/Cargo.toml +++ b/framework/jinux-frame/Cargo.toml @@ -13,6 +13,7 @@ volatile = { version = "0.4.5", features = ["unstable"] } buddy_system_allocator = "0.9.0" pod = { git = "https://github.com/jinzhao-dev/pod", rev = "d7dba56" } align_ext = { path = "../libs/align_ext" } +ktest = { path = "../libs/ktest" } intrusive-collections = "0.9.5" log = "0.4" lazy_static = { version = "1.0", features = ["spin_no_std"] } diff --git a/framework/jinux-frame/src/arch/x86/boot/linker.ld b/framework/jinux-frame/src/arch/x86/boot/linker.ld index ba9549430..f05898bcd 100644 --- a/framework/jinux-frame/src/arch/x86/boot/linker.ld +++ b/framework/jinux-frame/src/arch/x86/boot/linker.ld @@ -24,24 +24,6 @@ SECTIONS .text : AT(ADDR(.text) - KERNEL_VMA) { *(.text .text.*) } .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 = .; KEEP(*(.eh_frame_hdr)) @@ -53,8 +35,37 @@ SECTIONS __eh_frame_end = .; } - # The notes section are used to mark the PVH boot entry point, useful for QEMU and Xen - .notes : { *(.notes) } + .data.rel.ro : AT(ADDR(.data.rel.ro) - KERNEL_VMA) { *(.data.rel.ro .data.rel.ro.*) } + .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; -} +} \ No newline at end of file diff --git a/framework/jinux-frame/src/arch/x86/boot/linux_boot/mod.rs b/framework/jinux-frame/src/arch/x86/boot/linux_boot/mod.rs index 3d2657aa2..297e095d2 100644 --- a/framework/jinux-frame/src/arch/x86/boot/linux_boot/mod.rs +++ b/framework/jinux-frame/src/arch/x86/boot/linux_boot/mod.rs @@ -124,12 +124,6 @@ fn init_memory_regions(memory_regions: &'static Once>) { 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. #[no_mangle] 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_memory_regions, ); - jinux_main(); + crate::boot::call_jinux_main(); } diff --git a/framework/jinux-frame/src/arch/x86/boot/multiboot/mod.rs b/framework/jinux-frame/src/arch/x86/boot/multiboot/mod.rs index 411a29fa0..5276426b3 100644 --- a/framework/jinux-frame/src/arch/x86/boot/multiboot/mod.rs +++ b/framework/jinux-frame/src/arch/x86/boot/multiboot/mod.rs @@ -332,12 +332,6 @@ struct MemoryEntry { 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(); /// 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_memory_regions, ); - jinux_main(); + crate::boot::call_jinux_main(); } diff --git a/framework/jinux-frame/src/arch/x86/boot/multiboot2/mod.rs b/framework/jinux-frame/src/arch/x86/boot/multiboot2/mod.rs index ee7819967..2a94e0e70 100644 --- a/framework/jinux-frame/src/arch/x86/boot/multiboot2/mod.rs +++ b/framework/jinux-frame/src/arch/x86/boot/multiboot2/mod.rs @@ -163,12 +163,6 @@ fn init_memory_regions(memory_regions: &'static Once>) { 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. #[no_mangle] 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_memory_regions, ); - jinux_main(); + crate::boot::call_jinux_main(); } diff --git a/framework/jinux-frame/src/boot/mod.rs b/framework/jinux-frame/src/boot/mod.rs index 084620683..8c766d039 100644 --- a/framework/jinux-frame/src/boot/mod.rs +++ b/framework/jinux-frame/src/boot/mod.rs @@ -99,3 +99,24 @@ define_global_static_boot_arguments!( pub fn init() { 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!(); + } +} diff --git a/framework/jinux-frame/src/lib.rs b/framework/jinux-frame/src/lib.rs index 36312f92d..e6d657823 100644 --- a/framework/jinux-frame/src/lib.rs +++ b/framework/jinux-frame/src/lib.rs @@ -19,6 +19,8 @@ extern crate alloc; #[macro_use] +extern crate ktest; +#[macro_use] extern crate static_assertions; pub mod arch; @@ -44,7 +46,7 @@ pub use self::error::Error; pub use self::prelude::Result; use alloc::vec::Vec; use arch::irq::{IrqCallbackHandle, IrqLine}; -use core::{mem, panic::PanicInfo}; +use core::mem; #[cfg(feature = "intel_tdx")] use tdx_guest::init_tdx; use trapframe::TrapFrame; @@ -108,35 +110,6 @@ pub(crate) const fn zero() -> T { unsafe { mem::MaybeUninit::zeroed().assume_init() } } -pub trait Testable { - fn run(&self); -} - -impl Testable for T -where - T: Fn(), -{ - fn run(&self) { - print!("{}...\n", core::any::type_name::()); - 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() { // let mut fp: usize; // let stop = unsafe{ @@ -181,3 +154,11 @@ pub fn exit_qemu(exit_code: QemuExitCode) -> ! { } unreachable!() } + +#[if_cfg_ktest] +mod test { + #[ktest] + fn trivial_assertion() { + assert_eq!(0, 0); + } +} diff --git a/framework/jinux-frame/src/sync/atomic_bits.rs b/framework/jinux-frame/src/sync/atomic_bits.rs index 5b5cb682e..85656935c 100644 --- a/framework/jinux-frame/src/sync/atomic_bits.rs +++ b/framework/jinux-frame/src/sync/atomic_bits.rs @@ -284,11 +284,11 @@ impl fmt::Debug for AtomicBits { } } -#[cfg(test)] +#[if_cfg_ktest] mod test { use super::*; - #[test] + #[ktest] fn new() { let bits = AtomicBits::new_zeroes(1); assert!(bits.len() == 1); @@ -303,7 +303,7 @@ mod test { assert!(bits.len() == 65); } - #[test] + #[ktest] fn set_get() { let bits = AtomicBits::new_zeroes(128); for i in 0..bits.len() { @@ -328,7 +328,7 @@ mod test { } } - #[test] + #[ktest] fn iter_ones() { let bits = AtomicBits::new_zeroes(1); assert!(bits.iter_ones().count() == 0); @@ -353,7 +353,7 @@ mod test { assert!(bits.iter_ones().count() == 3); } - #[test] + #[ktest] fn iter_zeroes() { let bits = AtomicBits::new_ones(1); assert!(bits.iter_zeroes().count() == 0); @@ -380,7 +380,7 @@ mod test { assert!(bits.iter_zeroes().count() == 5); } - #[test] + #[ktest] fn iter() { let bits = AtomicBits::new_zeroes(7); assert!(bits.iter().all(|bit| bit == false)); diff --git a/framework/libs/ktest/Cargo.toml b/framework/libs/ktest/Cargo.toml new file mode 100644 index 000000000..f14cfac80 --- /dev/null +++ b/framework/libs/ktest/Cargo.toml @@ -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"] } diff --git a/framework/libs/ktest/src/lib.rs b/framework/libs/ktest/src/lib.rs new file mode 100644 index 000000000..00a09dab0 --- /dev/null +++ b/framework/libs/ktest/src/lib.rs @@ -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::() 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) +} diff --git a/kernel/main.rs b/kernel/main.rs index 5f750438c..1694c1401 100644 --- a/kernel/main.rs +++ b/kernel/main.rs @@ -1,11 +1,8 @@ #![no_std] #![no_main] -#![feature(custom_test_frameworks)] // 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. // #![forbid(unsafe_code)] -#![test_runner(jinux_frame::test_runner)] -#![reexport_test_harness_main = "test_main"] extern crate jinux_frame; use core::panic::PanicInfo; @@ -13,8 +10,6 @@ use jinux_frame::println; #[no_mangle] pub fn jinux_main() -> ! { - #[cfg(test)] - test_main(); jinux_frame::init(); println!("[kernel] finish init jinux_frame"); component::init_all(component::parse_metadata!()).unwrap(); @@ -22,7 +17,6 @@ pub fn jinux_main() -> ! { jinux_std::run_first_process(); } -#[cfg(not(test))] #[panic_handler] fn panic(info: &PanicInfo) -> ! { use jinux_frame::{exit_qemu, QemuExitCode}; @@ -31,14 +25,3 @@ fn panic(info: &PanicInfo) -> ! { jinux_frame::panic_handler(); 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); -} diff --git a/services/comps/framebuffer/src/lib.rs b/services/comps/framebuffer/src/lib.rs index 7c36e92a4..383bf5e2b 100644 --- a/services/comps/framebuffer/src/lib.rs +++ b/services/comps/framebuffer/src/lib.rs @@ -35,9 +35,7 @@ pub(crate) fn init() { let page_size = size / PAGE_SIZE; let start_paddr = framebuffer.address; - let io_mem = - IoMem::new(start_paddr..(start_paddr + jinux_frame::config::PAGE_SIZE * page_size)) - .unwrap(); + let io_mem = todo!("IoMem is private for components now, should fix it."); let mut buffer: Vec = Vec::with_capacity(size); for _ in 0..size { diff --git a/services/libs/jinux-std/Cargo.toml b/services/libs/jinux-std/Cargo.toml index 10e5ef326..d35b2504d 100644 --- a/services/libs/jinux-std/Cargo.toml +++ b/services/libs/jinux-std/Cargo.toml @@ -40,6 +40,7 @@ smoltcp = { version = "0.9.1", default-features = false, features = [ "socket-raw", "socket-dhcpv4", ] } +ktest = { path = "../../../framework/libs/ktest" } tdx-guest = { path = "../../../framework/libs/tdx-guest", optional = true } # parse elf file diff --git a/services/libs/jinux-std/src/lib.rs b/services/libs/jinux-std/src/lib.rs index 510fa46d8..a0f8b2883 100644 --- a/services/libs/jinux-std/src/lib.rs +++ b/services/libs/jinux-std/src/lib.rs @@ -34,6 +34,8 @@ extern crate alloc; extern crate lru; #[macro_use] extern crate controlled; +#[macro_use] +extern crate ktest; pub mod device; pub mod driver; diff --git a/services/libs/jinux-std/src/vm/vmar/options.rs b/services/libs/jinux-std/src/vm/vmar/options.rs index 15fc4b70d..cb43a81ca 100644 --- a/services/libs/jinux-std/src/vm/vmar/options.rs +++ b/services/libs/jinux-std/src/vm/vmar/options.rs @@ -132,7 +132,7 @@ impl VmarChildOptions { } } -#[cfg(test)] +#[if_cfg_ktest] mod test { use super::*; use crate::vm::page_fault_handler::PageFaultHandler; @@ -142,13 +142,13 @@ mod test { use jinux_frame::vm::VmIo; use jinux_rights::Full; - #[test] + #[ktest] fn root_vmar() { let vmar = Vmar::::new_root(); assert!(vmar.size() == ROOT_VMAR_HIGHEST_ADDR); } - #[test] + #[ktest] fn child_vmar() { let root_vmar = Vmar::::new_root(); let root_vmar_dup = root_vmar.dup().unwrap(); @@ -167,7 +167,7 @@ mod test { .is_err()); } - #[test] + #[ktest] fn map_vmo() { let root_vmar = Vmar::::new_root(); let vmo = VmoOptions::::new(PAGE_SIZE).alloc().unwrap().to_dyn(); @@ -193,7 +193,7 @@ mod test { assert!(root_vmar.read_val::(another_map_offset).unwrap() == 100); } - #[test] + #[ktest] fn handle_page_fault() { const OFFSET: usize = 0x1000_0000; let root_vmar = Vmar::::new_root(); diff --git a/services/libs/jinux-std/src/vm/vmo/options.rs b/services/libs/jinux-std/src/vm/vmo/options.rs index 468d2f453..beabfef31 100644 --- a/services/libs/jinux-std/src/vm/vmo/options.rs +++ b/services/libs/jinux-std/src/vm/vmo/options.rs @@ -516,13 +516,13 @@ impl VmoChildType for VmoSliceChild {} pub struct VmoCowChild; impl VmoChildType for VmoCowChild {} -#[cfg(test)] +#[if_cfg_ktest] mod test { use super::*; use jinux_frame::vm::VmIo; use jinux_rights::Full; - #[test] + #[ktest] fn alloc_vmo() { let vmo = VmoOptions::::new(PAGE_SIZE).alloc().unwrap(); assert!(vmo.size() == PAGE_SIZE); @@ -530,7 +530,8 @@ mod test { assert!(vmo.read_val::(0).unwrap() == 0); } - #[test] + #[ktest] + // FIXME: should_panic doesn't work with ktest, two negative makes a positive... #[should_panic] /// FIXME: alloc continuous frames is not supported now fn alloc_continuous_vmo() { @@ -541,7 +542,7 @@ mod test { assert!(vmo.size() == 10 * PAGE_SIZE); } - #[test] + #[ktest] fn write_and_read() { let vmo = VmoOptions::::new(PAGE_SIZE).alloc().unwrap(); let val = 42u8; @@ -555,7 +556,7 @@ mod test { assert!(read_val == 0x78563412) } - #[test] + #[ktest] fn slice_child() { let parent = VmoOptions::::new(2 * PAGE_SIZE).alloc().unwrap(); let parent_dup = parent.dup().unwrap(); @@ -570,7 +571,7 @@ mod test { assert!(parent.read_val::(99).unwrap() == 0x1234); } - #[test] + #[ktest] fn cow_child() { let parent = VmoOptions::::new(2 * PAGE_SIZE).alloc().unwrap(); let parent_dup = parent.dup().unwrap(); @@ -596,7 +597,7 @@ mod test { assert!(cow_child.read_val::(PAGE_SIZE + 10).unwrap() == 12345); } - #[test] + #[ktest] fn resize() { let vmo = VmoOptions::::new(PAGE_SIZE) .flags(VmoFlags::RESIZABLE) diff --git a/tests/console_input.rs b/tests/console_input.rs deleted file mode 100644 index effe35370..000000000 --- a/tests/console_input.rs +++ /dev/null @@ -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; - } -} diff --git a/tests/framebuffer.rs b/tests/framebuffer.rs deleted file mode 100644 index 4f590df1a..000000000 --- a/tests/framebuffer.rs +++ /dev/null @@ -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!"); - } -} diff --git a/tests/rtc.rs b/tests/rtc.rs deleted file mode 100644 index a353bc276..000000000 --- a/tests/rtc.rs +++ /dev/null @@ -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()); -} diff --git a/tests/test_example.rs b/tests/test_example.rs deleted file mode 100644 index b1275ccde..000000000 --- a/tests/test_example.rs +++ /dev/null @@ -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"); -} diff --git a/tests/timer_test.rs b/tests/timer_test.rs deleted file mode 100644 index bad91118a..000000000 --- a/tests/timer_test.rs +++ /dev/null @@ -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) { - unsafe { - TICK += 1; - println!("TICK:{}", TICK); - timer.set(Duration::from_secs(1)); - } -} diff --git a/tools/bump_version.sh b/tools/bump_version.sh index 37d215086..402e80a4c 100755 --- a/tools/bump_version.sh +++ b/tools/bump_version.sh @@ -5,11 +5,13 @@ # Update Cargo style versions (`version = "{version}"`) in file $1 update_cargo_versions() { + echo "Updating file $1" sed -i "s/^version = \"[[:digit:]]\+\.[[:digit:]]\+\.[[:digit:]]\+\"$/version = \"${new_version}\"/g" $1 } # Update Docker image versions (`jinuxdev/jinux:{version}`) in file $1 update_image_versions() { + echo "Updating file $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 Docker image versions in workflows -update_image_versions ${JINUX_SRC_DIR}/.github/workflows/syscall_test.yml -update_image_versions ${JINUX_SRC_DIR}/.github/workflows/cargo_check.yml +WORKFLOWS=$(find "${JINUX_SRC_DIR}/.github/workflows/" -type f -name "*.yml") +for workflow in $WORKFLOWS; do + update_image_versions $workflow +done # Create or update VERSION echo "${new_version}" > ${VERSION_PATH}