diff --git a/server/pkg/agent/codex_test.go b/server/pkg/agent/codex_test.go index 314af8d45..f379c7fa3 100644 --- a/server/pkg/agent/codex_test.go +++ b/server/pkg/agent/codex_test.go @@ -5,7 +5,6 @@ import ( "encoding/json" "fmt" "log/slog" - "os" "path/filepath" "runtime" "strings" @@ -974,9 +973,7 @@ func TestCodexExecuteSurfacesStderrWhenChildExitsEarly(t *testing.T) { script := "#!/bin/sh\n" + "echo \"error: unexpected argument '-m' found\" >&2\n" + "exit 2\n" - if err := os.WriteFile(fakePath, []byte(script), 0o755); err != nil { - t.Fatalf("write fake codex: %v", err) - } + writeTestExecutable(t, fakePath, []byte(script)) backend, err := New("codex", Config{ExecutablePath: fakePath, Logger: slog.Default()}) if err != nil { diff --git a/server/pkg/agent/exec_fixture_unix_test.go b/server/pkg/agent/exec_fixture_unix_test.go new file mode 100644 index 000000000..f73bf3945 --- /dev/null +++ b/server/pkg/agent/exec_fixture_unix_test.go @@ -0,0 +1,32 @@ +//go:build unix + +package agent + +import ( + "os" + "syscall" + "testing" +) + +// writeTestExecutable writes content to path with exec perms while holding +// syscall.ForkLock.RLock, so no concurrent t.Parallel() sibling can fork +// between our OpenFile and Close. Without this, Linux ETXTBSY fires when +// the sibling's fork child inherits our still-open write fd and the +// subsequent exec of the file sees "text file busy" (seen on CI as +// TestKimiBackendInvokesACPSubcommand: fork/exec ... text file busy). +func writeTestExecutable(tb testing.TB, path string, content []byte) { + tb.Helper() + syscall.ForkLock.RLock() + defer syscall.ForkLock.RUnlock() + f, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0o755) + if err != nil { + tb.Fatalf("write test executable %s: open: %v", path, err) + } + if _, err := f.Write(content); err != nil { + _ = f.Close() + tb.Fatalf("write test executable %s: write: %v", path, err) + } + if err := f.Close(); err != nil { + tb.Fatalf("write test executable %s: close: %v", path, err) + } +} diff --git a/server/pkg/agent/kimi_test.go b/server/pkg/agent/kimi_test.go index 707213a26..1630edf43 100644 --- a/server/pkg/agent/kimi_test.go +++ b/server/pkg/agent/kimi_test.go @@ -103,9 +103,7 @@ func TestKimiBackendSetModelFailureFailsTask(t *testing.T) { t.Parallel() fakePath := filepath.Join(t.TempDir(), "kimi") - if err := os.WriteFile(fakePath, []byte(fakeKimiACPScript()), 0o755); err != nil { - t.Fatalf("write fake kimi: %v", err) - } + writeTestExecutable(t, fakePath, []byte(fakeKimiACPScript())) backend, err := New("kimi", Config{ExecutablePath: fakePath, Logger: slog.Default()}) if err != nil { @@ -164,9 +162,7 @@ func TestKimiBackendInvokesACPSubcommand(t *testing.T) { tempDir := t.TempDir() argsFile := filepath.Join(tempDir, "argv.txt") fakePath := filepath.Join(tempDir, "kimi") - if err := os.WriteFile(fakePath, []byte(fakeKimiACPScript()), 0o755); err != nil { - t.Fatalf("write fake kimi: %v", err) - } + writeTestExecutable(t, fakePath, []byte(fakeKimiACPScript())) backend, err := New("kimi", Config{ ExecutablePath: fakePath,