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 testenv.MustInternalLink(t, false)
687
688 if runtime.GOOS == "windows" {
689 t.Skipf("skipping test on %q", runtime.GOOS)
690 }
691
692
693
694 ln, err := net.Listen("tcp", "127.0.0.1:0")
695 if err != nil {
696 t.Fatal(err)
697 }
698 defer ln.Close()
699
700
701 f, err := ln.(*net.TCPListener).File()
702 if err != nil {
703 t.Fatal(err)
704 }
705 defer f.Close()
706 ln2, err := net.FileListener(f)
707 if err != nil {
708 t.Fatal(err)
709 }
710 defer ln2.Close()
711
712
713
714 ts := httptest.NewUnstartedServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {}))
715
716 ts.Config.ErrorLog = log.New(io.Discard, "", 0)
717 ts.StartTLS()
718 defer ts.Close()
719 _, err = http.Get(ts.URL)
720 if err == nil {
721 t.Errorf("success trying to fetch %s; want an error", ts.URL)
722 }
723
724 tf, err := os.CreateTemp("", "")
725 if err != nil {
726 t.Fatalf("TempFile: %v", err)
727 }
728 defer os.Remove(tf.Name())
729 defer tf.Close()
730
731 const text = "Hello, fd 3!"
732 _, err = tf.Write([]byte(text))
733 if err != nil {
734 t.Fatalf("Write: %v", err)
735 }
736 _, err = tf.Seek(0, io.SeekStart)
737 if err != nil {
738 t.Fatalf("Seek: %v", err)
739 }
740
741 tempdir := t.TempDir()
742 exe := filepath.Join(tempdir, "read3.exe")
743
744 c := testenv.Command(t, testenv.GoToolPath(t), "build", "-o", exe, "read3.go")
745
746
747 c.Env = append(os.Environ(), "CGO_ENABLED=0")
748 if output, err := c.CombinedOutput(); err != nil {
749 t.Logf("go build -o %s read3.go\n%s", exe, output)
750 t.Fatalf("go build failed: %v", err)
751 }
752
753
754 ctx := context.Background()
755 if deadline, ok := t.Deadline(); ok {
756
757
758 deadline = deadline.Add(-time.Until(deadline) / 5)
759
760 var cancel context.CancelFunc
761 ctx, cancel = context.WithDeadline(ctx, deadline)
762 defer cancel()
763 }
764
765 c = exec.CommandContext(ctx, exe)
766 var stdout, stderr strings.Builder
767 c.Stdout = &stdout
768 c.Stderr = &stderr
769 c.ExtraFiles = []*os.File{tf}
770 if runtime.GOOS == "illumos" {
771
772
773
774
775
776
777
778
779
780 c.Env = append(os.Environ(), "GOMAXPROCS=1")
781 }
782 err = c.Run()
783 if err != nil {
784 t.Fatalf("Run: %v\n--- stdout:\n%s--- stderr:\n%s", err, stdout.String(), stderr.String())
785 }
786 if stdout.String() != text {
787 t.Errorf("got stdout %q, stderr %q; want %q on stdout", stdout.String(), stderr.String(), text)
788 }
789 }
790
791 func TestExtraFilesRace(t *testing.T) {
792 if runtime.GOOS == "windows" {
793 maySkipHelperCommand("describefiles")
794 t.Skip("no operating system support; skipping")
795 }
796 t.Parallel()
797
798 listen := func() net.Listener {
799 ln, err := net.Listen("tcp", "127.0.0.1:0")
800 if err != nil {
801 t.Fatal(err)
802 }
803 return ln
804 }
805 listenerFile := func(ln net.Listener) *os.File {
806 f, err := ln.(*net.TCPListener).File()
807 if err != nil {
808 t.Fatal(err)
809 }
810 return f
811 }
812 runCommand := func(c *exec.Cmd, out chan<- string) {
813 bout, err := c.CombinedOutput()
814 if err != nil {
815 out <- "ERROR:" + err.Error()
816 } else {
817 out <- string(bout)
818 }
819 }
820
821 for i := 0; i < 10; i++ {
822 if testing.Short() && i >= 3 {
823 break
824 }
825 la := listen()
826 ca := helperCommand(t, "describefiles")
827 ca.ExtraFiles = []*os.File{listenerFile(la)}
828 lb := listen()
829 cb := helperCommand(t, "describefiles")
830 cb.ExtraFiles = []*os.File{listenerFile(lb)}
831 ares := make(chan string)
832 bres := make(chan string)
833 go runCommand(ca, ares)
834 go runCommand(cb, bres)
835 if got, want := <-ares, fmt.Sprintf("fd3: listener %s\n", la.Addr()); got != want {
836 t.Errorf("iteration %d, process A got:\n%s\nwant:\n%s\n", i, got, want)
837 }
838 if got, want := <-bres, fmt.Sprintf("fd3: listener %s\n", lb.Addr()); got != want {
839 t.Errorf("iteration %d, process B got:\n%s\nwant:\n%s\n", i, got, want)
840 }
841 la.Close()
842 lb.Close()
843 for _, f := range ca.ExtraFiles {
844 f.Close()
845 }
846 for _, f := range cb.ExtraFiles {
847 f.Close()
848 }
849 }
850 }
851
852 type delayedInfiniteReader struct{}
853
854 func (delayedInfiniteReader) Read(b []byte) (int, error) {
855 time.Sleep(100 * time.Millisecond)
856 for i := range b {
857 b[i] = 'x'
858 }
859 return len(b), nil
860 }
861
862
863 func TestIgnorePipeErrorOnSuccess(t *testing.T) {
864 t.Parallel()
865
866 testWith := func(r io.Reader) func(*testing.T) {
867 return func(t *testing.T) {
868 t.Parallel()
869
870 cmd := helperCommand(t, "echo", "foo")
871 var out strings.Builder
872 cmd.Stdin = r
873 cmd.Stdout = &out
874 if err := cmd.Run(); err != nil {
875 t.Fatal(err)
876 }
877 if got, want := out.String(), "foo\n"; got != want {
878 t.Errorf("output = %q; want %q", got, want)
879 }
880 }
881 }
882 t.Run("10MB", testWith(strings.NewReader(strings.Repeat("x", 10<<20))))
883 t.Run("Infinite", testWith(delayedInfiniteReader{}))
884 }
885
886 type badWriter struct{}
887
888 func (w *badWriter) Write(data []byte) (int, error) {
889 return 0, io.ErrUnexpectedEOF
890 }
891
892 func TestClosePipeOnCopyError(t *testing.T) {
893 t.Parallel()
894
895 cmd := helperCommand(t, "yes")
896 cmd.Stdout = new(badWriter)
897 err := cmd.Run()
898 if err == nil {
899 t.Errorf("yes unexpectedly completed successfully")
900 }
901 }
902
903 func TestOutputStderrCapture(t *testing.T) {
904 t.Parallel()
905
906 cmd := helperCommand(t, "stderrfail")
907 _, err := cmd.Output()
908 ee, ok := err.(*exec.ExitError)
909 if !ok {
910 t.Fatalf("Output error type = %T; want ExitError", err)
911 }
912 got := string(ee.Stderr)
913 want := "some stderr text\n"
914 if got != want {
915 t.Errorf("ExitError.Stderr = %q; want %q", got, want)
916 }
917 }
918
919 func TestContext(t *testing.T) {
920 t.Parallel()
921
922 ctx, cancel := context.WithCancel(context.Background())
923 c := helperCommandContext(t, ctx, "pipetest")
924 stdin, err := c.StdinPipe()
925 if err != nil {
926 t.Fatal(err)
927 }
928 stdout, err := c.StdoutPipe()
929 if err != nil {
930 t.Fatal(err)
931 }
932 if err := c.Start(); err != nil {
933 t.Fatal(err)
934 }
935
936 if _, err := stdin.Write([]byte("O:hi\n")); err != nil {
937 t.Fatal(err)
938 }
939 buf := make([]byte, 5)
940 n, err := io.ReadFull(stdout, buf)
941 if n != len(buf) || err != nil || string(buf) != "O:hi\n" {
942 t.Fatalf("ReadFull = %d, %v, %q", n, err, buf[:n])
943 }
944 go cancel()
945
946 if err := c.Wait(); err == nil {
947 t.Fatal("expected Wait failure")
948 }
949 }
950
951 func TestContextCancel(t *testing.T) {
952 if runtime.GOOS == "netbsd" && runtime.GOARCH == "arm64" {
953 maySkipHelperCommand("cat")
954 testenv.SkipFlaky(t, 42061)
955 }
956
957
958
959 t.Parallel()
960
961 ctx, cancel := context.WithCancel(context.Background())
962 defer cancel()
963 c := helperCommandContext(t, ctx, "cat")
964
965 stdin, err := c.StdinPipe()
966 if err != nil {
967 t.Fatal(err)
968 }
969 defer stdin.Close()
970
971 if err := c.Start(); err != nil {
972 t.Fatal(err)
973 }
974
975
976 if _, err := io.WriteString(stdin, "echo"); err != nil {
977 t.Fatal(err)
978 }
979
980 cancel()
981
982
983
984 start := time.Now()
985 delay := 1 * time.Millisecond
986 for {
987 if _, err := io.WriteString(stdin, "echo"); err != nil {
988 break
989 }
990
991 if time.Since(start) > time.Minute {
992
993
994 debug.SetTraceback("system")
995 panic("canceling context did not stop program")
996 }
997
998
999
1000 delay *= 2
1001 if delay > 1*time.Second {
1002 delay = 1 * time.Second
1003 }
1004 time.Sleep(delay)
1005 }
1006
1007 if err := c.Wait(); err == nil {
1008 t.Error("program unexpectedly exited successfully")
1009 } else {
1010 t.Logf("exit status: %v", err)
1011 }
1012 }
1013
1014
1015 func TestDedupEnvEcho(t *testing.T) {
1016 t.Parallel()
1017
1018 cmd := helperCommand(t, "echoenv", "FOO")
1019 cmd.Env = append(cmd.Environ(), "FOO=bad", "FOO=good")
1020 out, err := cmd.CombinedOutput()
1021 if err != nil {
1022 t.Fatal(err)
1023 }
1024 if got, want := strings.TrimSpace(string(out)), "good"; got != want {
1025 t.Errorf("output = %q; want %q", got, want)
1026 }
1027 }
1028
1029 func TestEnvNULCharacter(t *testing.T) {
1030 if runtime.GOOS == "plan9" {
1031 t.Skip("plan9 explicitly allows NUL in the environment")
1032 }
1033 cmd := helperCommand(t, "echoenv", "FOO", "BAR")
1034 cmd.Env = append(cmd.Environ(), "FOO=foo\x00BAR=bar")
1035 out, err := cmd.CombinedOutput()
1036 if err == nil {
1037 t.Errorf("output = %q; want error", string(out))
1038 }
1039 }
1040
1041 func TestString(t *testing.T) {
1042 t.Parallel()
1043
1044 echoPath, err := exec.LookPath("echo")
1045 if err != nil {
1046 t.Skip(err)
1047 }
1048 tests := [...]struct {
1049 path string
1050 args []string
1051 want string
1052 }{
1053 {"echo", nil, echoPath},
1054 {"echo", []string{"a"}, echoPath + " a"},
1055 {"echo", []string{"a", "b"}, echoPath + " a b"},
1056 }
1057 for _, test := range tests {
1058 cmd := exec.Command(test.path, test.args...)
1059 if got := cmd.String(); got != test.want {
1060 t.Errorf("String(%q, %q) = %q, want %q", test.path, test.args, got, test.want)
1061 }
1062 }
1063 }
1064
1065 func TestStringPathNotResolved(t *testing.T) {
1066 t.Parallel()
1067
1068 _, err := exec.LookPath("makemeasandwich")
1069 if err == nil {
1070 t.Skip("wow, thanks")
1071 }
1072
1073 cmd := exec.Command("makemeasandwich", "-lettuce")
1074 want := "makemeasandwich -lettuce"
1075 if got := cmd.String(); got != want {
1076 t.Errorf("String(%q, %q) = %q, want %q", "makemeasandwich", "-lettuce", got, want)
1077 }
1078 }
1079
1080 func TestNoPath(t *testing.T) {
1081 err := new(exec.Cmd).Start()
1082 want := "exec: no command"
1083 if err == nil || err.Error() != want {
1084 t.Errorf("new(Cmd).Start() = %v, want %q", err, want)
1085 }
1086 }
1087
1088
1089
1090
1091 func TestDoubleStartLeavesPipesOpen(t *testing.T) {
1092 t.Parallel()
1093
1094 cmd := helperCommand(t, "pipetest")
1095 in, err := cmd.StdinPipe()
1096 if err != nil {
1097 t.Fatal(err)
1098 }
1099 out, err := cmd.StdoutPipe()
1100 if err != nil {
1101 t.Fatal(err)
1102 }
1103
1104 if err := cmd.Start(); err != nil {
1105 t.Fatal(err)
1106 }
1107 t.Cleanup(func() {
1108 if err := cmd.Wait(); err != nil {
1109 t.Error(err)
1110 }
1111 })
1112
1113 if err := cmd.Start(); err == nil || !strings.HasSuffix(err.Error(), "already started") {
1114 t.Fatalf("second call to Start returned a nil; want an 'already started' error")
1115 }
1116
1117 outc := make(chan []byte, 1)
1118 go func() {
1119 b, err := io.ReadAll(out)
1120 if err != nil {
1121 t.Error(err)
1122 }
1123 outc <- b
1124 }()
1125
1126 const msg = "O:Hello, pipe!\n"
1127
1128 _, err = io.WriteString(in, msg)
1129 if err != nil {
1130 t.Fatal(err)
1131 }
1132 in.Close()
1133
1134 b := <-outc
1135 if !bytes.Equal(b, []byte(msg)) {
1136 t.Fatalf("read %q from stdout pipe; want %q", b, msg)
1137 }
1138 }
1139
1140 func cmdHang(args ...string) {
1141 sleep, err := time.ParseDuration(args[0])
1142 if err != nil {
1143 panic(err)
1144 }
1145
1146 fs := flag.NewFlagSet("hang", flag.ExitOnError)
1147 exitOnInterrupt := fs.Bool("interrupt", false, "if true, commands should exit 0 on os.Interrupt")
1148 subsleep := fs.Duration("subsleep", 0, "amount of time for the 'hang' helper to leave an orphaned subprocess sleeping with stderr open")
1149 probe := fs.Duration("probe", 0, "if nonzero, the 'hang' helper should write to stderr at this interval, and exit nonzero if a write fails")
1150 read := fs.Bool("read", false, "if true, the 'hang' helper should read stdin to completion before sleeping")
1151 fs.Parse(args[1:])
1152
1153 pid := os.Getpid()
1154
1155 if *subsleep != 0 {
1156 cmd := exec.Command(testenv.Executable(nil), "hang", subsleep.String(), "-read=true", "-probe="+probe.String())
1157 cmd.Stdin = os.Stdin
1158 cmd.Stderr = os.Stderr
1159 out, err := cmd.StdoutPipe()
1160 if err != nil {
1161 fmt.Fprintln(os.Stderr, err)
1162 os.Exit(1)
1163 }
1164 cmd.Start()
1165
1166 buf := new(strings.Builder)
1167 if _, err := io.Copy(buf, out); err != nil {
1168 fmt.Fprintln(os.Stderr, err)
1169 cmd.Process.Kill()
1170 cmd.Wait()
1171 os.Exit(1)
1172 }
1173 fmt.Fprintf(os.Stderr, "%d: started %d: %v\n", pid, cmd.Process.Pid, cmd)
1174 go cmd.Wait()
1175 }
1176
1177 if *exitOnInterrupt {
1178 c := make(chan os.Signal, 1)
1179 signal.Notify(c, os.Interrupt)
1180 go func() {
1181 sig := <-c
1182 fmt.Fprintf(os.Stderr, "%d: received %v\n", pid, sig)
1183 os.Exit(0)
1184 }()
1185 } else {
1186 signal.Ignore(os.Interrupt)
1187 }
1188
1189
1190 os.Stdout.Close()
1191
1192 if *read {
1193 if pipeSignal != nil {
1194 signal.Ignore(pipeSignal)
1195 }
1196 r := bufio.NewReader(os.Stdin)
1197 for {
1198 line, err := r.ReadBytes('\n')
1199 if len(line) > 0 {
1200
1201 fmt.Fprintf(os.Stderr, "%d: read %s", pid, line)
1202 }
1203 if err != nil {
1204 fmt.Fprintf(os.Stderr, "%d: finished read: %v", pid, err)
1205 break
1206 }
1207 }
1208 }
1209
1210 if *probe != 0 {
1211 ticker := time.NewTicker(*probe)
1212 go func() {
1213 for range ticker.C {
1214 if _, err := fmt.Fprintf(os.Stderr, "%d: ok\n", pid); err != nil {
1215 os.Exit(1)
1216 }
1217 }
1218 }()
1219 }
1220
1221 if sleep != 0 {
1222 time.Sleep(sleep)
1223 fmt.Fprintf(os.Stderr, "%d: slept %v\n", pid, sleep)
1224 }
1225 }
1226
1227
1228
1229 type tickReader struct {
1230 interval time.Duration
1231 lastTick time.Time
1232 s string
1233 }
1234
1235 func newTickReader(interval time.Duration) *tickReader {
1236 return &tickReader{interval: interval}
1237 }
1238
1239 func (r *tickReader) Read(p []byte) (n int, err error) {
1240 if len(r.s) == 0 {
1241 if d := r.interval - time.Since(r.lastTick); d > 0 {
1242 time.Sleep(d)
1243 }
1244 r.lastTick = time.Now()
1245 r.s = r.lastTick.Format(time.RFC3339Nano + "\n")
1246 }
1247
1248 n = copy(p, r.s)
1249 r.s = r.s[n:]
1250 return n, nil
1251 }
1252
1253 func startHang(t *testing.T, ctx context.Context, hangTime time.Duration, interrupt os.Signal, waitDelay time.Duration, flags ...string) *exec.Cmd {
1254 t.Helper()
1255
1256 args := append([]string{hangTime.String()}, flags...)
1257 cmd := helperCommandContext(t, ctx, "hang", args...)
1258 cmd.Stdin = newTickReader(1 * time.Millisecond)
1259 cmd.Stderr = new(strings.Builder)
1260 if interrupt == nil {
1261 cmd.Cancel = nil
1262 } else {
1263 cmd.Cancel = func() error {
1264 return cmd.Process.Signal(interrupt)
1265 }
1266 }
1267 cmd.WaitDelay = waitDelay
1268 out, err := cmd.StdoutPipe()
1269 if err != nil {
1270 t.Fatal(err)
1271 }
1272
1273 t.Log(cmd)
1274 if err := cmd.Start(); err != nil {
1275 t.Fatal(err)
1276 }
1277
1278
1279 buf := new(strings.Builder)
1280 if _, err := io.Copy(buf, out); err != nil {
1281 t.Error(err)
1282 cmd.Process.Kill()
1283 cmd.Wait()
1284 t.FailNow()
1285 }
1286 if buf.Len() > 0 {
1287 t.Logf("stdout %v:\n%s", cmd.Args, buf)
1288 }
1289
1290 return cmd
1291 }
1292
1293 func TestWaitInterrupt(t *testing.T) {
1294 t.Parallel()
1295
1296
1297
1298
1299 const tooLong = 10 * time.Minute
1300
1301
1302
1303 t.Run("Wait", func(t *testing.T) {
1304 t.Parallel()
1305 cmd := startHang(t, context.Background(), 1*time.Millisecond, os.Kill, 0)
1306 err := cmd.Wait()
1307 t.Logf("stderr:\n%s", cmd.Stderr)
1308 t.Logf("[%d] %v", cmd.Process.Pid, err)
1309
1310 if err != nil {
1311 t.Errorf("Wait: %v; want <nil>", err)
1312 }
1313 if ps := cmd.ProcessState; !ps.Exited() {
1314 t.Errorf("cmd did not exit: %v", ps)
1315 } else if code := ps.ExitCode(); code != 0 {
1316 t.Errorf("cmd.ProcessState.ExitCode() = %v; want 0", code)
1317 }
1318 })
1319
1320
1321
1322 t.Run("WaitDelay", func(t *testing.T) {
1323 if runtime.GOOS == "windows" {
1324 t.Skipf("skipping: os.Interrupt is not implemented on Windows")
1325 }
1326 t.Parallel()
1327
1328 ctx, cancel := context.WithCancel(context.Background())
1329 cmd := startHang(t, ctx, tooLong, nil, tooLong, "-interrupt=true")
1330 cancel()
1331
1332 time.Sleep(1 * time.Millisecond)
1333
1334
1335
1336 if err := cmd.Process.Signal(os.Interrupt); err != nil {
1337 t.Error(err)
1338 }
1339
1340 err := cmd.Wait()
1341 t.Logf("stderr:\n%s", cmd.Stderr)
1342 t.Logf("[%d] %v", cmd.Process.Pid, err)
1343
1344
1345
1346
1347
1348
1349 if err != nil {
1350 t.Errorf("Wait: %v; want nil", err)
1351 }
1352 if ps := cmd.ProcessState; !ps.Exited() {
1353 t.Errorf("cmd did not exit: %v", ps)
1354 } else if code := ps.ExitCode(); code != 0 {
1355 t.Errorf("cmd.ProcessState.ExitCode() = %v; want 0", code)
1356 }
1357 })
1358
1359
1360
1361
1362
1363 t.Run("SIGKILL-hang", func(t *testing.T) {
1364 t.Parallel()
1365
1366 ctx, cancel := context.WithCancel(context.Background())
1367 cmd := startHang(t, ctx, tooLong, os.Kill, 10*time.Millisecond, "-subsleep=10m", "-probe=1ms")
1368 cancel()
1369 err := cmd.Wait()
1370 t.Logf("stderr:\n%s", cmd.Stderr)
1371 t.Logf("[%d] %v", cmd.Process.Pid, err)
1372
1373
1374
1375
1376
1377
1378 if ee := new(*exec.ExitError); !errors.As(err, ee) {
1379 t.Errorf("Wait error = %v; want %T", err, *ee)
1380 }
1381 })
1382
1383
1384
1385
1386
1387
1388 t.Run("Exit-hang", func(t *testing.T) {
1389 t.Parallel()
1390
1391 cmd := startHang(t, context.Background(), 1*time.Millisecond, nil, 10*time.Millisecond, "-subsleep=10m", "-probe=1ms")
1392 err := cmd.Wait()
1393 t.Logf("stderr:\n%s", cmd.Stderr)
1394 t.Logf("[%d] %v", cmd.Process.Pid, err)
1395
1396
1397
1398
1399
1400 if !errors.Is(err, exec.ErrWaitDelay) {
1401 t.Errorf("Wait error = %v; want %T", err, exec.ErrWaitDelay)
1402 }
1403 })
1404
1405
1406
1407
1408 t.Run("SIGINT-ignored", func(t *testing.T) {
1409 if runtime.GOOS == "windows" {
1410 t.Skipf("skipping: os.Interrupt is not implemented on Windows")
1411 }
1412 t.Parallel()
1413
1414 ctx, cancel := context.WithCancel(context.Background())
1415 cmd := startHang(t, ctx, tooLong, os.Interrupt, 10*time.Millisecond, "-interrupt=false")
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 if ee := new(*exec.ExitError); !errors.As(err, ee) {
1424 t.Errorf("Wait error = %v; want %T", err, *ee)
1425 }
1426 })
1427
1428
1429
1430
1431
1432 t.Run("SIGINT-handled", func(t *testing.T) {
1433 if runtime.GOOS == "windows" {
1434 t.Skipf("skipping: os.Interrupt is not implemented on Windows")
1435 }
1436 t.Parallel()
1437
1438 ctx, cancel := context.WithCancel(context.Background())
1439 cmd := startHang(t, ctx, tooLong, os.Interrupt, 0, "-interrupt=true")
1440 cancel()
1441 err := cmd.Wait()
1442 t.Logf("stderr:\n%s", cmd.Stderr)
1443 t.Logf("[%d] %v", cmd.Process.Pid, err)
1444
1445 if !errors.Is(err, ctx.Err()) {
1446 t.Errorf("Wait error = %v; want %v", err, ctx.Err())
1447 }
1448 if ps := cmd.ProcessState; !ps.Exited() {
1449 t.Errorf("cmd did not exit: %v", ps)
1450 } else if code := ps.ExitCode(); code != 0 {
1451 t.Errorf("cmd.ProcessState.ExitCode() = %v; want 0", code)
1452 }
1453 })
1454
1455
1456
1457
1458 t.Run("SIGQUIT", func(t *testing.T) {
1459 if quitSignal == nil {
1460 t.Skipf("skipping: SIGQUIT is not supported on %v", runtime.GOOS)
1461 }
1462 t.Parallel()
1463
1464 ctx, cancel := context.WithCancel(context.Background())
1465 cmd := startHang(t, ctx, tooLong, quitSignal, 0)
1466 cancel()
1467 err := cmd.Wait()
1468 t.Logf("stderr:\n%s", cmd.Stderr)
1469 t.Logf("[%d] %v", cmd.Process.Pid, err)
1470
1471 if ee := new(*exec.ExitError); !errors.As(err, ee) {
1472 t.Errorf("Wait error = %v; want %v", err, ctx.Err())
1473 }
1474
1475 if ps := cmd.ProcessState; !ps.Exited() {
1476 t.Errorf("cmd did not exit: %v", ps)
1477 } else if code := ps.ExitCode(); code != 2 {
1478
1479 t.Errorf("cmd.ProcessState.ExitCode() = %v; want 2", code)
1480 }
1481
1482 if !strings.Contains(fmt.Sprint(cmd.Stderr), "\n\ngoroutine ") {
1483 t.Errorf("cmd.Stderr does not contain a goroutine dump")
1484 }
1485 })
1486 }
1487
1488 func TestCancelErrors(t *testing.T) {
1489 t.Parallel()
1490
1491
1492
1493 t.Run("success after error", func(t *testing.T) {
1494 t.Parallel()
1495
1496 ctx, cancel := context.WithCancel(context.Background())
1497 defer cancel()
1498
1499 cmd := helperCommandContext(t, ctx, "pipetest")
1500 stdin, err := cmd.StdinPipe()
1501 if err != nil {
1502 t.Fatal(err)
1503 }
1504
1505 errArbitrary := errors.New("arbitrary error")
1506 cmd.Cancel = func() error {
1507 stdin.Close()
1508 t.Logf("Cancel returning %v", errArbitrary)
1509 return errArbitrary
1510 }
1511 if err := cmd.Start(); err != nil {
1512 t.Fatal(err)
1513 }
1514 cancel()
1515
1516 err = cmd.Wait()
1517 t.Logf("[%d] %v", cmd.Process.Pid, err)
1518 if !errors.Is(err, errArbitrary) || err == errArbitrary {
1519 t.Errorf("Wait error = %v; want an error wrapping %v", err, errArbitrary)
1520 }
1521 })
1522
1523
1524
1525
1526
1527 t.Run("success after ErrProcessDone", func(t *testing.T) {
1528 t.Parallel()
1529
1530 ctx, cancel := context.WithCancel(context.Background())
1531 defer cancel()
1532
1533 cmd := helperCommandContext(t, ctx, "pipetest")
1534 stdin, err := cmd.StdinPipe()
1535 if err != nil {
1536 t.Fatal(err)
1537 }
1538
1539 stdout, err := cmd.StdoutPipe()
1540 if err != nil {
1541 t.Fatal(err)
1542 }
1543
1544
1545
1546
1547 interruptCalled := make(chan struct{})
1548 done := make(chan struct{})
1549 cmd.Cancel = func() error {
1550 close(interruptCalled)
1551 <-done
1552 t.Logf("Cancel returning an error wrapping ErrProcessDone")
1553 return fmt.Errorf("%w: stdout closed", os.ErrProcessDone)
1554 }
1555
1556 if err := cmd.Start(); err != nil {
1557 t.Fatal(err)
1558 }
1559
1560 cancel()
1561 <-interruptCalled
1562 stdin.Close()
1563 io.Copy(io.Discard, stdout)
1564 close(done)
1565
1566 err = cmd.Wait()
1567 t.Logf("[%d] %v", cmd.Process.Pid, err)
1568 if err != nil {
1569 t.Errorf("Wait error = %v; want nil", err)
1570 }
1571 })
1572
1573
1574
1575
1576 t.Run("killed after error", func(t *testing.T) {
1577 t.Parallel()
1578
1579 ctx, cancel := context.WithCancel(context.Background())
1580 defer cancel()
1581
1582 cmd := helperCommandContext(t, ctx, "pipetest")
1583 stdin, err := cmd.StdinPipe()
1584 if err != nil {
1585 t.Fatal(err)
1586 }
1587 defer stdin.Close()
1588
1589 errArbitrary := errors.New("arbitrary error")
1590 var interruptCalled atomic.Bool
1591 cmd.Cancel = func() error {
1592 t.Logf("Cancel called")
1593 interruptCalled.Store(true)
1594 return errArbitrary
1595 }
1596 cmd.WaitDelay = 1 * time.Millisecond
1597 if err := cmd.Start(); err != nil {
1598 t.Fatal(err)
1599 }
1600 cancel()
1601
1602 err = cmd.Wait()
1603 t.Logf("[%d] %v", cmd.Process.Pid, err)
1604
1605
1606
1607 if !interruptCalled.Load() {
1608 t.Errorf("Cancel was not called when the context was canceled")
1609 }
1610
1611
1612
1613
1614 if _, ok := err.(*exec.ExitError); !ok {
1615 t.Errorf("Wait error = %v; want *exec.ExitError", err)
1616 }
1617 })
1618
1619
1620
1621
1622 t.Run("killed after spurious ErrProcessDone", func(t *testing.T) {
1623 t.Parallel()
1624
1625 ctx, cancel := context.WithCancel(context.Background())
1626 defer cancel()
1627
1628 cmd := helperCommandContext(t, ctx, "pipetest")
1629 stdin, err := cmd.StdinPipe()
1630 if err != nil {
1631 t.Fatal(err)
1632 }
1633 defer stdin.Close()
1634
1635 var interruptCalled atomic.Bool
1636 cmd.Cancel = func() error {
1637 t.Logf("Cancel returning an error wrapping ErrProcessDone")
1638 interruptCalled.Store(true)
1639 return fmt.Errorf("%w: stdout closed", os.ErrProcessDone)
1640 }
1641 cmd.WaitDelay = 1 * time.Millisecond
1642 if err := cmd.Start(); err != nil {
1643 t.Fatal(err)
1644 }
1645 cancel()
1646
1647 err = cmd.Wait()
1648 t.Logf("[%d] %v", cmd.Process.Pid, err)
1649
1650
1651
1652 if !interruptCalled.Load() {
1653 t.Errorf("Cancel was not called when the context was canceled")
1654 }
1655
1656
1657
1658
1659 if ee, ok := err.(*exec.ExitError); !ok {
1660 t.Errorf("Wait error of type %T; want %T", err, ee)
1661 }
1662 })
1663
1664
1665
1666
1667 t.Run("nonzero exit after error", func(t *testing.T) {
1668 t.Parallel()
1669
1670 ctx, cancel := context.WithCancel(context.Background())
1671 defer cancel()
1672
1673 cmd := helperCommandContext(t, ctx, "stderrfail")
1674 stderr, err := cmd.StderrPipe()
1675 if err != nil {
1676 t.Fatal(err)
1677 }
1678
1679 errArbitrary := errors.New("arbitrary error")
1680 interrupted := make(chan struct{})
1681 cmd.Cancel = func() error {
1682 close(interrupted)
1683 return errArbitrary
1684 }
1685 if err := cmd.Start(); err != nil {
1686 t.Fatal(err)
1687 }
1688 cancel()
1689 <-interrupted
1690 io.Copy(io.Discard, stderr)
1691
1692 err = cmd.Wait()
1693 t.Logf("[%d] %v", cmd.Process.Pid, err)
1694
1695 if ee, ok := err.(*exec.ExitError); !ok || ee.ProcessState.ExitCode() != 1 {
1696 t.Errorf("Wait error = %v; want exit status 1", err)
1697 }
1698 })
1699 }
1700
1701
1702
1703
1704
1705 func TestConcurrentExec(t *testing.T) {
1706 ctx, cancel := context.WithCancel(context.Background())
1707
1708
1709
1710
1711
1712
1713
1714
1715 var (
1716 nHangs = runtime.GOMAXPROCS(0)
1717 nExits = runtime.GOMAXPROCS(0)
1718 hangs, exits sync.WaitGroup
1719 )
1720 hangs.Add(nHangs)
1721 exits.Add(nExits)
1722
1723
1724
1725
1726
1727 var ready sync.WaitGroup
1728 ready.Add(nHangs + nExits)
1729
1730 for i := 0; i < nHangs; i++ {
1731 go func() {
1732 defer hangs.Done()
1733
1734 cmd := helperCommandContext(t, ctx, "pipetest")
1735 stdin, err := cmd.StdinPipe()
1736 if err != nil {
1737 ready.Done()
1738 t.Error(err)
1739 return
1740 }
1741 cmd.Cancel = stdin.Close
1742 ready.Done()
1743
1744 ready.Wait()
1745 if err := cmd.Start(); err != nil {
1746 if !errors.Is(err, context.Canceled) {
1747 t.Error(err)
1748 }
1749 return
1750 }
1751
1752 cmd.Wait()
1753 }()
1754 }
1755
1756 for i := 0; i < nExits; i++ {
1757 go func() {
1758 defer exits.Done()
1759
1760 cmd := helperCommandContext(t, ctx, "exit", "0")
1761 ready.Done()
1762
1763 ready.Wait()
1764 if err := cmd.Run(); err != nil {
1765 t.Error(err)
1766 }
1767 }()
1768 }
1769
1770 exits.Wait()
1771 cancel()
1772 hangs.Wait()
1773 }
1774
1775
1776
1777 func TestPathRace(t *testing.T) {
1778 cmd := helperCommand(t, "exit", "0")
1779
1780 done := make(chan struct{})
1781 go func() {
1782 out, err := cmd.CombinedOutput()
1783 t.Logf("%v: %v\n%s", cmd, err, out)
1784 close(done)
1785 }()
1786
1787 t.Logf("running in background: %v", cmd)
1788 <-done
1789 }
1790
1791 func TestAbsPathExec(t *testing.T) {
1792 testenv.MustHaveExec(t)
1793 testenv.MustHaveGoBuild(t)
1794
1795
1796
1797 exe := filepath.Join(testenv.GOROOT(t), "bin/gofmt")
1798 cmd := exec.Command(exe)
1799 if cmd.Path != exe {
1800 t.Errorf("exec.Command(%#q) set Path=%#q", exe, cmd.Path)
1801 }
1802 err := cmd.Run()
1803 if err != nil {
1804 t.Errorf("using exec.Command(%#q): %v", exe, err)
1805 }
1806
1807 cmd = &exec.Cmd{Path: exe}
1808 err = cmd.Run()
1809 if err != nil {
1810 t.Errorf("using exec.Cmd{Path: %#q}: %v", cmd.Path, err)
1811 }
1812
1813 cmd = &exec.Cmd{Path: "gofmt", Dir: "/"}
1814 err = cmd.Run()
1815 if err == nil {
1816 t.Errorf("using exec.Cmd{Path: %#q}: unexpected success", cmd.Path)
1817 }
1818
1819
1820
1821 t.Run("modified", func(t *testing.T) {
1822 if exec.Command(filepath.Join(testenv.GOROOT(t), "bin/go")).Run() == nil {
1823
1824
1825
1826 t.Fatal("test case needs updating to verify fix for go.dev/issue/68314")
1827 }
1828 exe1 := filepath.Join(testenv.GOROOT(t), "bin/go")
1829 exe2 := filepath.Join(testenv.GOROOT(t), "bin/gofmt")
1830 cmd := exec.Command(exe1)
1831 cmd.Path = exe2
1832 cmd.Args = []string{cmd.Path}
1833 err := cmd.Run()
1834 if err != nil {
1835 t.Error("ran wrong binary")
1836 }
1837 })
1838 }
1839
View as plain text