Keep macOS native utilities unchanged around timeout bootstrap

Expose only the installed GNU gtimeout as timeout through a new runner-owned directory. Keep native BSD utilities on their original PATH so installing the missing dependency cannot silently change the other platform checks. The connected workflow test verifies the symlink identity and that the directory contains no other utilities.

Contract-Neutral: Narrow the native CI dependency PATH and its proof; no shipped installer or runtime contract changes.
Change-source: pulse-maintainer
This commit is contained in:
pulse-triage[bot]
2026-10-05 09:44:52 +01:00
parent 13f6d10798
commit 443faa53b6
2 changed files with 24 additions and 11 deletions
+6 -4
View File
@@ -90,10 +90,12 @@ jobs:
run: |
set -euo pipefail
brew install coreutils
coreutils_bin="$(brew --prefix coreutils)/libexec/gnubin"
test -x "$coreutils_bin/timeout"
"$coreutils_bin/timeout" --version | grep -q 'GNU coreutils'
printf '%s\n' "$coreutils_bin" >> "$GITHUB_PATH"
gnu_timeout="$(brew --prefix coreutils)/bin/gtimeout"
test -x "$gnu_timeout"
"$gnu_timeout" --version | grep -q 'GNU coreutils'
timeout_bin="$(mktemp -d "$RUNNER_TEMP/pulse-guidance-tools.XXXXXX")"
ln -s "$gnu_timeout" "$timeout_bin/timeout"
printf '%s\n' "$timeout_bin" >> "$GITHUB_PATH"
- name: Verify native Unix timeout prerequisite
if: matrix.unix
@@ -65,8 +65,8 @@ func TestNativeGuidanceToolsSupplyAndVerifyRealGNUTimeout(t *testing.T) {
t.Run(scenario, func(t *testing.T) {
root := t.TempDir()
bin, prefix := filepath.Join(root, "bin"), filepath.Join(root, "coreutils with spaces")
gnuBin := filepath.Join(prefix, "libexec", "gnubin")
for _, dir := range []string{bin, gnuBin} {
coreutilsBin, runnerTemp := filepath.Join(prefix, "bin"), filepath.Join(root, "runner temp")
for _, dir := range []string{bin, coreutilsBin, runnerTemp} {
if err := os.MkdirAll(dir, 0700); err != nil {
t.Fatal(err)
}
@@ -84,7 +84,7 @@ esac
if err := os.WriteFile(filepath.Join(bin, "brew"), []byte(brew), 0700); err != nil {
t.Fatal(err)
}
tool := filepath.Join(gnuBin, "timeout")
tool := filepath.Join(coreutilsBin, "gtimeout")
switch scenario {
case "real GNU tool", "install failure":
if err := os.Symlink(realTimeout, tool); err != nil {
@@ -101,7 +101,7 @@ esac
installExit = "23"
}
env := append(os.Environ(), "PATH="+bin+":"+os.Getenv("PATH"), "GITHUB_PATH="+pathFile,
"COREUTILS_PREFIX="+prefix, "BREW_CALLS="+callsFile, "BREW_EXIT="+installExit)
"COREUTILS_PREFIX="+prefix, "BREW_CALLS="+callsFile, "BREW_EXIT="+installExit, "RUNNER_TEMP="+runnerTemp)
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
cmd := exec.CommandContext(ctx, "bash", "-c", bootstrap.Run)
@@ -128,15 +128,26 @@ esac
t.Fatalf("GNU prerequisite failed: %v: %s", runErr, output)
}
path, err := os.ReadFile(pathFile)
if err != nil || string(path) != gnuBin+"\n" {
t.Fatal("bootstrap did not export the exact installed gnubin path")
if err != nil {
t.Fatal(err)
}
timeoutBin := strings.TrimSuffix(string(path), "\n")
if filepath.Dir(timeoutBin) != runnerTemp || !strings.HasPrefix(filepath.Base(timeoutBin), "pulse-guidance-tools.") {
t.Fatal("bootstrap did not export its newly owned timeout directory")
}
entries, err := os.ReadDir(timeoutBin)
if err != nil || len(entries) != 1 || entries[0].Name() != "timeout" {
t.Fatal("bootstrap must not replace native macOS utilities other than timeout")
}
if target, err := os.Readlink(filepath.Join(timeoutBin, "timeout")); err != nil || target != tool {
t.Fatal("timeout must resolve to the exact installed GNU executable")
}
calls, err := os.ReadFile(callsFile)
if err != nil || string(calls) != "install coreutils\n--prefix coreutils\n" {
t.Fatal("bootstrap changed its fixed Homebrew dependency")
}
cmd = exec.CommandContext(ctx, "bash", "-c", verify.Run)
cmd.Env = append(env, "PATH="+strings.TrimSpace(string(path))+":"+os.Getenv("PATH"))
cmd.Env = append(env, "PATH="+timeoutBin+":"+os.Getenv("PATH"))
if output, err := cmd.CombinedOutput(); err != nil {
t.Fatalf("next workflow step cannot use supplied GNU timeout: %v: %s", err, output)
}