Integrate reviewed real GNU timeout prerequisite for macOS native checks

Change-source: pulse-maintainer
This commit is contained in:
pulse-triage[bot]
2026-10-05 11:06:18 +01:00
2 changed files with 184 additions and 0 deletions
@@ -84,6 +84,26 @@ jobs:
go-version-file: go.mod
cache: true
- name: Supply GNU timeout for macOS guidance checks
if: runner.os == 'macOS'
shell: bash
run: |
set -euo pipefail
brew install coreutils
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
shell: bash
run: |
set -euo pipefail
timeout --version | grep -q 'GNU coreutils'
- name: Test native agent runtime
run: >-
go test
@@ -0,0 +1,164 @@
//go:build !windows
package installtests
import (
"context"
"fmt"
"os"
"os/exec"
"path/filepath"
"strings"
"testing"
"time"
"gopkg.in/yaml.v3"
)
type nativeGuidanceStep struct {
Name, If, Shell, Run string
}
func nativeGuidanceToolSteps(content []byte) (nativeGuidanceStep, nativeGuidanceStep, error) {
var workflow struct {
Jobs map[string]struct{ Steps []nativeGuidanceStep }
}
if err := yaml.Unmarshal(content, &workflow); err != nil {
return nativeGuidanceStep{}, nativeGuidanceStep{}, err
}
steps := workflow.Jobs["native-agent"].Steps
bootstrapIndex, verifyIndex, installerIndex := -1, -1, -1
for i, step := range steps {
switch step.Name {
case "Supply GNU timeout for macOS guidance checks":
bootstrapIndex = i
case "Verify native Unix timeout prerequisite":
verifyIndex = i
case "Test native Unix installer contracts":
installerIndex = i
}
}
if bootstrapIndex < 0 || verifyIndex <= bootstrapIndex || installerIndex <= verifyIndex {
return nativeGuidanceStep{}, nativeGuidanceStep{}, fmt.Errorf("native guidance needs GNU tool supply and verification before installer tests")
}
bootstrap, verify := steps[bootstrapIndex], steps[verifyIndex]
if bootstrap.If != "runner.os == 'macOS'" || verify.If != "matrix.unix" || steps[installerIndex].If != "matrix.unix" || bootstrap.Shell != "bash" || verify.Shell != "bash" {
return bootstrap, verify, fmt.Errorf("GNU tool preparation must cover macOS, verification and installer tests must cover every native Unix job")
}
return bootstrap, verify, nil
}
func TestNativeGuidanceToolsSupplyAndVerifyRealGNUTimeout(t *testing.T) {
content, err := os.ReadFile(repoFile(".github", "workflows", "unified-agent-native.yml"))
if err != nil {
t.Fatal(err)
}
bootstrap, verify, err := nativeGuidanceToolSteps(content)
if err != nil {
t.Fatal(err)
}
realTimeout, err := exec.LookPath("timeout")
if err != nil {
t.Fatal("native Unix verification requires the real GNU timeout prerequisite")
}
for _, scenario := range []string{"real GNU tool", "install failure", "missing tool", "non-GNU tool"} {
t.Run(scenario, func(t *testing.T) {
root := t.TempDir()
bin, prefix := filepath.Join(root, "bin"), filepath.Join(root, "coreutils with spaces")
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)
}
}
// Only Homebrew's installer is simulated. The positive timeout is
// the real executable; the copied TERM/KILL recipes remain unchanged.
const brew = `#!/bin/sh
printf '%s\n' "$*" >> "$BREW_CALLS"
case "$*" in
'install coreutils') exit "${BREW_EXIT:-0}" ;;
'--prefix coreutils') printf '%s\n' "$COREUTILS_PREFIX" ;;
*) exit 64 ;;
esac
`
if err := os.WriteFile(filepath.Join(bin, "brew"), []byte(brew), 0700); err != nil {
t.Fatal(err)
}
tool := filepath.Join(coreutilsBin, "gtimeout")
switch scenario {
case "real GNU tool", "install failure":
if err := os.Symlink(realTimeout, tool); err != nil {
t.Fatal(err)
}
case "non-GNU tool":
if err := os.WriteFile(tool, []byte("#!/bin/sh\nprintf 'BSD timeout\\n'\n"), 0700); err != nil {
t.Fatal(err)
}
}
pathFile, callsFile := filepath.Join(root, "github-path"), filepath.Join(root, "brew-calls")
installExit := "0"
if scenario == "install failure" {
installExit = "23"
}
env := append(os.Environ(), "PATH="+bin+":"+os.Getenv("PATH"), "GITHUB_PATH="+pathFile,
"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)
cmd.Env = env
output, runErr := cmd.CombinedOutput()
if ctx.Err() != nil {
t.Fatalf("prerequisite observation did not finish: %v", ctx.Err())
}
if scenario != "real GNU tool" {
wantExit := 1
if scenario == "install failure" {
wantExit = 23
}
exit, ok := runErr.(*exec.ExitError)
if !ok || exit.ExitCode() != wantExit {
t.Fatalf("prerequisite exit %v, want %d: %s", runErr, wantExit, output)
}
if _, err := os.Stat(pathFile); !os.IsNotExist(err) {
t.Fatal("failed prerequisite exported an executable search path")
}
return
}
if runErr != nil {
t.Fatalf("GNU prerequisite failed: %v: %s", runErr, output)
}
path, err := os.ReadFile(pathFile)
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="+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)
}
cmd = exec.CommandContext(ctx, "bash", "-c", verify.Run)
cmd.Env = append(env, "PATH="+bin+":"+os.Getenv("PATH"))
if err := os.WriteFile(filepath.Join(bin, "timeout"), []byte("#!/bin/sh\nprintf 'BSD timeout\\n'\n"), 0700); err != nil {
t.Fatal(err)
}
if output, err := cmd.CombinedOutput(); err == nil {
t.Fatalf("Unix prerequisite probe accepted a non-GNU timeout: %s", output)
}
})
}
}