1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
//! Portable shell snippets for test fixtures.
//!
//! Contract, readiness, and gate commands bottom out in `cmd /C` on Windows
//! and `sh -c` everywhere else (see [`crate::command_exec`]'s `shell_argv`).
//! cmd.exe has no `true`, `false`, `test`, or `sleep` builtin and Windows
//! ships no such executables, so a fixture hard-coding them only resolves on a
//! machine with Git's `usr/bin` on PATH. Hosted CI happens to provide that; a
//! stock Windows developer box does not, which is why fixtures that looked
//! portable passed on `windows-latest` and failed on a real workstation.
//!
//! Prefer these helpers over literal POSIX binaries in fixtures. `exit 0` /
//! `exit 1` are the two constructs that genuinely parse the same in both
//! shells; everything else needs a branch.
/// A command that always succeeds, in both `sh` and `cmd`.
pub(crate) const SUCCEED: &str = "exit 0";
/// A command that always fails, in both `sh` and `cmd`.
pub(crate) const FAIL: &str = "exit 1";
/// Succeed iff `path` exists as a file, relative to the command's cwd.
pub(crate) fn file_exists(path: &str) -> String {
if cfg!(windows) {
// `if exist` is a cmd builtin; the trailing `else` makes the failure
// an explicit non-zero exit rather than cmd's implicit success.
format!("if exist {path} (exit 0) else (exit 1)")
} else {
format!("test -f {path}")
}
}
/// Succeed iff `path` exists, and then run `then` (itself a portable snippet).
pub(crate) fn if_file_exists(path: &str, then: &str) -> String {
if cfg!(windows) {
format!("if exist {path} ({then}) else (exit 1)")
} else {
format!("test -f {path} && {then}")
}
}
/// Write `text` to `path` as a single line.
pub(crate) fn write_line(text: &str, path: &str) -> String {
// `echo x > file` is spelled the same in both shells, but cmd would carry
// the space before `>` into the file, so close it up there. Closing it up
// makes a trailing digit dangerous: cmd reads `echo x1>f` as a redirect of
// file descriptor 1, silently writing "x" instead of "x1".
debug_assert!(
!text.ends_with(|c: char| c.is_ascii_digit()),
"write_line text must not end in a digit: cmd.exe would parse `{text}>` \
as a file-descriptor redirect"
);
if cfg!(windows) {
format!("echo {text}>{path}")
} else {
format!("echo {text} > {path}")
}
}
/// Block for at least `millis`, without relying on a `sleep` binary.
pub(crate) fn sleep_millis(millis: u64) -> String {
if cfg!(windows) {
// `timeout /t` refuses to run when stdin is redirected, which it
// always is here. `ping` to an unroutable TEST-NET-1 address waits out
// its `-w` timeout and needs no console.
format!("ping -n 1 -w {millis} 192.0.2.1 >nul")
} else {
let seconds = millis as f64 / 1000.0;
format!("sleep {seconds}")
}
}