#[cfg(any(miri, feature = "multi-stakker", feature = "multi-thread"))]
macro_rules! test_fn {
($(#[$attr:meta])* fn $name:ident() $code:expr) => {
#[test]
$(#[$attr])*
fn $name() {
$code
}
};
}
#[cfg(not(any(miri, feature = "multi-stakker", feature = "multi-thread")))]
macro_rules! test_fn {
($(#[$attr:meta])* fn $name:ident() $code:expr) => {
#[test]
$(#[$attr])*
fn $name() {
crate::test::test::worker::run_on_worker(|| $code);
}
};
}
#[cfg(not(any(miri, feature = "multi-stakker", feature = "multi-thread")))]
pub(crate) mod worker {
use std::sync::mpsc::{channel, Sender};
use std::sync::Mutex;
struct Job {
cb: Box<dyn FnOnce() + Send + 'static>,
ret: Sender<JobRet>,
}
struct JobRet {
panic: Option<String>,
}
struct Worker {
send: Sender<Job>,
}
static WORKER: Mutex<Option<Worker>> = Mutex::new(None);
fn start_worker() {
let mut guard = WORKER.lock().expect("Worker lock poisoned");
if guard.is_none() {
let (send, recv) = channel::<Job>();
std::thread::spawn(move || {
while let Ok(job) = recv.recv() {
let panic =
std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| (job.cb)()))
.map_err(|e| {
match e.downcast::<String>() {
Ok(v) => *v,
Err(e) => match e.downcast::<&str>() {
Ok(v) => v.to_string(),
Err(e) => {
format!("Panicked with unknown type: {:?}", e.type_id())
}
},
}
})
.err();
let ret = JobRet { panic };
job.ret.send(ret).expect("Test not waiting for result");
}
});
*guard = Some(Worker { send });
}
}
#[track_caller]
pub(crate) fn run_on_worker(cb: impl FnOnce() + Send + 'static) {
start_worker();
let (ret_send, ret_recv) = channel();
let job = Job {
cb: Box::new(cb),
ret: ret_send,
};
let mut guard = WORKER.lock().expect("Worker lock poisoned");
if let Some(ref mut worker) = *guard {
worker.send.send(job).expect("Worker thread died?");
let ret = ret_recv.recv().expect("Worker thread died?");
if let Some(msg) = ret.panic {
drop(guard);
panic!("{msg}");
}
} else {
unreachable!();
}
}
}