use alloc::vec::Vec;
use crate::{
axtest_println,
framework::{AxTestDescriptor, AxTestExecutionMode, AxTestResult, TestRunResult, TestSummary},
print::{AxTestPrintFn, set_printer},
};
pub trait AxTestExecutor: Sync {
fn name(&self) -> &'static str;
fn run(&self, test_fn: fn() -> AxTestResult) -> Result<AxTestResult, &'static str>;
}
#[derive(Clone, Copy)]
struct AxNamedExecutor {
pub name: &'static str,
pub run: AxTestRunFn,
}
type AxTestRunFn = fn(fn() -> AxTestResult) -> Result<AxTestResult, &'static str>;
#[derive(Default)]
pub struct InlineExecutor;
impl AxTestExecutor for InlineExecutor {
fn name(&self) -> &'static str {
"inner"
}
fn run(&self, test_fn: fn() -> AxTestResult) -> Result<AxTestResult, &'static str> {
Ok(test_fn())
}
}
const MAX_EXECUTORS: usize = 16;
#[derive(Clone)]
pub struct AxTestInitBuilder {
default_executor_name: &'static str,
executor_registry: [Option<AxNamedExecutor>; MAX_EXECUTORS],
executor_count: usize,
crate_filters: Vec<&'static str>,
printer: Option<AxTestPrintFn>,
}
pub fn init() -> AxTestInitBuilder {
AxTestInitBuilder {
default_executor_name: "inner",
executor_registry: [None; MAX_EXECUTORS],
executor_count: 0,
crate_filters: Vec::new(),
printer: None,
}
}
fn run_inline(test_fn: fn() -> AxTestResult) -> Result<AxTestResult, &'static str> {
Ok(test_fn())
}
fn run_with_type<E: AxTestExecutor + Default>(
test_fn: fn() -> AxTestResult,
) -> Result<AxTestResult, &'static str> {
E::default().run(test_fn)
}
fn module_crate_name(module: &str) -> &str {
module.split("::").next().unwrap_or(module)
}
impl AxTestInitBuilder {
fn matches_filter(&self, module: &str) -> bool {
if self.crate_filters.is_empty() {
return true;
}
let crate_name = module_crate_name(module);
self.crate_filters.contains(&crate_name)
}
fn collect_tests(&self) -> &'static [AxTestDescriptor] {
#[allow(improper_ctypes)]
unsafe extern "C" {
#[link_name = "__axtest_array"]
static _axtest_array: AxTestDescriptor;
#[link_name = "__axtest_array_end"]
static _axtest_array_end: AxTestDescriptor;
}
unsafe {
let start = core::ptr::addr_of!(_axtest_array);
let end = core::ptr::addr_of!(_axtest_array_end);
if start.is_null() || end.is_null() || start >= end {
&[]
} else {
core::slice::from_raw_parts(start, end.offset_from(start) as usize)
}
}
}
fn register_executor(&mut self, entry: AxNamedExecutor) {
let mut idx = 0;
while idx < self.executor_count {
if let Some(existing) = self.executor_registry[idx]
&& existing.name == entry.name
{
self.executor_registry[idx] = Some(entry);
return;
}
idx += 1;
}
if self.executor_count < MAX_EXECUTORS {
self.executor_registry[self.executor_count] = Some(entry);
self.executor_count += 1;
}
}
fn find_executor(&self, name: &'static str) -> Option<AxNamedExecutor> {
let mut idx = 0;
while idx < self.executor_count {
if let Some(entry) = self.executor_registry[idx]
&& entry.name == name
{
return Some(entry);
}
idx += 1;
}
None
}
pub(crate) fn run_with_executor_name(
&self,
name: &'static str,
test_fn: fn() -> AxTestResult,
) -> Result<AxTestResult, &'static str> {
let selected = if name.is_empty() {
self.default_executor_name
} else {
name
};
if let Some(entry) = self.find_executor(selected) {
return (entry.run)(test_fn);
}
if let Some(entry) = self.find_executor(self.default_executor_name) {
return (entry.run)(test_fn);
}
run_inline(test_fn)
}
fn run_single_test(&self, test: &AxTestDescriptor) -> TestRunResult {
match test.execution_mode {
AxTestExecutionMode::Ignore => TestRunResult::Ignored,
_ => {
let result = match self.run_with_executor_name(test.executor_name, test.test_fn) {
Ok(ret) => ret,
Err(reason) => return TestRunResult::Failed(reason),
};
match (test.should_panic, result) {
(true, AxTestResult::Failed) => TestRunResult::Ok,
(true, AxTestResult::Ok) => TestRunResult::Failed(
"expected failure (`#[should_panic]`) but test passed",
),
(false, AxTestResult::Ok) => TestRunResult::Ok,
(false, AxTestResult::Failed) => TestRunResult::Failed("test failed"),
}
}
}
}
pub fn add_executor<E>(mut self, executor: E) -> Self
where
E: AxTestExecutor + Default,
{
self.register_executor(AxNamedExecutor {
name: executor.name(),
run: run_with_type::<E>,
});
self
}
pub fn set_default_by_name(mut self, name: &'static str) -> Self {
self.default_executor_name = name;
self
}
pub fn set_default<E>(self, executor: E) -> Self
where
E: AxTestExecutor,
{
self.set_default_by_name(executor.name())
}
pub fn with_filter(mut self, crate_names: &[&'static str]) -> Self {
self.crate_filters.clear();
self.crate_filters.extend_from_slice(crate_names);
self
}
pub fn set_printer(mut self, printer: AxTestPrintFn) -> Self {
self.printer = Some(printer);
self
}
pub fn run_tests(self) -> TestSummary {
if let Some(printer) = self.printer {
set_printer(printer);
}
let tests = self.collect_tests();
let selected_count = tests
.iter()
.filter(|t| self.matches_filter(t.module))
.count();
let mut passed = 0;
let mut failed = 0;
let mut ignored = 0;
axtest_println!("AXTEST_BEGIN total={}", selected_count);
axtest_println!("KTAP version 1");
axtest_println!("1..{}", selected_count);
axtest_println!("# Running {} axtests", selected_count);
if !self.crate_filters.is_empty() {
axtest_println!("# Filter: {} crate(s)", self.crate_filters.len());
}
if tests
.iter()
.any(|t| self.matches_filter(t.module) && t.should_panic)
{
axtest_println!(
"# NOTE: #[should_panic] is treated as expected-failure in this no_std test \
runtime"
);
}
let mut case_no = 0;
for test in tests.iter() {
if !self.matches_filter(test.module) {
continue;
}
case_no += 1;
axtest_println!("# START {}::{}", test.module, test.name);
axtest_println!("# module: {}", test.module);
let result = self.run_single_test(test);
match result {
TestRunResult::Ok => {
passed += 1;
axtest_println!("ok {} {}::{}", case_no, test.module, test.name);
axtest_println!(
"AXTEST_CASE status=pass module={} name={}",
test.module,
test.name
);
}
TestRunResult::Failed(reason) => {
failed += 1;
axtest_println!(
"not ok {} {}::{} # {}",
case_no,
test.module,
test.name,
reason
);
axtest_println!(
"AXTEST_CASE status=fail module={} name={} reason={}",
test.module,
test.name,
reason
);
}
TestRunResult::Ignored => {
ignored += 1;
axtest_println!(
"ok {} {}::{} # SKIP {}",
case_no,
test.module,
test.name,
test.ignore_reason
);
axtest_println!(
"AXTEST_CASE status=skip module={} name={} reason={}",
test.module,
test.name,
test.ignore_reason
);
}
}
}
axtest_println!(
"AXTEST_SUMMARY pass={} fail={} skip={} total={}",
passed,
failed,
ignored,
selected_count
);
TestSummary {
total: selected_count,
passed,
failed,
ignored,
}
}
}