brokerd: runbook pointers for startup failures, no thread panics, args_os
M3a review findings 3, 5 (the cast), 6, 7 (brokerd), 11. A config, directory or socket failure at start now ends with docs/runbook.md#brokerd-start-failed, and losing a listener with #brokerd-listener-lost; both entries are new. Threads start through thread::Builder, so a refused thread is reported instead of silently killing a listener; an aborted connection no longer stops the daemon. brokerd reads args_os and keeps the config path as a path. The "requester went away" result is recorded at the time it happens. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,134 @@
|
||||
//! `brokerd serve` fails closed at startup with a runbook pointer for every reason it cannot start,
|
||||
//! and never panics on its own command line. Found in the M3a review (findings 3 and 7).
|
||||
|
||||
#[path = "support/tmp.rs"]
|
||||
mod tmp;
|
||||
|
||||
use std::ffi::OsStr;
|
||||
use std::os::unix::ffi::OsStrExt;
|
||||
use std::path::Path;
|
||||
use std::process::{Command, Output};
|
||||
|
||||
use tmp::TempDir;
|
||||
|
||||
const START_FAILED: &str = "see docs/runbook.md#brokerd-start-failed";
|
||||
|
||||
fn brokerd<I: IntoIterator<Item = S>, S: AsRef<OsStr>>(args: I) -> Output {
|
||||
Command::new(env!("CARGO_BIN_EXE_brokerd"))
|
||||
.args(args)
|
||||
.output()
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
fn stderr(out: &Output) -> String {
|
||||
String::from_utf8_lossy(&out.stderr).into_owned()
|
||||
}
|
||||
|
||||
/// A config whose home is `dir`, with `sockets` as the `[sockets]` table's body.
|
||||
fn config(dir: &TempDir, sockets: &str) -> std::path::PathBuf {
|
||||
let home = dir.path().join("home");
|
||||
let grants = dir.path().join("grants");
|
||||
std::fs::create_dir_all(&grants).unwrap();
|
||||
std::fs::create_dir_all(&home).unwrap();
|
||||
dir.write(
|
||||
"brokerd.toml",
|
||||
&format!(
|
||||
"[paths]\nhome = \"{}\"\ngrants = \"{}\"\n[sockets]\n{sockets}\n",
|
||||
home.display(),
|
||||
grants.display()
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
fn fails_with_pointer(out: &Output) {
|
||||
assert_eq!(out.status.code(), Some(1), "{}", stderr(out));
|
||||
assert!(
|
||||
stderr(out).trim_end().ends_with(START_FAILED),
|
||||
"{:?} must end with {START_FAILED:?}",
|
||||
stderr(out)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_missing_config_names_the_entry() {
|
||||
let dir = TempDir::new("ptr-missing");
|
||||
let out = brokerd([
|
||||
OsStr::new("serve"),
|
||||
OsStr::new("--config"),
|
||||
dir.path().join("nope.toml").as_os_str(),
|
||||
]);
|
||||
fails_with_pointer(&out);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn an_invalid_config_names_the_entry() {
|
||||
let dir = TempDir::new("ptr-invalid");
|
||||
let path = dir.write("brokerd.toml", "[paths]\nhome = 3\n");
|
||||
let out = brokerd([
|
||||
OsStr::new("serve"),
|
||||
OsStr::new("--config"),
|
||||
path.as_os_str(),
|
||||
]);
|
||||
fails_with_pointer(&out);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_socket_that_cannot_be_bound_names_the_entry() {
|
||||
let dir = TempDir::new("ptr-bind");
|
||||
let long = dir.path().join("s".repeat(120)).join("broker.sock");
|
||||
let path = config(&dir, &format!("broker = \"{}\"", long.display()));
|
||||
fails_with_pointer(&brokerd([
|
||||
OsStr::new("serve"),
|
||||
OsStr::new("--config"),
|
||||
path.as_os_str(),
|
||||
]));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_socket_directory_that_cannot_be_made_names_the_entry() {
|
||||
let dir = TempDir::new("ptr-dir");
|
||||
let file = dir.write("a-file", "");
|
||||
let path = config(
|
||||
&dir,
|
||||
&format!("broker = \"{}\"", file.join("run/broker.sock").display()),
|
||||
);
|
||||
fails_with_pointer(&brokerd([
|
||||
OsStr::new("serve"),
|
||||
OsStr::new("--config"),
|
||||
path.as_os_str(),
|
||||
]));
|
||||
}
|
||||
|
||||
/// A config path that is not UTF-8 is still a path: it is read (here: not found), not a panic.
|
||||
#[test]
|
||||
fn a_config_path_that_is_not_utf8_is_read_as_a_path() {
|
||||
let dir = TempDir::new("ptr-os");
|
||||
let name = dir.path().join(OsStr::from_bytes(b"conf-\xff.toml"));
|
||||
let out = brokerd([
|
||||
OsStr::new("serve"),
|
||||
OsStr::new("--config"),
|
||||
name.as_os_str(),
|
||||
]);
|
||||
fails_with_pointer(&out);
|
||||
}
|
||||
|
||||
/// A flag that is not UTF-8 is not a flag brokerd knows: usage, exit 2, no panic.
|
||||
#[test]
|
||||
fn a_flag_that_is_not_utf8_is_a_usage_error() {
|
||||
let out = brokerd([OsStr::new("serve"), OsStr::from_bytes(b"--\xff")]);
|
||||
assert_eq!(out.status.code(), Some(2), "{}", stderr(&out));
|
||||
assert!(stderr(&out).starts_with("usage: brokerd serve"));
|
||||
let out = brokerd([OsStr::from_bytes(b"\xff")]);
|
||||
assert_eq!(out.status.code(), Some(2), "{}", stderr(&out));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn the_entries_exist() {
|
||||
let runbook = std::fs::read_to_string(
|
||||
Path::new(env!("CARGO_MANIFEST_DIR")).join("../../docs/runbook.md"),
|
||||
)
|
||||
.unwrap();
|
||||
for entry in ["## brokerd-start-failed", "## brokerd-listener-lost"] {
|
||||
assert!(runbook.lines().any(|l| l == entry), "{entry}");
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user