haematite-cli 0.21.7

The haematite command: serve a store, its door and Studio, answer MCP, query with jq, and host the store's assistant seat
Documentation
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(())
}