server: keep paths on camino via camino-tempfile
Assisted-by: Claude Opus 5.5 via Claude Code
change xwzmkzyuttkvxxkskpovvuklwoxkvowz
commit 38a26dff2a7eaa9df181fbe40d312ace2cafa95d
author Alpha Chen <alpha@kejadlen.dev>
date
parent loyntvyq
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();