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