1 // Copyright 2009 The Go Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style
3 // license that can be found in the LICENSE file.
5 // Use an external test to avoid os/exec -> net/http -> crypto/x509 -> os/exec
6 // circular dependency on non-cgo darwin.
33 func helperCommandContext(t
*testing
.T
, ctx context
.Context
, s
...string) (cmd
*exec
.Cmd
) {
34 testenv
.MustHaveExec(t
)
36 cs
:= []string{"-test.run=TestHelperProcess", "--"}
39 cmd
= exec
.CommandContext(ctx
, os
.Args
[0], cs
...)
41 cmd
= exec
.Command(os
.Args
[0], cs
...)
43 cmd
.Env
= []string{"GO_WANT_HELPER_PROCESS=1"}
44 path
:= os
.Getenv("LD_LIBRARY_PATH")
46 cmd
.Env
= append(cmd
.Env
, "LD_LIBRARY_PATH="+path
)
51 func helperCommand(t
*testing
.T
, s
...string) *exec
.Cmd
{
52 return helperCommandContext(t
, nil, s
...)
55 func TestEcho(t
*testing
.T
) {
56 bs
, err
:= helperCommand(t
, "echo", "foo bar", "baz").Output()
58 t
.Errorf("echo: %v", err
)
60 if g
, e
:= string(bs
), "foo bar baz\n"; g
!= e
{
61 t
.Errorf("echo: want %q, got %q", e
, g
)
65 func TestCommandRelativeName(t
*testing
.T
) {
66 testenv
.MustHaveExec(t
)
68 // Run our own binary as a relative path
69 // (e.g. "_test/exec.test") our parent directory.
70 base
:= filepath
.Base(os
.Args
[0]) // "exec.test"
71 dir
:= filepath
.Dir(os
.Args
[0]) // "/tmp/go-buildNNNN/os/exec/_test"
73 t
.Skip("skipping; running test at root somehow")
75 parentDir
:= filepath
.Dir(dir
) // "/tmp/go-buildNNNN/os/exec"
76 dirBase
:= filepath
.Base(dir
) // "_test"
78 t
.Skipf("skipping; unexpected shallow dir of %q", dir
)
81 cmd
:= exec
.Command(filepath
.Join(dirBase
, base
), "-test.run=TestHelperProcess", "--", "echo", "foo")
83 cmd
.Env
= []string{"GO_WANT_HELPER_PROCESS=1"}
85 out
, err
:= cmd
.Output()
87 t
.Errorf("echo: %v", err
)
89 if g
, e
:= string(out
), "foo\n"; g
!= e
{
90 t
.Errorf("echo: want %q, got %q", e
, g
)
94 func TestCatStdin(t
*testing
.T
) {
95 // Cat, testing stdin and stdout.
96 input
:= "Input string\nLine 2"
97 p
:= helperCommand(t
, "cat")
98 p
.Stdin
= strings
.NewReader(input
)
101 t
.Errorf("cat: %v", err
)
105 t
.Errorf("cat: want %q, got %q", input
, s
)
109 func TestEchoFileRace(t
*testing
.T
) {
110 cmd
:= helperCommand(t
, "echo")
111 stdin
, err
:= cmd
.StdinPipe()
113 t
.Fatalf("StdinPipe: %v", err
)
115 if err
:= cmd
.Start(); err
!= nil {
116 t
.Fatalf("Start: %v", err
)
118 wrote
:= make(chan bool)
121 fmt
.Fprint(stdin
, "echo\n")
123 if err
:= cmd
.Wait(); err
!= nil {
124 t
.Fatalf("Wait: %v", err
)
129 func TestCatGoodAndBadFile(t
*testing
.T
) {
130 // Testing combined output and error values.
131 bs
, err
:= helperCommand(t
, "cat", "/bogus/file.foo", "exec_test.go").CombinedOutput()
132 if _
, ok
:= err
.(*exec
.ExitError
); !ok
{
133 t
.Errorf("expected *exec.ExitError from cat combined; got %T: %v", err
, err
)
136 sp
:= strings
.SplitN(s
, "\n", 2)
138 t
.Fatalf("expected two lines from cat; got %q", s
)
140 errLine
, body
:= sp
[0], sp
[1]
141 if !strings
.HasPrefix(errLine
, "Error: open /bogus/file.foo") {
142 t
.Errorf("expected stderr to complain about file; got %q", errLine
)
144 if !strings
.Contains(body
, "func TestHelperProcess(t *testing.T)") {
145 t
.Errorf("expected test code; got %q (len %d)", body
, len(body
))
149 func TestNoExistBinary(t
*testing
.T
) {
150 // Can't run a non-existent binary
151 err
:= exec
.Command("/no-exist-binary").Run()
153 t
.Error("expected error from /no-exist-binary")
157 func TestExitStatus(t
*testing
.T
) {
158 // Test that exit values are returned correctly
159 cmd
:= helperCommand(t
, "exit", "42")
161 want
:= "exit status 42"
162 switch runtime
.GOOS
{
164 want
= fmt
.Sprintf("exit status: '%s %d: 42'", filepath
.Base(cmd
.Path
), cmd
.ProcessState
.Pid())
166 if werr
, ok
:= err
.(*exec
.ExitError
); ok
{
167 if s
:= werr
.Error(); s
!= want
{
168 t
.Errorf("from exit 42 got exit %q, want %q", s
, want
)
171 t
.Fatalf("expected *exec.ExitError from exit 42; got %T: %v", err
, err
)
175 func TestPipes(t
*testing
.T
) {
176 check
:= func(what
string, err error
) {
178 t
.Fatalf("%s: %v", what
, err
)
181 // Cat, testing stdin and stdout.
182 c
:= helperCommand(t
, "pipetest")
183 stdin
, err
:= c
.StdinPipe()
184 check("StdinPipe", err
)
185 stdout
, err
:= c
.StdoutPipe()
186 check("StdoutPipe", err
)
187 stderr
, err
:= c
.StderrPipe()
188 check("StderrPipe", err
)
190 outbr
:= bufio
.NewReader(stdout
)
191 errbr
:= bufio
.NewReader(stderr
)
192 line
:= func(what
string, br
*bufio
.Reader
) string {
193 line
, _
, err
:= br
.ReadLine()
195 t
.Fatalf("%s: %v", what
, err
)
203 _
, err
= stdin
.Write([]byte("O:I am output\n"))
204 check("first stdin Write", err
)
205 if g
, e
:= line("first output line", outbr
), "O:I am output"; g
!= e
{
206 t
.Errorf("got %q, want %q", g
, e
)
209 _
, err
= stdin
.Write([]byte("E:I am error\n"))
210 check("second stdin Write", err
)
211 if g
, e
:= line("first error line", errbr
), "E:I am error"; g
!= e
{
212 t
.Errorf("got %q, want %q", g
, e
)
215 _
, err
= stdin
.Write([]byte("O:I am output2\n"))
216 check("third stdin Write 3", err
)
217 if g
, e
:= line("second output line", outbr
), "O:I am output2"; g
!= e
{
218 t
.Errorf("got %q, want %q", g
, e
)
226 const stdinCloseTestString
= "Some test string."
229 func TestStdinClose(t
*testing
.T
) {
230 check
:= func(what
string, err error
) {
232 t
.Fatalf("%s: %v", what
, err
)
235 cmd
:= helperCommand(t
, "stdinClose")
236 stdin
, err
:= cmd
.StdinPipe()
237 check("StdinPipe", err
)
238 // Check that we can access methods of the underlying os.File.`
239 if _
, ok
:= stdin
.(interface {
242 t
.Error("can't access methods of underlying *os.File")
244 check("Start", cmd
.Start())
246 _
, err
:= io
.Copy(stdin
, strings
.NewReader(stdinCloseTestString
))
248 // Before the fix, this next line would race with cmd.Wait.
249 check("Close", stdin
.Close())
251 check("Wait", cmd
.Wait())
255 // It used to be the case that TestStdinClose, above, would fail when
256 // run under the race detector. This test is a variant of TestStdinClose
257 // that also used to fail when run under the race detector.
258 // This test is run by cmd/dist under the race detector to verify that
259 // the race detector no longer reports any problems.
260 func TestStdinCloseRace(t
*testing
.T
) {
261 cmd
:= helperCommand(t
, "stdinClose")
262 stdin
, err
:= cmd
.StdinPipe()
264 t
.Fatalf("StdinPipe: %v", err
)
266 if err
:= cmd
.Start(); err
!= nil {
267 t
.Fatalf("Start: %v", err
)
270 // We don't check the error return of Kill. It is
271 // possible that the process has already exited, in
272 // which case Kill will return an error "process
273 // already finished". The purpose of this test is to
274 // see whether the race detector reports an error; it
275 // doesn't matter whether this Kill succeeds or not.
279 // Send the wrong string, so that the child fails even
280 // if the other goroutine doesn't manage to kill it first.
281 // This test is to check that the race detector does not
282 // falsely report an error, so it doesn't matter how the
283 // child process fails.
284 io
.Copy(stdin
, strings
.NewReader("unexpected string"))
285 if err
:= stdin
.Close(); err
!= nil {
286 t
.Errorf("stdin.Close: %v", err
)
289 if err
:= cmd
.Wait(); err
== nil {
290 t
.Fatalf("Wait: succeeded unexpectedly")
295 func TestPipeLookPathLeak(t
*testing
.T
) {
296 // If we are reading from /proc/self/fd we (should) get an exact result.
299 // Reading /proc/self/fd is more reliable than calling lsof, so try that
301 numOpenFDs
:= func() (int, []byte, error
) {
302 fds
, err
:= ioutil
.ReadDir("/proc/self/fd")
306 return len(fds
), nil, nil
308 want
, before
, err
:= numOpenFDs()
310 // We encountered a problem reading /proc/self/fd (we might be on
311 // a platform that doesn't have it). Fall back onto lsof.
312 t
.Logf("using lsof because: %v", err
)
313 numOpenFDs
= func() (int, []byte, error
) {
314 // Android's stock lsof does not obey the -p option,
315 // so extra filtering is needed.
316 // https://golang.org/issue/10206
317 if runtime
.GOOS
== "android" {
318 // numOpenFDsAndroid handles errors itself and
319 // might skip or fail the test.
320 n
, lsof
:= numOpenFDsAndroid(t
)
323 lsof
, err
:= exec
.Command("lsof", "-b", "-n", "-p", strconv
.Itoa(os
.Getpid())).Output()
324 return bytes
.Count(lsof
, []byte("\n")), lsof
, err
327 // lsof may see file descriptors associated with the fork itself,
328 // so we allow some extra margin if we have to use it.
329 // https://golang.org/issue/19243
332 // Retry reading the number of open file descriptors.
333 want
, before
, err
= numOpenFDs()
336 t
.Skipf("skipping test; error finding or running lsof")
340 for i
:= 0; i
< 6; i
++ {
341 cmd
:= exec
.Command("something-that-does-not-exist-binary")
345 if err
:= cmd
.Run(); err
== nil {
346 t
.Fatal("unexpected success")
349 got
, after
, err
:= numOpenFDs()
351 // numOpenFDs has already succeeded once, it should work here.
352 t
.Errorf("unexpected failure: %v", err
)
354 if got
-want
> tolerance
{
355 t
.Errorf("number of open file descriptors changed: got %v, want %v", got
, want
)
357 t
.Errorf("before:\n%v\n", before
)
360 t
.Errorf("after:\n%v\n", after
)
365 func numOpenFDsAndroid(t
*testing
.T
) (n
int, lsof
[]byte) {
366 raw
, err
:= exec
.Command("lsof").Output()
368 t
.Skip("skipping test; error finding or running lsof")
371 // First find the PID column index by parsing the first line, and
372 // select lines containing pid in the column.
373 pid
:= []byte(strconv
.Itoa(os
.Getpid()))
376 s
:= bufio
.NewScanner(bytes
.NewReader(raw
))
379 fields
:= bytes
.Fields(line
)
381 for i
, v
:= range fields
{
382 if bytes
.Equal(v
, []byte("PID")) {
387 lsof
= append(lsof
, line
...)
390 if bytes
.Equal(fields
[pidCol
], pid
) {
391 lsof
= append(lsof
, '\n')
392 lsof
= append(lsof
, line
...)
396 t
.Fatal("error processing lsof output: unexpected header format")
398 if err
:= s
.Err(); err
!= nil {
399 t
.Fatalf("error processing lsof output: %v", err
)
401 return bytes
.Count(lsof
, []byte("\n")), lsof
404 var testedAlreadyLeaked
= false
406 // basefds returns the number of expected file descriptors
407 // to be present in a process at start.
408 // stdin, stdout, stderr, epoll/kqueue
409 func basefds() uintptr {
410 return os
.Stderr
.Fd() + 1
413 func closeUnexpectedFds(t
*testing
.T
, m
string) {
414 for fd
:= basefds(); fd
<= 101; fd
++ {
415 if fd
== poll
.PollDescriptor() {
418 err
:= os
.NewFile(fd
, "").Close()
420 t
.Logf("%s: Something already leaked - closed fd %d", m
, fd
)
425 func TestExtraFilesFDShuffle(t
*testing
.T
) {
426 t
.Skip("flaky test; see https://golang.org/issue/5780")
427 switch runtime
.GOOS
{
429 // TODO(cnicolaou): https://golang.org/issue/2603
430 // leads to leaked file descriptors in this test when it's
431 // run from a builder.
432 closeUnexpectedFds(t
, "TestExtraFilesFDShuffle")
434 // https://golang.org/issue/3955
435 closeUnexpectedFds(t
, "TestExtraFilesFDShuffle")
437 t
.Skip("no operating system support; skipping")
440 // syscall.StartProcess maps all the FDs passed to it in
441 // ProcAttr.Files (the concatenation of stdin,stdout,stderr and
442 // ExtraFiles) into consecutive FDs in the child, that is:
443 // Files{11, 12, 6, 7, 9, 3} should result in the file
444 // represented by FD 11 in the parent being made available as 0
445 // in the child, 12 as 1, etc.
447 // We want to test that FDs in the child do not get overwritten
448 // by one another as this shuffle occurs. The original implementation
449 // was buggy in that in some data dependent cases it would overwrite
450 // stderr in the child with one of the ExtraFile members.
451 // Testing for this case is difficult because it relies on using
452 // the same FD values as that case. In particular, an FD of 3
453 // must be at an index of 4 or higher in ProcAttr.Files and
454 // the FD of the write end of the Stderr pipe (as obtained by
455 // StderrPipe()) must be the same as the size of ProcAttr.Files;
456 // therefore we test that the read end of this pipe (which is what
457 // is returned to the parent by StderrPipe() being one less than
458 // the size of ProcAttr.Files, i.e. 3+len(cmd.ExtraFiles).
460 // Moving this test case around within the overall tests may
461 // affect the FDs obtained and hence the checks to catch these cases.
463 c
:= helperCommand(t
, "extraFilesAndPipes", strconv
.Itoa(npipes
+1))
464 rd
, wr
, _
:= os
.Pipe()
467 t
.Errorf("bad test value for test pipe: fd %d", rd
.Fd())
469 stderr
, _
:= c
.StderrPipe()
470 wr
.WriteString("_LAST")
473 pipes
:= make([]struct {
476 data
:= []string{"a", "b"}
478 for i
:= 0; i
< npipes
; i
++ {
479 r
, w
, err
:= os
.Pipe()
481 t
.Fatalf("unexpected error creating pipe: %s", err
)
485 w
.WriteString(data
[i
])
486 c
.ExtraFiles
= append(c
.ExtraFiles
, pipes
[i
].r
)
492 // Put fd 3 at the end.
493 c
.ExtraFiles
= append(c
.ExtraFiles
, rd
)
495 stderrFd
:= int(stderr
.(*os
.File
).Fd())
496 if stderrFd
!= ((len(c
.ExtraFiles
) + 3) - 1) {
497 t
.Errorf("bad test value for stderr pipe")
500 expected
:= "child: " + strings
.Join(data
, "") + "_LAST"
504 t
.Fatalf("Run: %v", err
)
506 ch
:= make(chan string, 1)
507 go func(ch
chan string) {
508 buf
:= make([]byte, 512)
509 n
, err
:= stderr
.Read(buf
)
511 t
.Errorf("Read: %s", err
)
514 ch
<- string(buf
[:n
])
521 t
.Errorf("Read: '%s' not '%s'", m
, expected
)
523 case <-time
.After(5 * time
.Second
):
524 t
.Errorf("Read timedout")
529 func TestExtraFiles(t
*testing
.T
) {
530 testenv
.MustHaveExec(t
)
532 if runtime
.GOOS
== "windows" {
533 t
.Skipf("skipping test on %q", runtime
.GOOS
)
536 // Ensure that file descriptors have not already been leaked into
538 if !testedAlreadyLeaked
{
539 testedAlreadyLeaked
= true
540 closeUnexpectedFds(t
, "TestExtraFiles")
543 // Force network usage, to verify the epoll (or whatever) fd
544 // doesn't leak to the child,
545 ln
, err
:= net
.Listen("tcp", "127.0.0.1:0")
551 // Make sure duplicated fds don't leak to the child.
552 f
, err
:= ln
.(*net
.TCPListener
).File()
557 ln2
, err
:= net
.FileListener(f
)
563 // Force TLS root certs to be loaded (which might involve
564 // cgo), to make sure none of that potential C code leaks fds.
565 ts
:= httptest
.NewUnstartedServer(http
.HandlerFunc(func(w http
.ResponseWriter
, r
*http
.Request
) {}))
566 // quiet expected TLS handshake error "remote error: bad certificate"
567 ts
.Config
.ErrorLog
= log
.New(ioutil
.Discard
, "", 0)
570 _
, err
= http
.Get(ts
.URL
)
572 t
.Errorf("success trying to fetch %s; want an error", ts
.URL
)
575 tf
, err
:= ioutil
.TempFile("", "")
577 t
.Fatalf("TempFile: %v", err
)
579 defer os
.Remove(tf
.Name())
582 const text
= "Hello, fd 3!"
583 _
, err
= tf
.Write([]byte(text
))
585 t
.Fatalf("Write: %v", err
)
587 _
, err
= tf
.Seek(0, io
.SeekStart
)
589 t
.Fatalf("Seek: %v", err
)
592 c
:= helperCommand(t
, "read3")
593 var stdout
, stderr bytes
.Buffer
596 c
.ExtraFiles
= []*os
.File
{tf
}
599 t
.Fatalf("Run: %v; stdout %q, stderr %q", err
, stdout
.Bytes(), stderr
.Bytes())
601 if stdout
.String() != text
{
602 t
.Errorf("got stdout %q, stderr %q; want %q on stdout", stdout
.String(), stderr
.String(), text
)
606 func TestExtraFilesRace(t
*testing
.T
) {
607 if runtime
.GOOS
== "windows" {
608 t
.Skip("no operating system support; skipping")
610 listen
:= func() net
.Listener
{
611 ln
, err
:= net
.Listen("tcp", "127.0.0.1:0")
617 listenerFile
:= func(ln net
.Listener
) *os
.File
{
618 f
, err
:= ln
.(*net
.TCPListener
).File()
624 runCommand
:= func(c
*exec
.Cmd
, out
chan<- string) {
625 bout
, err
:= c
.CombinedOutput()
627 out
<- "ERROR:" + err
.Error()
633 for i
:= 0; i
< 10; i
++ {
635 ca
:= helperCommand(t
, "describefiles")
636 ca
.ExtraFiles
= []*os
.File
{listenerFile(la
)}
638 cb
:= helperCommand(t
, "describefiles")
639 cb
.ExtraFiles
= []*os
.File
{listenerFile(lb
)}
640 ares
:= make(chan string)
641 bres
:= make(chan string)
642 go runCommand(ca
, ares
)
643 go runCommand(cb
, bres
)
644 if got
, want
:= <-ares
, fmt
.Sprintf("fd3: listener %s\n", la
.Addr()); got
!= want
{
645 t
.Errorf("iteration %d, process A got:\n%s\nwant:\n%s\n", i
, got
, want
)
647 if got
, want
:= <-bres
, fmt
.Sprintf("fd3: listener %s\n", lb
.Addr()); got
!= want
{
648 t
.Errorf("iteration %d, process B got:\n%s\nwant:\n%s\n", i
, got
, want
)
652 for _
, f
:= range ca
.ExtraFiles
{
655 for _
, f
:= range cb
.ExtraFiles
{
662 // TestHelperProcess isn't a real test. It's used as a helper process
663 // for TestParameterRun.
664 func TestHelperProcess(*testing
.T
) {
665 if os
.Getenv("GO_WANT_HELPER_PROCESS") != "1" {
670 // Determine which command to use to display open files.
672 switch runtime
.GOOS
{
673 case "dragonfly", "freebsd", "netbsd", "openbsd":
688 fmt
.Fprintf(os
.Stderr
, "No command\n")
692 cmd
, args
:= args
[0], args
[1:]
695 iargs
:= []interface{}{}
696 for _
, s
:= range args
{
697 iargs
= append(iargs
, s
)
699 fmt
.Println(iargs
...)
701 for _
, s
:= range args
{
702 fmt
.Println(os
.Getenv(s
))
707 io
.Copy(os
.Stdout
, os
.Stdin
)
711 for _
, fn
:= range args
{
712 f
, err
:= os
.Open(fn
)
714 fmt
.Fprintf(os
.Stderr
, "Error: %v\n", err
)
718 io
.Copy(os
.Stdout
, f
)
723 bufr
:= bufio
.NewReader(os
.Stdin
)
725 line
, _
, err
:= bufr
.ReadLine()
728 } else if err
!= nil {
731 if bytes
.HasPrefix(line
, []byte("O:")) {
732 os
.Stdout
.Write(line
)
733 os
.Stdout
.Write([]byte{'\n'})
734 } else if bytes
.HasPrefix(line
, []byte("E:")) {
735 os
.Stderr
.Write(line
)
736 os
.Stderr
.Write([]byte{'\n'})
742 b
, err
:= ioutil
.ReadAll(os
.Stdin
)
744 fmt
.Fprintf(os
.Stderr
, "Error: %v\n", err
)
747 if s
:= string(b
); s
!= stdinCloseTestString
{
748 fmt
.Fprintf(os
.Stderr
, "Error: Read %q, want %q", s
, stdinCloseTestString
)
752 case "read3": // read fd 3
753 fd3
:= os
.NewFile(3, "fd3")
754 bs
, err
:= ioutil
.ReadAll(fd3
)
756 fmt
.Printf("ReadAll from fd 3: %v", err
)
759 switch runtime
.GOOS
{
761 // TODO(jsing): Determine why DragonFly is leaking
762 // file descriptors...
764 // TODO(bradfitz): broken? Sometimes.
765 // https://golang.org/issue/2603
766 // Skip this additional part of the test for now.
768 // TODO(jsing): This currently fails on NetBSD due to
769 // the cloned file descriptors that result from opening
771 // https://golang.org/issue/3955
773 // TODO(aram): This fails on Solaris because libc opens
774 // its own files, as it sees fit. Darwin does the same,
775 // see: https://golang.org/issue/2603
777 // Now verify that there are no other open fds.
779 for wantfd
:= basefds() + 1; wantfd
<= 100; wantfd
++ {
780 if wantfd
== poll
.PollDescriptor() {
783 f
, err
:= os
.Open(os
.Args
[0])
785 fmt
.Printf("error opening file with expected fd %d: %v", wantfd
, err
)
788 if got
:= f
.Fd(); got
!= wantfd
{
789 fmt
.Printf("leaked parent file. fd = %d; want %d\n", got
, wantfd
)
791 switch runtime
.GOOS
{
793 args
= []string{fmt
.Sprintf("/proc/%d/fd", os
.Getpid())}
795 args
= []string{"-p", fmt
.Sprint(os
.Getpid())}
797 out
, _
:= exec
.Command(ofcmd
, args
...).CombinedOutput()
798 fmt
.Print(string(out
))
801 files
= append(files
, f
)
803 for _
, f
:= range files
{
807 // Referring to fd3 here ensures that it is not
808 // garbage collected, and therefore closed, while
809 // executing the wantfd loop above. It doesn't matter
810 // what we do with fd3 as long as we refer to it;
811 // closing it is the easy choice.
815 n
, _
:= strconv
.Atoi(args
[0])
817 case "describefiles":
818 f
:= os
.NewFile(3, fmt
.Sprintf("fd3"))
819 ln
, err
:= net
.FileListener(f
)
821 fmt
.Printf("fd3: listener %s\n", ln
.Addr())
825 case "extraFilesAndPipes":
826 n
, _
:= strconv
.Atoi(args
[0])
827 pipes
:= make([]*os
.File
, n
)
828 for i
:= 0; i
< n
; i
++ {
829 pipes
[i
] = os
.NewFile(uintptr(3+i
), strconv
.Itoa(i
))
832 for i
, r
:= range pipes
{
833 ch
:= make(chan string, 1)
834 go func(c
chan string) {
835 buf
:= make([]byte, 10)
836 n
, err
:= r
.Read(buf
)
838 fmt
.Fprintf(os
.Stderr
, "Child: read error: %v on pipe %d\n", err
, i
)
846 response
= response
+ m
847 case <-time
.After(5 * time
.Second
):
848 fmt
.Fprintf(os
.Stderr
, "Child: Timeout reading from pipe: %d\n", i
)
852 fmt
.Fprintf(os
.Stderr
, "child: %s", response
)
855 cmd
:= exec
.Command(args
[1])
857 output
, err
:= cmd
.CombinedOutput()
859 fmt
.Fprintf(os
.Stderr
, "Child: %s %s", err
, string(output
))
862 fmt
.Printf("%s", string(output
))
865 p
, err
:= exec
.LookPath(args
[0])
867 fmt
.Fprintf(os
.Stderr
, "LookPath failed: %v\n", err
)
873 fmt
.Fprintf(os
.Stderr
, "some stderr text\n")
876 time
.Sleep(3 * time
.Second
)
879 fmt
.Fprintf(os
.Stderr
, "Unknown command %q\n", cmd
)
884 type delayedInfiniteReader
struct{}
886 func (delayedInfiniteReader
) Read(b
[]byte) (int, error
) {
887 time
.Sleep(100 * time
.Millisecond
)
894 // Issue 9173: ignore stdin pipe writes if the program completes successfully.
895 func TestIgnorePipeErrorOnSuccess(t
*testing
.T
) {
896 testenv
.MustHaveExec(t
)
898 // We really only care about testing this on Unixy and Windowsy things.
899 if runtime
.GOOS
== "plan9" {
900 t
.Skipf("skipping test on %q", runtime
.GOOS
)
903 testWith
:= func(r io
.Reader
) func(*testing
.T
) {
904 return func(t
*testing
.T
) {
905 cmd
:= helperCommand(t
, "echo", "foo")
909 if err
:= cmd
.Run(); err
!= nil {
912 if got
, want
:= out
.String(), "foo\n"; got
!= want
{
913 t
.Errorf("output = %q; want %q", got
, want
)
917 t
.Run("10MB", testWith(strings
.NewReader(strings
.Repeat("x", 10<<20))))
918 t
.Run("Infinite", testWith(delayedInfiniteReader
{}))
921 type badWriter
struct{}
923 func (w
*badWriter
) Write(data
[]byte) (int, error
) {
924 return 0, io
.ErrUnexpectedEOF
927 func TestClosePipeOnCopyError(t
*testing
.T
) {
928 testenv
.MustHaveExec(t
)
930 if runtime
.GOOS
== "windows" || runtime
.GOOS
== "plan9" {
931 t
.Skipf("skipping test on %s - no yes command", runtime
.GOOS
)
933 cmd
:= exec
.Command("yes")
934 cmd
.Stdout
= new(badWriter
)
935 c
:= make(chan int, 1)
939 t
.Errorf("yes completed successfully")
946 case <-time
.After(5 * time
.Second
):
947 t
.Fatalf("yes got stuck writing to bad writer")
951 func TestOutputStderrCapture(t
*testing
.T
) {
952 testenv
.MustHaveExec(t
)
954 cmd
:= helperCommand(t
, "stderrfail")
955 _
, err
:= cmd
.Output()
956 ee
, ok
:= err
.(*exec
.ExitError
)
958 t
.Fatalf("Output error type = %T; want ExitError", err
)
960 got
:= string(ee
.Stderr
)
961 want
:= "some stderr text\n"
963 t
.Errorf("ExitError.Stderr = %q; want %q", got
, want
)
967 func TestContext(t
*testing
.T
) {
968 ctx
, cancel
:= context
.WithCancel(context
.Background())
969 c
:= helperCommandContext(t
, ctx
, "pipetest")
970 stdin
, err
:= c
.StdinPipe()
974 stdout
, err
:= c
.StdoutPipe()
978 if err
:= c
.Start(); err
!= nil {
982 if _
, err
:= stdin
.Write([]byte("O:hi\n")); err
!= nil {
985 buf
:= make([]byte, 5)
986 n
, err
:= io
.ReadFull(stdout
, buf
)
987 if n
!= len(buf
) || err
!= nil ||
string(buf
) != "O:hi\n" {
988 t
.Fatalf("ReadFull = %d, %v, %q", n
, err
, buf
[:n
])
990 waitErr
:= make(chan error
, 1)
996 case err
:= <-waitErr
:
998 t
.Fatal("expected Wait failure")
1000 case <-time
.After(3 * time
.Second
):
1001 t
.Fatal("timeout waiting for child process death")
1005 func TestContextCancel(t
*testing
.T
) {
1006 ctx
, cancel
:= context
.WithCancel(context
.Background())
1008 c
:= helperCommandContext(t
, ctx
, "cat")
1010 r
, w
, err
:= os
.Pipe()
1016 stdout
, err
:= c
.StdoutPipe()
1020 readDone
:= make(chan struct{})
1022 defer close(readDone
)
1025 n
, err
:= stdout
.Read(a
[:])
1028 t
.Errorf("unexpected read error: %v", err
)
1036 if err
:= c
.Start(); err
!= nil {
1040 if err
:= r
.Close(); err
!= nil {
1044 if _
, err
:= io
.WriteString(w
, "echo"); err
!= nil {
1050 // Calling cancel should have killed the process, so writes
1051 // should now fail. Give the process a little while to die.
1054 if _
, err
:= io
.WriteString(w
, "echo"); err
!= nil {
1057 if time
.Since(start
) > time
.Second
{
1058 t
.Fatal("canceling context did not stop program")
1060 time
.Sleep(time
.Millisecond
)
1063 if err
:= w
.Close(); err
!= nil {
1064 t
.Errorf("error closing write end of pipe: %v", err
)
1068 if err
:= c
.Wait(); err
== nil {
1069 t
.Error("program unexpectedly exited successfully")
1071 t
.Logf("exit status: %v", err
)
1075 // test that environment variables are de-duped.
1076 func TestDedupEnvEcho(t
*testing
.T
) {
1077 testenv
.MustHaveExec(t
)
1079 cmd
:= helperCommand(t
, "echoenv", "FOO")
1080 cmd
.Env
= append(cmd
.Env
, "FOO=bad", "FOO=good")
1081 out
, err
:= cmd
.CombinedOutput()
1085 if got
, want
:= strings
.TrimSpace(string(out
)), "good"; got
!= want
{
1086 t
.Errorf("output = %q; want %q", got
, want
)