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.
29 func helperCommand(s
...string) *exec
.Cmd
{
30 cs
:= []string{"-test.run=TestHelperProcess", "--"}
32 cmd
:= exec
.Command(os
.Args
[0], cs
...)
33 cmd
.Env
= []string{"GO_WANT_HELPER_PROCESS=1"}
34 path
:= os
.Getenv("LD_LIBRARY_PATH")
36 cmd
.Env
= append(cmd
.Env
, "LD_LIBRARY_PATH="+path
)
41 func TestEcho(t
*testing
.T
) {
42 bs
, err
:= helperCommand("echo", "foo bar", "baz").Output()
44 t
.Errorf("echo: %v", err
)
46 if g
, e
:= string(bs
), "foo bar baz\n"; g
!= e
{
47 t
.Errorf("echo: want %q, got %q", e
, g
)
51 func TestCatStdin(t
*testing
.T
) {
52 // Cat, testing stdin and stdout.
53 input
:= "Input string\nLine 2"
54 p
:= helperCommand("cat")
55 p
.Stdin
= strings
.NewReader(input
)
58 t
.Errorf("cat: %v", err
)
62 t
.Errorf("cat: want %q, got %q", input
, s
)
66 func TestCatGoodAndBadFile(t
*testing
.T
) {
67 // Testing combined output and error values.
68 bs
, err
:= helperCommand("cat", "/bogus/file.foo", "exec_test.go").CombinedOutput()
69 if _
, ok
:= err
.(*exec
.ExitError
); !ok
{
70 t
.Errorf("expected *exec.ExitError from cat combined; got %T: %v", err
, err
)
73 sp
:= strings
.SplitN(s
, "\n", 2)
75 t
.Fatalf("expected two lines from cat; got %q", s
)
77 errLine
, body
:= sp
[0], sp
[1]
78 if !strings
.HasPrefix(errLine
, "Error: open /bogus/file.foo") {
79 t
.Errorf("expected stderr to complain about file; got %q", errLine
)
81 if !strings
.Contains(body
, "func TestHelperProcess(t *testing.T)") {
82 t
.Errorf("expected test code; got %q (len %d)", body
, len(body
))
86 func TestNoExistBinary(t
*testing
.T
) {
87 // Can't run a non-existent binary
88 err
:= exec
.Command("/no-exist-binary").Run()
90 t
.Error("expected error from /no-exist-binary")
94 func TestExitStatus(t
*testing
.T
) {
95 // Test that exit values are returned correctly
96 cmd
:= helperCommand("exit", "42")
98 want
:= "exit status 42"
101 want
= fmt
.Sprintf("exit status: '%s %d: 42'", filepath
.Base(cmd
.Path
), cmd
.ProcessState
.Pid())
103 if werr
, ok
:= err
.(*exec
.ExitError
); ok
{
104 if s
:= werr
.Error(); s
!= want
{
105 t
.Errorf("from exit 42 got exit %q, want %q", s
, want
)
108 t
.Fatalf("expected *exec.ExitError from exit 42; got %T: %v", err
, err
)
112 func TestPipes(t
*testing
.T
) {
113 check
:= func(what
string, err error
) {
115 t
.Fatalf("%s: %v", what
, err
)
118 // Cat, testing stdin and stdout.
119 c
:= helperCommand("pipetest")
120 stdin
, err
:= c
.StdinPipe()
121 check("StdinPipe", err
)
122 stdout
, err
:= c
.StdoutPipe()
123 check("StdoutPipe", err
)
124 stderr
, err
:= c
.StderrPipe()
125 check("StderrPipe", err
)
127 outbr
:= bufio
.NewReader(stdout
)
128 errbr
:= bufio
.NewReader(stderr
)
129 line
:= func(what
string, br
*bufio
.Reader
) string {
130 line
, _
, err
:= br
.ReadLine()
132 t
.Fatalf("%s: %v", what
, err
)
140 _
, err
= stdin
.Write([]byte("O:I am output\n"))
141 check("first stdin Write", err
)
142 if g
, e
:= line("first output line", outbr
), "O:I am output"; g
!= e
{
143 t
.Errorf("got %q, want %q", g
, e
)
146 _
, err
= stdin
.Write([]byte("E:I am error\n"))
147 check("second stdin Write", err
)
148 if g
, e
:= line("first error line", errbr
), "E:I am error"; g
!= e
{
149 t
.Errorf("got %q, want %q", g
, e
)
152 _
, err
= stdin
.Write([]byte("O:I am output2\n"))
153 check("third stdin Write 3", err
)
154 if g
, e
:= line("second output line", outbr
), "O:I am output2"; g
!= e
{
155 t
.Errorf("got %q, want %q", g
, e
)
163 const stdinCloseTestString
= "Some test string."
166 func TestStdinClose(t
*testing
.T
) {
167 check
:= func(what
string, err error
) {
169 t
.Fatalf("%s: %v", what
, err
)
172 cmd
:= helperCommand("stdinClose")
173 stdin
, err
:= cmd
.StdinPipe()
174 check("StdinPipe", err
)
175 // Check that we can access methods of the underlying os.File.`
176 if _
, ok
:= stdin
.(interface {
179 t
.Error("can't access methods of underlying *os.File")
181 check("Start", cmd
.Start())
183 _
, err
:= io
.Copy(stdin
, strings
.NewReader(stdinCloseTestString
))
185 // Before the fix, this next line would race with cmd.Wait.
186 check("Close", stdin
.Close())
188 check("Wait", cmd
.Wait())
192 func TestPipeLookPathLeak(t
*testing
.T
) {
194 for i
:= 0; i
< 4; i
++ {
195 cmd
:= exec
.Command("something-that-does-not-exist-binary")
199 if err
:= cmd
.Run(); err
== nil {
200 t
.Fatal("unexpected success")
203 fdGrowth
:= numOpenFDS(t
) - fd0
205 t
.Errorf("leaked %d fds; want ~0", fdGrowth
)
209 func numOpenFDS(t
*testing
.T
) int {
210 lsof
, err
:= exec
.Command("lsof", "-n", "-p", strconv
.Itoa(os
.Getpid())).Output()
212 t
.Skip("skipping test; error finding or running lsof")
215 return bytes
.Count(lsof
, []byte("\n"))
218 var testedAlreadyLeaked
= false
220 // basefds returns the number of expected file descriptors
221 // to be present in a process at start.
222 func basefds() uintptr {
223 n
:= os
.Stderr
.Fd() + 1
225 // Go runtime for 32-bit Plan 9 requires that /dev/bintime
227 // See ../../runtime/time_plan9_386.c:/^runtime·nanotime
228 if runtime
.GOOS
== "plan9" && runtime
.GOARCH
== "386" {
234 func closeUnexpectedFds(t
*testing
.T
, m
string) {
235 for fd
:= basefds(); fd
<= 101; fd
++ {
236 err
:= os
.NewFile(fd
, "").Close()
238 t
.Logf("%s: Something already leaked - closed fd %d", m
, fd
)
243 func TestExtraFilesFDShuffle(t
*testing
.T
) {
244 t
.Skip("flaky test; see http://golang.org/issue/5780")
245 switch runtime
.GOOS
{
247 // TODO(cnicolaou): http://golang.org/issue/2603
248 // leads to leaked file descriptors in this test when it's
249 // run from a builder.
250 closeUnexpectedFds(t
, "TestExtraFilesFDShuffle")
252 // http://golang.org/issue/3955
253 closeUnexpectedFds(t
, "TestExtraFilesFDShuffle")
255 t
.Skip("no operating system support; skipping")
258 // syscall.StartProcess maps all the FDs passed to it in
259 // ProcAttr.Files (the concatenation of stdin,stdout,stderr and
260 // ExtraFiles) into consecutive FDs in the child, that is:
261 // Files{11, 12, 6, 7, 9, 3} should result in the file
262 // represented by FD 11 in the parent being made available as 0
263 // in the child, 12 as 1, etc.
265 // We want to test that FDs in the child do not get overwritten
266 // by one another as this shuffle occurs. The original implementation
267 // was buggy in that in some data dependent cases it would ovewrite
268 // stderr in the child with one of the ExtraFile members.
269 // Testing for this case is difficult because it relies on using
270 // the same FD values as that case. In particular, an FD of 3
271 // must be at an index of 4 or higher in ProcAttr.Files and
272 // the FD of the write end of the Stderr pipe (as obtained by
273 // StderrPipe()) must be the same as the size of ProcAttr.Files;
274 // therefore we test that the read end of this pipe (which is what
275 // is returned to the parent by StderrPipe() being one less than
276 // the size of ProcAttr.Files, i.e. 3+len(cmd.ExtraFiles).
278 // Moving this test case around within the overall tests may
279 // affect the FDs obtained and hence the checks to catch these cases.
281 c
:= helperCommand("extraFilesAndPipes", strconv
.Itoa(npipes
+1))
282 rd
, wr
, _
:= os
.Pipe()
285 t
.Errorf("bad test value for test pipe: fd %d", rd
.Fd())
287 stderr
, _
:= c
.StderrPipe()
288 wr
.WriteString("_LAST")
291 pipes
:= make([]struct {
294 data
:= []string{"a", "b"}
296 for i
:= 0; i
< npipes
; i
++ {
297 r
, w
, err
:= os
.Pipe()
299 t
.Fatalf("unexpected error creating pipe: %s", err
)
303 w
.WriteString(data
[i
])
304 c
.ExtraFiles
= append(c
.ExtraFiles
, pipes
[i
].r
)
310 // Put fd 3 at the end.
311 c
.ExtraFiles
= append(c
.ExtraFiles
, rd
)
313 stderrFd
:= int(stderr
.(*os
.File
).Fd())
314 if stderrFd
!= ((len(c
.ExtraFiles
) + 3) - 1) {
315 t
.Errorf("bad test value for stderr pipe")
318 expected
:= "child: " + strings
.Join(data
, "") + "_LAST"
322 t
.Fatalf("Run: %v", err
)
324 ch
:= make(chan string, 1)
325 go func(ch
chan string) {
326 buf
:= make([]byte, 512)
327 n
, err
:= stderr
.Read(buf
)
329 t
.Fatalf("Read: %s", err
)
332 ch
<- string(buf
[:n
])
339 t
.Errorf("Read: '%s' not '%s'", m
, expected
)
341 case <-time
.After(5 * time
.Second
):
342 t
.Errorf("Read timedout")
347 func TestExtraFiles(t
*testing
.T
) {
348 if runtime
.GOOS
== "windows" {
349 t
.Skip("no operating system support; skipping")
352 // Ensure that file descriptors have not already been leaked into
354 if !testedAlreadyLeaked
{
355 testedAlreadyLeaked
= true
356 closeUnexpectedFds(t
, "TestExtraFiles")
359 // Force network usage, to verify the epoll (or whatever) fd
360 // doesn't leak to the child,
361 ln
, err
:= net
.Listen("tcp", "127.0.0.1:0")
367 // Make sure duplicated fds don't leak to the child.
368 f
, err
:= ln
.(*net
.TCPListener
).File()
373 ln2
, err
:= net
.FileListener(f
)
379 // Force TLS root certs to be loaded (which might involve
380 // cgo), to make sure none of that potential C code leaks fds.
381 ts
:= httptest
.NewTLSServer(http
.HandlerFunc(func(w http
.ResponseWriter
, r
*http
.Request
) {
382 w
.Write([]byte("Hello"))
385 http
.Get(ts
.URL
) // ignore result; just calling to force root cert loading
387 tf
, err
:= ioutil
.TempFile("", "")
389 t
.Fatalf("TempFile: %v", err
)
391 defer os
.Remove(tf
.Name())
394 const text
= "Hello, fd 3!"
395 _
, err
= tf
.Write([]byte(text
))
397 t
.Fatalf("Write: %v", err
)
399 _
, err
= tf
.Seek(0, os
.SEEK_SET
)
401 t
.Fatalf("Seek: %v", err
)
404 c
:= helperCommand("read3")
405 var stdout
, stderr bytes
.Buffer
408 c
.ExtraFiles
= []*os
.File
{tf
}
411 t
.Fatalf("Run: %v; stdout %q, stderr %q", err
, stdout
.Bytes(), stderr
.Bytes())
413 if stdout
.String() != text
{
414 t
.Errorf("got stdout %q, stderr %q; want %q on stdout", stdout
.String(), stderr
.String(), text
)
418 func TestExtraFilesRace(t
*testing
.T
) {
419 if runtime
.GOOS
== "windows" {
420 t
.Skip("no operating system support; skipping")
422 listen
:= func() net
.Listener
{
423 ln
, err
:= net
.Listen("tcp", "127.0.0.1:0")
429 listenerFile
:= func(ln net
.Listener
) *os
.File
{
430 f
, err
:= ln
.(*net
.TCPListener
).File()
436 runCommand
:= func(c
*exec
.Cmd
, out
chan<- string) {
437 bout
, err
:= c
.CombinedOutput()
439 out
<- "ERROR:" + err
.Error()
445 for i
:= 0; i
< 10; i
++ {
447 ca
:= helperCommand("describefiles")
448 ca
.ExtraFiles
= []*os
.File
{listenerFile(la
)}
450 cb
:= helperCommand("describefiles")
451 cb
.ExtraFiles
= []*os
.File
{listenerFile(lb
)}
452 ares
:= make(chan string)
453 bres
:= make(chan string)
454 go runCommand(ca
, ares
)
455 go runCommand(cb
, bres
)
456 if got
, want
:= <-ares
, fmt
.Sprintf("fd3: listener %s\n", la
.Addr()); got
!= want
{
457 t
.Errorf("iteration %d, process A got:\n%s\nwant:\n%s\n", i
, got
, want
)
459 if got
, want
:= <-bres
, fmt
.Sprintf("fd3: listener %s\n", lb
.Addr()); got
!= want
{
460 t
.Errorf("iteration %d, process B got:\n%s\nwant:\n%s\n", i
, got
, want
)
464 for _
, f
:= range ca
.ExtraFiles
{
467 for _
, f
:= range cb
.ExtraFiles
{
474 // TestHelperProcess isn't a real test. It's used as a helper process
475 // for TestParameterRun.
476 func TestHelperProcess(*testing
.T
) {
477 if os
.Getenv("GO_WANT_HELPER_PROCESS") != "1" {
482 // Determine which command to use to display open files.
484 switch runtime
.GOOS
{
485 case "dragonfly", "freebsd", "netbsd", "openbsd":
498 fmt
.Fprintf(os
.Stderr
, "No command\n")
502 cmd
, args
:= args
[0], args
[1:]
505 iargs
:= []interface{}{}
506 for _
, s
:= range args
{
507 iargs
= append(iargs
, s
)
509 fmt
.Println(iargs
...)
512 io
.Copy(os
.Stdout
, os
.Stdin
)
516 for _
, fn
:= range args
{
517 f
, err
:= os
.Open(fn
)
519 fmt
.Fprintf(os
.Stderr
, "Error: %v\n", err
)
523 io
.Copy(os
.Stdout
, f
)
528 bufr
:= bufio
.NewReader(os
.Stdin
)
530 line
, _
, err
:= bufr
.ReadLine()
533 } else if err
!= nil {
536 if bytes
.HasPrefix(line
, []byte("O:")) {
537 os
.Stdout
.Write(line
)
538 os
.Stdout
.Write([]byte{'\n'})
539 } else if bytes
.HasPrefix(line
, []byte("E:")) {
540 os
.Stderr
.Write(line
)
541 os
.Stderr
.Write([]byte{'\n'})
547 b
, err
:= ioutil
.ReadAll(os
.Stdin
)
549 fmt
.Fprintf(os
.Stderr
, "Error: %v\n", err
)
552 if s
:= string(b
); s
!= stdinCloseTestString
{
553 fmt
.Fprintf(os
.Stderr
, "Error: Read %q, want %q", s
, stdinCloseTestString
)
557 case "read3": // read fd 3
558 fd3
:= os
.NewFile(3, "fd3")
559 bs
, err
:= ioutil
.ReadAll(fd3
)
561 fmt
.Printf("ReadAll from fd 3: %v", err
)
564 switch runtime
.GOOS
{
566 // TODO(jsing): Determine why DragonFly is leaking
567 // file descriptors...
569 // TODO(bradfitz): broken? Sometimes.
570 // http://golang.org/issue/2603
571 // Skip this additional part of the test for now.
573 // TODO(jsing): This currently fails on NetBSD due to
574 // the cloned file descriptors that result from opening
576 // http://golang.org/issue/3955
578 // Now verify that there are no other open fds.
580 for wantfd
:= basefds() + 1; wantfd
<= 100; wantfd
++ {
581 f
, err
:= os
.Open(os
.Args
[0])
583 fmt
.Printf("error opening file with expected fd %d: %v", wantfd
, err
)
586 if got
:= f
.Fd(); got
!= wantfd
{
587 fmt
.Printf("leaked parent file. fd = %d; want %d\n", got
, wantfd
)
588 out
, _
:= exec
.Command(ofcmd
, "-p", fmt
.Sprint(os
.Getpid())).CombinedOutput()
589 fmt
.Print(string(out
))
592 files
= append(files
, f
)
594 for _
, f
:= range files
{
598 // Referring to fd3 here ensures that it is not
599 // garbage collected, and therefore closed, while
600 // executing the wantfd loop above. It doesn't matter
601 // what we do with fd3 as long as we refer to it;
602 // closing it is the easy choice.
606 n
, _
:= strconv
.Atoi(args
[0])
608 case "describefiles":
609 f
:= os
.NewFile(3, fmt
.Sprintf("fd3"))
610 ln
, err
:= net
.FileListener(f
)
612 fmt
.Printf("fd3: listener %s\n", ln
.Addr())
616 case "extraFilesAndPipes":
617 n
, _
:= strconv
.Atoi(args
[0])
618 pipes
:= make([]*os
.File
, n
)
619 for i
:= 0; i
< n
; i
++ {
620 pipes
[i
] = os
.NewFile(uintptr(3+i
), strconv
.Itoa(i
))
623 for i
, r
:= range pipes
{
624 ch
:= make(chan string, 1)
625 go func(c
chan string) {
626 buf
:= make([]byte, 10)
627 n
, err
:= r
.Read(buf
)
629 fmt
.Fprintf(os
.Stderr
, "Child: read error: %v on pipe %d\n", err
, i
)
637 response
= response
+ m
638 case <-time
.After(5 * time
.Second
):
639 fmt
.Fprintf(os
.Stderr
, "Child: Timeout reading from pipe: %d\n", i
)
643 fmt
.Fprintf(os
.Stderr
, "child: %s", response
)
646 fmt
.Fprintf(os
.Stderr
, "Unknown command %q\n", cmd
)