diff --git a/swarm-builder/src/nix_build.rs b/swarm-builder/src/nix_build.rs index b5c6b90d..6784b265 100644 --- a/swarm-builder/src/nix_build.rs +++ b/swarm-builder/src/nix_build.rs @@ -548,36 +548,40 @@ mod tests { assert!(error_tail.ends_with("error: attribute missing")); } + /// Write the shell script `body` to `path` as an executable, from a child + /// process rather than from this one: a write fd on it held here could be + /// inherited by a child another test thread forks, and exec'ing a file + /// some process still holds open for writing fails with `ETXTBSY`. + fn write_executable(path: &std::path::Path, body: &str) { + let status = std::process::Command::new("/bin/sh") + .args([ + "-c", + r#"printf '%s' "$1" > "$2" && chmod 755 "$2""#, + "sh", + &format!("#!/bin/sh\n{body}\n"), + ]) + .arg(path) + .status() + .expect("run /bin/sh"); + assert!(status.success(), "writing {} failed", path.display()); + } + /// A `nix` stand-in: runs `body` as a shell script, ignoring its arguments. fn fake_nix(tag: &str, body: &str) -> PathBuf { - use std::os::unix::fs::PermissionsExt as _; - let dir = std::env::temp_dir().join(format!( "swarm-builder-fake-nix-{tag}-{}", std::process::id() )); std::fs::create_dir_all(&dir).expect("scratch dir"); let path = dir.join("nix"); - std::fs::write(&path, format!("#!/bin/sh\n{body}\n")).expect("write fake nix"); - std::fs::set_permissions(&path, std::fs::Permissions::from_mode(0o755)).expect("chmod"); + write_executable(&path, body); path } - /// Builds with `nix`, retrying while the kernel refuses to exec the script - /// just written (`ETXTBSY`, when another test's fork briefly inherits its - /// open write handle). + /// Builds with `nix`. async fn build_with(nix: PathBuf) -> Reply { let builder = Builder { nix, ..builder() }; - for _ in 0..20 { - let reply = builder.build(&job(), "token").await; - match &reply { - Reply::Failed { error_tail } if error_tail.contains("Text file busy") => { - tokio::time::sleep(Duration::from_millis(50)).await; - } - _ => return reply, - } - } - panic!("the fake nix stayed busy") + builder.build(&job(), "token").await } #[tokio::test] @@ -677,13 +681,9 @@ mod tests { /// A builder whose `nix` is the shell script `body`, in a directory the /// returned guard removes. fn scripted(body: &str) -> (Builder, Scratch) { - use std::os::unix::fs::PermissionsExt as _; - let dir = Scratch::new().expect("a temp dir"); let nix = dir.0.join("nix"); - std::fs::write(&nix, format!("#!/bin/sh\n{body}\n")).expect("the script is written"); - std::fs::set_permissions(&nix, std::fs::Permissions::from_mode(0o755)) - .expect("the script is executable"); + write_executable(&nix, body); (Builder { nix, ..builder() }, dir) }