mirror of
https://github.com/multica-ai/multica.git
synced 2026-07-05 13:29:44 +02:00
test(agent): serialize fake-executable writes to avoid ETXTBSY on CI
TestKimiBackendInvokesACPSubcommand (and its Kimi/Codex siblings) write a shell script to a per-test TempDir and then fork/exec it. With t.Parallel() enabled across the package, a concurrent goroutine's fork can inherit the still-open write fd to another test's new executable; Linux then rejects the subsequent exec with ETXTBSY (seen as fork/exec /tmp/.../kimi: text file busy on GitHub Actions). Introduce writeTestExecutable, which holds syscall.ForkLock.RLock across OpenFile→Write→Close. Fork (which takes ForkLock.Lock) cannot run while we hold RLock, so no sibling fork inherits our write fd. Ran the three callers with -count=10 under -p=1 and the full package with no failures.
This commit is contained in:
@@ -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 {
|
||||
|
||||
32
server/pkg/agent/exec_fixture_unix_test.go
Normal file
32
server/pkg/agent/exec_fixture_unix_test.go
Normal file
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user