Extract the OSTD test runner as a kernel

This commit is contained in:
Zhang Junyang
2024-08-13 14:51:27 +00:00
committed by Tate, Hongliang Tian
parent cad36ecdab
commit be3492d7f0
24 changed files with 330 additions and 174 deletions

View File

@ -48,15 +48,18 @@ jobs:
steps:
- uses: actions/checkout@v4
- name: Check Publish OSTD
- name: Check Publish OSTD and the test runner
# On pull request, set `--dry-run` to check whether OSDK can publish
if: github.event_name == 'pull_request'
run: |
cd ostd
cargo publish --target ${{ matrix.target }} --dry-run
cargo doc --target ${{ matrix.target }}
cd osdk/test-kernel
cargo publish --target ${{ matrix.target }} --dry-run
cargo doc --target ${{ matrix.target }}
- name: Publish OSTD
- name: Publish OSTD and the test runner
if: github.event_name == 'push'
env:
REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }}
@ -66,4 +69,6 @@ jobs:
run: |
cd ostd
cargo publish --target x86_64-unknown-none --token ${REGISTRY_TOKEN}
cd osdk/test-kernel
cargo publish --target x86_64-unknown-none --token ${REGISTRY_TOKEN}

20
Cargo.lock generated
View File

@ -1045,6 +1045,15 @@ version = "1.18.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d"
[[package]]
name = "osdk-test-kernel"
version = "0.1.0"
dependencies = [
"ostd",
"owo-colors 4.0.0",
"unwinding",
]
[[package]]
name = "ostd"
version = "0.7.0"
@ -1075,7 +1084,7 @@ dependencies = [
"ostd-macros",
"ostd-pod",
"ostd-test",
"owo-colors",
"owo-colors 3.5.0",
"rsdp",
"spin 0.9.8",
"static_assertions",
@ -1119,9 +1128,6 @@ dependencies = [
[[package]]
name = "ostd-test"
version = "0.1.0"
dependencies = [
"owo-colors",
]
[[package]]
name = "owo-colors"
@ -1129,6 +1135,12 @@ version = "3.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c1b04fb49957986fdce4d6ee7a65027d55d4b6d2265e5848bbb507b58ccfdb6f"
[[package]]
name = "owo-colors"
version = "4.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "caff54706df99d2a78a5a4e3455ff45448d81ef1bb63c22cd14052ca0e993a3f"
[[package]]
name = "paste"
version = "1.0.14"

View File

@ -1,6 +1,7 @@
[workspace]
resolver = "2"
members = [
"osdk/test-kernel",
"ostd",
"ostd/libs/align_ext",
"ostd/libs/ostd-macros",

View File

@ -110,6 +110,7 @@ NON_OSDK_CRATES := \
# In contrast, OSDK crates depend on OSTD (or being `ostd` itself)
# and need to be built or tested with OSDK.
OSDK_CRATES := \
osdk/test-kernel \
ostd \
ostd/libs/linux-bzimage/setup \
kernel \
@ -130,7 +131,10 @@ all: build
# To uninstall, do `cargo uninstall cargo-osdk`
.PHONY: install_osdk
install_osdk:
@cargo install cargo-osdk --path osdk
@# The `OSDK_LOCAL_DEV` environment variable is used for local development
@# without the need to publish the changes of OSDK's self-hosted
@# dependencies to `crates.io`.
@OSDK_LOCAL_DEV=1 cargo install cargo-osdk --path osdk
# This will install OSDK if it is not already installed
# To update OSDK, we need to run `install_osdk` manually

View File

@ -13,6 +13,7 @@
#![feature(int_roundings)]
#![feature(iter_repeat_n)]
#![feature(let_chains)]
#![feature(linkage)]
#![feature(linked_list_remove)]
#![feature(negative_impls)]
#![feature(register_tool)]

View File

@ -2,6 +2,7 @@
#![no_std]
#![no_main]
#![feature(linkage)]
#![deny(unsafe_code)]
extern crate ostd;

View File

@ -1,8 +1,7 @@
// SPDX-License-Identifier: MPL-2.0
//! The base crate is the OSDK generated crate that is ultimately built by cargo.
//! It will depend on the kernel crate.
//!
//! It will depend on the to-be-built kernel crate or the to-be-tested crate.
use std::{
fs,
@ -12,10 +11,16 @@ use std::{
use crate::util::get_cargo_metadata;
/// Create a new base crate that will be built by cargo.
///
/// The dependencies of the base crate will be the target crate. If
/// `link_unit_test_runner` is set to true, the base crate will also depend on
/// the `ostd-test-runner` crate.
pub fn new_base_crate(
base_crate_path: impl AsRef<Path>,
dep_crate_name: &str,
dep_crate_path: impl AsRef<Path>,
link_unit_test_runner: bool,
) {
let workspace_root = {
let meta = get_cargo_metadata(None::<&str>, None::<&[&str]>).unwrap();
@ -82,7 +87,7 @@ pub fn new_base_crate(
fs::write("src/main.rs", main_rs).unwrap();
// Add dependencies to the Cargo.toml
add_manifest_dependency(dep_crate_name, dep_crate_path);
add_manifest_dependency(dep_crate_name, dep_crate_path, link_unit_test_runner);
// Copy the manifest configurations from the target crate to the base crate
copy_profile_configurations(workspace_root);
@ -94,7 +99,11 @@ pub fn new_base_crate(
std::env::set_current_dir(original_dir).unwrap();
}
fn add_manifest_dependency(crate_name: &str, crate_path: impl AsRef<Path>) {
fn add_manifest_dependency(
crate_name: &str,
crate_path: impl AsRef<Path>,
link_unit_test_runner: bool,
) {
let mainfest_path = "Cargo.toml";
let mut manifest: toml::Table = {
@ -112,13 +121,26 @@ fn add_manifest_dependency(crate_name: &str, crate_path: impl AsRef<Path>) {
let dependencies = manifest.get_mut("dependencies").unwrap();
let dep = toml::Table::from_str(&format!(
let target_dep = toml::Table::from_str(&format!(
"{} = {{ path = \"{}\", default-features = false }}",
crate_name,
crate_path.as_ref().display()
))
.unwrap();
dependencies.as_table_mut().unwrap().extend(dep);
dependencies.as_table_mut().unwrap().extend(target_dep);
if link_unit_test_runner {
let dep_str = match option_env!("OSDK_LOCAL_DEV") {
Some("1") => "osdk-test-kernel = { path = \"../../../osdk/test-kernel\" }",
_ => concat!(
"osdk-test-kernel = { version = \"",
env!("CARGO_PKG_VERSION"),
"\" }"
),
};
let test_runner_dep = toml::Table::from_str(dep_str).unwrap();
dependencies.as_table_mut().unwrap().extend(test_runner_dep);
}
let content = toml::to_string(&manifest).unwrap();
fs::write(mainfest_path, content).unwrap();

View File

@ -171,7 +171,7 @@ fn install_setup_with_arch(
cmd.arg("install").arg("linux-bzimage-setup");
cmd.arg("--force");
cmd.arg("--root").arg(install_dir.as_ref());
if std::env::var("AUTO_TEST").is_ok() || std::env::var("OSDK_INTEGRATION_TEST").is_ok() {
if matches!(option_env!("OSDK_LOCAL_DEV"), Some("1")) {
cmd.arg("--path")
.arg("../../../ostd/libs/linux-bzimage/setup");
}

View File

@ -72,6 +72,7 @@ pub fn create_base_and_cached_build(
&base_crate_path,
&get_current_crate_info().name,
get_current_crate_info().path,
false,
);
let original_dir = std::env::current_dir().unwrap();
std::env::set_current_dir(&base_crate_path).unwrap();

View File

@ -1,4 +1,6 @@
#![no_std]
// The feature `linkage` is required for `ostd::main` to work.
#![feature(linkage)]
#![deny(unsafe_code)]
use ostd::prelude::*;

View File

@ -7,6 +7,8 @@ use crate::{
base_crate::new_base_crate,
cli::TestArgs,
config::{scheme::ActionChoice, Config},
error::Errno,
error_msg,
util::{
get_cargo_metadata, get_current_crate_info, get_target_directory, parse_package_id_string,
},
@ -25,7 +27,26 @@ pub fn test_current_crate(config: &Config, args: &TestArgs) {
let cargo_target_directory = get_target_directory();
let osdk_output_directory = cargo_target_directory.join(DEFAULT_TARGET_RELPATH);
let target_crate_dir = osdk_output_directory.join("base");
new_base_crate(&target_crate_dir, &current_crate.name, &current_crate.path);
// A special case is that we use OSDK to test the OSDK test runner crate
// itself. We check it by name.
let runner_self_test = if current_crate.name == "osdk-test-kernel" {
if matches!(option_env!("OSDK_LOCAL_DEV"), Some("1")) {
true
} else {
error_msg!("The tested crate name collides with the OSDK test runner crate");
std::process::exit(Errno::BadCrateName as _);
}
} else {
false
};
new_base_crate(
&target_crate_dir,
&current_crate.name,
&current_crate.path,
!runner_self_test,
);
let main_rs_path = target_crate_dir.join("src").join("main.rs");
@ -39,19 +60,29 @@ pub fn test_current_crate(config: &Config, args: &TestArgs) {
ktest_crate_whitelist.push(name.clone());
}
let ktest_static_var = format!(
// Append the ktest static variable and the runner reference to the
// `main.rs` file.
let ktest_main_rs = format!(
r#"
{}
#[no_mangle]
pub static KTEST_TEST_WHITELIST: Option<&[&str]> = {};
#[no_mangle]
pub static KTEST_CRATE_WHITELIST: Option<&[&str]> = Some(&{:#?});
"#,
ktest_test_whitelist, ktest_crate_whitelist,
);
// Append the ktest static variable to the main.rs file
"#,
if runner_self_test {
""
} else {
"extern crate osdk_test_kernel;"
},
ktest_test_whitelist,
ktest_crate_whitelist,
);
let mut main_rs_content = fs::read_to_string(&main_rs_path).unwrap();
main_rs_content.push_str(&ktest_static_var);
main_rs_content.push_str(&ktest_main_rs);
fs::write(&main_rs_path, main_rs_content).unwrap();
// Build the kernel with the given base crate

View File

@ -10,6 +10,7 @@ pub enum Errno {
ExecuteCommand = 5,
BuildCrate = 6,
RunBundle = 7,
BadCrateName = 8,
}
/// Print error message to console

View File

@ -0,0 +1,14 @@
[package]
name = "osdk-test-kernel"
version = "0.1.0"
edition = "2021"
description = "The OSTD-based kernel for running unit tests with OSDK."
license = "MPL-2.0"
repository ="https://github.com/asterinas/asterinas"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
ostd = { version = "0.7.0", path = "../../ostd" }
owo-colors = "4.0.0"
unwinding = { version = "0.2.2", default-features = false, features = ["fde-gnu-eh-frame-hdr", "hide-trace", "panic", "personality", "unwinder"] }

View File

@ -1,24 +1,59 @@
// SPDX-License-Identifier: MPL-2.0
//! Test runner enabling control over the tests.
//!
//! The OSTD unit test runner is a kernel that runs the tests defined by the
//! `#[ostd::ktest]` attribute. The kernel should be automatically selected to
//! run when OSDK is used to test a specific crate.
use alloc::{collections::BTreeSet, string::String, vec::Vec};
use core::format_args;
#![no_std]
#![forbid(unsafe_code)]
use owo_colors::OwoColorize;
extern crate alloc;
use crate::{
path::{KtestPath, SuffixTrie},
tree::{KtestCrate, KtestTree},
CatchUnwindImpl, KtestError, KtestItem, KtestIter,
mod path;
mod tree;
use alloc::{boxed::Box, collections::BTreeSet, string::String, vec::Vec};
use core::{any::Any, format_args};
use ostd::{
early_print,
ktest::{
get_ktest_crate_whitelist, get_ktest_test_whitelist, KtestError, KtestItem, KtestIter,
},
};
use owo_colors::OwoColorize;
use path::{KtestPath, SuffixTrie};
use tree::{KtestCrate, KtestTree};
pub enum KtestResult {
Ok,
Failed,
}
/// The entry point of the test runner.
#[ostd::ktest::main]
fn main() {
use ostd::task::TaskOptions;
let test_task = move || {
use alloc::string::ToString;
use ostd::arch::qemu::{exit_qemu, QemuExitCode};
match run_ktests(
get_ktest_test_whitelist().map(|s| s.iter().map(|s| s.to_string())),
get_ktest_crate_whitelist(),
) {
KtestResult::Ok => exit_qemu(QemuExitCode::Success),
KtestResult::Failed => exit_qemu(QemuExitCode::Failed),
};
};
let _ = TaskOptions::new(test_task).data(()).spawn();
unreachable!("The spawn method will NOT return in the boot context")
}
/// Run all the tests registered by `#[ktest]` in the `.ktest_array` section.
///
/// Need to provide a print function `print` to print the test result, and a `catch_unwind`
@ -32,27 +67,18 @@ pub enum KtestResult {
///
/// If a test inside a crate fails, the test runner will continue to run the rest of the tests
/// inside the crate. But the tests in the following crates will not be run.
pub fn run_ktests<PrintFn, PathsIter>(
print: &PrintFn,
catch_unwind: &CatchUnwindImpl,
fn run_ktests<PathsIter>(
test_whitelist: Option<PathsIter>,
crate_whitelist: Option<&[&str]>,
) -> KtestResult
where
PrintFn: Fn(core::fmt::Arguments),
PathsIter: Iterator<Item = String>,
{
macro_rules! print {
($fmt: literal $(, $($arg: tt)+)?) => {
print(format_args!($fmt $(, $($arg)+)?))
}
}
let whitelist_trie =
test_whitelist.map(|paths| SuffixTrie::from_paths(paths.map(|p| KtestPath::from(&p))));
let tree = KtestTree::from_iter(KtestIter::new());
print!(
early_print!(
"\n[ktest runner] running {} tests in {} crates\n",
tree.nr_tot_tests(),
tree.nr_tot_crates()
@ -62,36 +88,22 @@ where
for crate_ in tree.iter() {
if let Some(crate_set) = &crate_set {
if !crate_set.contains(crate_.name()) {
print!("\n[ktest runner] skipping crate \"{}\".\n", crate_.name());
early_print!("\n[ktest runner] skipping crate \"{}\".\n", crate_.name());
continue;
}
}
match run_crate_ktests(crate_, print, catch_unwind, &whitelist_trie) {
match run_crate_ktests(crate_, &whitelist_trie) {
KtestResult::Ok => {}
KtestResult::Failed => return KtestResult::Failed,
}
}
print!("\n[ktest runner] All crates tested.\n");
early_print!("\n[ktest runner] All crates tested.\n");
KtestResult::Ok
}
fn run_crate_ktests<PrintFn>(
crate_: &KtestCrate,
print: &PrintFn,
catch_unwind: &CatchUnwindImpl,
whitelist: &Option<SuffixTrie>,
) -> KtestResult
where
PrintFn: Fn(core::fmt::Arguments),
{
macro_rules! print {
($fmt: literal $(, $($arg: tt)+)?) => {
print(format_args!($fmt $(, $($arg)+)?))
}
}
fn run_crate_ktests(crate_: &KtestCrate, whitelist: &Option<SuffixTrie>) -> KtestResult {
let crate_name = crate_.name();
print!(
early_print!(
"\nrunning {} tests in crate \"{}\"\n\n",
crate_.nr_tot_tests(),
crate_name
@ -110,19 +122,22 @@ where
continue;
}
}
print!(
early_print!(
"test {}::{} ...",
test.info().module_path,
test.info().fn_name
);
debug_assert_eq!(test.info().package, crate_name);
match test.run(catch_unwind) {
match test.run(
&(unwinding::panic::catch_unwind::<(), fn()>
as fn(fn()) -> Result<(), Box<(dyn Any + Send + 'static)>>),
) {
Ok(()) => {
print!(" {}\n", "ok".green());
early_print!(" {}\n", "ok".green());
passed += 1;
}
Err(e) => {
print!(" {}\n", "FAILED".red());
early_print!(" {}\n", "FAILED".red());
failed_tests.push((test.clone(), e.clone()));
}
}
@ -130,19 +145,21 @@ where
}
let failed = failed_tests.len();
if failed == 0 {
print!("\ntest result: {}.", "ok".green());
early_print!("\ntest result: {}.", "ok".green());
} else {
print!("\ntest result: {}.", "FAILED".red());
early_print!("\ntest result: {}.", "FAILED".red());
}
print!(
early_print!(
" {} passed; {} failed; {} filtered out.\n",
passed, failed, filtered
passed,
failed,
filtered
);
assert!(passed + failed + filtered == crate_.nr_tot_tests());
if failed > 0 {
print!("\nfailures:\n\n");
early_print!("\nfailures:\n\n");
for (t, e) in failed_tests {
print!(
early_print!(
"---- {}:{}:{} - {} ----\n\n",
t.info().source,
t.info().line,
@ -151,18 +168,18 @@ where
);
match e {
KtestError::Panic(s) => {
print!("[caught panic] {}\n", s);
early_print!("[caught panic] {}\n", s);
}
KtestError::ShouldPanicButNoPanic => {
print!("test did not panic as expected\n");
early_print!("test did not panic as expected\n");
}
KtestError::ExpectedPanicNotMatch(expected, s) => {
print!("[caught panic] expected panic not match\n");
print!("expected: {}\n", expected);
print!("caught: {}\n", s);
early_print!("[caught panic] expected panic not match\n");
early_print!("expected: {}\n", expected);
early_print!("caught: {}\n", s);
}
KtestError::Unknown => {
print!("[caught panic] unknown panic payload! (fatal panic handling error in ktest)\n");
early_print!("[caught panic] unknown panic payload! (fatal panic handling error in ktest)\n");
}
}
}

View File

@ -112,11 +112,13 @@ impl Display for KtestPath {
}
}
#[cfg(test)]
#[cfg(ktest)]
mod path_test {
use ostd::prelude::ktest;
use super::*;
#[test]
#[ktest]
fn test_ktest_path() {
let mut path = KtestPath::new();
path.push_back("a");
@ -129,7 +131,7 @@ mod path_test {
assert_eq!(path.pop_back(), None);
}
#[test]
#[ktest]
fn test_ktest_path_starts_with() {
let mut path = KtestPath::new();
path.push_back("a");
@ -144,7 +146,7 @@ mod path_test {
assert!(!path.starts_with(&KtestPath::from("d")));
}
#[test]
#[ktest]
fn test_ktest_path_ends_with() {
let mut path = KtestPath::new();
path.push_back("a");
@ -238,8 +240,10 @@ impl Default for SuffixTrie {
}
}
#[cfg(test)]
#[cfg(ktest)]
mod suffix_trie_test {
use ostd::prelude::ktest;
use super::*;
static TEST_PATHS: &[&str] = &[
@ -252,7 +256,7 @@ mod suffix_trie_test {
"m::n",
];
#[test]
#[ktest]
fn test_contains() {
let trie = SuffixTrie::from_paths(TEST_PATHS.iter().map(|&s| KtestPath::from(s)));
@ -269,7 +273,7 @@ mod suffix_trie_test {
assert!(!trie.contains(KtestPath::from("n").iter()));
}
#[test]
#[ktest]
fn test_matches() {
let trie = SuffixTrie::from_paths(TEST_PATHS.iter().map(|&s| KtestPath::from(s)));

View File

@ -213,8 +213,10 @@ impl<'a> Iterator for KtestModuleIter<'a> {
}
}
#[cfg(test)]
#[cfg(ktest)]
mod tests {
use ostd::prelude::ktest;
use super::*;
macro_rules! gen_test_case {
@ -227,7 +229,7 @@ mod tests {
KtestItem::new(
dummy_fn,
(false, None),
crate::KtestItemInfo {
ostd::ktest::KtestItemInfo {
module_path: m,
fn_name: f,
package: p,
@ -250,7 +252,7 @@ mod tests {
}};
}
#[test]
#[ktest]
fn test_tree_iter() {
let tree = gen_test_case!();
let mut iter = tree.iter();
@ -261,7 +263,7 @@ mod tests {
assert!(iter.next().is_none());
}
#[test]
#[ktest]
fn test_crate_iter() {
let tree = gen_test_case!();
for crate_ in tree.iter() {
@ -285,7 +287,7 @@ mod tests {
}
}
#[test]
#[ktest]
fn test_module_iter() {
let tree = gen_test_case!();
let mut collection = Vec::<&KtestItem>::new();

View File

@ -1,6 +1,7 @@
// SPDX-License-Identifier: MPL-2.0
#![no_std]
#![feature(linkage)]
#![deny(unsafe_code)]
use ostd::prelude::*;

View File

@ -1,6 +1,8 @@
// SPDX-License-Identifier: MPL-2.0
#![no_std]
// The feature `linkage` is required for `ostd::main` to work.
#![feature(linkage)]
extern crate alloc;

View File

@ -7,12 +7,13 @@ use quote::quote;
use rand::{distributions::Alphanumeric, Rng};
use syn::{parse_macro_input, Expr, Ident, ItemFn};
/// This macro is used to mark the kernel entry point.
/// A macro attribute to mark the kernel entry point.
///
/// # Example
///
/// ```ignore
/// #![no_std]
/// #![feature(linkage)]
///
/// use ostd::prelude::*;
///
@ -28,8 +29,37 @@ pub fn main(_attr: TokenStream, item: TokenStream) -> TokenStream {
quote!(
#[no_mangle]
pub fn __ostd_main() -> ! {
ostd::init();
#[linkage = "weak"]
extern "Rust" fn __ostd_main() -> ! {
// SAFETY: The function is called only once on the BSP.
unsafe { ostd::init() };
#main_fn_name();
ostd::prelude::abort();
}
#main_fn
)
.into()
}
/// A macro attribute for the unit test kernel entry point.
///
/// This macro is used for internal OSDK implementation. Do not use it
/// directly.
///
/// It is a strong version of the `main` macro attribute. So if it exists (
/// which means the unit test kernel is linked to perform testing), the actual
/// kernel entry point will be replaced by this one.
#[proc_macro_attribute]
pub fn test_main(_attr: TokenStream, item: TokenStream) -> TokenStream {
let main_fn = parse_macro_input!(item as ItemFn);
let main_fn_name = &main_fn.sig.ident;
quote!(
#[no_mangle]
extern "Rust" fn __ostd_main() -> ! {
// SAFETY: The function is called only once on the BSP.
unsafe { ostd::init() };
#main_fn_name();
ostd::prelude::abort();
}

View File

@ -2,11 +2,8 @@
name = "ostd-test"
version = "0.1.0"
edition = "2021"
description = "The kernel mode testing framework of OSTD"
description = "The kernel mode unit testing framework of OSTD"
license = "MPL-2.0"
repository ="https://github.com/asterinas/asterinas"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
owo-colors = "3.5.0"

View File

@ -45,12 +45,6 @@
//!
//! Any crates using the ostd-test framework should be linked with ostd.
//!
//! ```toml
//! # Cargo.toml
//! [dependencies]
//! ostd = { path = "relative/path/to/ostd" }
//! ```
//!
//! 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]`
@ -58,27 +52,10 @@
//! 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 simply
//! need to set a list of cfgs by specifying `KTEST=1` to the Makefile, e.g.:
//!
//! ```bash
//! make run KTEST=1
//! ```
//!
//! Also, you can run a subset of tests by specifying the `KTEST_WHITELIST` variable.
//! This is achieved by a whitelist filter on the test name.
//!
//! ```bash
//! make run KTEST=1 KTEST_WHITELIST=failing_assertion,ostd::test::expect_panic
//! ```
//!
//! `KTEST_CRATES` variable is used to specify in which crates the tests to be run.
//! This is achieved by conditionally compiling the test module using the `#[cfg]`.
//!
//! ```bash
//! make run KTEST=1 KTEST_CRATES=ostd
//! ``
//! with the `ktest` framework, OSDK will set the `RUSTFLAGS` environment variable
//! to pass the cfgs to all rustc invocations. To run the tests, you simply need
//! to use the command `cargo osdk test` in the crate directory. For more information,
//! please refer to the OSDK documentation.
//!
//! We support the `#[should_panic]` attribute just in the same way as the standard
//! library do, but the implementation is quite slow currently. Use it with cautious.
@ -90,10 +67,6 @@
#![cfg_attr(not(test), no_std)]
#![feature(panic_info_message)]
pub mod path;
pub mod runner;
pub mod tree;
extern crate alloc;
use alloc::{boxed::Box, string::String};
@ -113,6 +86,7 @@ impl core::fmt::Display for PanicInfo {
}
}
/// The error that may occur during the test.
#[derive(Clone)]
pub enum KtestError {
Panic(Box<PanicInfo>),
@ -121,13 +95,22 @@ pub enum KtestError {
Unknown,
}
/// The information of the unit test.
#[derive(Clone, PartialEq, Debug)]
pub struct KtestItemInfo {
/// The path of the module, not including the function name.
///
/// It would be separated by `::`.
pub module_path: &'static str,
/// The name of the unit test function.
pub fn_name: &'static str,
/// The name of the crate.
pub package: &'static str,
/// The source file where the test function resides.
pub source: &'static str,
/// The line number of the test function in the file.
pub line: usize,
/// The column number of the test function in the file.
pub col: usize,
}
@ -141,6 +124,11 @@ pub struct KtestItem {
type CatchUnwindImpl = fn(f: fn() -> ()) -> Result<(), Box<dyn core::any::Any + Send>>;
impl KtestItem {
/// Create a new [`KtestItem`].
///
/// Do not use this function directly. Instead, use the `#[ktest]`
/// attribute to mark the test function.
#[doc(hidden)]
pub const fn new(
fn_: fn() -> (),
should_panic: (bool, Option<&'static str>),
@ -153,6 +141,7 @@ impl KtestItem {
}
}
/// Get the information of the test.
pub fn info(&self) -> &KtestItemInfo {
&self.info
}
@ -206,12 +195,22 @@ macro_rules! ktest_array {
}};
}
/// The iterator of the ktest array.
pub struct KtestIter {
index: usize,
}
impl Default for KtestIter {
fn default() -> Self {
Self::new()
}
}
impl KtestIter {
fn new() -> Self {
/// Create a new [`KtestIter`].
///
/// It will iterate over all the tests (marked with `#[ktest]`).
pub fn new() -> Self {
Self { index: 0 }
}
}
@ -225,3 +224,28 @@ impl core::iter::Iterator for KtestIter {
Some(ktest_item.clone())
}
}
// The whitelists that will be generated by the OSDK as static consts.
// They deliver the target tests that the user wants to run.
extern "Rust" {
static KTEST_TEST_WHITELIST: Option<&'static [&'static str]>;
static KTEST_CRATE_WHITELIST: Option<&'static [&'static str]>;
}
/// Get the whitelist of the tests.
///
/// The whitelist is generated by the OSDK runner, indicating name of the
/// target tests that the user wants to run.
pub fn get_ktest_test_whitelist() -> Option<&'static [&'static str]> {
// SAFETY: The two extern statics in the base crate are generated by OSDK.
unsafe { KTEST_TEST_WHITELIST }
}
/// Get the whitelist of the crates.
///
/// The whitelist is generated by the OSDK runner, indicating the target crate
/// that the user wants to test.
pub fn get_ktest_crate_whitelist() -> Option<&'static [&'static str]> {
// SAFETY: The two extern statics in the base crate are generated by OSDK.
unsafe { KTEST_CRATE_WHITELIST }
}

View File

@ -115,8 +115,9 @@ pub fn init() {
///
/// Any kernel that uses the `ostd` crate should define a function marked with
/// `ostd::main` as the entrypoint.
pub fn call_ostd_main() -> ! {
#[cfg(not(ktest))]
///
/// This function should be only called from the bootloader-specific module.
pub(crate) fn call_ostd_main() -> ! {
unsafe {
// The entry point of kernel code, which should be defined by the package that
// uses OSTD.
@ -125,42 +126,4 @@ pub fn call_ostd_main() -> ! {
}
__ostd_main();
}
#[cfg(ktest)]
unsafe {
use crate::task::TaskOptions;
crate::init();
// The whitelists that will be generated by OSDK runner as static consts.
extern "Rust" {
static KTEST_TEST_WHITELIST: Option<&'static [&'static str]>;
static KTEST_CRATE_WHITELIST: Option<&'static [&'static str]>;
}
let test_task = move || {
run_ktests(KTEST_TEST_WHITELIST, KTEST_CRATE_WHITELIST);
};
let _ = TaskOptions::new(test_task).data(()).spawn();
unreachable!("The spawn method will NOT return in the boot context")
}
}
fn run_ktests(test_whitelist: Option<&[&str]>, crate_whitelist: Option<&[&str]>) -> ! {
use alloc::{boxed::Box, string::ToString};
use core::any::Any;
use crate::arch::qemu::{exit_qemu, QemuExitCode};
let fn_catch_unwind = &(unwinding::panic::catch_unwind::<(), fn()>
as fn(fn()) -> Result<(), Box<(dyn Any + Send + 'static)>>);
use ostd_test::runner::{run_ktests, KtestResult};
match run_ktests(
&crate::console::early_print,
fn_catch_unwind,
test_whitelist.map(|s| s.iter().map(|s| s.to_string())),
crate_whitelist,
) {
KtestResult::Ok => exit_qemu(QemuExitCode::Success),
KtestResult::Failed => exit_qemu(QemuExitCode::Failed),
};
}

View File

@ -56,10 +56,15 @@ pub(crate) use crate::cpu::local::cpu_local_cell;
/// This function represents the first phase booting up the system. It makes
/// all functionalities of OSTD available after the call.
///
/// TODO: We need to refactor this function to make it more modular and
/// make inter-initialization-dependencies more clear and reduce usages of
/// boot stage only global variables.
pub fn init() {
/// # Safety
///
/// This function should be called only once and only on the BSP.
//
// TODO: We need to refactor this function to make it more modular and
// make inter-initialization-dependencies more clear and reduce usages of
// boot stage only global variables.
#[doc(hidden)]
pub unsafe fn init() {
arch::enable_cpu_features();
arch::serial::init();
@ -131,8 +136,14 @@ mod test {
}
}
/// The module re-exports everything from the ktest crate
#[cfg(ktest)]
#[doc(hidden)]
pub mod ktest {
//! The module re-exports everything from the [`ostd_test`] crate, as well
//! as the test entry point macro.
//!
//! It is rather discouraged to use the definitions here directly. The
//! `ktest` attribute is sufficient for all normal use cases.
pub use ostd_macros::test_main as main;
pub use ostd_test::*;
}

View File

@ -20,6 +20,13 @@ update_package_version() {
sed -i "0,/${pattern}/s/${pattern}/version = \"${new_version}\"/1" $1
}
# Update the version of the `ostd` dependency (`ostd = { version = "", ...`) in file $1
update_ostd_dep_version() {
echo "Updating file $1"
pattern="^ostd = { version = \"[[:digit:]]\+\.[[:digit:]]\+\.[[:digit:]]\+\""
sed -i "0,/${pattern}/s/${pattern}/ostd = { version = \"${new_version}\"/1" $1
}
# Update Docker image versions (`asterinas/asterinas:{version}`) in file $1
update_image_versions() {
echo "Updating file $1"
@ -98,6 +105,7 @@ ASTER_SRC_DIR=${SCRIPT_DIR}/..
DOCS_DIR=${ASTER_SRC_DIR}/docs
OSTD_CARGO_TOML_PATH=${ASTER_SRC_DIR}/ostd/Cargo.toml
OSDK_CARGO_TOML_PATH=${ASTER_SRC_DIR}/osdk/Cargo.toml
OSTD_TEST_RUNNER_CARGO_TOML_PATH=${ASTER_SRC_DIR}/osdk/test-kernel/Cargo.toml
VERSION_PATH=${ASTER_SRC_DIR}/VERSION
current_version=$(cat ${VERSION_PATH})
@ -114,6 +122,8 @@ new_version=$(bump_version ${current_version})
# Update the package version in Cargo.toml
update_package_version ${OSTD_CARGO_TOML_PATH}
update_package_version ${OSDK_CARGO_TOML_PATH}
update_package_version ${OSTD_TEST_RUNNER_CARGO_TOML_PATH}
update_ostd_dep_version ${OSTD_TEST_RUNNER_CARGO_TOML_PATH}
# Automatically bump Cargo.lock file
cargo update -p asterinas --precise $new_version