From cfa02479c1d2d4c6ca248cd977884b809f76fa81 Mon Sep 17 00:00:00 2001 From: "K. Isom" Date: Tue, 22 Sep 2026 21:52:54 -0700 Subject: [PATCH] Test --accept-break on a real break through the brokerd binary A plain start refuses, --accept-break records the break and serves, and the next plain start serves. The given tests covered this only at library level. Co-Authored-By: Claude Opus 5.5 (1M context) --- crates/brokerd/tests/serve_accept_break.rs | 111 +++++++++++++++++++++ 1 file changed, 111 insertions(+) create mode 100644 crates/brokerd/tests/serve_accept_break.rs diff --git a/crates/brokerd/tests/serve_accept_break.rs b/crates/brokerd/tests/serve_accept_break.rs new file mode 100644 index 0000000..976a2b2 --- /dev/null +++ b/crates/brokerd/tests/serve_accept_break.rs @@ -0,0 +1,111 @@ +//! `brokerd serve --accept-break` on a real break, through the binary: it records the break and +//! serves, and the next plain start serves too (spec section 12, "Audit edges"; missing from the +//! given tests, found in the M3a review). + +#[path = "support/tmp.rs"] +mod tmp; + +use std::io::Read; +use std::os::unix::net::UnixStream; +use std::path::Path; +use std::process::{Command, Stdio}; +use std::time::{Duration, Instant}; + +use brokerd::audit::verify_dir; +use tmp::TempDir; + +/// Start `brokerd serve` with `extra`, wait until it listens, stop it, and return its stderr. +fn serve_once(config: &Path, tools: &Path, extra: &[&str]) -> String { + let mut child = Command::new(env!("CARGO_BIN_EXE_brokerd")) + .args(["serve", "--config"]) + .arg(config) + .args(extra) + .stdout(Stdio::null()) + .stderr(Stdio::piped()) + .spawn() + .unwrap(); + let until = Instant::now() + Duration::from_secs(10); + while UnixStream::connect(tools).is_err() { + if let Some(status) = child.try_wait().unwrap() { + let mut err = String::new(); + child + .stderr + .take() + .unwrap() + .read_to_string(&mut err) + .unwrap(); + panic!("brokerd exited with {status}: {err}"); + } + assert!(Instant::now() < until, "brokerd never listened"); + std::thread::sleep(Duration::from_millis(20)); + } + child.kill().unwrap(); + child.wait().unwrap(); + let mut err = String::new(); + child + .stderr + .take() + .unwrap() + .read_to_string(&mut err) + .unwrap(); + err +} + +#[test] +fn a_break_is_accepted_and_the_next_plain_start_serves() { + let dir = TempDir::new("accept-break"); + let audit = dir.path().join("audit"); + std::fs::create_dir_all(&audit).unwrap(); + std::fs::create_dir_all(dir.path().join("grants")).unwrap(); + let fixture = + Path::new(env!("CARGO_MANIFEST_DIR")).join("../proto/tests/fixtures/audit/changed-byte"); + // The damage is in the first file; an ordinary start checks only the latest, so copy that one + // alone, as `audit_startup.rs` does. + std::fs::copy( + fixture.join("2026-09-17.jsonl"), + audit.join("2026-09-17.jsonl"), + ) + .unwrap(); + let config = dir.write( + "brokerd.toml", + &format!( + "[paths]\nhome = \"{0}\"\ngrants = \"{0}/grants\"\n", + dir.path().display() + ), + ); + let tools = dir.path().join("run/loop-broker/broker.sock"); + + // A plain start must refuse: wait for it to exit, and fail (not hang) if it serves instead. + let mut plain = Command::new(env!("CARGO_BIN_EXE_brokerd")) + .args(["serve", "--config"]) + .arg(&config) + .stdout(Stdio::null()) + .stderr(Stdio::null()) + .spawn() + .unwrap(); + let until = Instant::now() + Duration::from_secs(10); + let status = loop { + if let Some(status) = plain.try_wait().unwrap() { + break status; + } + if Instant::now() > until { + plain.kill().unwrap(); + panic!("a plain start served on a broken chain"); + } + std::thread::sleep(Duration::from_millis(20)); + }; + assert_eq!(status.code(), Some(1), "a plain start refuses first"); + + let printed = serve_once(&config, &tools, &["--accept-break"]); + assert!( + printed.contains("audit: accepted the break at"), + "{printed}" + ); + + let printed = serve_once(&config, &tools, &[]); + assert!(!printed.contains("accepted the break"), "{printed}"); + + let report = verify_dir(&audit).unwrap(); + assert_eq!(report.failure, None, "{:?}", report.failure); + assert_eq!(report.accepted_breaks.len(), 1); +}