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) <noreply@anthropic.com>
This commit is contained in:
@@ -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);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user