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