1use crate::evidence::Evidence;
9use crate::rig::{Acquire, Rig};
10use libtest_mimic::{Arguments, Completion, Failed, Trial};
11use std::pin::Pin;
12use std::process::ExitCode;
13use std::sync::{Arc, OnceLock};
14
15pub struct TestCx {
16 pub rig: Arc<Rig>,
17 pub evidence: Evidence,
18}
19
20pub type TestFuture = Pin<Box<dyn Future<Output = Result<(), Failed>> + Send>>;
21
22pub struct BancTest {
23 name: String,
24 f: Box<dyn FnOnce(TestCx) -> TestFuture + Send>,
25}
26
27impl BancTest {
28 pub fn new(
29 name: impl Into<String>,
30 f: impl FnOnce(TestCx) -> TestFuture + Send + 'static,
31 ) -> Self {
32 BancTest { name: name.into(), f: Box::new(f) }
33 }
34}
35
36enum RigState {
38 Ready(Arc<Rig>),
39 Skip(String),
40 Fail(String),
41}
42
43pub fn run(tests: Vec<BancTest>) -> ExitCode {
44 let mut args = Arguments::from_args();
45 args.test_threads = Some(1);
47
48 let rt = Arc::new(
49 tokio::runtime::Builder::new_multi_thread()
50 .enable_all()
51 .build()
52 .expect("building tokio runtime"),
53 );
54 let rig_state: Arc<OnceLock<RigState>> = Arc::new(OnceLock::new());
55
56 let trials: Vec<Trial> = tests
57 .into_iter()
58 .map(|test| {
59 let rt = rt.clone();
60 let rig_state = rig_state.clone();
61 let name = test.name.clone();
62 Trial::ignorable_test(test.name, move || {
63 let state = rig_state.get_or_init(|| match rt.block_on(Rig::acquire()) {
64 Ok(rig) => RigState::Ready(Arc::new(rig)),
65 Err(Acquire::Skip(reason)) => RigState::Skip(reason),
66 Err(Acquire::Fail(e)) => RigState::Fail(format!("{e:#}")),
67 });
68 let rig = match state {
69 RigState::Ready(rig) => rig.clone(),
70 RigState::Skip(reason) => return Ok(Completion::ignored_with(reason.clone())),
71 RigState::Fail(e) => return Err(Failed::from(format!("rig unavailable: {e}"))),
72 };
73 let evidence = Evidence::new(&name);
74 let cx = TestCx { rig: rig.clone(), evidence: evidence.clone() };
75 let result = rt.block_on((test.f)(cx));
76 match result {
77 Ok(()) => Ok(Completion::Completed),
78 Err(failed) => {
79 let mut msg = failed
80 .message()
81 .map(|m| m.to_string())
82 .unwrap_or_else(|| "test failed".to_owned());
83 if !evidence.is_empty() {
84 let dir = artifacts_dir(&rig);
85 match evidence.persist(&dir) {
86 Ok(path) => {
87 msg.push_str(&format!(
88 "\n--- evidence (tail) ---\n{}full log: {}",
89 evidence.tail(40),
90 path.display()
91 ));
92 }
93 Err(e) => {
94 msg.push_str(&format!(
95 "\n--- evidence (tail; persist failed: {e}) ---\n{}",
96 evidence.tail(40)
97 ));
98 }
99 }
100 }
101 Err(Failed::from(msg))
102 }
103 }
104 })
105 })
106 .collect();
107
108 let conclusion = libtest_mimic::run(&args, trials);
109 conclusion.exit_code()
110}
111
112fn artifacts_dir(rig: &Rig) -> std::path::PathBuf {
113 std::env::var_os("BANC_ARTIFACTS")
114 .map(std::path::PathBuf::from)
115 .unwrap_or_else(|| rig.base_dir.join("target").join("banc-artifacts"))
116}