use std::path::Path;
use std::process::Command;
#[cfg(windows)]
const TRANSIENT_SPAWN_CODES: &[i32] = &[
0xC000_0142u32 as i32, 0xC000_0135u32 as i32, 0xC000_007Bu32 as i32, 0xC000_0017u32 as i32, 0xC000_0018u32 as i32, ];
#[cfg(windows)]
pub fn is_transient_spawn_failure(status: &std::process::ExitStatus) -> bool {
status
.code()
.is_some_and(|c| TRANSIENT_SPAWN_CODES.contains(&c))
}
#[cfg(not(windows))]
pub fn is_transient_spawn_failure(_status: &std::process::ExitStatus) -> bool {
false
}
pub fn output_with_spawn_retry(
mut build: impl FnMut() -> Command,
what: &str,
) -> std::process::Output {
const MAX_ATTEMPTS: u32 = 5;
for attempt in 0..MAX_ATTEMPTS {
let out = build()
.output()
.unwrap_or_else(|e| panic!("{what} failed to spawn: {e}"));
let last = attempt + 1 == MAX_ATTEMPTS;
if is_transient_spawn_failure(&out.status) && !last {
let backoff = std::time::Duration::from_millis(50u64 << attempt);
std::thread::sleep(backoff);
continue;
}
return out;
}
unreachable!("loop returns on the final attempt")
}
pub fn git_test_output(dir: &Path, args: &[&str]) -> std::process::Output {
output_with_spawn_retry(
|| {
let mut cmd = Command::new("git");
cmd.args([
"-c",
"user.name=Anodizer Test",
"-c",
"user.email=test@anodizer.local",
"-c",
"commit.gpgsign=false",
])
.args(args)
.current_dir(dir)
.env("GIT_TERMINAL_PROMPT", "0");
cmd
},
"git",
)
}
pub fn git_test_ok(dir: &Path, args: &[&str]) {
let out = git_test_output(dir, args);
assert!(
out.status.success(),
"git {args:?} failed: {}",
String::from_utf8_lossy(&out.stderr)
);
}
pub fn git_test_stdout(dir: &Path, args: &[&str]) -> String {
let out = git_test_output(dir, args);
assert!(
out.status.success(),
"git {args:?} failed: {}",
String::from_utf8_lossy(&out.stderr)
);
String::from_utf8_lossy(&out.stdout).trim().to_string()
}