libgo: Merge from revision 18783:00cce3a34d7e of master library.
[official-gcc.git] / libgo / go / os / exec / exec_test.go
blob32868fc7463b80e10cbfbb331b727b841a7796b9
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.
8 package exec_test
10 import (
11 "bufio"
12 "bytes"
13 "fmt"
14 "io"
15 "io/ioutil"
16 "net"
17 "net/http"
18 "net/http/httptest"
19 "os"
20 "os/exec"
21 "path/filepath"
22 "runtime"
23 "strconv"
24 "strings"
25 "testing"
26 "time"
29 func helperCommand(s ...string) *exec.Cmd {
30 cs := []string{"-test.run=TestHelperProcess", "--"}
31 cs = append(cs, s...)
32 cmd := exec.Command(os.Args[0], cs...)
33 cmd.Env = []string{"GO_WANT_HELPER_PROCESS=1"}
34 path := os.Getenv("LD_LIBRARY_PATH")
35 if path != "" {
36 cmd.Env = append(cmd.Env, "LD_LIBRARY_PATH="+path)
38 return cmd
41 func TestEcho(t *testing.T) {
42 bs, err := helperCommand("echo", "foo bar", "baz").Output()
43 if err != nil {
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)
56 bs, err := p.Output()
57 if err != nil {
58 t.Errorf("cat: %v", err)
60 s := string(bs)
61 if s != input {
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)
72 s := string(bs)
73 sp := strings.SplitN(s, "\n", 2)
74 if len(sp) != 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()
89 if err == nil {
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")
97 err := cmd.Run()
98 want := "exit status 42"
99 switch runtime.GOOS {
100 case "plan9":
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)
107 } else {
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) {
114 if err != nil {
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()
131 if err != nil {
132 t.Fatalf("%s: %v", what, err)
134 return string(line)
137 err = c.Start()
138 check("Start", 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)
158 stdin.Close()
159 err = c.Wait()
160 check("Wait", err)
163 const stdinCloseTestString = "Some test string."
165 // Issue 6270.
166 func TestStdinClose(t *testing.T) {
167 check := func(what string, err error) {
168 if err != nil {
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 {
177 Fd() uintptr
178 }); !ok {
179 t.Error("can't access methods of underlying *os.File")
181 check("Start", cmd.Start())
182 go func() {
183 _, err := io.Copy(stdin, strings.NewReader(stdinCloseTestString))
184 check("Copy", err)
185 // Before the fix, this next line would race with cmd.Wait.
186 check("Close", stdin.Close())
188 check("Wait", cmd.Wait())
191 // Issue 5071
192 func TestPipeLookPathLeak(t *testing.T) {
193 fd0 := numOpenFDS(t)
194 for i := 0; i < 4; i++ {
195 cmd := exec.Command("something-that-does-not-exist-binary")
196 cmd.StdoutPipe()
197 cmd.StderrPipe()
198 cmd.StdinPipe()
199 if err := cmd.Run(); err == nil {
200 t.Fatal("unexpected success")
203 fdGrowth := numOpenFDS(t) - fd0
204 if fdGrowth > 2 {
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()
211 if err != nil {
212 t.Skip("skipping test; error finding or running lsof")
213 return 0
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
226 // be kept open.
227 // See ../../runtime/time_plan9_386.c:/^runtime·nanotime
228 if runtime.GOOS == "plan9" && runtime.GOARCH == "386" {
231 return n
234 func closeUnexpectedFds(t *testing.T, m string) {
235 for fd := basefds(); fd <= 101; fd++ {
236 err := os.NewFile(fd, "").Close()
237 if err == nil {
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 {
246 case "darwin":
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")
251 case "netbsd":
252 // http://golang.org/issue/3955
253 closeUnexpectedFds(t, "TestExtraFilesFDShuffle")
254 case "windows":
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.
280 npipes := 2
281 c := helperCommand("extraFilesAndPipes", strconv.Itoa(npipes+1))
282 rd, wr, _ := os.Pipe()
283 defer rd.Close()
284 if rd.Fd() != 3 {
285 t.Errorf("bad test value for test pipe: fd %d", rd.Fd())
287 stderr, _ := c.StderrPipe()
288 wr.WriteString("_LAST")
289 wr.Close()
291 pipes := make([]struct {
292 r, w *os.File
293 }, npipes)
294 data := []string{"a", "b"}
296 for i := 0; i < npipes; i++ {
297 r, w, err := os.Pipe()
298 if err != nil {
299 t.Fatalf("unexpected error creating pipe: %s", err)
301 pipes[i].r = r
302 pipes[i].w = w
303 w.WriteString(data[i])
304 c.ExtraFiles = append(c.ExtraFiles, pipes[i].r)
305 defer func() {
306 r.Close()
307 w.Close()
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"
320 err := c.Start()
321 if err != nil {
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)
328 if err != nil {
329 t.Fatalf("Read: %s", err)
330 ch <- err.Error()
331 } else {
332 ch <- string(buf[:n])
334 close(ch)
335 }(ch)
336 select {
337 case m := <-ch:
338 if m != expected {
339 t.Errorf("Read: '%s' not '%s'", m, expected)
341 case <-time.After(5 * time.Second):
342 t.Errorf("Read timedout")
344 c.Wait()
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
353 // our environment.
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")
362 if err != nil {
363 t.Fatal(err)
365 defer ln.Close()
367 // Make sure duplicated fds don't leak to the child.
368 f, err := ln.(*net.TCPListener).File()
369 if err != nil {
370 t.Fatal(err)
372 defer f.Close()
373 ln2, err := net.FileListener(f)
374 if err != nil {
375 t.Fatal(err)
377 defer ln2.Close()
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"))
384 defer ts.Close()
385 http.Get(ts.URL) // ignore result; just calling to force root cert loading
387 tf, err := ioutil.TempFile("", "")
388 if err != nil {
389 t.Fatalf("TempFile: %v", err)
391 defer os.Remove(tf.Name())
392 defer tf.Close()
394 const text = "Hello, fd 3!"
395 _, err = tf.Write([]byte(text))
396 if err != nil {
397 t.Fatalf("Write: %v", err)
399 _, err = tf.Seek(0, os.SEEK_SET)
400 if err != nil {
401 t.Fatalf("Seek: %v", err)
404 c := helperCommand("read3")
405 var stdout, stderr bytes.Buffer
406 c.Stdout = &stdout
407 c.Stderr = &stderr
408 c.ExtraFiles = []*os.File{tf}
409 err = c.Run()
410 if err != nil {
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")
424 if err != nil {
425 t.Fatal(err)
427 return ln
429 listenerFile := func(ln net.Listener) *os.File {
430 f, err := ln.(*net.TCPListener).File()
431 if err != nil {
432 t.Fatal(err)
434 return f
436 runCommand := func(c *exec.Cmd, out chan<- string) {
437 bout, err := c.CombinedOutput()
438 if err != nil {
439 out <- "ERROR:" + err.Error()
440 } else {
441 out <- string(bout)
445 for i := 0; i < 10; i++ {
446 la := listen()
447 ca := helperCommand("describefiles")
448 ca.ExtraFiles = []*os.File{listenerFile(la)}
449 lb := listen()
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)
462 la.Close()
463 lb.Close()
464 for _, f := range ca.ExtraFiles {
465 f.Close()
467 for _, f := range cb.ExtraFiles {
468 f.Close()
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" {
478 return
480 defer os.Exit(0)
482 // Determine which command to use to display open files.
483 ofcmd := "lsof"
484 switch runtime.GOOS {
485 case "dragonfly", "freebsd", "netbsd", "openbsd":
486 ofcmd = "fstat"
489 args := os.Args
490 for len(args) > 0 {
491 if args[0] == "--" {
492 args = args[1:]
493 break
495 args = args[1:]
497 if len(args) == 0 {
498 fmt.Fprintf(os.Stderr, "No command\n")
499 os.Exit(2)
502 cmd, args := args[0], args[1:]
503 switch cmd {
504 case "echo":
505 iargs := []interface{}{}
506 for _, s := range args {
507 iargs = append(iargs, s)
509 fmt.Println(iargs...)
510 case "cat":
511 if len(args) == 0 {
512 io.Copy(os.Stdout, os.Stdin)
513 return
515 exit := 0
516 for _, fn := range args {
517 f, err := os.Open(fn)
518 if err != nil {
519 fmt.Fprintf(os.Stderr, "Error: %v\n", err)
520 exit = 2
521 } else {
522 defer f.Close()
523 io.Copy(os.Stdout, f)
526 os.Exit(exit)
527 case "pipetest":
528 bufr := bufio.NewReader(os.Stdin)
529 for {
530 line, _, err := bufr.ReadLine()
531 if err == io.EOF {
532 break
533 } else if err != nil {
534 os.Exit(1)
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'})
542 } else {
543 os.Exit(1)
546 case "stdinClose":
547 b, err := ioutil.ReadAll(os.Stdin)
548 if err != nil {
549 fmt.Fprintf(os.Stderr, "Error: %v\n", err)
550 os.Exit(1)
552 if s := string(b); s != stdinCloseTestString {
553 fmt.Fprintf(os.Stderr, "Error: Read %q, want %q", s, stdinCloseTestString)
554 os.Exit(1)
556 os.Exit(0)
557 case "read3": // read fd 3
558 fd3 := os.NewFile(3, "fd3")
559 bs, err := ioutil.ReadAll(fd3)
560 if err != nil {
561 fmt.Printf("ReadAll from fd 3: %v", err)
562 os.Exit(1)
564 switch runtime.GOOS {
565 case "dragonfly":
566 // TODO(jsing): Determine why DragonFly is leaking
567 // file descriptors...
568 case "darwin":
569 // TODO(bradfitz): broken? Sometimes.
570 // http://golang.org/issue/2603
571 // Skip this additional part of the test for now.
572 case "netbsd":
573 // TODO(jsing): This currently fails on NetBSD due to
574 // the cloned file descriptors that result from opening
575 // /dev/urandom.
576 // http://golang.org/issue/3955
577 default:
578 // Now verify that there are no other open fds.
579 var files []*os.File
580 for wantfd := basefds() + 1; wantfd <= 100; wantfd++ {
581 f, err := os.Open(os.Args[0])
582 if err != nil {
583 fmt.Printf("error opening file with expected fd %d: %v", wantfd, err)
584 os.Exit(1)
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))
590 os.Exit(1)
592 files = append(files, f)
594 for _, f := range files {
595 f.Close()
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.
603 fd3.Close()
604 os.Stdout.Write(bs)
605 case "exit":
606 n, _ := strconv.Atoi(args[0])
607 os.Exit(n)
608 case "describefiles":
609 f := os.NewFile(3, fmt.Sprintf("fd3"))
610 ln, err := net.FileListener(f)
611 if err == nil {
612 fmt.Printf("fd3: listener %s\n", ln.Addr())
613 ln.Close()
615 os.Exit(0)
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))
622 response := ""
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)
628 if err != nil {
629 fmt.Fprintf(os.Stderr, "Child: read error: %v on pipe %d\n", err, i)
630 os.Exit(1)
632 c <- string(buf[:n])
633 close(c)
634 }(ch)
635 select {
636 case m := <-ch:
637 response = response + m
638 case <-time.After(5 * time.Second):
639 fmt.Fprintf(os.Stderr, "Child: Timeout reading from pipe: %d\n", i)
640 os.Exit(1)
643 fmt.Fprintf(os.Stderr, "child: %s", response)
644 os.Exit(0)
645 default:
646 fmt.Fprintf(os.Stderr, "Unknown command %q\n", cmd)
647 os.Exit(2)