server: keep paths on camino via camino-tempfile
Assisted-by: Claude Opus 5.5 via Claude Code
diff --git a/Cargo.lock b/Cargo.lock
index b7d94b7..828fba6 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -214,6 +214,16 @@ version = "1.2.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bbbad30e4b4c14a39e3cc8aed085a12a327257c316619c93581e017bc52be591"
+[[package]]
+name = "camino-tempfile"
+version = "1.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "64308c4c82a5c38679945ddf88738dc1483dcc563bbb5780755ae9f8497d2b20"
+dependencies = [
+ "camino",
+ "tempfile",
+]
+
[[package]]
name = "cc"
version = "1.5.1"
@@ -1023,6 +1033,7 @@ version = "0.1.0"
dependencies = [
"assert_cmd",
"camino",
+ "camino-tempfile",
"clap",
"clap_complete",
"fs-err",
diff --git a/Cargo.toml b/Cargo.toml
index 5962549..2d1b571 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -9,6 +9,7 @@ path = "src/bin/parallax/main.rs"
[dependencies]
camino = "*"
+camino-tempfile = "*"
clap = { version = "*", features = ["derive", "env"] }
clap_complete = "*"
fs-err = "*"
@@ -18,7 +19,6 @@ mlua = { version = "*", features = ["lua54", "vendored", "send", "serde"] }
serde = { version = "*", features = ["derive"] }
serde_json = "*"
serde_path_to_error = "*"
-tempfile = "*"
thiserror = "*"
tokio = { version = "*", features = ["full"] }
tracing = "*"
@@ -27,6 +27,7 @@ tracing-subscriber = { version = "*", features = ["env-filter"] }
[dev-dependencies]
assert_cmd = "*"
predicates = "*"
+tempfile = "*"
[lints]
workspace = true
diff --git a/src/parallax.rs b/src/parallax.rs
index 96e94f2..6fd72b9 100644
--- a/src/parallax.rs
+++ b/src/parallax.rs
@@ -136,9 +136,9 @@ impl Parallax {
// cache defaults land; nothing should litter the cwd.
let scratch;
let root = match output {
- Some(path) => path.as_std_path(),
+ Some(path) => path,
None => {
- scratch = tempfile::tempdir().map_err(Error::Io)?;
+ scratch = camino_tempfile::tempdir().map_err(Error::Io)?;
scratch.path()
}
};
diff --git a/src/server.rs b/src/server.rs
index cf7b3bd..493fa84 100644
--- a/src/server.rs
+++ b/src/server.rs
@@ -10,8 +10,6 @@
use std::io;
use std::os::unix::process::CommandExt as _;
-use std::path::Path;
-use std::path::PathBuf;
use std::process::Child;
use std::process::Command;
use std::process::ExitStatus;
@@ -19,6 +17,8 @@ use std::process::Stdio;
use std::time::Duration;
use std::time::Instant;
+use camino::Utf8Path;
+use camino::Utf8PathBuf;
use serde::Deserialize;
use serde_json::Value;
@@ -56,17 +56,17 @@ pub(crate) struct Server {
/// The child's pid, which is also its pgid: it was spawned as the
/// leader of a fresh group.
pgid: i32,
- log: PathBuf,
+ log: Utf8PathBuf,
stopped: bool,
/// Keeps the directory holding the handshake file alive.
- _dir: tempfile::TempDir,
+ _dir: camino_tempfile::Utf8TempDir,
}
impl Server {
pub(crate) async fn start(
spec: &ServerSpec,
variant: &str,
- output: &Path,
+ output: &Utf8Path,
) -> Result<Server, Error> {
let spec = spec.clone();
let variant = variant.to_owned();
@@ -110,8 +110,12 @@ impl Server {
.map_err(|error| Error::Io(io::Error::other(error)))?
}
- fn spawn_and_wait(spec: &ServerSpec, variant: &str, output: &Path) -> Result<Server, Error> {
- let dir = tempfile::tempdir().map_err(Error::Io)?;
+ fn spawn_and_wait(
+ spec: &ServerSpec,
+ variant: &str,
+ output: &Utf8Path,
+ ) -> Result<Server, Error> {
+ let dir = camino_tempfile::tempdir().map_err(Error::Io)?;
let handshake_path = dir.path().join("handshake.json");
let variant_dir = output.join(variant);
@@ -209,7 +213,7 @@ enum HandshakeError {
/// error is parallax failing to check, the inner one the app failing.
#[allow(clippy::arithmetic_side_effects)] // Instant + Duration only panics near heat death
fn wait_for_handshake(
- path: &Path,
+ path: &Utf8Path,
timeout: f64,
mut exited: impl FnMut() -> io::Result<Option<ExitStatus>>,
) -> io::Result<Result<Handshake, HandshakeError>> {
@@ -228,7 +232,7 @@ fn wait_for_handshake(
}
}
-fn read_handshake(path: &Path) -> Option<Handshake> {
+fn read_handshake(path: &Utf8Path) -> Option<Handshake> {
let text = fs_err::read_to_string(path).ok()?;
serde_json::from_str(&text).ok()
}
@@ -248,7 +252,7 @@ fn signal_group(pgid: i32, signal: libc::c_int) -> io::Result<()> {
/// The end of the server's log, cut forward to a line boundary so the
/// quote starts on one.
-fn tail(path: &Path) -> String {
+fn tail(path: &Utf8Path) -> String {
#[allow(clippy::disallowed_methods)]
// a log tail is arbitrary bytes headed for an error message
fn lossy(bytes: &[u8]) -> String {
@@ -308,7 +312,7 @@ mod tests {
#[tokio::test]
async fn reads_the_handshake_url_and_data() {
- let output = tempfile::tempdir().expect("tempdir");
+ let output = camino_tempfile::tempdir().expect("tempdir");
let script = r#"printf '{"url":"http://127.0.0.1:1","data":{"x":1}}' > "$PARALLAX_HANDSHAKE"; sleep 300"#;
let server = Server::start(&sh(script, 5.0), "desktop", output.path())
.await
@@ -320,7 +324,7 @@ mod tests {
#[tokio::test]
async fn the_fixture_handshake_reaches_parallax() {
- let output = tempfile::tempdir().expect("tempdir");
+ let output = camino_tempfile::tempdir().expect("tempdir");
let cmd = spec(
"cargo",
&["run", "--quiet", "--package", "parallax-fixture"],
@@ -337,7 +341,7 @@ mod tests {
#[tokio::test]
async fn wrappers_leave_no_orphans() {
- let output = tempfile::tempdir().expect("tempdir");
+ let output = camino_tempfile::tempdir().expect("tempdir");
let script = format!("sleep 300 & {HANDSHAKE}; sleep 300");
let server = Server::start(&sh(&script, 5.0), "desktop", output.path())
.await
@@ -349,7 +353,7 @@ mod tests {
#[tokio::test]
async fn dropping_a_server_kills_its_group() {
- let output = tempfile::tempdir().expect("tempdir");
+ let output = camino_tempfile::tempdir().expect("tempdir");
let script = format!("sleep 300 & {HANDSHAKE}; sleep 300");
let server = Server::start(&sh(&script, 5.0), "desktop", output.path())
.await
@@ -361,7 +365,7 @@ mod tests {
#[tokio::test]
async fn sigterm_runs_before_sigkill() {
- let output = tempfile::tempdir().expect("tempdir");
+ let output = camino_tempfile::tempdir().expect("tempdir");
// sh logs from its TERM handler before exiting; SIGKILL first
// would kill it before the handler could run. The handshake
// comes after the trap, so the handler is installed by then.
@@ -377,7 +381,7 @@ mod tests {
#[tokio::test]
async fn sigkill_escalates_when_sigterm_is_ignored() {
- let output = tempfile::tempdir().expect("tempdir");
+ let output = camino_tempfile::tempdir().expect("tempdir");
// trap '' leaves SIGTERM ignored, and the fg sleep inherits
// that, so only SIGKILL can take the group down.
let script = format!("trap '' TERM; {HANDSHAKE}; sleep 300");
@@ -394,7 +398,7 @@ mod tests {
#[tokio::test]
async fn an_app_that_exits_before_the_handshake_fails_with_its_tail() {
- let output = tempfile::tempdir().expect("tempdir");
+ let output = camino_tempfile::tempdir().expect("tempdir");
let error = Server::start(&sh("echo boom >&2; exit 3", 5.0), "desktop", output.path())
.await
.expect_err("server must fail");
@@ -408,7 +412,7 @@ mod tests {
#[tokio::test]
async fn a_server_that_never_handshakes_times_out() {
- let output = tempfile::tempdir().expect("tempdir");
+ let output = camino_tempfile::tempdir().expect("tempdir");
let cmd = sh("echo starting >&2; sleep 300", 0.25);
let error = Server::start(&cmd, "desktop", output.path())
.await
@@ -422,7 +426,7 @@ mod tests {
#[tokio::test]
async fn a_handshake_without_a_url_keeps_waiting() {
- let output = tempfile::tempdir().expect("tempdir");
+ let output = camino_tempfile::tempdir().expect("tempdir");
let script = r#"printf '{"data":{"x":1}}' > "$PARALLAX_HANDSHAKE"; sleep 300"#;
let error = Server::start(&sh(script, 0.25), "desktop", output.path())
.await
@@ -432,7 +436,7 @@ mod tests {
#[tokio::test]
async fn a_missing_command_is_named() {
- let output = tempfile::tempdir().expect("tempdir");
+ let output = camino_tempfile::tempdir().expect("tempdir");
let cmd = spec("parallax-no-such-command-xyzzy", &["serve"], 1.0);
let error = Server::start(&cmd, "desktop", output.path())
.await
@@ -446,7 +450,7 @@ mod tests {
#[tokio::test]
async fn server_output_lands_in_the_log() {
- let output = tempfile::tempdir().expect("tempdir");
+ let output = camino_tempfile::tempdir().expect("tempdir");
let script = format!("echo an-out-line; echo an-err-line >&2; {HANDSHAKE}; sleep 300");
let server = Server::start(&sh(&script, 5.0), "desktop", output.path())
.await
@@ -461,7 +465,7 @@ mod tests {
#[test]
fn a_failed_exit_check_is_an_io_error() {
- let dir = tempfile::tempdir().expect("tempdir");
+ let dir = camino_tempfile::tempdir().expect("tempdir");
let path = dir.path().join("handshake.json");
let outcome = wait_for_handshake(&path, 5.0, || Err(io::Error::other("no child")));
assert!(outcome.is_err());
@@ -477,13 +481,13 @@ mod tests {
#[test]
fn the_tail_of_a_missing_log_is_empty() {
- let dir = tempfile::tempdir().expect("tempdir");
+ let dir = camino_tempfile::tempdir().expect("tempdir");
assert_eq!(tail(&dir.path().join("server.log")), "");
}
#[test]
fn the_tail_of_a_long_log_starts_on_a_line() {
- let dir = tempfile::tempdir().expect("tempdir");
+ let dir = camino_tempfile::tempdir().expect("tempdir");
let path = dir.path().join("server.log");
// 300 ten-byte lines; the 2048-byte window cuts into line 95.
let text: String = (0..300).map(|n| format!("line-{n:04}\n")).collect();