返回 DeepSeek-Reasonix
bash_timeout_test.go
根目录 / internal / tool / builtin / bash_timeout_test.go
1 package builtin
2
3 import (
4 "context"
5 "errors"
6 "os/exec"
7 "strings"
8 "testing"
9 "time"
10
11 "reasonix/internal/sandbox"
12 )
13
14 func TestBashForegroundTimeoutConfig(t *testing.T) {
15 sh := sandbox.ResolveShell("", "", nil)
16 b := bash{shell: sh, timeout: 150 * time.Millisecond}
17
18 start := time.Now()
19 out, err := b.Execute(context.Background(), argsJSON(t, map[string]any{"command": longSleepCommand(sh)}))
20 elapsed := time.Since(start)
21 if err == nil {
22 t.Fatalf("expected timeout error, got nil (out=%q)", out)
23 }
24 if !strings.Contains(err.Error(), "timed out") {
25 t.Fatalf("error = %v, want timeout", err)
26 }
27 if elapsed > 5*time.Second {
28 t.Fatalf("configured timeout returned too slowly: %v", elapsed)
29 }
30 }
31
32 func TestBashExplicitZeroTimeoutDoesNotCapForeground(t *testing.T) {
33 sh := sandbox.ResolveShell("", "", nil)
34 ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
35 defer cancel()
36
37 start := time.Now()
38 out, err := (bash{shell: sh, timeout: 0}).Execute(ctx, argsJSON(t, map[string]any{"command": oneSecondCommand(sh)}))
39 elapsed := time.Since(start)
40 if err != nil {
41 t.Fatalf("zero-timeout foreground command failed: %v (out=%q)", err, out)
42 }
43 if !strings.Contains(out, "done") {
44 t.Fatalf("output = %q, want done", out)
45 }
46 if elapsed < 800*time.Millisecond {
47 t.Fatalf("command returned too quickly (%v), so the sleep did not run", elapsed)
48 }
49 }
50
51 func TestWorkspacePassesBashTimeout(t *testing.T) {
52 sh := sandbox.ResolveShell("", "", nil)
53 b := byName(Workspace{Dir: t.TempDir(), BashTimeout: 150 * time.Millisecond}.Tools())["bash"]
54
55 out, err := b.Execute(context.Background(), argsJSON(t, map[string]any{"command": longSleepCommand(sh)}))
56 if err == nil {
57 t.Fatalf("expected workspace bash timeout, got nil (out=%q)", out)
58 }
59 if !strings.Contains(err.Error(), "timed out") {
60 t.Fatalf("error = %v, want timeout", err)
61 }
62 }
63
64 func TestNormalizeBashRunErrorAllowsPreservedWaitDelay(t *testing.T) {
65 if err := normalizeBashRunError(context.Background(), exec.ErrWaitDelay, true); err != nil {
66 t.Fatalf("preserved post-exit WaitDelay should be ignored, got %v", err)
67 }
68 if err := normalizeBashRunError(context.Background(), exec.ErrWaitDelay, false); !errors.Is(err, exec.ErrWaitDelay) {
69 t.Fatalf("ordinary WaitDelay should remain visible, got %v", err)
70 }
71
72 ctx, cancel := context.WithCancel(context.Background())
73 cancel()
74 if err := normalizeBashRunError(ctx, exec.ErrWaitDelay, true); !errors.Is(err, exec.ErrWaitDelay) {
75 t.Fatalf("cancelled WaitDelay should remain visible, got %v", err)
76 }
77 }
78
79 func longSleepCommand(sh sandbox.Shell) string {
80 if sh.Kind == sandbox.ShellPowerShell {
81 return "Start-Sleep -Seconds 2"
82 }
83 return "sleep 2"
84 }
85
86 func oneSecondCommand(sh sandbox.Shell) string {
87 if sh.Kind == sandbox.ShellPowerShell {
88 return "Start-Sleep -Seconds 1; Write-Output done"
89 }
90 return "sleep 1; printf done"
91 }
92
93 func BenchmarkBashForegroundTimeoutExplicitZero(b *testing.B) {
94 bt := bash{timeout: 0}
95 ctx := context.Background()
96 for b.Loop() {
97 runCtx := ctx
98 timeout := bt.foregroundTimeout()
99 if timeout > 0 {
100 b.Fatal("zero-value bash should not create a timeout context")
101 }
102 if runCtx == nil {
103 b.Fatal("nil context")
104 }
105 }
106 }
107
108 func BenchmarkBashForegroundTimeoutConfiguredCap(b *testing.B) {
109 bt := bash{timeout: 120 * time.Second}
110 ctx := context.Background()
111 for b.Loop() {
112 runCtx := ctx
113 timeout := bt.foregroundTimeout()
114 if timeout > 0 {
115 var cancel context.CancelFunc
116 runCtx, cancel = context.WithTimeoutCause(ctx, timeout, errors.New("bash foreground timeout"))
117 cancel()
118 }
119 if runCtx == nil {
120 b.Fatal("nil context")
121 }
122 }
123 }
124
124 lines GO