Files
2026-07-13 13:00:08 +08:00

124 lines
3.5 KiB
Go

package builtin
import (
"context"
"errors"
"os/exec"
"strings"
"testing"
"time"
"reasonix/internal/sandbox"
)
func TestBashForegroundTimeoutConfig(t *testing.T) {
sh := sandbox.ResolveShell("", "", nil)
b := bash{shell: sh, timeout: 150 * time.Millisecond}
start := time.Now()
out, err := b.Execute(context.Background(), argsJSON(t, map[string]any{"command": longSleepCommand(sh)}))
elapsed := time.Since(start)
if err == nil {
t.Fatalf("expected timeout error, got nil (out=%q)", out)
}
if !strings.Contains(err.Error(), "timed out") {
t.Fatalf("error = %v, want timeout", err)
}
if elapsed > 5*time.Second {
t.Fatalf("configured timeout returned too slowly: %v", elapsed)
}
}
func TestBashExplicitZeroTimeoutDoesNotCapForeground(t *testing.T) {
sh := sandbox.ResolveShell("", "", nil)
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
start := time.Now()
out, err := (bash{shell: sh, timeout: 0}).Execute(ctx, argsJSON(t, map[string]any{"command": oneSecondCommand(sh)}))
elapsed := time.Since(start)
if err != nil {
t.Fatalf("zero-timeout foreground command failed: %v (out=%q)", err, out)
}
if !strings.Contains(out, "done") {
t.Fatalf("output = %q, want done", out)
}
if elapsed < 800*time.Millisecond {
t.Fatalf("command returned too quickly (%v), so the sleep did not run", elapsed)
}
}
func TestWorkspacePassesBashTimeout(t *testing.T) {
sh := sandbox.ResolveShell("", "", nil)
b := byName(Workspace{Dir: t.TempDir(), BashTimeout: 150 * time.Millisecond}.Tools())["bash"]
out, err := b.Execute(context.Background(), argsJSON(t, map[string]any{"command": longSleepCommand(sh)}))
if err == nil {
t.Fatalf("expected workspace bash timeout, got nil (out=%q)", out)
}
if !strings.Contains(err.Error(), "timed out") {
t.Fatalf("error = %v, want timeout", err)
}
}
func TestNormalizeBashRunErrorAllowsPreservedWaitDelay(t *testing.T) {
if err := normalizeBashRunError(context.Background(), exec.ErrWaitDelay, true); err != nil {
t.Fatalf("preserved post-exit WaitDelay should be ignored, got %v", err)
}
if err := normalizeBashRunError(context.Background(), exec.ErrWaitDelay, false); !errors.Is(err, exec.ErrWaitDelay) {
t.Fatalf("ordinary WaitDelay should remain visible, got %v", err)
}
ctx, cancel := context.WithCancel(context.Background())
cancel()
if err := normalizeBashRunError(ctx, exec.ErrWaitDelay, true); !errors.Is(err, exec.ErrWaitDelay) {
t.Fatalf("cancelled WaitDelay should remain visible, got %v", err)
}
}
func longSleepCommand(sh sandbox.Shell) string {
if sh.Kind == sandbox.ShellPowerShell {
return "Start-Sleep -Seconds 2"
}
return "sleep 2"
}
func oneSecondCommand(sh sandbox.Shell) string {
if sh.Kind == sandbox.ShellPowerShell {
return "Start-Sleep -Seconds 1; Write-Output done"
}
return "sleep 1; printf done"
}
func BenchmarkBashForegroundTimeoutExplicitZero(b *testing.B) {
bt := bash{timeout: 0}
ctx := context.Background()
for b.Loop() {
runCtx := ctx
timeout := bt.foregroundTimeout()
if timeout > 0 {
b.Fatal("zero-value bash should not create a timeout context")
}
if runCtx == nil {
b.Fatal("nil context")
}
}
}
func BenchmarkBashForegroundTimeoutConfiguredCap(b *testing.B) {
bt := bash{timeout: 120 * time.Second}
ctx := context.Background()
for b.Loop() {
runCtx := ctx
timeout := bt.foregroundTimeout()
if timeout > 0 {
var cancel context.CancelFunc
runCtx, cancel = context.WithTimeoutCause(ctx, timeout, errBashTimeout)
cancel()
}
if runCtx == nil {
b.Fatal("nil context")
}
}
}