1package claudetool
2
3import (
4 "context"
5 "encoding/json"
6 "os"
7 "os/exec"
8 "path/filepath"
9 "strings"
10 "syscall"
11 "testing"
12 "time"
13)
14
15func TestBashSlowOk(t *testing.T) {
16 // Test that slow_ok flag is properly handled
17 t.Run("SlowOk Flag", func(t *testing.T) {
18 input := json.RawMessage(`{"command":"echo 'slow test'","slow_ok":true}`)
19
20 bashTool := (&BashTool{WorkingDir: NewMutableWorkingDir("/")}).Tool()
21 toolOut := bashTool.Run(context.Background(), input)
22 if toolOut.Error != nil {
23 t.Fatalf("Unexpected error: %v", toolOut.Error)
24 }
25 result := toolOut.LLMContent
26
27 expected := "slow test\n"
28 if len(result) == 0 || result[0].Text != expected {
29 t.Errorf("Expected %q, got %q", expected, result[0].Text)
30 }
31 })
32
33 // Test that slow_ok with background works
34 t.Run("SlowOk with Background", func(t *testing.T) {
35 input := json.RawMessage(`{"command":"echo 'slow background test'","slow_ok":true,"background":true}`)
36
37 bashTool := (&BashTool{WorkingDir: NewMutableWorkingDir("/")}).Tool()
38 toolOut := bashTool.Run(context.Background(), input)
39 if toolOut.Error != nil {
40 t.Fatalf("Unexpected error: %v", toolOut.Error)
41 }
42 result := toolOut.LLMContent
43
44 // Should return background result XML-ish format
45 resultStr := result[0].Text
46 if !strings.Contains(resultStr, "<pid>") || !strings.Contains(resultStr, "<output_file>") {
47 t.Errorf("Expected XML-ish background result format, got: %s", resultStr)
48 }
49
50 // Extract PID and output file from XML-ish format for cleanup
51 // This is a simple extraction for test cleanup - in real usage the agent would parse this
52 lines := strings.Split(resultStr, "\n")
53 var outFile string
54 for _, line := range lines {
55 if strings.Contains(line, "<output_file>") {
56 start := strings.Index(line, "<output_file>") + len("<output_file>")
57 end := strings.Index(line, "</output_file>")
58 if end > start {
59 outFile = line[start:end]
60 }
61 break
62 }
63 }
64
65 if outFile != "" {
66 // Clean up
67 os.Remove(outFile)
68 os.Remove(filepath.Dir(outFile))
69 }
70 })
71}
72
73func TestBashTool(t *testing.T) {
74 bashTool := &BashTool{WorkingDir: NewMutableWorkingDir("/")}
75 tool := bashTool.Tool()
76
77 // Test basic functionality
78 t.Run("Basic Command", func(t *testing.T) {
79 input := json.RawMessage(`{"command":"echo 'Hello, world!'"}`)
80
81 toolOut := tool.Run(context.Background(), input)
82 if toolOut.Error != nil {
83 t.Fatalf("Unexpected error: %v", toolOut.Error)
84 }
85 result := toolOut.LLMContent
86
87 expected := "Hello, world!\n"
88 if len(result) == 0 || result[0].Text != expected {
89 t.Errorf("Expected %q, got %q", expected, result[0].Text)
90 }
91
92 // Verify Display data contains working directory
93 display, ok := toolOut.Display.(BashDisplayData)
94 if !ok {
95 t.Fatalf("Expected Display to be BashDisplayData, got %T", toolOut.Display)
96 }
97 if display.WorkingDir != "/" {
98 t.Errorf("Expected WorkingDir to be '/', got %q", display.WorkingDir)
99 }
100 })
101
102 // Test with arguments
103 t.Run("Command With Arguments", func(t *testing.T) {
104 input := json.RawMessage(`{"command":"echo -n foo && echo -n bar"}`)
105
106 toolOut := tool.Run(context.Background(), input)
107 if toolOut.Error != nil {
108 t.Fatalf("Unexpected error: %v", toolOut.Error)
109 }
110 result := toolOut.LLMContent
111
112 expected := "foobar"
113 if len(result) == 0 || result[0].Text != expected {
114 t.Errorf("Expected %q, got %q", expected, result[0].Text)
115 }
116 })
117
118 // Test with slow_ok parameter
119 t.Run("With SlowOK", func(t *testing.T) {
120 inputObj := struct {
121 Command string `json:"command"`
122 SlowOK bool `json:"slow_ok"`
123 }{
124 Command: "sleep 0.1 && echo 'Completed'",
125 SlowOK: true,
126 }
127 inputJSON, err := json.Marshal(inputObj)
128 if err != nil {
129 t.Fatalf("Failed to marshal input: %v", err)
130 }
131
132 toolOut := tool.Run(context.Background(), inputJSON)
133 if toolOut.Error != nil {
134 t.Fatalf("Unexpected error: %v", toolOut.Error)
135 }
136 result := toolOut.LLMContent
137
138 expected := "Completed\n"
139 if len(result) == 0 || result[0].Text != expected {
140 t.Errorf("Expected %q, got %q", expected, result[0].Text)
141 }
142 })
143
144 // Test command timeout with custom timeout config
145 t.Run("Command Timeout", func(t *testing.T) {
146 // Use a custom BashTool with very short timeout
147 customTimeouts := &Timeouts{
148 Fast: 100 * time.Millisecond,
149 Slow: 100 * time.Millisecond,
150 Background: 100 * time.Millisecond,
151 }
152 customBash := &BashTool{
153 WorkingDir: NewMutableWorkingDir("/"),
154 Timeouts: customTimeouts,
155 }
156 tool := customBash.Tool()
157
158 input := json.RawMessage(`{"command":"sleep 0.5 && echo 'Should not see this'"}`)
159
160 toolOut := tool.Run(context.Background(), input)
161 if toolOut.Error == nil {
162 t.Errorf("Expected timeout error, got none")
163 } else if !strings.Contains(toolOut.Error.Error(), "timed out") {
164 t.Errorf("Expected timeout error, got: %v", toolOut.Error)
165 }
166 })
167
168 // Test command that fails
169 t.Run("Failed Command", func(t *testing.T) {
170 input := json.RawMessage(`{"command":"exit 1"}`)
171
172 toolOut := tool.Run(context.Background(), input)
173 if toolOut.Error == nil {
174 t.Errorf("Expected error for failed command, got none")
175 }
176 })
177
178 // Test invalid input
179 t.Run("Invalid JSON Input", func(t *testing.T) {
180 input := json.RawMessage(`{"command":123}`) // Invalid JSON (command must be string)
181
182 toolOut := tool.Run(context.Background(), input)
183 if toolOut.Error == nil {
184 t.Errorf("Expected error for invalid input, got none")
185 }
186 })
187}
188
189func TestExecuteBash(t *testing.T) {
190 ctx := context.Background()
191 bashTool := &BashTool{WorkingDir: NewMutableWorkingDir("/")}
192
193 // Test successful command
194 t.Run("Successful Command", func(t *testing.T) {
195 req := bashInput{
196 Command: "echo 'Success'",
197 }
198
199 output, err := bashTool.executeBash(ctx, req, 5*time.Second)
200 if err != nil {
201 t.Fatalf("Unexpected error: %v", err)
202 }
203
204 want := "Success\n"
205 if output != want {
206 t.Errorf("Expected %q, got %q", want, output)
207 }
208 })
209
210 // Test SKETCH=1 environment variable is set
211 t.Run("SKETCH Environment Variable", func(t *testing.T) {
212 req := bashInput{
213 Command: "echo $SKETCH",
214 }
215
216 output, err := bashTool.executeBash(ctx, req, 5*time.Second)
217 if err != nil {
218 t.Fatalf("Unexpected error: %v", err)
219 }
220
221 want := "1\n"
222 if output != want {
223 t.Errorf("Expected SKETCH=1, got %q", output)
224 }
225 })
226
227 // Test SHELLEY_CONVERSATION_ID environment variable is set when configured
228 t.Run("SHELLEY_CONVERSATION_ID Environment Variable", func(t *testing.T) {
229 bashWithConvID := &BashTool{
230 WorkingDir: NewMutableWorkingDir("/"),
231 ConversationID: "test-conv-123",
232 }
233 req := bashInput{
234 Command: "echo $SHELLEY_CONVERSATION_ID",
235 }
236
237 output, err := bashWithConvID.executeBash(ctx, req, 5*time.Second)
238 if err != nil {
239 t.Fatalf("Unexpected error: %v", err)
240 }
241
242 want := "test-conv-123\n"
243 if output != want {
244 t.Errorf("Expected SHELLEY_CONVERSATION_ID=test-conv-123, got %q", output)
245 }
246 })
247
248 // Test SHELLEY_CONVERSATION_ID is not set when not configured
249 t.Run("SHELLEY_CONVERSATION_ID Not Set When Empty", func(t *testing.T) {
250 req := bashInput{
251 Command: "echo \"conv_id:$SHELLEY_CONVERSATION_ID:\"",
252 }
253
254 output, err := bashTool.executeBash(ctx, req, 5*time.Second)
255 if err != nil {
256 t.Fatalf("Unexpected error: %v", err)
257 }
258
259 // Should be empty since ConversationID is not set on bashTool
260 want := "conv_id::\n"
261 if output != want {
262 t.Errorf("Expected empty SHELLEY_CONVERSATION_ID, got %q", output)
263 }
264 })
265
266 // Test command with output to stderr
267 t.Run("Command with stderr", func(t *testing.T) {
268 req := bashInput{
269 Command: "echo 'Error message' >&2 && echo 'Success'",
270 }
271
272 output, err := bashTool.executeBash(ctx, req, 5*time.Second)
273 if err != nil {
274 t.Fatalf("Unexpected error: %v", err)
275 }
276
277 want := "Error message\nSuccess\n"
278 if output != want {
279 t.Errorf("Expected %q, got %q", want, output)
280 }
281 })
282
283 // Test command that fails with stderr
284 t.Run("Failed Command with stderr", func(t *testing.T) {
285 req := bashInput{
286 Command: "echo 'Error message' >&2 && exit 1",
287 }
288
289 _, err := bashTool.executeBash(ctx, req, 5*time.Second)
290 if err == nil {
291 t.Errorf("Expected error for failed command, got none")
292 } else if !strings.Contains(err.Error(), "Error message") {
293 t.Errorf("Expected stderr in error message, got: %v", err)
294 }
295 })
296
297 // Test timeout
298 t.Run("Command Timeout", func(t *testing.T) {
299 req := bashInput{
300 Command: "sleep 1 && echo 'Should not see this'",
301 }
302
303 start := time.Now()
304 _, err := bashTool.executeBash(ctx, req, 100*time.Millisecond)
305 elapsed := time.Since(start)
306
307 // Command should time out after ~100ms, not wait for full 1 second
308 if elapsed >= 1*time.Second {
309 t.Errorf("Command did not respect timeout, took %v", elapsed)
310 }
311
312 if err == nil {
313 t.Errorf("Expected timeout error, got none")
314 } else if !strings.Contains(err.Error(), "timed out") {
315 t.Errorf("Expected timeout error, got: %v", err)
316 }
317 })
318}
319
320func TestBackgroundBash(t *testing.T) {
321 bashTool := &BashTool{WorkingDir: NewMutableWorkingDir("/")}
322 tool := bashTool.Tool()
323
324 // Test basic background execution
325 t.Run("Basic Background Command", func(t *testing.T) {
326 inputObj := struct {
327 Command string `json:"command"`
328 Background bool `json:"background"`
329 }{
330 Command: "echo 'Hello from background' $SKETCH",
331 Background: true,
332 }
333 inputJSON, err := json.Marshal(inputObj)
334 if err != nil {
335 t.Fatalf("Failed to marshal input: %v", err)
336 }
337
338 toolOut := tool.Run(context.Background(), inputJSON)
339 if toolOut.Error != nil {
340 t.Fatalf("Unexpected error: %v", toolOut.Error)
341 }
342 result := toolOut.LLMContent
343
344 // Parse the returned XML-ish format
345 resultStr := result[0].Text
346 if !strings.Contains(resultStr, "<pid>") || !strings.Contains(resultStr, "<output_file>") {
347 t.Fatalf("Expected XML-ish background result format, got: %s", resultStr)
348 }
349
350 // Extract PID and output file from XML-ish format
351 lines := strings.Split(resultStr, "\n")
352 var pidStr, outFile string
353 for _, line := range lines {
354 if strings.Contains(line, "<pid>") {
355 start := strings.Index(line, "<pid>") + len("<pid>")
356 end := strings.Index(line, "</pid>")
357 if end > start {
358 pidStr = line[start:end]
359 }
360 } else if strings.Contains(line, "<output_file>") {
361 start := strings.Index(line, "<output_file>") + len("<output_file>")
362 end := strings.Index(line, "</output_file>")
363 if end > start {
364 outFile = line[start:end]
365 }
366 }
367 }
368
369 // Verify we got valid values
370 if pidStr == "" || outFile == "" {
371 t.Errorf("Failed to extract PID or output file from result: %s", resultStr)
372 return
373 }
374
375 // Verify output file exists
376 if _, err := os.Stat(outFile); os.IsNotExist(err) {
377 t.Errorf("Output file doesn't exist: %s", outFile)
378 }
379
380 // Wait for the command output to be written to file
381 waitForFile(t, outFile)
382
383 // Check file contents
384 outputContent, err := os.ReadFile(outFile)
385 if err != nil {
386 t.Fatalf("Failed to read output file: %v", err)
387 }
388 // The implementation appends a completion message to the output
389 outputStr := string(outputContent)
390 if !strings.Contains(outputStr, "Hello from background 1") {
391 t.Errorf("Expected output to contain 'Hello from background 1', got %q", outputStr)
392 }
393 if !strings.Contains(outputStr, "[background process completed]") {
394 t.Errorf("Expected output to contain completion message, got %q", outputStr)
395 }
396
397 // Clean up
398 os.Remove(outFile)
399 os.Remove(filepath.Dir(outFile))
400 })
401
402 // Test background command with stderr output
403 t.Run("Background Command with stderr", func(t *testing.T) {
404 inputObj := struct {
405 Command string `json:"command"`
406 Background bool `json:"background"`
407 }{
408 Command: "echo 'Output to stdout' && echo 'Output to stderr' >&2",
409 Background: true,
410 }
411 inputJSON, err := json.Marshal(inputObj)
412 if err != nil {
413 t.Fatalf("Failed to marshal input: %v", err)
414 }
415
416 toolOut := tool.Run(context.Background(), inputJSON)
417 if toolOut.Error != nil {
418 t.Fatalf("Unexpected error: %v", toolOut.Error)
419 }
420 result := toolOut.LLMContent
421
422 // Parse the returned XML-ish format
423 resultStr := result[0].Text
424 lines := strings.Split(resultStr, "\n")
425 var outFile string
426 for _, line := range lines {
427 if strings.Contains(line, "<output_file>") {
428 start := strings.Index(line, "<output_file>") + len("<output_file>")
429 end := strings.Index(line, "</output_file>")
430 if end > start {
431 outFile = line[start:end]
432 }
433 break
434 }
435 }
436
437 // Wait for the command output to be written to file
438 waitForFile(t, outFile)
439
440 // Check output content (stdout and stderr are combined in implementation)
441 outputContent, err := os.ReadFile(outFile)
442 if err != nil {
443 t.Fatalf("Failed to read output file: %v", err)
444 }
445 // Implementation combines stdout and stderr into one file
446 outputStr := string(outputContent)
447 if !strings.Contains(outputStr, "Output to stdout") || !strings.Contains(outputStr, "Output to stderr") {
448 t.Errorf("Expected both stdout and stderr content, got %q", outputStr)
449 }
450
451 // Clean up
452 os.Remove(outFile)
453 os.Remove(filepath.Dir(outFile))
454 })
455
456 // Test background command running without waiting
457 t.Run("Background Command Running", func(t *testing.T) {
458 // Create a script that will continue running after we check
459 inputObj := struct {
460 Command string `json:"command"`
461 Background bool `json:"background"`
462 }{
463 Command: "echo 'Running in background' && sleep 5",
464 Background: true,
465 }
466 inputJSON, err := json.Marshal(inputObj)
467 if err != nil {
468 t.Fatalf("Failed to marshal input: %v", err)
469 }
470
471 // Start the command in the background
472 toolOut := tool.Run(context.Background(), inputJSON)
473 if toolOut.Error != nil {
474 t.Fatalf("Unexpected error: %v", toolOut.Error)
475 }
476 result := toolOut.LLMContent
477
478 // Parse the returned XML-ish format
479 resultStr := result[0].Text
480 lines := strings.Split(resultStr, "\n")
481 var pidStr, outFile string
482 for _, line := range lines {
483 if strings.Contains(line, "<pid>") {
484 start := strings.Index(line, "<pid>") + len("<pid>")
485 end := strings.Index(line, "</pid>")
486 if end > start {
487 pidStr = line[start:end]
488 }
489 } else if strings.Contains(line, "<output_file>") {
490 start := strings.Index(line, "<output_file>") + len("<output_file>")
491 end := strings.Index(line, "</output_file>")
492 if end > start {
493 outFile = line[start:end]
494 }
495 }
496 }
497
498 // Wait for the command output to be written to file
499 waitForFile(t, outFile)
500
501 // Check output content
502 outputContent, err := os.ReadFile(outFile)
503 if err != nil {
504 t.Fatalf("Failed to read output file: %v", err)
505 }
506 expectedOutput := "Running in background\n"
507 if string(outputContent) != expectedOutput {
508 t.Errorf("Expected output content %q, got %q", expectedOutput, string(outputContent))
509 }
510
511 // Verify the process is still running by parsing PID
512 if pidStr != "" {
513 // We can't easily test if the process is still running without importing strconv
514 // and the process might have finished by now anyway due to timing
515 t.Log("Process started in background with PID:", pidStr)
516 }
517
518 // Clean up
519 os.Remove(outFile)
520 os.Remove(filepath.Dir(outFile))
521 })
522}
523
524func TestBashTimeout(t *testing.T) {
525 // Test default timeout values
526 t.Run("Default Timeout Values", func(t *testing.T) {
527 // Test foreground default timeout
528 foreground := bashInput{
529 Command: "echo 'test'",
530 Background: false,
531 }
532 fgTimeout := foreground.timeout(nil)
533 expectedFg := 30 * time.Second
534 if fgTimeout != expectedFg {
535 t.Errorf("Expected foreground default timeout to be %v, got %v", expectedFg, fgTimeout)
536 }
537
538 // Test background default timeout
539 background := bashInput{
540 Command: "echo 'test'",
541 Background: true,
542 }
543 bgTimeout := background.timeout(nil)
544 expectedBg := 24 * time.Hour
545 if bgTimeout != expectedBg {
546 t.Errorf("Expected background default timeout to be %v, got %v", expectedBg, bgTimeout)
547 }
548
549 // Test slow_ok timeout
550 slowOk := bashInput{
551 Command: "echo 'test'",
552 Background: false,
553 SlowOK: true,
554 }
555 slowTimeout := slowOk.timeout(nil)
556 expectedSlow := 15 * time.Minute
557 if slowTimeout != expectedSlow {
558 t.Errorf("Expected slow_ok timeout to be %v, got %v", expectedSlow, slowTimeout)
559 }
560
561 // Test custom timeout config
562 customTimeouts := &Timeouts{
563 Fast: 5 * time.Second,
564 Slow: 2 * time.Minute,
565 Background: 1 * time.Hour,
566 }
567 customFast := bashInput{
568 Command: "echo 'test'",
569 Background: false,
570 }
571 customTimeout := customFast.timeout(customTimeouts)
572 expectedCustom := 5 * time.Second
573 if customTimeout != expectedCustom {
574 t.Errorf("Expected custom timeout to be %v, got %v", expectedCustom, customTimeout)
575 }
576 })
577}
578
579func TestFormatForegroundBashOutput(t *testing.T) {
580 // Test small output (under threshold) - should pass through unchanged
581 t.Run("Small Output", func(t *testing.T) {
582 smallOutput := "line 1\nline 2\nline 3\n"
583 result, err := formatForegroundBashOutput(smallOutput)
584 if err != nil {
585 t.Fatalf("Unexpected error: %v", err)
586 }
587 if result != smallOutput {
588 t.Errorf("Expected small output to pass through unchanged, got %q", result)
589 }
590 })
591
592 // Test large output (over 50KB) - should save to file and return summary
593 t.Run("Large Output With Lines", func(t *testing.T) {
594 // Generate output > 50KB with many lines
595 var lines []string
596 for i := 1; i <= 1000; i++ {
597 lines = append(lines, strings.Repeat("x", 60)+" line "+string(rune('0'+i%10)))
598 }
599 largeOutput := strings.Join(lines, "\n")
600 if len(largeOutput) < largeOutputThreshold {
601 t.Fatalf("Test setup error: output is only %d bytes, need > %d", len(largeOutput), largeOutputThreshold)
602 }
603
604 result, err := formatForegroundBashOutput(largeOutput)
605 if err != nil {
606 t.Fatalf("Unexpected error: %v", err)
607 }
608
609 // Should mention the file
610 if !strings.Contains(result, "saved to:") {
611 t.Errorf("Expected result to mention saved file, got:\n%s", result)
612 }
613
614 // Should have first 2 lines numbered
615 if !strings.Contains(result, " 1:") || !strings.Contains(result, " 2:") {
616 t.Errorf("Expected first 2 numbered lines, got:\n%s", result)
617 }
618
619 // Should have last 5 lines numbered
620 if !strings.Contains(result, " 996:") || !strings.Contains(result, " 1000:") {
621 t.Errorf("Expected last 5 numbered lines, got:\n%s", result)
622 }
623
624 t.Logf("Large output result:\n%s", result)
625 })
626
627 // Test large output with few/no lines (binary-like)
628 t.Run("Large Output No Lines", func(t *testing.T) {
629 // Generate > 50KB of data with no newlines
630 largeOutput := strings.Repeat("x", largeOutputThreshold+1000)
631
632 result, err := formatForegroundBashOutput(largeOutput)
633 if err != nil {
634 t.Fatalf("Unexpected error: %v", err)
635 }
636
637 // Should mention the file
638 if !strings.Contains(result, "saved to:") {
639 t.Errorf("Expected result to mention saved file, got:\n%s", result)
640 }
641
642 // Should indicate line count
643 if !strings.Contains(result, "1 lines") {
644 t.Errorf("Expected result to indicate line count, got:\n%s", result)
645 }
646
647 t.Logf("Large binary-like output result:\n%s", result)
648 })
649
650 // Test large output with very long lines (e.g., minified JS)
651 t.Run("Large Output With Long Lines", func(t *testing.T) {
652 // Generate output > 50KB with few very long lines
653 longLine := strings.Repeat("abcdefghij", 1000) // 10KB per line
654 lines := []string{longLine, longLine, longLine, longLine, longLine, longLine}
655 largeOutput := strings.Join(lines, "\n")
656 if len(largeOutput) < largeOutputThreshold {
657 t.Fatalf("Test setup error: output is only %d bytes, need > %d", len(largeOutput), largeOutputThreshold)
658 }
659
660 result, err := formatForegroundBashOutput(largeOutput)
661 if err != nil {
662 t.Fatalf("Unexpected error: %v", err)
663 }
664
665 // Result should be reasonable size (not blow up context)
666 if len(result) > 4096 {
667 t.Errorf("Expected truncated result < 4KB, got %d bytes:\n%s", len(result), result)
668 }
669
670 // Should mention the file
671 if !strings.Contains(result, "saved to:") {
672 t.Errorf("Expected result to mention saved file, got:\n%s", result)
673 }
674
675 // Lines should be truncated
676 if !strings.Contains(result, "...") {
677 t.Errorf("Expected truncated lines with '...', got:\n%s", result)
678 }
679
680 t.Logf("Large output with long lines result:\n%s", result)
681 })
682}
683
684// waitForFile waits for a file to exist and be non-empty or times out
685func waitForFile(t *testing.T, filepath string) {
686 timeout := time.After(5 * time.Second)
687 tick := time.NewTicker(10 * time.Millisecond)
688 defer tick.Stop()
689
690 for {
691 select {
692 case <-timeout:
693 t.Fatalf("Timed out waiting for file to exist and have contents: %s", filepath)
694 return
695 case <-tick.C:
696 info, err := os.Stat(filepath)
697 if err == nil && info.Size() > 0 {
698 return // File exists and has content
699 }
700 }
701 }
702}
703
704// waitForProcessDeath waits for a process to no longer exist or times out
705func waitForProcessDeath(t *testing.T, pid int) {
706 timeout := time.After(5 * time.Second)
707 tick := time.NewTicker(50 * time.Millisecond)
708 defer tick.Stop()
709
710 for {
711 select {
712 case <-timeout:
713 t.Fatalf("Timed out waiting for process %d to exit", pid)
714 return
715 case <-tick.C:
716 process, _ := os.FindProcess(pid)
717 err := process.Signal(syscall.Signal(0))
718 if err != nil {
719 // Process doesn't exist
720 return
721 }
722 }
723 }
724}
725
726func TestIsNoTrailerSet(t *testing.T) {
727 bashTool := &BashTool{WorkingDir: NewMutableWorkingDir("/")}
728
729 // Test when config is not set (default)
730 t.Run("Default No Config", func(t *testing.T) {
731 if bashTool.isNoTrailerSet() {
732 t.Error("Expected isNoTrailerSet() to be false when not configured")
733 }
734 })
735
736 // Test when config is set to true
737 t.Run("Config Set True", func(t *testing.T) {
738 // Set the global config
739 cmd := exec.Command("git", "config", "--global", "shelley.no-trailer", "true")
740 if err := cmd.Run(); err != nil {
741 t.Skipf("Could not set git config: %v", err)
742 }
743 defer exec.Command("git", "config", "--global", "--unset", "shelley.no-trailer").Run()
744
745 if !bashTool.isNoTrailerSet() {
746 t.Error("Expected isNoTrailerSet() to be true when shelley.no-trailer=true")
747 }
748 })
749
750 // Test when config is set to false
751 t.Run("Config Set False", func(t *testing.T) {
752 cmd := exec.Command("git", "config", "--global", "shelley.no-trailer", "false")
753 if err := cmd.Run(); err != nil {
754 t.Skipf("Could not set git config: %v", err)
755 }
756 defer exec.Command("git", "config", "--global", "--unset", "shelley.no-trailer").Run()
757
758 if bashTool.isNoTrailerSet() {
759 t.Error("Expected isNoTrailerSet() to be false when shelley.no-trailer=false")
760 }
761 })
762}