mirror of
https://github.com/asterinas/asterinas.git
synced 2025-06-22 00:43:24 +00:00
Extract the OSTD test runner as a kernel
This commit is contained in:
committed by
Tate, Hongliang Tian
parent
cad36ecdab
commit
be3492d7f0
9
.github/workflows/publish_osdk_and_ostd.yml
vendored
9
.github/workflows/publish_osdk_and_ostd.yml
vendored
@ -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
20
Cargo.lock
generated
@ -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"
|
||||
|
@ -1,6 +1,7 @@
|
||||
[workspace]
|
||||
resolver = "2"
|
||||
members = [
|
||||
"osdk/test-kernel",
|
||||
"ostd",
|
||||
"ostd/libs/align_ext",
|
||||
"ostd/libs/ostd-macros",
|
||||
|
6
Makefile
6
Makefile
@ -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
|
||||
|
@ -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)]
|
||||
|
@ -2,6 +2,7 @@
|
||||
|
||||
#![no_std]
|
||||
#![no_main]
|
||||
#![feature(linkage)]
|
||||
#![deny(unsafe_code)]
|
||||
extern crate ostd;
|
||||
|
||||
|
@ -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();
|
||||
|
@ -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");
|
||||
}
|
||||
|
@ -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();
|
||||
|
@ -1,4 +1,6 @@
|
||||
#![no_std]
|
||||
// The feature `linkage` is required for `ostd::main` to work.
|
||||
#![feature(linkage)]
|
||||
#![deny(unsafe_code)]
|
||||
|
||||
use ostd::prelude::*;
|
||||
|
@ -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, ¤t_crate.name, ¤t_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,
|
||||
¤t_crate.name,
|
||||
¤t_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
|
||||
|
@ -10,6 +10,7 @@ pub enum Errno {
|
||||
ExecuteCommand = 5,
|
||||
BuildCrate = 6,
|
||||
RunBundle = 7,
|
||||
BadCrateName = 8,
|
||||
}
|
||||
|
||||
/// Print error message to console
|
||||
|
14
osdk/test-kernel/Cargo.toml
Normal file
14
osdk/test-kernel/Cargo.toml
Normal 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"] }
|
@ -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");
|
||||
}
|
||||
}
|
||||
}
|
@ -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)));
|
||||
|
@ -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();
|
@ -1,6 +1,7 @@
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
#![no_std]
|
||||
#![feature(linkage)]
|
||||
#![deny(unsafe_code)]
|
||||
|
||||
use ostd::prelude::*;
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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"
|
||||
|
@ -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 }
|
||||
}
|
||||
|
@ -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),
|
||||
};
|
||||
}
|
||||
|
@ -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::*;
|
||||
}
|
||||
|
@ -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
|
||||
|
Reference in New Issue
Block a user