Source file
src/os/exec/exec_test.go
Documentation: os/exec
1
2
3
4
5
6
7
8 package exec_test
9
10 import (
11 "bufio"
12 "bytes"
13 "context"
14 "fmt"
15 "internal/poll"
16 "internal/testenv"
17 "io"
18 "log"
19 "net"
20 "net/http"
21 "net/http/httptest"
22 "os"
23 "os/exec"
24 "path/filepath"
25 "runtime"
26 "strconv"
27 "strings"
28 "testing"
29 "time"
30 )
31
32
33
34 var haveUnexpectedFDs bool
35
36
37
38
39 var unfinalizedFiles []*os.File
40
41 func init() {
42 if os.Getenv("GO_WANT_HELPER_PROCESS") == "1" {
43 return
44 }
45 if runtime.GOOS == "windows" {
46 return
47 }
48 for fd := uintptr(3); fd <= 100; fd++ {
49 if poll.IsPollDescriptor(fd) {
50 continue
51 }
52
53
54
55
56 f := os.NewFile(fd, "")
57 if _, err := f.Stat(); err != nil {
58
59
60 f.Close()
61 } else {
62 fmt.Printf("fd %d open at test start\n", fd)
63 haveUnexpectedFDs = true
64
65
66 unfinalizedFiles = append(unfinalizedFiles, f)
67 }
68 }
69 }
70
71 func helperCommandContext(t *testing.T, ctx context.Context, s ...string) (cmd *exec.Cmd) {
72 testenv.MustHaveExec(t)
73
74 cs := []string{"-test.run=TestHelperProcess", "--"}
75 cs = append(cs, s...)
76 if ctx != nil {
77 cmd = exec.CommandContext(ctx, os.Args[0], cs...)
78 } else {
79 cmd = exec.Command(os.Args[0], cs...)
80 }
81 cmd.Env = append(os.Environ(), "GO_WANT_HELPER_PROCESS=1")
82 return cmd
83 }
84
85 func helperCommand(t *testing.T, s ...string) *exec.Cmd {
86 return helperCommandContext(t, nil, s...)
87 }
88
89 func TestEcho(t *testing.T) {
90 bs, err := helperCommand(t, "echo", "foo bar", "baz").Output()
91 if err != nil {
92 t.Errorf("echo: %v", err)
93 }
94 if g, e := string(bs), "foo bar baz\n"; g != e {
95 t.Errorf("echo: want %q, got %q", e, g)
96 }
97 }
98
99 func TestCommandRelativeName(t *testing.T) {
100 testenv.MustHaveExec(t)
101
102
103
104 base := filepath.Base(os.Args[0])
105 dir := filepath.Dir(os.Args[0])
106 if dir == "." {
107 t.Skip("skipping; running test at root somehow")
108 }
109 parentDir := filepath.Dir(dir)
110 dirBase := filepath.Base(dir)
111 if dirBase == "." {
112 t.Skipf("skipping; unexpected shallow dir of %q", dir)
113 }
114
115 cmd := exec.Command(filepath.Join(dirBase, base), "-test.run=TestHelperProcess", "--", "echo", "foo")
116 cmd.Dir = parentDir
117 cmd.Env = []string{"GO_WANT_HELPER_PROCESS=1"}
118
119 out, err := cmd.Output()
120 if err != nil {
121 t.Errorf("echo: %v", err)
122 }
123 if g, e := string(out), "foo\n"; g != e {
124 t.Errorf("echo: want %q, got %q", e, g)
125 }
126 }
127
128 func TestCatStdin(t *testing.T) {
129
130 input := "Input string\nLine 2"
131 p := helperCommand(t, "cat")
132 p.Stdin = strings.NewReader(input)
133 bs, err := p.Output()
134 if err != nil {
135 t.Errorf("cat: %v", err)
136 }
137 s := string(bs)
138 if s != input {
139 t.Errorf("cat: want %q, got %q", input, s)
140 }
141 }
142
143 func TestEchoFileRace(t *testing.T) {
144 cmd := helperCommand(t, "echo")
145 stdin, err := cmd.StdinPipe()
146 if err != nil {
147 t.Fatalf("StdinPipe: %v", err)
148 }
149 if err := cmd.Start(); err != nil {
150 t.Fatalf("Start: %v", err)
151 }
152 wrote := make(chan bool)
153 go func() {
154 defer close(wrote)
155 fmt.Fprint(stdin, "echo\n")
156 }()
157 if err := cmd.Wait(); err != nil {
158 t.Fatalf("Wait: %v", err)
159 }
160 <-wrote
161 }
162
163 func TestCatGoodAndBadFile(t *testing.T) {
164
165 bs, err := helperCommand(t, "cat", "/bogus/file.foo", "exec_test.go").CombinedOutput()
166 if _, ok := err.(*exec.ExitError); !ok {
167 t.Errorf("expected *exec.ExitError from cat combined; got %T: %v", err, err)
168 }
169 s := string(bs)
170 sp := strings.SplitN(s, "\n", 2)
171 if len(sp) != 2 {
172 t.Fatalf("expected two lines from cat; got %q", s)
173 }
174 errLine, body := sp[0], sp[1]
175 if !strings.HasPrefix(errLine, "Error: open /bogus/file.foo") {
176 t.Errorf("expected stderr to complain about file; got %q", errLine)
177 }
178 if !strings.Contains(body, "func TestHelperProcess(t *testing.T)") {
179 t.Errorf("expected test code; got %q (len %d)", body, len(body))
180 }
181 }
182
183 func TestNoExistExecutable(t *testing.T) {
184
185 err := exec.Command("/no-exist-executable").Run()
186 if err == nil {
187 t.Error("expected error from /no-exist-executable")
188 }
189 }
190
191 func TestExitStatus(t *testing.T) {
192
193 cmd := helperCommand(t, "exit", "42")
194 err := cmd.Run()
195 want := "exit status 42"
196 switch runtime.GOOS {
197 case "plan9":
198 want = fmt.Sprintf("exit status: '%s %d: 42'", filepath.Base(cmd.Path), cmd.ProcessState.Pid())
199 }
200 if werr, ok := err.(*exec.ExitError); ok {
201 if s := werr.Error(); s != want {
202 t.Errorf("from exit 42 got exit %q, want %q", s, want)
203 }
204 } else {
205 t.Fatalf("expected *exec.ExitError from exit 42; got %T: %v", err, err)
206 }
207 }
208
209 func TestExitCode(t *testing.T) {
210
211 cmd := helperCommand(t, "exit", "42")
212 cmd.Run()
213 want := 42
214 if runtime.GOOS == "plan9" {
215 want = 1
216 }
217 got := cmd.ProcessState.ExitCode()
218 if want != got {
219 t.Errorf("ExitCode got %d, want %d", got, want)
220 }
221
222 cmd = helperCommand(t, "/no-exist-executable")
223 cmd.Run()
224 want = 2
225 if runtime.GOOS == "plan9" {
226 want = 1
227 }
228 got = cmd.ProcessState.ExitCode()
229 if want != got {
230 t.Errorf("ExitCode got %d, want %d", got, want)
231 }
232
233 cmd = helperCommand(t, "exit", "255")
234 cmd.Run()
235 want = 255
236 if runtime.GOOS == "plan9" {
237 want = 1
238 }
239 got = cmd.ProcessState.ExitCode()
240 if want != got {
241 t.Errorf("ExitCode got %d, want %d", got, want)
242 }
243
244 cmd = helperCommand(t, "cat")
245 cmd.Run()
246 want = 0
247 got = cmd.ProcessState.ExitCode()
248 if want != got {
249 t.Errorf("ExitCode got %d, want %d", got, want)
250 }
251
252
253 cmd = helperCommand(t, "cat")
254 want = -1
255 got = cmd.ProcessState.ExitCode()
256 if want != got {
257 t.Errorf("ExitCode got %d, want %d", got, want)
258 }
259 }
260
261 func TestPipes(t *testing.T) {
262 check := func(what string, err error) {
263 if err != nil {
264 t.Fatalf("%s: %v", what, err)
265 }
266 }
267
268 c := helperCommand(t, "pipetest")
269 stdin, err := c.StdinPipe()
270 check("StdinPipe", err)
271 stdout, err := c.StdoutPipe()
272 check("StdoutPipe", err)
273 stderr, err := c.StderrPipe()
274 check("StderrPipe", err)
275
276 outbr := bufio.NewReader(stdout)
277 errbr := bufio.NewReader(stderr)
278 line := func(what string, br *bufio.Reader) string {
279 line, _, err := br.ReadLine()
280 if err != nil {
281 t.Fatalf("%s: %v", what, err)
282 }
283 return string(line)
284 }
285
286 err = c.Start()
287 check("Start", err)
288
289 _, err = stdin.Write([]byte("O:I am output\n"))
290 check("first stdin Write", err)
291 if g, e := line("first output line", outbr), "O:I am output"; g != e {
292 t.Errorf("got %q, want %q", g, e)
293 }
294
295 _, err = stdin.Write([]byte("E:I am error\n"))
296 check("second stdin Write", err)
297 if g, e := line("first error line", errbr), "E:I am error"; g != e {
298 t.Errorf("got %q, want %q", g, e)
299 }
300
301 _, err = stdin.Write([]byte("O:I am output2\n"))
302 check("third stdin Write 3", err)
303 if g, e := line("second output line", outbr), "O:I am output2"; g != e {
304 t.Errorf("got %q, want %q", g, e)
305 }
306
307 stdin.Close()
308 err = c.Wait()
309 check("Wait", err)
310 }
311
312 const stdinCloseTestString = "Some test string."
313
314
315 func TestStdinClose(t *testing.T) {
316 check := func(what string, err error) {
317 if err != nil {
318 t.Fatalf("%s: %v", what, err)
319 }
320 }
321 cmd := helperCommand(t, "stdinClose")
322 stdin, err := cmd.StdinPipe()
323 check("StdinPipe", err)
324
325 if _, ok := stdin.(interface {
326 Fd() uintptr
327 }); !ok {
328 t.Error("can't access methods of underlying *os.File")
329 }
330 check("Start", cmd.Start())
331 go func() {
332 _, err := io.Copy(stdin, strings.NewReader(stdinCloseTestString))
333 check("Copy", err)
334
335 check("Close", stdin.Close())
336 }()
337 check("Wait", cmd.Wait())
338 }
339
340
341
342
343
344
345
346 func TestStdinCloseRace(t *testing.T) {
347 cmd := helperCommand(t, "stdinClose")
348 stdin, err := cmd.StdinPipe()
349 if err != nil {
350 t.Fatalf("StdinPipe: %v", err)
351 }
352 if err := cmd.Start(); err != nil {
353 t.Fatalf("Start: %v", err)
354 }
355 go func() {
356
357
358
359
360
361
362 cmd.Process.Kill()
363 }()
364 go func() {
365
366
367
368
369
370 io.Copy(stdin, strings.NewReader("unexpected string"))
371 if err := stdin.Close(); err != nil {
372 t.Errorf("stdin.Close: %v", err)
373 }
374 }()
375 if err := cmd.Wait(); err == nil {
376 t.Fatalf("Wait: succeeded unexpectedly")
377 }
378 }
379
380
381 func TestPipeLookPathLeak(t *testing.T) {
382
383 tolerance := 0
384
385
386
387 numOpenFDs := func() (int, []byte, error) {
388 fds, err := os.ReadDir("/proc/self/fd")
389 if err != nil {
390 return 0, nil, err
391 }
392 return len(fds), nil, nil
393 }
394 want, before, err := numOpenFDs()
395 if err != nil {
396
397
398 t.Logf("using lsof because: %v", err)
399 numOpenFDs = func() (int, []byte, error) {
400
401
402
403 if runtime.GOOS == "android" {
404
405
406 n, lsof := numOpenFDsAndroid(t)
407 return n, lsof, nil
408 }
409 lsof, err := exec.Command("lsof", "-b", "-n", "-p", strconv.Itoa(os.Getpid())).Output()
410 return bytes.Count(lsof, []byte("\n")), lsof, err
411 }
412
413
414
415
416 tolerance = 5
417
418
419 want, before, err = numOpenFDs()
420 if err != nil {
421 t.Log(err)
422 t.Skipf("skipping test; error finding or running lsof")
423 }
424 }
425
426 for i := 0; i < 6; i++ {
427 cmd := exec.Command("something-that-does-not-exist-executable")
428 cmd.StdoutPipe()
429 cmd.StderrPipe()
430 cmd.StdinPipe()
431 if err := cmd.Run(); err == nil {
432 t.Fatal("unexpected success")
433 }
434 }
435 got, after, err := numOpenFDs()
436 if err != nil {
437
438 t.Errorf("unexpected failure: %v", err)
439 }
440 if got-want > tolerance {
441 t.Errorf("number of open file descriptors changed: got %v, want %v", got, want)
442 if before != nil {
443 t.Errorf("before:\n%v\n", before)
444 }
445 if after != nil {
446 t.Errorf("after:\n%v\n", after)
447 }
448 }
449 }
450
451 func numOpenFDsAndroid(t *testing.T) (n int, lsof []byte) {
452 raw, err := exec.Command("lsof").Output()
453 if err != nil {
454 t.Skip("skipping test; error finding or running lsof")
455 }
456
457
458
459 pid := []byte(strconv.Itoa(os.Getpid()))
460 pidCol := -1
461
462 s := bufio.NewScanner(bytes.NewReader(raw))
463 for s.Scan() {
464 line := s.Bytes()
465 fields := bytes.Fields(line)
466 if pidCol < 0 {
467 for i, v := range fields {
468 if bytes.Equal(v, []byte("PID")) {
469 pidCol = i
470 break
471 }
472 }
473 lsof = append(lsof, line...)
474 continue
475 }
476 if bytes.Equal(fields[pidCol], pid) {
477 lsof = append(lsof, '\n')
478 lsof = append(lsof, line...)
479 }
480 }
481 if pidCol < 0 {
482 t.Fatal("error processing lsof output: unexpected header format")
483 }
484 if err := s.Err(); err != nil {
485 t.Fatalf("error processing lsof output: %v", err)
486 }
487 return bytes.Count(lsof, []byte("\n")), lsof
488 }
489
490 func TestExtraFilesFDShuffle(t *testing.T) {
491 testenv.SkipFlaky(t, 5780)
492 switch runtime.GOOS {
493 case "windows":
494 t.Skip("no operating system support; skipping")
495 }
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519 npipes := 2
520 c := helperCommand(t, "extraFilesAndPipes", strconv.Itoa(npipes+1))
521 rd, wr, _ := os.Pipe()
522 defer rd.Close()
523 if rd.Fd() != 3 {
524 t.Errorf("bad test value for test pipe: fd %d", rd.Fd())
525 }
526 stderr, _ := c.StderrPipe()
527 wr.WriteString("_LAST")
528 wr.Close()
529
530 pipes := make([]struct {
531 r, w *os.File
532 }, npipes)
533 data := []string{"a", "b"}
534
535 for i := 0; i < npipes; i++ {
536 r, w, err := os.Pipe()
537 if err != nil {
538 t.Fatalf("unexpected error creating pipe: %s", err)
539 }
540 pipes[i].r = r
541 pipes[i].w = w
542 w.WriteString(data[i])
543 c.ExtraFiles = append(c.ExtraFiles, pipes[i].r)
544 defer func() {
545 r.Close()
546 w.Close()
547 }()
548 }
549
550 c.ExtraFiles = append(c.ExtraFiles, rd)
551
552 stderrFd := int(stderr.(*os.File).Fd())
553 if stderrFd != ((len(c.ExtraFiles) + 3) - 1) {
554 t.Errorf("bad test value for stderr pipe")
555 }
556
557 expected := "child: " + strings.Join(data, "") + "_LAST"
558
559 err := c.Start()
560 if err != nil {
561 t.Fatalf("Run: %v", err)
562 }
563 ch := make(chan string, 1)
564 go func(ch chan string) {
565 buf := make([]byte, 512)
566 n, err := stderr.Read(buf)
567 if err != nil {
568 t.Errorf("Read: %s", err)
569 ch <- err.Error()
570 } else {
571 ch <- string(buf[:n])
572 }
573 close(ch)
574 }(ch)
575 select {
576 case m := <-ch:
577 if m != expected {
578 t.Errorf("Read: '%s' not '%s'", m, expected)
579 }
580 case <-time.After(5 * time.Second):
581 t.Errorf("Read timedout")
582 }
583 c.Wait()
584 }
585
586 func TestExtraFiles(t *testing.T) {
587 if haveUnexpectedFDs {
588
589
590
591
592
593
594
595
596
597
598
599
600
601 t.Skip("skipping test because test was run with FDs open")
602 }
603
604 testenv.MustHaveExec(t)
605 testenv.MustHaveGoBuild(t)
606
607
608
609 testenv.MustInternalLink(t)
610
611 if runtime.GOOS == "windows" {
612 t.Skipf("skipping test on %q", runtime.GOOS)
613 }
614
615
616
617 ln, err := net.Listen("tcp", "127.0.0.1:0")
618 if err != nil {
619 t.Fatal(err)
620 }
621 defer ln.Close()
622
623
624 f, err := ln.(*net.TCPListener).File()
625 if err != nil {
626 t.Fatal(err)
627 }
628 defer f.Close()
629 ln2, err := net.FileListener(f)
630 if err != nil {
631 t.Fatal(err)
632 }
633 defer ln2.Close()
634
635
636
637 ts := httptest.NewUnstartedServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {}))
638
639 ts.Config.ErrorLog = log.New(io.Discard, "", 0)
640 ts.StartTLS()
641 defer ts.Close()
642 _, err = http.Get(ts.URL)
643 if err == nil {
644 t.Errorf("success trying to fetch %s; want an error", ts.URL)
645 }
646
647 tf, err := os.CreateTemp("", "")
648 if err != nil {
649 t.Fatalf("TempFile: %v", err)
650 }
651 defer os.Remove(tf.Name())
652 defer tf.Close()
653
654 const text = "Hello, fd 3!"
655 _, err = tf.Write([]byte(text))
656 if err != nil {
657 t.Fatalf("Write: %v", err)
658 }
659 _, err = tf.Seek(0, io.SeekStart)
660 if err != nil {
661 t.Fatalf("Seek: %v", err)
662 }
663
664 tempdir := t.TempDir()
665 exe := filepath.Join(tempdir, "read3.exe")
666
667 c := exec.Command(testenv.GoToolPath(t), "build", "-o", exe, "read3.go")
668
669
670 c.Env = append(os.Environ(), "CGO_ENABLED=0")
671 if output, err := c.CombinedOutput(); err != nil {
672 t.Logf("go build -o %s read3.go\n%s", exe, output)
673 t.Fatalf("go build failed: %v", err)
674 }
675
676
677 ctx := context.Background()
678 if deadline, ok := t.Deadline(); ok {
679
680
681 deadline = deadline.Add(-time.Until(deadline) / 5)
682
683 var cancel context.CancelFunc
684 ctx, cancel = context.WithDeadline(ctx, deadline)
685 defer cancel()
686 }
687
688 c = exec.CommandContext(ctx, exe)
689 var stdout, stderr bytes.Buffer
690 c.Stdout = &stdout
691 c.Stderr = &stderr
692 c.ExtraFiles = []*os.File{tf}
693 if runtime.GOOS == "illumos" {
694
695
696
697
698
699
700
701
702
703 c.Env = append(os.Environ(), "GOMAXPROCS=1")
704 }
705 err = c.Run()
706 if err != nil {
707 t.Fatalf("Run: %v\n--- stdout:\n%s--- stderr:\n%s", err, stdout.Bytes(), stderr.Bytes())
708 }
709 if stdout.String() != text {
710 t.Errorf("got stdout %q, stderr %q; want %q on stdout", stdout.String(), stderr.String(), text)
711 }
712 }
713
714 func TestExtraFilesRace(t *testing.T) {
715 if runtime.GOOS == "windows" {
716 t.Skip("no operating system support; skipping")
717 }
718 listen := func() net.Listener {
719 ln, err := net.Listen("tcp", "127.0.0.1:0")
720 if err != nil {
721 t.Fatal(err)
722 }
723 return ln
724 }
725 listenerFile := func(ln net.Listener) *os.File {
726 f, err := ln.(*net.TCPListener).File()
727 if err != nil {
728 t.Fatal(err)
729 }
730 return f
731 }
732 runCommand := func(c *exec.Cmd, out chan<- string) {
733 bout, err := c.CombinedOutput()
734 if err != nil {
735 out <- "ERROR:" + err.Error()
736 } else {
737 out <- string(bout)
738 }
739 }
740
741 for i := 0; i < 10; i++ {
742 if testing.Short() && i >= 3 {
743 break
744 }
745 la := listen()
746 ca := helperCommand(t, "describefiles")
747 ca.ExtraFiles = []*os.File{listenerFile(la)}
748 lb := listen()
749 cb := helperCommand(t, "describefiles")
750 cb.ExtraFiles = []*os.File{listenerFile(lb)}
751 ares := make(chan string)
752 bres := make(chan string)
753 go runCommand(ca, ares)
754 go runCommand(cb, bres)
755 if got, want := <-ares, fmt.Sprintf("fd3: listener %s\n", la.Addr()); got != want {
756 t.Errorf("iteration %d, process A got:\n%s\nwant:\n%s\n", i, got, want)
757 }
758 if got, want := <-bres, fmt.Sprintf("fd3: listener %s\n", lb.Addr()); got != want {
759 t.Errorf("iteration %d, process B got:\n%s\nwant:\n%s\n", i, got, want)
760 }
761 la.Close()
762 lb.Close()
763 for _, f := range ca.ExtraFiles {
764 f.Close()
765 }
766 for _, f := range cb.ExtraFiles {
767 f.Close()
768 }
769
770 }
771 }
772
773
774
775 func TestHelperProcess(*testing.T) {
776 if os.Getenv("GO_WANT_HELPER_PROCESS") != "1" {
777 return
778 }
779 defer os.Exit(0)
780
781 args := os.Args
782 for len(args) > 0 {
783 if args[0] == "--" {
784 args = args[1:]
785 break
786 }
787 args = args[1:]
788 }
789 if len(args) == 0 {
790 fmt.Fprintf(os.Stderr, "No command\n")
791 os.Exit(2)
792 }
793
794 cmd, args := args[0], args[1:]
795 switch cmd {
796 case "echo":
797 iargs := []interface{}{}
798 for _, s := range args {
799 iargs = append(iargs, s)
800 }
801 fmt.Println(iargs...)
802 case "echoenv":
803 for _, s := range args {
804 fmt.Println(os.Getenv(s))
805 }
806 os.Exit(0)
807 case "cat":
808 if len(args) == 0 {
809 io.Copy(os.Stdout, os.Stdin)
810 return
811 }
812 exit := 0
813 for _, fn := range args {
814 f, err := os.Open(fn)
815 if err != nil {
816 fmt.Fprintf(os.Stderr, "Error: %v\n", err)
817 exit = 2
818 } else {
819 defer f.Close()
820 io.Copy(os.Stdout, f)
821 }
822 }
823 os.Exit(exit)
824 case "pipetest":
825 bufr := bufio.NewReader(os.Stdin)
826 for {
827 line, _, err := bufr.ReadLine()
828 if err == io.EOF {
829 break
830 } else if err != nil {
831 os.Exit(1)
832 }
833 if bytes.HasPrefix(line, []byte("O:")) {
834 os.Stdout.Write(line)
835 os.Stdout.Write([]byte{'\n'})
836 } else if bytes.HasPrefix(line, []byte("E:")) {
837 os.Stderr.Write(line)
838 os.Stderr.Write([]byte{'\n'})
839 } else {
840 os.Exit(1)
841 }
842 }
843 case "stdinClose":
844 b, err := io.ReadAll(os.Stdin)
845 if err != nil {
846 fmt.Fprintf(os.Stderr, "Error: %v\n", err)
847 os.Exit(1)
848 }
849 if s := string(b); s != stdinCloseTestString {
850 fmt.Fprintf(os.Stderr, "Error: Read %q, want %q", s, stdinCloseTestString)
851 os.Exit(1)
852 }
853 os.Exit(0)
854 case "exit":
855 n, _ := strconv.Atoi(args[0])
856 os.Exit(n)
857 case "describefiles":
858 f := os.NewFile(3, fmt.Sprintf("fd3"))
859 ln, err := net.FileListener(f)
860 if err == nil {
861 fmt.Printf("fd3: listener %s\n", ln.Addr())
862 ln.Close()
863 }
864 os.Exit(0)
865 case "extraFilesAndPipes":
866 n, _ := strconv.Atoi(args[0])
867 pipes := make([]*os.File, n)
868 for i := 0; i < n; i++ {
869 pipes[i] = os.NewFile(uintptr(3+i), strconv.Itoa(i))
870 }
871 response := ""
872 for i, r := range pipes {
873 ch := make(chan string, 1)
874 go func(c chan string) {
875 buf := make([]byte, 10)
876 n, err := r.Read(buf)
877 if err != nil {
878 fmt.Fprintf(os.Stderr, "Child: read error: %v on pipe %d\n", err, i)
879 os.Exit(1)
880 }
881 c <- string(buf[:n])
882 close(c)
883 }(ch)
884 select {
885 case m := <-ch:
886 response = response + m
887 case <-time.After(5 * time.Second):
888 fmt.Fprintf(os.Stderr, "Child: Timeout reading from pipe: %d\n", i)
889 os.Exit(1)
890 }
891 }
892 fmt.Fprintf(os.Stderr, "child: %s", response)
893 os.Exit(0)
894 case "exec":
895 cmd := exec.Command(args[1])
896 cmd.Dir = args[0]
897 output, err := cmd.CombinedOutput()
898 if err != nil {
899 fmt.Fprintf(os.Stderr, "Child: %s %s", err, string(output))
900 os.Exit(1)
901 }
902 fmt.Printf("%s", string(output))
903 os.Exit(0)
904 case "lookpath":
905 p, err := exec.LookPath(args[0])
906 if err != nil {
907 fmt.Fprintf(os.Stderr, "LookPath failed: %v\n", err)
908 os.Exit(1)
909 }
910 fmt.Print(p)
911 os.Exit(0)
912 case "stderrfail":
913 fmt.Fprintf(os.Stderr, "some stderr text\n")
914 os.Exit(1)
915 case "sleep":
916 time.Sleep(3 * time.Second)
917 os.Exit(0)
918 case "pipehandle":
919 handle, _ := strconv.ParseUint(args[0], 16, 64)
920 pipe := os.NewFile(uintptr(handle), "")
921 _, err := fmt.Fprint(pipe, args[1])
922 if err != nil {
923 fmt.Fprintf(os.Stderr, "writing to pipe failed: %v\n", err)
924 os.Exit(1)
925 }
926 pipe.Close()
927 os.Exit(0)
928 default:
929 fmt.Fprintf(os.Stderr, "Unknown command %q\n", cmd)
930 os.Exit(2)
931 }
932 }
933
934 type delayedInfiniteReader struct{}
935
936 func (delayedInfiniteReader) Read(b []byte) (int, error) {
937 time.Sleep(100 * time.Millisecond)
938 for i := range b {
939 b[i] = 'x'
940 }
941 return len(b), nil
942 }
943
944
945 func TestIgnorePipeErrorOnSuccess(t *testing.T) {
946 testenv.MustHaveExec(t)
947
948 testWith := func(r io.Reader) func(*testing.T) {
949 return func(t *testing.T) {
950 cmd := helperCommand(t, "echo", "foo")
951 var out bytes.Buffer
952 cmd.Stdin = r
953 cmd.Stdout = &out
954 if err := cmd.Run(); err != nil {
955 t.Fatal(err)
956 }
957 if got, want := out.String(), "foo\n"; got != want {
958 t.Errorf("output = %q; want %q", got, want)
959 }
960 }
961 }
962 t.Run("10MB", testWith(strings.NewReader(strings.Repeat("x", 10<<20))))
963 t.Run("Infinite", testWith(delayedInfiniteReader{}))
964 }
965
966 type badWriter struct{}
967
968 func (w *badWriter) Write(data []byte) (int, error) {
969 return 0, io.ErrUnexpectedEOF
970 }
971
972 func TestClosePipeOnCopyError(t *testing.T) {
973 testenv.MustHaveExec(t)
974
975 if runtime.GOOS == "windows" || runtime.GOOS == "plan9" {
976 t.Skipf("skipping test on %s - no yes command", runtime.GOOS)
977 }
978 cmd := exec.Command("yes")
979 cmd.Stdout = new(badWriter)
980 c := make(chan int, 1)
981 go func() {
982 err := cmd.Run()
983 if err == nil {
984 t.Errorf("yes completed successfully")
985 }
986 c <- 1
987 }()
988 select {
989 case <-c:
990
991 case <-time.After(5 * time.Second):
992 t.Fatalf("yes got stuck writing to bad writer")
993 }
994 }
995
996 func TestOutputStderrCapture(t *testing.T) {
997 testenv.MustHaveExec(t)
998
999 cmd := helperCommand(t, "stderrfail")
1000 _, err := cmd.Output()
1001 ee, ok := err.(*exec.ExitError)
1002 if !ok {
1003 t.Fatalf("Output error type = %T; want ExitError", err)
1004 }
1005 got := string(ee.Stderr)
1006 want := "some stderr text\n"
1007 if got != want {
1008 t.Errorf("ExitError.Stderr = %q; want %q", got, want)
1009 }
1010 }
1011
1012 func TestContext(t *testing.T) {
1013 ctx, cancel := context.WithCancel(context.Background())
1014 c := helperCommandContext(t, ctx, "pipetest")
1015 stdin, err := c.StdinPipe()
1016 if err != nil {
1017 t.Fatal(err)
1018 }
1019 stdout, err := c.StdoutPipe()
1020 if err != nil {
1021 t.Fatal(err)
1022 }
1023 if err := c.Start(); err != nil {
1024 t.Fatal(err)
1025 }
1026
1027 if _, err := stdin.Write([]byte("O:hi\n")); err != nil {
1028 t.Fatal(err)
1029 }
1030 buf := make([]byte, 5)
1031 n, err := io.ReadFull(stdout, buf)
1032 if n != len(buf) || err != nil || string(buf) != "O:hi\n" {
1033 t.Fatalf("ReadFull = %d, %v, %q", n, err, buf[:n])
1034 }
1035 waitErr := make(chan error, 1)
1036 go func() {
1037 waitErr <- c.Wait()
1038 }()
1039 cancel()
1040 select {
1041 case err := <-waitErr:
1042 if err == nil {
1043 t.Fatal("expected Wait failure")
1044 }
1045 case <-time.After(3 * time.Second):
1046 t.Fatal("timeout waiting for child process death")
1047 }
1048 }
1049
1050 func TestContextCancel(t *testing.T) {
1051 ctx, cancel := context.WithCancel(context.Background())
1052 defer cancel()
1053 c := helperCommandContext(t, ctx, "cat")
1054
1055 stdin, err := c.StdinPipe()
1056 if err != nil {
1057 t.Fatal(err)
1058 }
1059 defer stdin.Close()
1060
1061 if err := c.Start(); err != nil {
1062 t.Fatal(err)
1063 }
1064
1065
1066 if _, err := io.WriteString(stdin, "echo"); err != nil {
1067 t.Fatal(err)
1068 }
1069
1070 cancel()
1071
1072
1073
1074 start := time.Now()
1075 for {
1076 if _, err := io.WriteString(stdin, "echo"); err != nil {
1077 break
1078 }
1079 if time.Since(start) > time.Minute {
1080 t.Fatal("canceling context did not stop program")
1081 }
1082 time.Sleep(time.Millisecond)
1083 }
1084
1085 if err := c.Wait(); err == nil {
1086 t.Error("program unexpectedly exited successfully")
1087 } else {
1088 t.Logf("exit status: %v", err)
1089 }
1090 }
1091
1092
1093 func TestDedupEnvEcho(t *testing.T) {
1094 testenv.MustHaveExec(t)
1095
1096 cmd := helperCommand(t, "echoenv", "FOO")
1097 cmd.Env = append(cmd.Env, "FOO=bad", "FOO=good")
1098 out, err := cmd.CombinedOutput()
1099 if err != nil {
1100 t.Fatal(err)
1101 }
1102 if got, want := strings.TrimSpace(string(out)), "good"; got != want {
1103 t.Errorf("output = %q; want %q", got, want)
1104 }
1105 }
1106
1107 func TestString(t *testing.T) {
1108 echoPath, err := exec.LookPath("echo")
1109 if err != nil {
1110 t.Skip(err)
1111 }
1112 tests := [...]struct {
1113 path string
1114 args []string
1115 want string
1116 }{
1117 {"echo", nil, echoPath},
1118 {"echo", []string{"a"}, echoPath + " a"},
1119 {"echo", []string{"a", "b"}, echoPath + " a b"},
1120 }
1121 for _, test := range tests {
1122 cmd := exec.Command(test.path, test.args...)
1123 if got := cmd.String(); got != test.want {
1124 t.Errorf("String(%q, %q) = %q, want %q", test.path, test.args, got, test.want)
1125 }
1126 }
1127 }
1128
1129 func TestStringPathNotResolved(t *testing.T) {
1130 _, err := exec.LookPath("makemeasandwich")
1131 if err == nil {
1132 t.Skip("wow, thanks")
1133 }
1134 cmd := exec.Command("makemeasandwich", "-lettuce")
1135 want := "makemeasandwich -lettuce"
1136 if got := cmd.String(); got != want {
1137 t.Errorf("String(%q, %q) = %q, want %q", "makemeasandwich", "-lettuce", got, want)
1138 }
1139 }
1140
1141
1142
1143
1144 func TestChildCriticalEnv(t *testing.T) {
1145 testenv.MustHaveExec(t)
1146 if runtime.GOOS != "windows" {
1147 t.Skip("only testing on Windows")
1148 }
1149 cmd := helperCommand(t, "echoenv", "SYSTEMROOT")
1150 cmd.Env = []string{"GO_WANT_HELPER_PROCESS=1"}
1151 out, err := cmd.CombinedOutput()
1152 if err != nil {
1153 t.Fatal(err)
1154 }
1155 if strings.TrimSpace(string(out)) == "" {
1156 t.Error("no SYSTEMROOT found")
1157 }
1158 }
1159
1160 func TestNoPath(t *testing.T) {
1161 err := new(exec.Cmd).Start()
1162 want := "exec: no command"
1163 if err == nil || err.Error() != want {
1164 t.Errorf("new(Cmd).Start() = %v, want %q", err, want)
1165 }
1166 }
1167
View as plain text