use super::{Config, Entry, Launch, Phase, Shared, State};
use std::cell::RefCell;
use std::sync::{Arc, Condvar, Mutex, atomic::AtomicBool};
thread_local! {
static OBSERVED: RefCell<Option<Arc<Shared>>> = const { RefCell::new(None) };
}
pub(super) fn observe(line: std::fmt::Arguments<'_>) {
if line.to_string().starts_with("door_child_ready:") {
OBSERVED.with(|observed| {
if let Some(shared) = observed.borrow().as_ref() {
let state = shared.state.try_lock();
assert!(state.is_ok(), "ready output holds the state mutex");
if let Ok(state) = state {
assert!(
!state.entries[0].seen_ready,
"readiness was published before output"
);
assert!(state.entries[0].phase == Phase::Ready);
drop(state);
shared.changed.notify_all();
}
}
});
}
std::eprintln!("{line}");
}
#[test]
fn review_004_ready_output_releases_mutex_before_publishing_readiness()
-> Result<(), Box<dyn std::error::Error>> {
let shared = Arc::new(Shared {
state: Mutex::new(State {
stopped: false,
entries: vec![Entry {
phase: Phase::NotReady,
generation: 1,
..Entry::default()
}],
}),
changed: Condvar::new(),
});
let config: Config = serde_json::from_value(serde_json::json!({
"name":"fixture", "program":"/unused", "args":[], "ready":"fixture ready",
"log":"/unused-log", "give_up_after":3
}))?;
OBSERVED.with(|observed| *observed.borrow_mut() = Some(Arc::clone(&shared)));
let result = super::io::read_lines(
&shared,
0,
Launch {
generation: 1,
started: std::time::Instant::now(),
},
&config,
std::io::Cursor::new(b"fixture ready\n"),
Vec::new(),
Arc::new(AtomicBool::new(false)),
);
OBSERVED.with(|observed| *observed.borrow_mut() = None);
result?;
assert!(
shared
.state
.lock()
.map_err(|error| error.to_string())?
.entries[0]
.seen_ready
);
Ok(())
}