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