blob: 64c415861f32e6d6efba7de7565c6375fc109b40 [file] [log] [blame]
Philip Zeyliger064f8ae2025-05-14 00:47:41 +00001package test
2
3import (
4 "context"
5 "encoding/json"
6 "testing"
7
8 "sketch.dev/claudetool"
Philip Zeyliger064f8ae2025-05-14 00:47:41 +00009)
10
11func TestBashTimeout(t *testing.T) {
12 // Create a bash tool
Josh Bleecher Snyder495c1fa2025-05-29 00:37:22 +000013 bashTool := claudetool.NewBashTool(nil, claudetool.NoBashToolJITInstall)
Philip Zeyliger064f8ae2025-05-14 00:47:41 +000014
15 // Create a command that will output text and then sleep
16 cmd := `echo "Starting command..."; echo "This should appear in partial output"; sleep 5; echo "This shouldn't appear"`
17
18 // Prepare the input with a very short timeout
Philip Zeyligerec398cb2025-05-14 00:48:06 +000019 input := map[string]any{
Philip Zeyliger064f8ae2025-05-14 00:47:41 +000020 "command": cmd,
21 "timeout": "1s", // Very short timeout to trigger the timeout case
22 }
23
24 // Marshal the input to JSON
25 inputJSON, err := json.Marshal(input)
26 if err != nil {
27 t.Fatalf("Failed to marshal input: %v", err)
28 }
29
30 // Run the bash tool
31 ctx := context.Background()
32 result, err := bashTool.Run(ctx, inputJSON)
33
34 // Check that we got an error (due to timeout)
35 if err == nil {
36 t.Fatalf("Expected timeout error, got nil")
37 }
38
39 // Error should mention timeout
40 if !containsString(err.Error(), "timed out") {
41 t.Errorf("Error doesn't mention timeout: %v", err)
42 }
43
Philip Zeyliger8a1b89a2025-05-13 17:58:41 -070044 // No output should be returned directly, it should be in the error message
45 if len(result) > 0 {
46 t.Fatalf("Expected no direct output, got: %v", result)
Philip Zeyliger064f8ae2025-05-14 00:47:41 +000047 }
48
Philip Zeyliger8a1b89a2025-05-13 17:58:41 -070049 // The error should contain the partial output
50 errorMsg := err.Error()
51 if !containsString(errorMsg, "Starting command") || !containsString(errorMsg, "should appear in partial output") {
52 t.Errorf("Error should contain the partial output: %v", errorMsg)
Philip Zeyliger064f8ae2025-05-14 00:47:41 +000053 }
54
Philip Zeyliger8a1b89a2025-05-13 17:58:41 -070055 // The error should indicate a timeout
56 if !containsString(errorMsg, "timed out") {
57 t.Errorf("Error should indicate a timeout: %v", errorMsg)
Philip Zeyliger064f8ae2025-05-14 00:47:41 +000058 }
59
Philip Zeyliger8a1b89a2025-05-13 17:58:41 -070060 // The error should not contain the output that would appear after the sleep
61 if containsString(err.Error(), "shouldn't appear") {
62 t.Errorf("Error contains output that should not have been captured (after timeout): %s", err.Error())
Philip Zeyliger064f8ae2025-05-14 00:47:41 +000063 }
64}
65
66func containsString(s, substr string) bool {
67 return s != "" && s != "<nil>" && stringIndexOf(s, substr) >= 0
68}
69
70func stringIndexOf(s, substr string) int {
71 for i := 0; i <= len(s)-len(substr); i++ {
72 if s[i:i+len(substr)] == substr {
73 return i
74 }
75 }
76 return -1
77}