pub(crate) mod handler;
#[cfg(feature = "capture_tracing")]
pub(crate) mod logger;
pub(crate) mod panic;
pub(crate) mod result_map;
pub(crate) mod test_repo;
use crate::{
algos::ExecutionStrategy,
config::{Commands, RunArgs},
context::ResourceId,
error::TestErrorDetails,
runner::{result_map::ExecutionResultMap, test_repo::TestRepoInfo},
Context, DepdencyStrategy, Persister, Printer, RerunStrategy,
};
use backtrace::Backtrace;
use core::cell::RefCell;
use handler::HandlerError;
#[cfg(feature = "capture_tracing")]
use logger::default_subscriber;
use panic::PanicDetails;
use result_map::ExecutionResult;
#[cfg(feature = "capture_tracing")]
use std::sync::Arc;
use std::{collections::HashSet, time::Instant};
use test_repo::{RunnerParams, TestCase, TestRepo};
use self::{panic::PanicError, result_map::ExecutionStatus};
thread_local! {
pub(crate) static CURRENT_EXECUTABLE_FUNCTION: RefCell<Option<(u64, String)>> = const { RefCell::new(None) };
static BACKTRACE: RefCell<Vec<PanicDetails>> = const { RefCell::new(Vec::new()) };
}
#[derive(Default)]
pub struct Runner {}
impl Runner {
#[allow(private_bounds)]
pub fn test_all<T: ExecutionStrategy>(
strategy: T,
mut repo: TestRepo,
initial: Vec<ResourceId>,
) -> (ExecutionResultMap, TestRepoInfo) {
let old_panic_hook = std::panic::take_hook();
std::panic::set_hook(Box::new(|pi| {
let executable_function = CURRENT_EXECUTABLE_FUNCTION.with(move |b| b.borrow_mut().take());
let trace = Backtrace::new();
if let Some(executable_function) = executable_function {
const MESSAGE_START: &str = "message: Some(";
const MESSAGE_END: &str = "), location: Location { file: \"";
BACKTRACE.with(move |b| {
b.borrow_mut().push(PanicDetails {
backtrace: trace,
payload: None,
message: format!("{pi:?}")
.split(MESSAGE_START)
.nth(1)
.and_then(|m| m.split(MESSAGE_END).next())
.map(|s| s.to_string()),
location: pi.location().map(|l| (l.file().to_string(), l.line(), l.column())),
backtrace_function_name: executable_function.1,
test_id: executable_function.0,
})
});
} else {
tracing::warn!(?trace, "Could not find test info while panic. cannot catch panic!");
}
}));
let model = strategy.run(&mut repo, &initial);
std::panic::set_hook(old_panic_hook);
(model, repo.repo_info())
}
pub(crate) fn exec_testcase(
tc: &TestCase,
#[allow(unused_variables)] params: &RunnerParams,
) -> Result<ExecutionResult, ResourceId> {
let test_func = &tc.test_func;
let test_func_name = tc.display_name().to_string();
let test_id = tc.test_id();
CURRENT_EXECUTABLE_FUNCTION.with(move |b| {
*b.borrow_mut() = Some((test_id, test_func_name));
});
let test_id = tc.test_id();
#[cfg(feature = "capture_tracing")]
let (subscriber, tracing_bytes) = default_subscriber(params.log_level);
let start_time = Instant::now();
#[cfg(feature = "capture_tracing")]
let res = tracing::subscriber::with_default(subscriber, test_func);
#[cfg(not(feature = "capture_tracing"))]
let res = (test_func)();
let execution_time = start_time.elapsed();
#[cfg(feature = "capture_tracing")]
let tracing_logs = {
let data = Arc::try_unwrap(tracing_bytes.0)
.ok()
.and_then(|mutex| mutex.into_inner().ok().map(|wrtier| wrtier.into_inner().freeze()));
if let Some(data) = data {
String::from_utf8_lossy(&data).to_string()
} else {
tracing::warn!("tracing_logs seem to be locked");
String::new()
}
};
match res {
Ok(Ok(())) => Ok(ExecutionResult {
status: ExecutionStatus::Success,
execution_time,
#[cfg(feature = "capture_tracing")]
tracing_logs,
}),
Ok(Err(e)) => Ok(ExecutionResult {
status: ExecutionStatus::Error(TestErrorDetails::new(e)),
execution_time,
#[cfg(feature = "capture_tracing")]
tracing_logs,
}),
Err(HandlerError::NotInContext(missing)) => Err(missing),
Err(HandlerError::Panic(payload)) => {
let panic_details = BACKTRACE.with(move |b| {
let mut bt = b.borrow_mut();
if let Some(i) = bt.iter().position(|b| b.test_id == test_id) {
let mut pd = bt.remove(i);
pd.payload = Some(payload);
Some(pd)
} else {
tracing::warn!("expected backtrace, but couln't fine one");
None
}
});
Ok(ExecutionResult {
status: ExecutionStatus::Error(TestErrorDetails::new(Box::new(PanicError::new(panic_details)))),
execution_time,
#[cfg(feature = "capture_tracing")]
tracing_logs,
})
},
}
}
}
pub fn main_run(repo: TestRepo, run_config: crate::config::RunConfig<Context>) -> std::process::ExitCode {
use clap::Parser;
let args = crate::config::Args::parse();
let exit_code = match args.command {
Commands::Run { args, output } => {
let (results, info) = run_strategy(repo, run_config, DepdencyStrategy::default(), &args);
let exit_code = results.exitcode();
Printer::print_to_stdout(&results, &info);
if let Some(location) = output {
if let Err(e) = Persister::persist_to_file(results, info, args, &location) {
tracing::error!(?e, "could not store to file");
}
}
exit_code
},
Commands::ReRun { file } => {
let (results, _, args) = Persister::load_from_file(&file).unwrap();
let (results, info) = run_strategy(repo, run_config, RerunStrategy::new(results), &args);
let exit_code = results.exitcode();
Printer::print_to_stdout(&results, &info);
exit_code
},
Commands::Display { file } => {
let (results, info, _) = Persister::load_from_file(&file).unwrap();
Printer::print_to_stdout(&results, &info);
results.exitcode()
},
};
std::process::ExitCode::from(exit_code)
}
fn run_strategy<T: ExecutionStrategy>(
mut repo: TestRepo,
run_config: crate::config::RunConfig<Context>,
strategy: T,
args: &RunArgs,
) -> (ExecutionResultMap, TestRepoInfo) {
let optional_tests: HashSet<_> = args.optional_tests.iter().collect();
for c in repo.cases_mut().values_mut() {
if optional_tests.contains(&c.info.display_name) {
c.info.test_arguments.optional = true;
}
}
repo.set_runner_params(RunnerParams {
#[cfg(feature = "capture_tracing")]
log_level: args.test_tracing_log_level,
});
let initial_resources = run_config.context.get_resources();
Runner::test_all(strategy, repo, initial_resources)
}