toolkit: the tool program, read_file and write_file

Implemented-By: OpenCode session (model recorded in docs/implementer-log.md)
This commit is contained in:
2026-09-23 00:00:03 -07:00
parent 644fda14da
commit 0d444dd5bf
10 changed files with 446 additions and 6 deletions
+68
View File
@@ -0,0 +1,68 @@
//! Running the `toolkit` binary as `brokerd` does: arguments on standard input. Do not edit.
#![allow(dead_code)] // each test file uses its own part
use std::io::Write;
use std::path::{Path, PathBuf};
use std::process::{Command, Stdio};
use std::sync::atomic::{AtomicU32, Ordering};
static NEXT: AtomicU32 = AtomicU32::new(0);
/// A temporary directory, removed when dropped.
pub struct TempDir(pub PathBuf);
impl TempDir {
pub fn new(tag: &str) -> TempDir {
let n = NEXT.fetch_add(1, Ordering::SeqCst);
let path = std::env::temp_dir().join(format!("tk-{tag}-{}-{n}", std::process::id()));
let _ = std::fs::remove_dir_all(&path);
std::fs::create_dir_all(&path).unwrap();
TempDir(path)
}
pub fn path(&self) -> &Path {
&self.0
}
/// The path of `name` inside, as a string (the tools take strings).
pub fn at(&self, name: &str) -> String {
self.0.join(name).to_str().unwrap().to_string()
}
}
impl Drop for TempDir {
fn drop(&mut self) {
let _ = std::fs::remove_dir_all(&self.0);
}
}
pub struct Ran {
pub code: i32,
pub stdout: String,
pub stderr: String,
}
/// Run `toolkit <args…>` with `input` on standard input.
pub fn toolkit(args: &[&str], input: &[u8]) -> Ran {
let mut child = Command::new(env!("CARGO_BIN_EXE_toolkit"))
.args(args)
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.spawn()
.unwrap();
let mut stdin = child.stdin.take().unwrap();
// A broken pipe here only means toolkit stopped reading, which some tests expect.
let _ = stdin.write_all(input);
drop(stdin);
let out = child.wait_with_output().unwrap();
Ran {
code: out.status.code().unwrap_or(-1),
stdout: String::from_utf8(out.stdout).unwrap(),
stderr: String::from_utf8_lossy(&out.stderr).into_owned(),
}
}
/// The JSON for one argument struct.
pub fn json<T: serde::Serialize>(value: &T) -> Vec<u8> {
serde_json::to_vec(value).unwrap()
}