Source file
src/os/exec/exec_test.go
1
2
3
4
5
6
7
8 package exec_test
9
10 import (
11 "bufio"
12 "bytes"
13 "context"
14 "errors"
15 "flag"
16 "fmt"
17 "internal/poll"
18 "internal/testenv"
19 "io"
20 "log"
21 "net"
22 "net/http"
23 "net/http/httptest"
24 "os"
25 "os/exec"
26 "os/exec/internal/fdtest"
27 "os/signal"
28 "path/filepath"
29 "runtime"
30 "runtime/debug"
31 "strconv"
32 "strings"
33 "sync"
34 "sync/atomic"
35 "testing"
36 "time"
37 )
38
39
40
41 var haveUnexpectedFDs bool
42
43 func init() {
44 godebug := os.Getenv("GODEBUG")
45 if godebug != "" {
46 godebug += ","
47 }
48 godebug += "execwait=2"
49 os.Setenv("GODEBUG", godebug)
50
51 if os.Getenv("GO_EXEC_TEST_PID") != "" {
52 return
53 }
54 if runtime.GOOS == "windows" {
55 return
56 }
57 for fd := uintptr(3); fd <= 100; fd++ {
58 if poll.IsPollDescriptor(fd) {
59 continue
60 }
61
62 if fdtest.Exists(fd) {
63 haveUnexpectedFDs = true
64 return
65 }
66 }
67 }
68
69
70
71
72
73 func TestMain(m *testing.M) {
74 flag.Parse()
75
76 pid := os.Getpid()
77 if os.Getenv("GO_EXEC_TEST_PID") == "" {
78 os.Setenv("GO_EXEC_TEST_PID", strconv.Itoa(pid))
79
80 if runtime.GOOS == "windows" {
81
82
83
84
85
86
87
88
89
90
91
92 os.Setenv("NoDefaultCurrentDirectoryInExePath", "TRUE")
93 }
94
95 code := m.Run()
96 if code == 0 && flag.Lookup("test.run").Value.String() == "" && flag.Lookup("test.list").Value.String() == "" {
97 for cmd := range helperCommands {
98 if _, ok := helperCommandUsed.Load(cmd); !ok {
99 fmt.Fprintf(os.Stderr, "helper command unused: %q\n", cmd)
100 code = 1
101 }
102 }
103 }
104
105 if !testing.Short() {
106
107
108 runtime.GC()
109 runtime.GC()
110 }
111
112 os.Exit(code)
113 }
114
115 args := flag.Args()
116 if len(args) == 0 {
117 fmt.Fprintf(os.Stderr, "No command\n")
118 os.Exit(2)
119 }
120
121 cmd, args := args[0], args[1:]
122 f, ok := helperCommands[cmd]
123 if !ok {
124 fmt.Fprintf(os.Stderr, "Unknown command %q\n", cmd)
125 os.Exit(2)
126 }
127 f(args...)
128 os.Exit(0)
129 }
130
131
132
133
134
135
136 func registerHelperCommand(name string, f func(...string)) {
137 if helperCommands[name] != nil {
138 panic("duplicate command registered: " + name)
139 }
140 helperCommands[name] = f
141 }
142
143
144
145
146 func maySkipHelperCommand(name string) {
147 helperCommandUsed.Store(name, true)
148 }
149
150
151 func helperCommand(t *testing.T, name string, args ...string) *exec.Cmd {
152 t.Helper()
153 return helperCommandContext(t, nil, name, args...)
154 }
155
156
157
158 func helperCommandContext(t *testing.T, ctx context.Context, name string, args ...string) (cmd *exec.Cmd) {
159 helperCommandUsed.LoadOrStore(name, true)
160
161 t.Helper()
162 testenv.MustHaveExec(t)
163
164 cs := append([]string{name}, args...)
165 if ctx != nil {
166 cmd = exec.CommandContext(ctx, exePath(t), cs...)
167 } else {
168 cmd = exec.Command(exePath(t), cs...)
169 }
170 return cmd
171 }
172
173
174 func exePath(t testing.TB) string {
175 exeOnce.Do(func() {
176
177
178
179 exeOnce.path, exeOnce.err = os.Executable()
180 })
181
182 if exeOnce.err != nil {
183 if t == nil {
184 panic(exeOnce.err)
185 }
186 t.Fatal(exeOnce.err)
187 }
188
189 return exeOnce.path
190 }
191
192 var exeOnce struct {
193 path string
194 err error
195 sync.Once
196 }
197
198 func chdir(t *testing.T, dir string) {
199 t.Helper()
200
201 prev, err := os.Getwd()
202 if err != nil {
203 t.Fatal(err)
204 }
205 if err := os.Chdir(dir); err != nil {
206 t.Fatal(err)
207 }
208 t.Logf("Chdir(%#q)", dir)
209
210 t.Cleanup(func() {
211 if err := os.Chdir(prev); err != nil {
212
213
214
215 panic("couldn't restore working directory: " + err.Error())
216 }
217 })
218 }
219
220 var helperCommandUsed sync.Map
221
222 var helperCommands = map[string]func(...string){
223 "echo": cmdEcho,
224 "echoenv": cmdEchoEnv,
225 "cat": cmdCat,
226 "pipetest": cmdPipeTest,
227 "stdinClose": cmdStdinClose,
228 "exit": cmdExit,
229 "describefiles": cmdDescribeFiles,
230 "stderrfail": cmdStderrFail,
231 "yes": cmdYes,
232 "hang": cmdHang,
233 }
234
235 func cmdEcho(args ...string) {
236 iargs := []any{}
237 for _, s := range args {
238 iargs = append(iargs, s)
239 }
240 fmt.Println(iargs...)
241 }
242
243 func cmdEchoEnv(args ...string) {
244 for _, s := range args {
245 fmt.Println(os.Getenv(s))
246 }
247 }
248
249 func cmdCat(args ...string) {
250 if len(args) == 0 {
251 io.Copy(os.Stdout, os.Stdin)
252 return
253 }
254 exit := 0
255 for _, fn := range args {
256 f, err := os.Open(fn)
257 if err != nil {
258 fmt.Fprintf(os.Stderr, "Error: %v\n", err)
259 exit = 2
260 } else {
261 defer f.Close()
262 io.Copy(os.Stdout, f)
263 }
264 }
265 os.Exit(exit)
266 }
267
268 func cmdPipeTest(...string) {
269 bufr := bufio.NewReader(os.Stdin)
270 for {
271 line, _, err := bufr.ReadLine()
272 if err == io.EOF {
273 break
274 } else if err != nil {
275 os.Exit(1)
276 }
277 if bytes.HasPrefix(line, []byte("O:")) {
278 os.Stdout.Write(line)
279 os.Stdout.Write([]byte{'\n'})
280 } else if bytes.HasPrefix(line, []byte("E:")) {
281 os.Stderr.Write(line)
282 os.Stderr.Write([]byte{'\n'})
283 } else {
284 os.Exit(1)
285 }
286 }
287 }
288
289 func cmdStdinClose(...string) {
290 b, err := io.ReadAll(os.Stdin)
291 if err != nil {
292 fmt.Fprintf(os.Stderr, "Error: %v\n", err)
293 os.Exit(1)
294 }
295 if s := string(b); s != stdinCloseTestString {
296 fmt.Fprintf(os.Stderr, "Error: Read %q, want %q", s, stdinCloseTestString)
297 os.Exit(1)
298 }
299 }
300
301 func cmdExit(args ...string) {
302 n, _ := strconv.Atoi(args[0])
303 os.Exit(n)
304 }
305
306 func cmdDescribeFiles(args ...string) {
307 f := os.NewFile(3, "fd3")
308 ln, err := net.FileListener(f)
309 if err == nil {
310 fmt.Printf("fd3: listener %s\n", ln.Addr())
311 ln.Close()
312 }
313 }
314
315 func cmdStderrFail(...string) {
316 fmt.Fprintf(os.Stderr, "some stderr text\n")
317 os.Exit(1)
318 }
319
320 func cmdYes(args ...string) {
321 if len(args) == 0 {
322 args = []string{"y"}
323 }
324 s := strings.Join(args, " ") + "\n"
325 for {
326 _, err := os.Stdout.WriteString(s)
327 if err != nil {
328 os.Exit(1)
329 }
330 }
331 }
332
333 func TestEcho(t *testing.T) {
334 t.Parallel()
335
336 bs, err := helperCommand(t, "echo", "foo bar", "baz").Output()
337 if err != nil {
338 t.Errorf("echo: %v", err)
339 }
340 if g, e := string(bs), "foo bar baz\n"; g != e {
341 t.Errorf("echo: want %q, got %q", e, g)
342 }
343 }
344
345 func TestCommandRelativeName(t *testing.T) {
346 t.Parallel()
347
348 cmd := helperCommand(t, "echo", "foo")
349
350
351
352 base := filepath.Base(os.Args[0])
353 dir := filepath.Dir(os.Args[0])
354 if dir == "." {
355 t.Skip("skipping; running test at root somehow")
356 }
357 parentDir := filepath.Dir(dir)
358 dirBase := filepath.Base(dir)
359 if dirBase == "." {
360 t.Skipf("skipping; unexpected shallow dir of %q", dir)
361 }
362
363 cmd.Path = filepath.Join(dirBase, base)
364 cmd.Dir = parentDir
365
366 out, err := cmd.Output()
367 if err != nil {
368 t.Errorf("echo: %v", err)
369 }
370 if g, e := string(out), "foo\n"; g != e {
371 t.Errorf("echo: want %q, got %q", e, g)
372 }
373 }
374
375 func TestCatStdin(t *testing.T) {
376 t.Parallel()
377
378
379 input := "Input string\nLine 2"
380 p := helperCommand(t, "cat")
381 p.Stdin = strings.NewReader(input)
382 bs, err := p.Output()
383 if err != nil {
384 t.Errorf("cat: %v", err)
385 }
386 s := string(bs)
387 if s != input {
388 t.Errorf("cat: want %q, got %q", input, s)
389 }
390 }
391
392 func TestEchoFileRace(t *testing.T) {
393 t.Parallel()
394
395 cmd := helperCommand(t, "echo")
396 stdin, err := cmd.StdinPipe()
397 if err != nil {
398 t.Fatalf("StdinPipe: %v", err)
399 }
400 if err := cmd.Start(); err != nil {
401 t.Fatalf("Start: %v", err)
402 }
403 wrote := make(chan bool)
404 go func() {
405 defer close(wrote)
406 fmt.Fprint(stdin, "echo\n")
407 }()
408 if err := cmd.Wait(); err != nil {
409 t.Fatalf("Wait: %v", err)
410 }
411 <-wrote
412 }
413
414 func TestCatGoodAndBadFile(t *testing.T) {
415 t.Parallel()
416
417
418 bs, err := helperCommand(t, "cat", "/bogus/file.foo", "exec_test.go").CombinedOutput()
419 if _, ok := err.(*exec.ExitError); !ok {
420 t.Errorf("expected *exec.ExitError from cat combined; got %T: %v", err, err)
421 }
422 errLine, body, ok := strings.Cut(string(bs), "\n")
423 if !ok {
424 t.Fatalf("expected two lines from cat; got %q", bs)
425 }
426 if !strings.HasPrefix(errLine, "Error: open /bogus/file.foo") {
427 t.Errorf("expected stderr to complain about file; got %q", errLine)
428 }
429 if !strings.Contains(body, "func TestCatGoodAndBadFile(t *testing.T)") {
430 t.Errorf("expected test code; got %q (len %d)", body, len(body))
431 }
432 }
433
434 func TestNoExistExecutable(t *testing.T) {
435 t.Parallel()
436
437
438 err := exec.Command("/no-exist-executable").Run()
439 if err == nil {
440 t.Error("expected error from /no-exist-executable")
441 }
442 }
443
444 func TestExitStatus(t *testing.T) {
445 t.Parallel()
446
447
448 cmd := helperCommand(t, "exit", "42")
449 err := cmd.Run()
450 want := "exit status 42"
451 switch runtime.GOOS {
452 case "plan9":
453 want = fmt.Sprintf("exit status: '%s %d: 42'", filepath.Base(cmd.Path), cmd.ProcessState.Pid())
454 }
455 if werr, ok := err.(*exec.ExitError); ok {
456 if s := werr.Error(); s != want {
457 t.Errorf("from exit 42 got exit %q, want %q", s, want)
458 }
459 } else {
460 t.Fatalf("expected *exec.ExitError from exit 42; got %T: %v", err, err)
461 }
462 }
463
464 func TestExitCode(t *testing.T) {
465 t.Parallel()
466
467
468 cmd := helperCommand(t, "exit", "42")
469 cmd.Run()
470 want := 42
471 if runtime.GOOS == "plan9" {
472 want = 1
473 }
474 got := cmd.ProcessState.ExitCode()
475 if want != got {
476 t.Errorf("ExitCode got %d, want %d", got, want)
477 }
478
479 cmd = helperCommand(t, "/no-exist-executable")
480 cmd.Run()
481 want = 2
482 if runtime.GOOS == "plan9" {
483 want = 1
484 }
485 got = cmd.ProcessState.ExitCode()
486 if want != got {
487 t.Errorf("ExitCode got %d, want %d", got, want)
488 }
489
490 cmd = helperCommand(t, "exit", "255")
491 cmd.Run()
492 want = 255
493 if runtime.GOOS == "plan9" {
494 want = 1
495 }
496 got = cmd.ProcessState.ExitCode()
497 if want != got {
498 t.Errorf("ExitCode got %d, want %d", got, want)
499 }
500
501 cmd = helperCommand(t, "cat")
502 cmd.Run()
503 want = 0
504 got = cmd.ProcessState.ExitCode()
505 if want != got {
506 t.Errorf("ExitCode got %d, want %d", got, want)
507 }
508
509
510 cmd = helperCommand(t, "cat")
511 want = -1
512 got = cmd.ProcessState.ExitCode()
513 if want != got {
514 t.Errorf("ExitCode got %d, want %d", got, want)
515 }
516 }
517
518 func TestPipes(t *testing.T) {
519 t.Parallel()
520
521 check := func(what string, err error) {
522 if err != nil {
523 t.Fatalf("%s: %v", what, err)
524 }
525 }
526
527 c := helperCommand(t, "pipetest")
528 stdin, err := c.StdinPipe()
529 check("StdinPipe", err)
530 stdout, err := c.StdoutPipe()
531 check("StdoutPipe", err)
532 stderr, err := c.StderrPipe()
533 check("StderrPipe", err)
534
535 outbr := bufio.NewReader(stdout)
536 errbr := bufio.NewReader(stderr)
537 line := func(what string, br *bufio.Reader) string {
538 line, _, err := br.ReadLine()
539 if err != nil {
540 t.Fatalf("%s: %v", what, err)
541 }
542 return string(line)
543 }
544
545 err = c.Start()
546 check("Start", err)
547
548 _, err = stdin.Write([]byte("O:I am output\n"))
549 check("first stdin Write", err)
550 if g, e := line("first output line", outbr), "O:I am output"; g != e {
551 t.Errorf("got %q, want %q", g, e)
552 }
553
554 _, err = stdin.Write([]byte("E:I am error\n"))
555 check("second stdin Write", err)
556 if g, e := line("first error line", errbr), "E:I am error"; g != e {
557 t.Errorf("got %q, want %q", g, e)
558 }
559
560 _, err = stdin.Write([]byte("O:I am output2\n"))
561 check("third stdin Write 3", err)
562 if g, e := line("second output line", outbr), "O:I am output2"; g != e {
563 t.Errorf("got %q, want %q", g, e)
564 }
565
566 stdin.Close()
567 err = c.Wait()
568 check("Wait", err)
569 }
570
571 const stdinCloseTestString = "Some test string."
572
573
574 func TestStdinClose(t *testing.T) {
575 t.Parallel()
576
577 check := func(what string, err error) {
578 if err != nil {
579 t.Fatalf("%s: %v", what, err)
580 }
581 }
582 cmd := helperCommand(t, "stdinClose")
583 stdin, err := cmd.StdinPipe()
584 check("StdinPipe", err)
585
586 if _, ok := stdin.(interface {
587 Fd() uintptr
588 }); !ok {
589 t.Error("can't access methods of underlying *os.File")
590 }
591 check("Start", cmd.Start())
592
593 var wg sync.WaitGroup
594 wg.Add(1)
595 defer wg.Wait()
596 go func() {
597 defer wg.Done()
598
599 _, err := io.Copy(stdin, strings.NewReader(stdinCloseTestString))
600 check("Copy", err)
601
602
603 if err := stdin.Close(); err != nil && !errors.Is(err, os.ErrClosed) {
604 t.Errorf("Close: %v", err)
605 }
606 }()
607
608 check("Wait", cmd.Wait())
609 }
610
611
612
613
614
615
616
617 func TestStdinCloseRace(t *testing.T) {
618 t.Parallel()
619
620 cmd := helperCommand(t, "stdinClose")
621 stdin, err := cmd.StdinPipe()
622 if err != nil {
623 t.Fatalf("StdinPipe: %v", err)
624 }
625 if err := cmd.Start(); err != nil {
626 t.Fatalf("Start: %v", err)
627
628 }
629
630 var wg sync.WaitGroup
631 wg.Add(2)
632 defer wg.Wait()
633
634 go func() {
635 defer wg.Done()
636
637
638
639
640
641
642 cmd.Process.Kill()
643 }()
644
645 go func() {
646 defer wg.Done()
647
648
649
650
651
652 io.Copy(stdin, strings.NewReader("unexpected string"))
653 if err := stdin.Close(); err != nil && !errors.Is(err, os.ErrClosed) {
654 t.Errorf("stdin.Close: %v", err)
655 }
656 }()
657
658 if err := cmd.Wait(); err == nil {
659 t.Fatalf("Wait: succeeded unexpectedly")
660 }
661 }
662
663
664 func TestPipeLookPathLeak(t *testing.T) {
665 if runtime.GOOS == "windows" {
666 t.Skip("we don't currently suppore counting open handles on windows")
667 }
668
669
670 openFDs := func() []uintptr {
671 var fds []uintptr
672 for i := uintptr(0); i < 100; i++ {
673 if fdtest.Exists(i) {
674 fds = append(fds, i)
675 }
676 }
677 return fds
678 }
679
680 old := map[uintptr]bool{}
681 for _, fd := range openFDs() {
682 old[fd] = true
683 }
684
685 for i := 0; i < 6; i++ {
686 cmd := exec.Command("something-that-does-not-exist-executable")
687 cmd.StdoutPipe()
688 cmd.StderrPipe()
689 cmd.StdinPipe()
690 if err := cmd.Run(); err == nil {
691 t.Fatal("unexpected success")
692 }
693 }
694
695
696
697
698
699
700 for _, fd := range openFDs() {
701 if !old[fd] {
702 t.Errorf("leaked file descriptor %v", fd)
703 }
704 }
705 }
706
707 func TestExtraFiles(t *testing.T) {
708 if testing.Short() {
709 t.Skipf("skipping test in short mode that would build a helper binary")
710 }
711
712 if haveUnexpectedFDs {
713
714
715
716
717
718
719
720
721
722
723
724
725
726 t.Skip("skipping test because test was run with FDs open")
727 }
728
729 testenv.MustHaveExec(t)
730 testenv.MustHaveGoBuild(t)
731
732
733
734 testenv.MustInternalLink(t, false)
735
736 if runtime.GOOS == "windows" {
737 t.Skipf("skipping test on %q", runtime.GOOS)
738 }
739
740
741
742 ln, err := net.Listen("tcp", "127.0.0.1:0")
743 if err != nil {
744 t.Fatal(err)
745 }
746 defer ln.Close()
747
748
749 f, err := ln.(*net.TCPListener).File()
750 if err != nil {
751 t.Fatal(err)
752 }
753 defer f.Close()
754 ln2, err := net.FileListener(f)
755 if err != nil {
756 t.Fatal(err)
757 }
758 defer ln2.Close()
759
760
761
762 ts := httptest.NewUnstartedServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {}))
763
764 ts.Config.ErrorLog = log.New(io.Discard, "", 0)
765 ts.StartTLS()
766 defer ts.Close()
767 _, err = http.Get(ts.URL)
768 if err == nil {
769 t.Errorf("success trying to fetch %s; want an error", ts.URL)
770 }
771
772 tf, err := os.CreateTemp("", "")
773 if err != nil {
774 t.Fatalf("TempFile: %v", err)
775 }
776 defer os.Remove(tf.Name())
777 defer tf.Close()
778
779 const text = "Hello, fd 3!"
780 _, err = tf.Write([]byte(text))
781 if err != nil {
782 t.Fatalf("Write: %v", err)
783 }
784 _, err = tf.Seek(0, io.SeekStart)
785 if err != nil {
786 t.Fatalf("Seek: %v", err)
787 }
788
789 tempdir := t.TempDir()
790 exe := filepath.Join(tempdir, "read3.exe")
791
792 c := testenv.Command(t, testenv.GoToolPath(t), "build", "-o", exe, "read3.go")
793
794
795 c.Env = append(os.Environ(), "CGO_ENABLED=0")
796 if output, err := c.CombinedOutput(); err != nil {
797 t.Logf("go build -o %s read3.go\n%s", exe, output)
798 t.Fatalf("go build failed: %v", err)
799 }
800
801
802 ctx := context.Background()
803 if deadline, ok := t.Deadline(); ok {
804
805
806 deadline = deadline.Add(-time.Until(deadline) / 5)
807
808 var cancel context.CancelFunc
809 ctx, cancel = context.WithDeadline(ctx, deadline)
810 defer cancel()
811 }
812
813 c = exec.CommandContext(ctx, exe)
814 var stdout, stderr strings.Builder
815 c.Stdout = &stdout
816 c.Stderr = &stderr
817 c.ExtraFiles = []*os.File{tf}
818 if runtime.GOOS == "illumos" {
819
820
821
822
823
824
825
826
827
828 c.Env = append(os.Environ(), "GOMAXPROCS=1")
829 }
830 err = c.Run()
831 if err != nil {
832 t.Fatalf("Run: %v\n--- stdout:\n%s--- stderr:\n%s", err, stdout.String(), stderr.String())
833 }
834 if stdout.String() != text {
835 t.Errorf("got stdout %q, stderr %q; want %q on stdout", stdout.String(), stderr.String(), text)
836 }
837 }
838
839 func TestExtraFilesRace(t *testing.T) {
840 if runtime.GOOS == "windows" {
841 maySkipHelperCommand("describefiles")
842 t.Skip("no operating system support; skipping")
843 }
844 t.Parallel()
845
846 listen := func() net.Listener {
847 ln, err := net.Listen("tcp", "127.0.0.1:0")
848 if err != nil {
849 t.Fatal(err)
850 }
851 return ln
852 }
853 listenerFile := func(ln net.Listener) *os.File {
854 f, err := ln.(*net.TCPListener).File()
855 if err != nil {
856 t.Fatal(err)
857 }
858 return f
859 }
860 runCommand := func(c *exec.Cmd, out chan<- string) {
861 bout, err := c.CombinedOutput()
862 if err != nil {
863 out <- "ERROR:" + err.Error()
864 } else {
865 out <- string(bout)
866 }
867 }
868
869 for i := 0; i < 10; i++ {
870 if testing.Short() && i >= 3 {
871 break
872 }
873 la := listen()
874 ca := helperCommand(t, "describefiles")
875 ca.ExtraFiles = []*os.File{listenerFile(la)}
876 lb := listen()
877 cb := helperCommand(t, "describefiles")
878 cb.ExtraFiles = []*os.File{listenerFile(lb)}
879 ares := make(chan string)
880 bres := make(chan string)
881 go runCommand(ca, ares)
882 go runCommand(cb, bres)
883 if got, want := <-ares, fmt.Sprintf("fd3: listener %s\n", la.Addr()); got != want {
884 t.Errorf("iteration %d, process A got:\n%s\nwant:\n%s\n", i, got, want)
885 }
886 if got, want := <-bres, fmt.Sprintf("fd3: listener %s\n", lb.Addr()); got != want {
887 t.Errorf("iteration %d, process B got:\n%s\nwant:\n%s\n", i, got, want)
888 }
889 la.Close()
890 lb.Close()
891 for _, f := range ca.ExtraFiles {
892 f.Close()
893 }
894 for _, f := range cb.ExtraFiles {
895 f.Close()
896 }
897 }
898 }
899
900 type delayedInfiniteReader struct{}
901
902 func (delayedInfiniteReader) Read(b []byte) (int, error) {
903 time.Sleep(100 * time.Millisecond)
904 for i := range b {
905 b[i] = 'x'
906 }
907 return len(b), nil
908 }
909
910
911 func TestIgnorePipeErrorOnSuccess(t *testing.T) {
912 t.Parallel()
913
914 testWith := func(r io.Reader) func(*testing.T) {
915 return func(t *testing.T) {
916 t.Parallel()
917
918 cmd := helperCommand(t, "echo", "foo")
919 var out strings.Builder
920 cmd.Stdin = r
921 cmd.Stdout = &out
922 if err := cmd.Run(); err != nil {
923 t.Fatal(err)
924 }
925 if got, want := out.String(), "foo\n"; got != want {
926 t.Errorf("output = %q; want %q", got, want)
927 }
928 }
929 }
930 t.Run("10MB", testWith(strings.NewReader(strings.Repeat("x", 10<<20))))
931 t.Run("Infinite", testWith(delayedInfiniteReader{}))
932 }
933
934 type badWriter struct{}
935
936 func (w *badWriter) Write(data []byte) (int, error) {
937 return 0, io.ErrUnexpectedEOF
938 }
939
940 func TestClosePipeOnCopyError(t *testing.T) {
941 t.Parallel()
942
943 cmd := helperCommand(t, "yes")
944 cmd.Stdout = new(badWriter)
945 err := cmd.Run()
946 if err == nil {
947 t.Errorf("yes unexpectedly completed successfully")
948 }
949 }
950
951 func TestOutputStderrCapture(t *testing.T) {
952 t.Parallel()
953
954 cmd := helperCommand(t, "stderrfail")
955 _, err := cmd.Output()
956 ee, ok := err.(*exec.ExitError)
957 if !ok {
958 t.Fatalf("Output error type = %T; want ExitError", err)
959 }
960 got := string(ee.Stderr)
961 want := "some stderr text\n"
962 if got != want {
963 t.Errorf("ExitError.Stderr = %q; want %q", got, want)
964 }
965 }
966
967 func TestContext(t *testing.T) {
968 t.Parallel()
969
970 ctx, cancel := context.WithCancel(context.Background())
971 c := helperCommandContext(t, ctx, "pipetest")
972 stdin, err := c.StdinPipe()
973 if err != nil {
974 t.Fatal(err)
975 }
976 stdout, err := c.StdoutPipe()
977 if err != nil {
978 t.Fatal(err)
979 }
980 if err := c.Start(); err != nil {
981 t.Fatal(err)
982 }
983
984 if _, err := stdin.Write([]byte("O:hi\n")); err != nil {
985 t.Fatal(err)
986 }
987 buf := make([]byte, 5)
988 n, err := io.ReadFull(stdout, buf)
989 if n != len(buf) || err != nil || string(buf) != "O:hi\n" {
990 t.Fatalf("ReadFull = %d, %v, %q", n, err, buf[:n])
991 }
992 go cancel()
993
994 if err := c.Wait(); err == nil {
995 t.Fatal("expected Wait failure")
996 }
997 }
998
999 func TestContextCancel(t *testing.T) {
1000 if runtime.GOOS == "netbsd" && runtime.GOARCH == "arm64" {
1001 maySkipHelperCommand("cat")
1002 testenv.SkipFlaky(t, 42061)
1003 }
1004
1005
1006
1007 t.Parallel()
1008
1009 ctx, cancel := context.WithCancel(context.Background())
1010 defer cancel()
1011 c := helperCommandContext(t, ctx, "cat")
1012
1013 stdin, err := c.StdinPipe()
1014 if err != nil {
1015 t.Fatal(err)
1016 }
1017 defer stdin.Close()
1018
1019 if err := c.Start(); err != nil {
1020 t.Fatal(err)
1021 }
1022
1023
1024 if _, err := io.WriteString(stdin, "echo"); err != nil {
1025 t.Fatal(err)
1026 }
1027
1028 cancel()
1029
1030
1031
1032 start := time.Now()
1033 delay := 1 * time.Millisecond
1034 for {
1035 if _, err := io.WriteString(stdin, "echo"); err != nil {
1036 break
1037 }
1038
1039 if time.Since(start) > time.Minute {
1040
1041
1042 debug.SetTraceback("system")
1043 panic("canceling context did not stop program")
1044 }
1045
1046
1047
1048 delay *= 2
1049 if delay > 1*time.Second {
1050 delay = 1 * time.Second
1051 }
1052 time.Sleep(delay)
1053 }
1054
1055 if err := c.Wait(); err == nil {
1056 t.Error("program unexpectedly exited successfully")
1057 } else {
1058 t.Logf("exit status: %v", err)
1059 }
1060 }
1061
1062
1063 func TestDedupEnvEcho(t *testing.T) {
1064 t.Parallel()
1065
1066 cmd := helperCommand(t, "echoenv", "FOO")
1067 cmd.Env = append(cmd.Environ(), "FOO=bad", "FOO=good")
1068 out, err := cmd.CombinedOutput()
1069 if err != nil {
1070 t.Fatal(err)
1071 }
1072 if got, want := strings.TrimSpace(string(out)), "good"; got != want {
1073 t.Errorf("output = %q; want %q", got, want)
1074 }
1075 }
1076
1077 func TestEnvNULCharacter(t *testing.T) {
1078 if runtime.GOOS == "plan9" {
1079 t.Skip("plan9 explicitly allows NUL in the environment")
1080 }
1081 cmd := helperCommand(t, "echoenv", "FOO", "BAR")
1082 cmd.Env = append(cmd.Environ(), "FOO=foo\x00BAR=bar")
1083 out, err := cmd.CombinedOutput()
1084 if err == nil {
1085 t.Errorf("output = %q; want error", string(out))
1086 }
1087 }
1088
1089 func TestString(t *testing.T) {
1090 t.Parallel()
1091
1092 echoPath, err := exec.LookPath("echo")
1093 if err != nil {
1094 t.Skip(err)
1095 }
1096 tests := [...]struct {
1097 path string
1098 args []string
1099 want string
1100 }{
1101 {"echo", nil, echoPath},
1102 {"echo", []string{"a"}, echoPath + " a"},
1103 {"echo", []string{"a", "b"}, echoPath + " a b"},
1104 }
1105 for _, test := range tests {
1106 cmd := exec.Command(test.path, test.args...)
1107 if got := cmd.String(); got != test.want {
1108 t.Errorf("String(%q, %q) = %q, want %q", test.path, test.args, got, test.want)
1109 }
1110 }
1111 }
1112
1113 func TestStringPathNotResolved(t *testing.T) {
1114 t.Parallel()
1115
1116 _, err := exec.LookPath("makemeasandwich")
1117 if err == nil {
1118 t.Skip("wow, thanks")
1119 }
1120
1121 cmd := exec.Command("makemeasandwich", "-lettuce")
1122 want := "makemeasandwich -lettuce"
1123 if got := cmd.String(); got != want {
1124 t.Errorf("String(%q, %q) = %q, want %q", "makemeasandwich", "-lettuce", got, want)
1125 }
1126 }
1127
1128 func TestNoPath(t *testing.T) {
1129 err := new(exec.Cmd).Start()
1130 want := "exec: no command"
1131 if err == nil || err.Error() != want {
1132 t.Errorf("new(Cmd).Start() = %v, want %q", err, want)
1133 }
1134 }
1135
1136
1137
1138
1139 func TestDoubleStartLeavesPipesOpen(t *testing.T) {
1140 t.Parallel()
1141
1142 cmd := helperCommand(t, "pipetest")
1143 in, err := cmd.StdinPipe()
1144 if err != nil {
1145 t.Fatal(err)
1146 }
1147 out, err := cmd.StdoutPipe()
1148 if err != nil {
1149 t.Fatal(err)
1150 }
1151
1152 if err := cmd.Start(); err != nil {
1153 t.Fatal(err)
1154 }
1155 t.Cleanup(func() {
1156 if err := cmd.Wait(); err != nil {
1157 t.Error(err)
1158 }
1159 })
1160
1161 if err := cmd.Start(); err == nil || !strings.HasSuffix(err.Error(), "already started") {
1162 t.Fatalf("second call to Start returned a nil; want an 'already started' error")
1163 }
1164
1165 outc := make(chan []byte, 1)
1166 go func() {
1167 b, err := io.ReadAll(out)
1168 if err != nil {
1169 t.Error(err)
1170 }
1171 outc <- b
1172 }()
1173
1174 const msg = "O:Hello, pipe!\n"
1175
1176 _, err = io.WriteString(in, msg)
1177 if err != nil {
1178 t.Fatal(err)
1179 }
1180 in.Close()
1181
1182 b := <-outc
1183 if !bytes.Equal(b, []byte(msg)) {
1184 t.Fatalf("read %q from stdout pipe; want %q", b, msg)
1185 }
1186 }
1187
1188 func cmdHang(args ...string) {
1189 sleep, err := time.ParseDuration(args[0])
1190 if err != nil {
1191 panic(err)
1192 }
1193
1194 fs := flag.NewFlagSet("hang", flag.ExitOnError)
1195 exitOnInterrupt := fs.Bool("interrupt", false, "if true, commands should exit 0 on os.Interrupt")
1196 subsleep := fs.Duration("subsleep", 0, "amount of time for the 'hang' helper to leave an orphaned subprocess sleeping with stderr open")
1197 probe := fs.Duration("probe", 0, "if nonzero, the 'hang' helper should write to stderr at this interval, and exit nonzero if a write fails")
1198 read := fs.Bool("read", false, "if true, the 'hang' helper should read stdin to completion before sleeping")
1199 fs.Parse(args[1:])
1200
1201 pid := os.Getpid()
1202
1203 if *subsleep != 0 {
1204 cmd := exec.Command(exePath(nil), "hang", subsleep.String(), "-read=true", "-probe="+probe.String())
1205 cmd.Stdin = os.Stdin
1206 cmd.Stderr = os.Stderr
1207 out, err := cmd.StdoutPipe()
1208 if err != nil {
1209 fmt.Fprintln(os.Stderr, err)
1210 os.Exit(1)
1211 }
1212 cmd.Start()
1213
1214 buf := new(strings.Builder)
1215 if _, err := io.Copy(buf, out); err != nil {
1216 fmt.Fprintln(os.Stderr, err)
1217 cmd.Process.Kill()
1218 cmd.Wait()
1219 os.Exit(1)
1220 }
1221 fmt.Fprintf(os.Stderr, "%d: started %d: %v\n", pid, cmd.Process.Pid, cmd)
1222 go cmd.Wait()
1223 }
1224
1225 if *exitOnInterrupt {
1226 c := make(chan os.Signal, 1)
1227 signal.Notify(c, os.Interrupt)
1228 go func() {
1229 sig := <-c
1230 fmt.Fprintf(os.Stderr, "%d: received %v\n", pid, sig)
1231 os.Exit(0)
1232 }()
1233 } else {
1234 signal.Ignore(os.Interrupt)
1235 }
1236
1237
1238 os.Stdout.Close()
1239
1240 if *read {
1241 if pipeSignal != nil {
1242 signal.Ignore(pipeSignal)
1243 }
1244 r := bufio.NewReader(os.Stdin)
1245 for {
1246 line, err := r.ReadBytes('\n')
1247 if len(line) > 0 {
1248
1249 fmt.Fprintf(os.Stderr, "%d: read %s", pid, line)
1250 }
1251 if err != nil {
1252 fmt.Fprintf(os.Stderr, "%d: finished read: %v", pid, err)
1253 break
1254 }
1255 }
1256 }
1257
1258 if *probe != 0 {
1259 ticker := time.NewTicker(*probe)
1260 go func() {
1261 for range ticker.C {
1262 if _, err := fmt.Fprintf(os.Stderr, "%d: ok\n", pid); err != nil {
1263 os.Exit(1)
1264 }
1265 }
1266 }()
1267 }
1268
1269 if sleep != 0 {
1270 time.Sleep(sleep)
1271 fmt.Fprintf(os.Stderr, "%d: slept %v\n", pid, sleep)
1272 }
1273 }
1274
1275
1276
1277 type tickReader struct {
1278 interval time.Duration
1279 lastTick time.Time
1280 s string
1281 }
1282
1283 func newTickReader(interval time.Duration) *tickReader {
1284 return &tickReader{interval: interval}
1285 }
1286
1287 func (r *tickReader) Read(p []byte) (n int, err error) {
1288 if len(r.s) == 0 {
1289 if d := r.interval - time.Since(r.lastTick); d > 0 {
1290 time.Sleep(d)
1291 }
1292 r.lastTick = time.Now()
1293 r.s = r.lastTick.Format(time.RFC3339Nano + "\n")
1294 }
1295
1296 n = copy(p, r.s)
1297 r.s = r.s[n:]
1298 return n, nil
1299 }
1300
1301 func startHang(t *testing.T, ctx context.Context, hangTime time.Duration, interrupt os.Signal, waitDelay time.Duration, flags ...string) *exec.Cmd {
1302 t.Helper()
1303
1304 args := append([]string{hangTime.String()}, flags...)
1305 cmd := helperCommandContext(t, ctx, "hang", args...)
1306 cmd.Stdin = newTickReader(1 * time.Millisecond)
1307 cmd.Stderr = new(strings.Builder)
1308 if interrupt == nil {
1309 cmd.Cancel = nil
1310 } else {
1311 cmd.Cancel = func() error {
1312 return cmd.Process.Signal(interrupt)
1313 }
1314 }
1315 cmd.WaitDelay = waitDelay
1316 out, err := cmd.StdoutPipe()
1317 if err != nil {
1318 t.Fatal(err)
1319 }
1320
1321 t.Log(cmd)
1322 if err := cmd.Start(); err != nil {
1323 t.Fatal(err)
1324 }
1325
1326
1327 buf := new(strings.Builder)
1328 if _, err := io.Copy(buf, out); err != nil {
1329 t.Error(err)
1330 cmd.Process.Kill()
1331 cmd.Wait()
1332 t.FailNow()
1333 }
1334 if buf.Len() > 0 {
1335 t.Logf("stdout %v:\n%s", cmd.Args, buf)
1336 }
1337
1338 return cmd
1339 }
1340
1341 func TestWaitInterrupt(t *testing.T) {
1342 t.Parallel()
1343
1344
1345
1346
1347 const tooLong = 10 * time.Minute
1348
1349
1350
1351 t.Run("Wait", func(t *testing.T) {
1352 t.Parallel()
1353 cmd := startHang(t, context.Background(), 1*time.Millisecond, os.Kill, 0)
1354 err := cmd.Wait()
1355 t.Logf("stderr:\n%s", cmd.Stderr)
1356 t.Logf("[%d] %v", cmd.Process.Pid, err)
1357
1358 if err != nil {
1359 t.Errorf("Wait: %v; want <nil>", err)
1360 }
1361 if ps := cmd.ProcessState; !ps.Exited() {
1362 t.Errorf("cmd did not exit: %v", ps)
1363 } else if code := ps.ExitCode(); code != 0 {
1364 t.Errorf("cmd.ProcessState.ExitCode() = %v; want 0", code)
1365 }
1366 })
1367
1368
1369
1370 t.Run("WaitDelay", func(t *testing.T) {
1371 if runtime.GOOS == "windows" {
1372 t.Skipf("skipping: os.Interrupt is not implemented on Windows")
1373 }
1374 t.Parallel()
1375
1376 ctx, cancel := context.WithCancel(context.Background())
1377 cmd := startHang(t, ctx, tooLong, nil, tooLong, "-interrupt=true")
1378 cancel()
1379
1380 time.Sleep(1 * time.Millisecond)
1381
1382
1383
1384 if err := cmd.Process.Signal(os.Interrupt); err != nil {
1385 t.Error(err)
1386 }
1387
1388 err := cmd.Wait()
1389 t.Logf("stderr:\n%s", cmd.Stderr)
1390 t.Logf("[%d] %v", cmd.Process.Pid, err)
1391
1392
1393
1394
1395
1396
1397 if err != nil {
1398 t.Errorf("Wait: %v; want nil", err)
1399 }
1400 if ps := cmd.ProcessState; !ps.Exited() {
1401 t.Errorf("cmd did not exit: %v", ps)
1402 } else if code := ps.ExitCode(); code != 0 {
1403 t.Errorf("cmd.ProcessState.ExitCode() = %v; want 0", code)
1404 }
1405 })
1406
1407
1408
1409
1410
1411 t.Run("SIGKILL-hang", func(t *testing.T) {
1412 t.Parallel()
1413
1414 ctx, cancel := context.WithCancel(context.Background())
1415 cmd := startHang(t, ctx, tooLong, os.Kill, 10*time.Millisecond, "-subsleep=10m", "-probe=1ms")
1416 cancel()
1417 err := cmd.Wait()
1418 t.Logf("stderr:\n%s", cmd.Stderr)
1419 t.Logf("[%d] %v", cmd.Process.Pid, err)
1420
1421
1422
1423
1424
1425
1426 if ee := new(*exec.ExitError); !errors.As(err, ee) {
1427 t.Errorf("Wait error = %v; want %T", err, *ee)
1428 }
1429 })
1430
1431
1432
1433
1434
1435
1436 t.Run("Exit-hang", func(t *testing.T) {
1437 t.Parallel()
1438
1439 cmd := startHang(t, context.Background(), 1*time.Millisecond, nil, 10*time.Millisecond, "-subsleep=10m", "-probe=1ms")
1440 err := cmd.Wait()
1441 t.Logf("stderr:\n%s", cmd.Stderr)
1442 t.Logf("[%d] %v", cmd.Process.Pid, err)
1443
1444
1445
1446
1447
1448 if !errors.Is(err, exec.ErrWaitDelay) {
1449 t.Errorf("Wait error = %v; want %T", err, exec.ErrWaitDelay)
1450 }
1451 })
1452
1453
1454
1455
1456 t.Run("SIGINT-ignored", func(t *testing.T) {
1457 if runtime.GOOS == "windows" {
1458 t.Skipf("skipping: os.Interrupt is not implemented on Windows")
1459 }
1460 t.Parallel()
1461
1462 ctx, cancel := context.WithCancel(context.Background())
1463 cmd := startHang(t, ctx, tooLong, os.Interrupt, 10*time.Millisecond, "-interrupt=false")
1464 cancel()
1465 err := cmd.Wait()
1466 t.Logf("stderr:\n%s", cmd.Stderr)
1467 t.Logf("[%d] %v", cmd.Process.Pid, err)
1468
1469
1470
1471 if ee := new(*exec.ExitError); !errors.As(err, ee) {
1472 t.Errorf("Wait error = %v; want %T", err, *ee)
1473 }
1474 })
1475
1476
1477
1478
1479
1480 t.Run("SIGINT-handled", func(t *testing.T) {
1481 if runtime.GOOS == "windows" {
1482 t.Skipf("skipping: os.Interrupt is not implemented on Windows")
1483 }
1484 t.Parallel()
1485
1486 ctx, cancel := context.WithCancel(context.Background())
1487 cmd := startHang(t, ctx, tooLong, os.Interrupt, 0, "-interrupt=true")
1488 cancel()
1489 err := cmd.Wait()
1490 t.Logf("stderr:\n%s", cmd.Stderr)
1491 t.Logf("[%d] %v", cmd.Process.Pid, err)
1492
1493 if !errors.Is(err, ctx.Err()) {
1494 t.Errorf("Wait error = %v; want %v", err, ctx.Err())
1495 }
1496 if ps := cmd.ProcessState; !ps.Exited() {
1497 t.Errorf("cmd did not exit: %v", ps)
1498 } else if code := ps.ExitCode(); code != 0 {
1499 t.Errorf("cmd.ProcessState.ExitCode() = %v; want 0", code)
1500 }
1501 })
1502
1503
1504
1505
1506 t.Run("SIGQUIT", func(t *testing.T) {
1507 if quitSignal == nil {
1508 t.Skipf("skipping: SIGQUIT is not supported on %v", runtime.GOOS)
1509 }
1510 t.Parallel()
1511
1512 ctx, cancel := context.WithCancel(context.Background())
1513 cmd := startHang(t, ctx, tooLong, quitSignal, 0)
1514 cancel()
1515 err := cmd.Wait()
1516 t.Logf("stderr:\n%s", cmd.Stderr)
1517 t.Logf("[%d] %v", cmd.Process.Pid, err)
1518
1519 if ee := new(*exec.ExitError); !errors.As(err, ee) {
1520 t.Errorf("Wait error = %v; want %v", err, ctx.Err())
1521 }
1522
1523 if ps := cmd.ProcessState; !ps.Exited() {
1524 t.Errorf("cmd did not exit: %v", ps)
1525 } else if code := ps.ExitCode(); code != 2 {
1526
1527 t.Errorf("cmd.ProcessState.ExitCode() = %v; want 2", code)
1528 }
1529
1530 if !strings.Contains(fmt.Sprint(cmd.Stderr), "\n\ngoroutine ") {
1531 t.Errorf("cmd.Stderr does not contain a goroutine dump")
1532 }
1533 })
1534 }
1535
1536 func TestCancelErrors(t *testing.T) {
1537 t.Parallel()
1538
1539
1540
1541 t.Run("success after error", func(t *testing.T) {
1542 t.Parallel()
1543
1544 ctx, cancel := context.WithCancel(context.Background())
1545 defer cancel()
1546
1547 cmd := helperCommandContext(t, ctx, "pipetest")
1548 stdin, err := cmd.StdinPipe()
1549 if err != nil {
1550 t.Fatal(err)
1551 }
1552
1553 errArbitrary := errors.New("arbitrary error")
1554 cmd.Cancel = func() error {
1555 stdin.Close()
1556 t.Logf("Cancel returning %v", errArbitrary)
1557 return errArbitrary
1558 }
1559 if err := cmd.Start(); err != nil {
1560 t.Fatal(err)
1561 }
1562 cancel()
1563
1564 err = cmd.Wait()
1565 t.Logf("[%d] %v", cmd.Process.Pid, err)
1566 if !errors.Is(err, errArbitrary) || err == errArbitrary {
1567 t.Errorf("Wait error = %v; want an error wrapping %v", err, errArbitrary)
1568 }
1569 })
1570
1571
1572
1573
1574
1575 t.Run("success after ErrProcessDone", func(t *testing.T) {
1576 t.Parallel()
1577
1578 ctx, cancel := context.WithCancel(context.Background())
1579 defer cancel()
1580
1581 cmd := helperCommandContext(t, ctx, "pipetest")
1582 stdin, err := cmd.StdinPipe()
1583 if err != nil {
1584 t.Fatal(err)
1585 }
1586
1587 stdout, err := cmd.StdoutPipe()
1588 if err != nil {
1589 t.Fatal(err)
1590 }
1591
1592
1593
1594
1595 interruptCalled := make(chan struct{})
1596 done := make(chan struct{})
1597 cmd.Cancel = func() error {
1598 close(interruptCalled)
1599 <-done
1600 t.Logf("Cancel returning an error wrapping ErrProcessDone")
1601 return fmt.Errorf("%w: stdout closed", os.ErrProcessDone)
1602 }
1603
1604 if err := cmd.Start(); err != nil {
1605 t.Fatal(err)
1606 }
1607
1608 cancel()
1609 <-interruptCalled
1610 stdin.Close()
1611 io.Copy(io.Discard, stdout)
1612 close(done)
1613
1614 err = cmd.Wait()
1615 t.Logf("[%d] %v", cmd.Process.Pid, err)
1616 if err != nil {
1617 t.Errorf("Wait error = %v; want nil", err)
1618 }
1619 })
1620
1621
1622
1623
1624 t.Run("killed after error", func(t *testing.T) {
1625 t.Parallel()
1626
1627 ctx, cancel := context.WithCancel(context.Background())
1628 defer cancel()
1629
1630 cmd := helperCommandContext(t, ctx, "pipetest")
1631 stdin, err := cmd.StdinPipe()
1632 if err != nil {
1633 t.Fatal(err)
1634 }
1635 defer stdin.Close()
1636
1637 errArbitrary := errors.New("arbitrary error")
1638 var interruptCalled atomic.Bool
1639 cmd.Cancel = func() error {
1640 t.Logf("Cancel called")
1641 interruptCalled.Store(true)
1642 return errArbitrary
1643 }
1644 cmd.WaitDelay = 1 * time.Millisecond
1645 if err := cmd.Start(); err != nil {
1646 t.Fatal(err)
1647 }
1648 cancel()
1649
1650 err = cmd.Wait()
1651 t.Logf("[%d] %v", cmd.Process.Pid, err)
1652
1653
1654
1655 if !interruptCalled.Load() {
1656 t.Errorf("Cancel was not called when the context was canceled")
1657 }
1658
1659
1660
1661
1662 if _, ok := err.(*exec.ExitError); !ok {
1663 t.Errorf("Wait error = %v; want *exec.ExitError", err)
1664 }
1665 })
1666
1667
1668
1669
1670 t.Run("killed after spurious ErrProcessDone", func(t *testing.T) {
1671 t.Parallel()
1672
1673 ctx, cancel := context.WithCancel(context.Background())
1674 defer cancel()
1675
1676 cmd := helperCommandContext(t, ctx, "pipetest")
1677 stdin, err := cmd.StdinPipe()
1678 if err != nil {
1679 t.Fatal(err)
1680 }
1681 defer stdin.Close()
1682
1683 var interruptCalled atomic.Bool
1684 cmd.Cancel = func() error {
1685 t.Logf("Cancel returning an error wrapping ErrProcessDone")
1686 interruptCalled.Store(true)
1687 return fmt.Errorf("%w: stdout closed", os.ErrProcessDone)
1688 }
1689 cmd.WaitDelay = 1 * time.Millisecond
1690 if err := cmd.Start(); err != nil {
1691 t.Fatal(err)
1692 }
1693 cancel()
1694
1695 err = cmd.Wait()
1696 t.Logf("[%d] %v", cmd.Process.Pid, err)
1697
1698
1699
1700 if !interruptCalled.Load() {
1701 t.Errorf("Cancel was not called when the context was canceled")
1702 }
1703
1704
1705
1706
1707 if ee, ok := err.(*exec.ExitError); !ok {
1708 t.Errorf("Wait error of type %T; want %T", err, ee)
1709 }
1710 })
1711
1712
1713
1714
1715 t.Run("nonzero exit after error", func(t *testing.T) {
1716 t.Parallel()
1717
1718 ctx, cancel := context.WithCancel(context.Background())
1719 defer cancel()
1720
1721 cmd := helperCommandContext(t, ctx, "stderrfail")
1722 stderr, err := cmd.StderrPipe()
1723 if err != nil {
1724 t.Fatal(err)
1725 }
1726
1727 errArbitrary := errors.New("arbitrary error")
1728 interrupted := make(chan struct{})
1729 cmd.Cancel = func() error {
1730 close(interrupted)
1731 return errArbitrary
1732 }
1733 if err := cmd.Start(); err != nil {
1734 t.Fatal(err)
1735 }
1736 cancel()
1737 <-interrupted
1738 io.Copy(io.Discard, stderr)
1739
1740 err = cmd.Wait()
1741 t.Logf("[%d] %v", cmd.Process.Pid, err)
1742
1743 if ee, ok := err.(*exec.ExitError); !ok || ee.ProcessState.ExitCode() != 1 {
1744 t.Errorf("Wait error = %v; want exit status 1", err)
1745 }
1746 })
1747 }
1748
1749
1750
1751
1752
1753 func TestConcurrentExec(t *testing.T) {
1754 ctx, cancel := context.WithCancel(context.Background())
1755
1756
1757
1758
1759
1760
1761
1762
1763 var (
1764 nHangs = runtime.GOMAXPROCS(0)
1765 nExits = runtime.GOMAXPROCS(0)
1766 hangs, exits sync.WaitGroup
1767 )
1768 hangs.Add(nHangs)
1769 exits.Add(nExits)
1770
1771
1772
1773
1774
1775 var ready sync.WaitGroup
1776 ready.Add(nHangs + nExits)
1777
1778 for i := 0; i < nHangs; i++ {
1779 go func() {
1780 defer hangs.Done()
1781
1782 cmd := helperCommandContext(t, ctx, "pipetest")
1783 stdin, err := cmd.StdinPipe()
1784 if err != nil {
1785 ready.Done()
1786 t.Error(err)
1787 return
1788 }
1789 cmd.Cancel = stdin.Close
1790 ready.Done()
1791
1792 ready.Wait()
1793 if err := cmd.Start(); err != nil {
1794 if !errors.Is(err, context.Canceled) {
1795 t.Error(err)
1796 }
1797 return
1798 }
1799
1800 cmd.Wait()
1801 }()
1802 }
1803
1804 for i := 0; i < nExits; i++ {
1805 go func() {
1806 defer exits.Done()
1807
1808 cmd := helperCommandContext(t, ctx, "exit", "0")
1809 ready.Done()
1810
1811 ready.Wait()
1812 if err := cmd.Run(); err != nil {
1813 t.Error(err)
1814 }
1815 }()
1816 }
1817
1818 exits.Wait()
1819 cancel()
1820 hangs.Wait()
1821 }
1822
1823
1824
1825 func TestPathRace(t *testing.T) {
1826 cmd := helperCommand(t, "exit", "0")
1827
1828 done := make(chan struct{})
1829 go func() {
1830 out, err := cmd.CombinedOutput()
1831 t.Logf("%v: %v\n%s", cmd, err, out)
1832 close(done)
1833 }()
1834
1835 t.Logf("running in background: %v", cmd)
1836 <-done
1837 }
1838
1839 func TestAbsPathExec(t *testing.T) {
1840 testenv.MustHaveExec(t)
1841 testenv.MustHaveGoBuild(t)
1842
1843
1844
1845 exe := filepath.Join(testenv.GOROOT(t), "bin/gofmt")
1846 cmd := exec.Command(exe)
1847 if cmd.Path != exe {
1848 t.Errorf("exec.Command(%#q) set Path=%#q", exe, cmd.Path)
1849 }
1850 err := cmd.Run()
1851 if err != nil {
1852 t.Errorf("using exec.Command(%#q): %v", exe, err)
1853 }
1854
1855 cmd = &exec.Cmd{Path: exe}
1856 err = cmd.Run()
1857 if err != nil {
1858 t.Errorf("using exec.Cmd{Path: %#q}: %v", cmd.Path, err)
1859 }
1860
1861 cmd = &exec.Cmd{Path: "gofmt", Dir: "/"}
1862 err = cmd.Run()
1863 if err == nil {
1864 t.Errorf("using exec.Cmd{Path: %#q}: unexpected success", cmd.Path)
1865 }
1866
1867
1868
1869 t.Run("modified", func(t *testing.T) {
1870 if exec.Command(filepath.Join(testenv.GOROOT(t), "bin/go")).Run() == nil {
1871
1872
1873
1874 t.Fatal("test case needs updating to verify fix for go.dev/issue/68314")
1875 }
1876 exe1 := filepath.Join(testenv.GOROOT(t), "bin/go")
1877 exe2 := filepath.Join(testenv.GOROOT(t), "bin/gofmt")
1878 cmd := exec.Command(exe1)
1879 cmd.Path = exe2
1880 cmd.Args = []string{cmd.Path}
1881 err := cmd.Run()
1882 if err != nil {
1883 t.Error("ran wrong binary")
1884 }
1885 })
1886 }
1887
View as plain text