use super::*;
use std::sync::{Arc, Mutex};
use crate::output::record::Record;
#[derive(Debug, Default)]
struct Said {
announced: Option<(usize, u64)>,
lines: Vec<String>,
finished: Option<i32>,
refuse_announcement: bool,
}
#[derive(Clone)]
struct Recorder(Arc<Mutex<Said>>);
impl Recorder {
fn new() -> Self {
Self(Arc::new(Mutex::new(Said::default())))
}
fn refusing() -> Self {
let recorder = Self::new();
recorder.0.lock().unwrap().refuse_announcement = true;
recorder
}
fn said(&self) -> std::sync::MutexGuard<'_, Said> {
self.0.lock().unwrap()
}
}
impl Report for Recorder {
fn announce(&self, _run: &Run, preface: &[Line], dropped: u64) -> Result<(), String> {
let mut said = self.0.lock().unwrap();
if said.refuse_announcement {
return Err("this daemon is not answering".to_string());
}
said.announced = Some((preface.len(), dropped));
for line in preface {
said.lines.push(text_of(line));
}
Ok(())
}
fn lines(&self, _run: &Run, lines: &[Line]) -> Result<(), String> {
let mut said = self.0.lock().unwrap();
for line in lines {
said.lines.push(text_of(line));
}
Ok(())
}
fn finished(&self, _run: &Run, exit_code: i32) -> Result<(), String> {
self.0.lock().unwrap().finished = Some(exit_code);
Ok(())
}
}
fn text_of(line: &Line) -> String {
match &line.record {
Record::Stdout { text } => text.clone(),
other => format!("{other:?}"),
}
}
fn line(text: &str) -> Line {
Line {
seq: 0,
ts: "2026-08-29T00:00:00Z".to_string(),
record: Record::Stdout {
text: text.to_string(),
},
}
}
fn a_run() -> Run {
Run {
id: uuid::Uuid::new_v4(),
kind: "update".to_string(),
label: "wsb update --recursive".to_string(),
argv: vec![
"wsb".to_string(),
"update".to_string(),
"--recursive".to_string(),
],
started_at: chrono::Utc::now(),
log_path: "/tmp/logs/ops/2026-08-29/update.jsonl".to_string(),
}
}
const SOON: Duration = Duration::from_millis(120);
#[test]
fn test11216_a_run_that_ends_quickly_is_never_announced() {
let recorder = Recorder::new();
let reporter =
Reporter::watching(recorder.clone(), a_run(), Vec::new(), 0, SOON).expect("a thread");
reporter.line(&line("reading the workspace"));
reporter.finished(0);
let said = recorder.said();
assert!(
said.announced.is_none(),
"a run that finished before the threshold was announced anyway"
);
assert!(
said.finished.is_none(),
"a run that was never announced reported how it ended"
);
assert!(said.lines.is_empty(), "lines were sent for a run nothing knows about");
}
#[test]
fn test11217_the_announcement_carries_what_the_run_already_said() {
let recorder = Recorder::new();
let reporter = Reporter::watching(
recorder.clone(),
a_run(),
vec![line("attached the transcript")],
0,
SOON,
)
.expect("a thread");
reporter.line(&line("reading the workspace"));
reporter.line(&line("rendering templates"));
std::thread::sleep(SOON * 3);
reporter.line(&line("wrote version.txt"));
reporter.finished(0);
let said = recorder.said();
let (carried, dropped) = said
.announced
.expect("a run past the threshold must be announced");
assert_eq!(
carried, 3,
"the announcement did not carry the lines said before it"
);
assert_eq!(dropped, 0);
assert_eq!(
said.lines,
vec![
"attached the transcript",
"reading the workspace",
"rendering templates",
"wrote version.txt",
],
"the run was not reported in the order it happened"
);
assert_eq!(said.finished, Some(0), "the run did not say how it ended");
}
#[test]
fn test11218_the_exit_code_is_what_the_outcome_is_taken_from() {
let recorder = Recorder::new();
let reporter =
Reporter::watching(recorder.clone(), a_run(), Vec::new(), 0, SOON).expect("a thread");
std::thread::sleep(SOON * 3);
reporter.finished(2);
assert_eq!(
recorder.said().finished,
Some(2),
"the status the run exited with was not reported"
);
}
#[test]
fn test11219_a_daemon_that_refuses_does_not_delay_the_command() {
let recorder = Recorder::refusing();
let reporter =
Reporter::watching(recorder.clone(), a_run(), Vec::new(), 0, SOON).expect("a thread");
std::thread::sleep(SOON * 3);
reporter.line(&line("still going"));
let waited = std::time::Instant::now();
reporter.finished(0);
let waited = waited.elapsed();
assert!(
waited < Duration::from_millis(500),
"the command waited {waited:?} on a daemon that had already refused"
);
let said = recorder.said();
assert!(said.announced.is_none());
assert!(
said.lines.is_empty() && said.finished.is_none(),
"lines were sent after the announcement was refused"
);
}
#[test]
fn test11220_lines_dropped_before_the_announcement_are_counted() {
let recorder = Recorder::new();
let reporter =
Reporter::watching(recorder.clone(), a_run(), Vec::new(), 0, SOON).expect("a thread");
for n in 0..(PREFACE + 25) {
reporter.line(&line(&format!("line {n}")));
}
std::thread::sleep(SOON * 3);
reporter.finished(0);
let said = recorder.said();
let (carried, dropped) = said.announced.expect("announced");
assert_eq!(carried, PREFACE, "more lines were held than the cap allows");
assert_eq!(
dropped, 25,
"the lines that had to be dropped were not counted"
);
assert_eq!(
said.lines.first().map(String::as_str),
Some("line 25"),
"the OLDEST lines must be the ones dropped: a run's most recent output \
is the part somebody is looking at"
);
}