From bb0560530f9fea42b2c323591c7e2dc3e618b820 Mon Sep 17 00:00:00 2001 From: Zhang Junyang Date: Sat, 4 Nov 2023 17:01:12 +0800 Subject: [PATCH] Enable usermode unit test for specific crates --- .github/workflows/unit_test.yml | 4 +- Cargo.lock | 17 +++---- Cargo.toml | 61 ++++++++++++++++++++++---- README.md | 19 +++++++- framework/jinux-frame/src/lib.rs | 20 ++++++++- framework/libs/align_ext/src/lib.rs | 20 +++++---- kernel/main.rs | 10 ++--- services/libs/cpio-decoder/src/test.rs | 52 +++++++++++----------- services/libs/int-to-c-enum/src/lib.rs | 2 +- services/libs/jinux-rights/src/lib.rs | 2 + services/libs/typeflags/Cargo.toml | 5 ++- services/libs/typeflags/src/lib.rs | 13 +++--- tools/test/run_tests.py | 52 ++++++++++++++++++++++ 13 files changed, 203 insertions(+), 74 deletions(-) create mode 100644 tools/test/run_tests.py diff --git a/.github/workflows/unit_test.yml b/.github/workflows/unit_test.yml index 3f30963d8..ed6d65df9 100644 --- a/.github/workflows/unit_test.yml +++ b/.github/workflows/unit_test.yml @@ -20,6 +20,8 @@ jobs: 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. + - name: Usermode Unit test + id: usermode_unit_test + run: make test # TODO: add component check. diff --git a/Cargo.lock b/Cargo.lock index 5643b1780..ef58c18c8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -216,7 +216,7 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.38", + "syn 2.0.29", ] [[package]] @@ -368,7 +368,7 @@ dependencies = [ "proc-macro-error", "proc-macro2", "quote", - "syn 2.0.38", + "syn 2.0.29", ] [[package]] @@ -475,7 +475,7 @@ checksum = "ba330b70a5341d3bc730b8e205aaee97ddab5d9c448c4f51a7c2d924266fa8f9" dependencies = [ "proc-macro2", "quote", - "syn 2.0.38", + "syn 2.0.29", ] [[package]] @@ -567,7 +567,7 @@ version = "0.1.0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.38", + "syn 2.0.29", ] [[package]] @@ -839,7 +839,7 @@ dependencies = [ "proc-macro2", "quote", "rand", - "syn 2.0.38", + "syn 2.0.29", ] [[package]] @@ -1290,9 +1290,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.38" +version = "2.0.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e96b79aaa137db8f61e26363a0c9b47d8b4ec75da28b7d1d614c2303e232408b" +checksum = "c324c494eba9d92503e6f1ef2e6df781e78f6a7705a0202d9801b198807d518a" dependencies = [ "proc-macro2", "quote", @@ -1332,7 +1332,7 @@ checksum = "090198534930841fab3a5d1bb637cde49e339654e606195f8d9c76eeb081dc96" dependencies = [ "proc-macro2", "quote", - "syn 2.0.38", + "syn 2.0.29", ] [[package]] @@ -1405,6 +1405,7 @@ dependencies = [ "proc-macro2", "quote", "syn 1.0.109", + "typeflags-util", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index d14c54fca..edd6d08ac 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,22 +24,65 @@ members = [ "framework/jinux-frame", "framework/jinux-frame/src/arch/x86/boot/linux_boot/setup", "framework/libs/align_ext", - "services/comps/virtio", - "services/comps/input", + "framework/libs/ktest", + "framework/libs/tdx-guest", "services/comps/block", - "services/comps/network", "services/comps/framebuffer", + "services/comps/input", + "services/comps/network", "services/comps/time", - "services/libs/jinux-std", - "services/libs/jinux-rights-proc", - "services/libs/typeflags", - "services/libs/typeflags-util", - "services/libs/jinux-util", + "services/comps/virtio", "services/libs/cpio-decoder", "services/libs/int-to-c-enum", + "services/libs/int-to-c-enum/derive", + "services/libs/jinux-rights", + "services/libs/jinux-rights-proc", + "services/libs/jinux-std", + "services/libs/jinux-util", + "services/libs/keyable-arc", + "services/libs/typeflags", + "services/libs/typeflags-util", ] -exclude = ["services/libs/comp-sys/controlled", "services/libs/comp-sys/cargo-component"] +exclude = [ + "services/libs/comp-sys/cargo-component", + "services/libs/comp-sys/component", + "services/libs/comp-sys/component-macro", + "services/libs/comp-sys/controlled", +] + +[workspace.metadata] + +usermode_testable = [ + "runner", + "framework/libs/align_ext", + "framework/libs/ktest", + "services/libs/cpio-decoder", + "services/libs/int-to-c-enum", + "services/libs/int-to-c-enum/derive", + "services/libs/jinux-rights", + "services/libs/jinux-rights-proc", + "services/libs/keyable-arc", + "services/libs/typeflags", + "services/libs/typeflags-util", +] + +ktest_testable = [ + "framework/jinux-frame", + "framework/libs/tdx-guest", + "services/comps/block", + "services/comps/framebuffer", + "services/comps/input", + "services/comps/network", + "services/comps/time", + "services/comps/virtio", + "services/libs/jinux-std", + "services/libs/jinux-util", +] + +untestable = [ + "framework/jinux-frame/src/arch/x86/boot/linux_boot/setup", +] [features] intel_tdx = ["jinux-frame/intel_tdx", "jinux-std/intel_tdx"] diff --git a/README.md b/README.md index b65fcc195..c5b38d888 100644 --- a/README.md +++ b/README.md @@ -56,9 +56,22 @@ make build make run ``` -### Unit Test +### Unit Test -We can run unit tests if building succeeds. This is powered by our [ktest](framework/libs/ktest) framework. +#### User mode unit test + +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. + +There is a tool `./tools/test/run_tests.py` to run all the user mode tests, and can be invoked through Make. +```bash +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 + +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 make run KTEST=all ``` @@ -68,6 +81,8 @@ You could also specify tests in a crate or a subset of tests to run, as long as make run KTEST=jinux-frame,jinux-std ``` +#### Component check + If we want to check access control policy among components, install some standalone tools (e.g., `cargo-component`). ``` bash make tools diff --git a/framework/jinux-frame/src/lib.rs b/framework/jinux-frame/src/lib.rs index e6d657823..dcdace9f1 100644 --- a/framework/jinux-frame/src/lib.rs +++ b/framework/jinux-frame/src/lib.rs @@ -46,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; +use core::{mem, panic::PanicInfo}; #[cfg(feature = "intel_tdx")] use tdx_guest::init_tdx; use trapframe::TrapFrame; @@ -110,7 +110,22 @@ pub(crate) const fn zero() -> T { unsafe { mem::MaybeUninit::zeroed().assume_init() } } -pub fn panic_handler() { +/// The panic handler provided by Jinux Frame. +/// +/// The definition of the real panic handler is located at the kernel binary +/// crate with the `#[panic_handler]` attribute. This function provides a +/// default implementation of the panic handler, which can forwarded to by the +/// kernel binary crate. +/// +/// ```rust +/// extern crate jinux_frame; +/// #[panic_handler] +/// fn panic(info: &PanicInfo) -> ! { +/// jinux_frame::panic_handler(info); +/// } +/// ``` +pub fn panic_handler(info: &PanicInfo) -> ! { + println!("[panic]:{:#?}", info); // let mut fp: usize; // let stop = unsafe{ // Task::current().kstack.get_top() @@ -130,6 +145,7 @@ pub fn panic_handler() { // } // println!("---END BACKTRACE---"); // } + exit_qemu(QemuExitCode::Failed); } /// The exit code of x86 QEMU isa debug device. In `qemu-system-x86_64` the diff --git a/framework/libs/align_ext/src/lib.rs b/framework/libs/align_ext/src/lib.rs index a7bcb0106..f3078beb9 100644 --- a/framework/libs/align_ext/src/lib.rs +++ b/framework/libs/align_ext/src/lib.rs @@ -1,4 +1,4 @@ -#![no_std] +#![cfg_attr(not(test), no_std)] /// An extension trait for Rust integer types, including `u8`, `u16`, `u32`, /// `u64`, and `usize`, to provide methods to make integers aligned to a @@ -17,10 +17,11 @@ pub trait AlignExt { /// # Examples /// /// ``` - /// assert!(align_up(12, 2), 12); - /// assert!(align_up(12, 4), 12); - /// assert!(align_up(12, 8), 16); - /// assert!(align_up(12, 16), 16); + /// use crate::align_ext::AlignExt; + /// assert_eq!(12usize.align_up(2), 12); + /// assert_eq!(12usize.align_up(4), 12); + /// assert_eq!(12usize.align_up(8), 16); + /// assert_eq!(12usize.align_up(16), 16); /// ``` fn align_up(self, power_of_two: Self) -> Self; @@ -34,10 +35,11 @@ pub trait AlignExt { /// # Examples /// /// ``` - /// assert!(align_down(12, 2), 12); - /// assert!(align_down(12, 4), 12); - /// assert!(align_down(12, 8), 8); - /// assert!(align_down(12, 16), 0); + /// use crate::align_ext::AlignExt; + /// assert_eq!(12usize.align_down(2), 12); + /// assert_eq!(12usize.align_down(4), 12); + /// assert_eq!(12usize.align_down(8), 8); + /// assert_eq!(12usize.align_down(16), 0); /// ``` fn align_down(self, power_of_two: Self) -> Self; } diff --git a/kernel/main.rs b/kernel/main.rs index 1694c1401..c685fde05 100644 --- a/kernel/main.rs +++ b/kernel/main.rs @@ -1,7 +1,7 @@ #![no_std] #![no_main] -// 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. +// The `no_mangle`` attribute for the `jinux_main` entrypoint requires the removal of safety check. +// Please be aware that the kernel is not allowed to introduce any other unsafe operations. // #![forbid(unsafe_code)] extern crate jinux_frame; @@ -19,9 +19,5 @@ pub fn jinux_main() -> ! { #[panic_handler] fn panic(info: &PanicInfo) -> ! { - use jinux_frame::{exit_qemu, QemuExitCode}; - - println!("[panic]:{:#?}", info); - jinux_frame::panic_handler(); - exit_qemu(QemuExitCode::Failed); + jinux_frame::panic_handler(info); } diff --git a/services/libs/cpio-decoder/src/test.rs b/services/libs/cpio-decoder/src/test.rs index 2573eb467..871f73959 100644 --- a/services/libs/cpio-decoder/src/test.rs +++ b/services/libs/cpio-decoder/src/test.rs @@ -6,10 +6,13 @@ use lending_iterator::LendingIterator; fn test_decoder() { use std::process::{Command, Stdio}; + let manifest_path = std::env::var("CARGO_MANIFEST_DIR").unwrap(); + let manifest_path = std::path::Path::new(manifest_path.as_str()); + // Prepare the cpio buffer let buffer = { let mut find_process = Command::new("find") - .arg(".") + .arg(manifest_path.as_os_str()) .stdout(Stdio::piped()) .spawn() .expect("find command is not started"); @@ -26,38 +29,35 @@ fn test_decoder() { }; let mut decoder = CpioDecoder::new(buffer.as_slice()); - // 1st entry + // 1st entry must be the root entry let entry = { let entry_result = decoder.next().unwrap(); entry_result.unwrap() }; - assert!(entry.name() == "."); - assert!(entry.metadata().file_type() == FileType::Dir); - assert!(entry.metadata().ino() > 0); - // 2nd entry - let entry = { - let entry_result = decoder.next().unwrap(); - entry_result.unwrap() - }; - assert!(entry.name() == "src"); + assert_eq!(entry.name(), manifest_path.as_os_str()); assert!(entry.metadata().file_type() == FileType::Dir); assert!(entry.metadata().ino() > 0); - // 3rd entry - let mut entry = { - let entry_result = decoder.next().unwrap(); - entry_result.unwrap() - }; - assert!( - entry.name() == "src/lib.rs" - || entry.name() == "src/test.rs" - || entry.name() == "src/error.rs" - ); - assert!(entry.metadata().file_type() == FileType::File); - assert!(entry.metadata().ino() > 0); - assert!(entry.metadata().size() > 0); - let mut buffer: Vec = Vec::new(); - assert!(entry.read_all(&mut buffer).is_ok()); + // Other entries + while let Some(decode_result) = decoder.next() { + let mut entry = decode_result.unwrap(); + assert!(entry.metadata().ino() > 0); + if entry.name() == manifest_path.join("src").as_os_str() { + assert!(entry.metadata().file_type() == FileType::Dir); + assert!(entry.metadata().ino() > 0); + } else if entry.name() == manifest_path.join("src").join("lib.rs").as_os_str() + || entry.name() == manifest_path.join("src").join("test.rs").as_os_str() + || entry.name() == manifest_path.join("src").join("error.rs").as_os_str() + || entry.name() == manifest_path.join("Cargo.toml").as_os_str() + { + assert!(entry.metadata().file_type() == FileType::File); + assert!(entry.metadata().size() > 0); + let mut buffer: Vec = Vec::new(); + assert!(entry.read_all(&mut buffer).is_ok()); + } else { + panic!("unexpected entry: {:?}", entry.name()); + } + } } #[test] diff --git a/services/libs/int-to-c-enum/src/lib.rs b/services/libs/int-to-c-enum/src/lib.rs index 11de5dda6..b3a58ec49 100644 --- a/services/libs/int-to-c-enum/src/lib.rs +++ b/services/libs/int-to-c-enum/src/lib.rs @@ -35,7 +35,7 @@ //! ``` //! -#![no_std] +#![cfg_attr(not(test), no_std)] /// Error type for TryFromInt derive macro #[derive(Debug, Clone, Copy)] diff --git a/services/libs/jinux-rights/src/lib.rs b/services/libs/jinux-rights/src/lib.rs index 2c2266c4b..75222815d 100644 --- a/services/libs/jinux-rights/src/lib.rs +++ b/services/libs/jinux-rights/src/lib.rs @@ -57,6 +57,8 @@ pub type WriteOp = TRights![Write]; /// Example: /// /// ```rust +/// use jinux_rights::{Rights, TRights, TRightSet}; +/// /// pub struct Vmo(R); /// /// impl Vmo>{ diff --git a/services/libs/typeflags/Cargo.toml b/services/libs/typeflags/Cargo.toml index df3f5875d..a4bd82e61 100644 --- a/services/libs/typeflags/Cargo.toml +++ b/services/libs/typeflags/Cargo.toml @@ -9,7 +9,8 @@ edition = "2021" proc-macro = true [dependencies] +itertools = "0.10.5" proc-macro2 = "1.0" quote = "1.0" -syn = {version = "1.0.90"} -itertools = "0.10.5" +syn = { version = "1.0.90" } +typeflags-util = { path = "../typeflags-util" } diff --git a/services/libs/typeflags/src/lib.rs b/services/libs/typeflags/src/lib.rs index 2aff5b9df..669daac22 100644 --- a/services/libs/typeflags/src/lib.rs +++ b/services/libs/typeflags/src/lib.rs @@ -6,25 +6,24 @@ //! typeflags is used to define another declarive macro to define type set. //! It can be used as the following example. //! ```rust +//! use typeflags::typeflags; //! typeflags! { //! pub trait RightSet: u32 { //! struct Read = 1 << 1; //! struct Write = 1 << 2; //! } //! } -//! ``` -//! The code will generate a macro with the name as RightSet, we can use this macro to define typesets with different types. -//! Usage example: -//! ```rust +//! +//! // The above code will generate a macro with the name as RightSet, we can use this macro to define typesets with different types. +//! // Usage example: //! type O = RightSet![]; // Nil //! type R = RightSet![Read]; // Cons //! type W = RightSet![Write]; // Cons //! type RW = RightSet![Read, Write]; // Cons> //! type WR = RightSet![Write, Read]; // Cons> -//! ``` //! -//! Test Example -//! ```rust +//! // Test Example +//! extern crate typeflags_util; //! use typeflags_util::*; //! assert_eq!(O::BITS, 0); //! assert_eq!(R::BITS, 2); diff --git a/tools/test/run_tests.py b/tools/test/run_tests.py new file mode 100644 index 000000000..794924f66 --- /dev/null +++ b/tools/test/run_tests.py @@ -0,0 +1,52 @@ +#!/usr/bin/python3 + +# Use cargo metadata to get the manifest in json format. +def get_manifest(): + import json + import subprocess + manifest = subprocess.check_output( + ["cargo", "metadata", "--no-deps", "--format-version", "1"] + ) + return json.loads(manifest) + +# Run the user mode tests for the crates and exit if any test fails. +def run_usermode_tests(crates): + import os + import subprocess + for crate in crates: + print("Running tests for", crate) + result = subprocess.check_call(["cargo", "test", "--manifest-path", crate + "/Cargo.toml"]) + if result != 0: + print("Test failed for", crate) + os.exit(result) + +# The member id returned by the cargo metadata command is +# ` (path+file:///)`. +# We need a relative path as we specify them in `Cargo.toml`. +def member_id_to_crate_rel_path(member_id): + import os + annotation = member_id.split(" ")[2] + abs_path = annotation \ + .replace("(", "") \ + .replace(")", "") \ + .replace("path+file://", "") + return os.path.relpath(abs_path, os.getcwd()) + +def main(): + import os + manifest = get_manifest() + usermode_testables = manifest["metadata"]["usermode_testable"] + ktest_testables = manifest["metadata"]["ktest_testable"] + untestables = manifest["metadata"]["untestable"] + # A sanity check to make sure we have registered all crates. + all_members = sorted([member_id_to_crate_rel_path(p["id"]) for p in manifest["packages"]]) + test_members = sorted(usermode_testables + ktest_testables + untestables + ["."]) + if (all_members != test_members): + print("Test members does not match all the workspace members in Cargo.toml. " + "Please setup the testablity of all the crates in Cargo.toml correctly.") + os._exit(1) + + run_usermode_tests(usermode_testables) + +if __name__ == "__main__": + main()