Home | History | Annotate | Download | only in exec
      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.
      4 
      5 // Use an external test to avoid os/exec -> net/http -> crypto/x509 -> os/exec
      6 // circular dependency on non-cgo darwin.
      7 
      8 package exec_test
      9 
     10 import (
     11 	"bufio"
     12 	"bytes"
     13 	"fmt"
     14 	"internal/testenv"
     15 	"io"
     16 	"io/ioutil"
     17 	"log"
     18 	"net"
     19 	"net/http"
     20 	"net/http/httptest"
     21 	"os"
     22 	"os/exec"
     23 	"path/filepath"
     24 	"runtime"
     25 	"strconv"
     26 	"strings"
     27 	"testing"
     28 	"time"
     29 )
     30 
     31 func helperCommand(t *testing.T, s ...string) *exec.Cmd {
     32 	testenv.MustHaveExec(t)
     33 
     34 	cs := []string{"-test.run=TestHelperProcess", "--"}
     35 	cs = append(cs, s...)
     36 	cmd := exec.Command(os.Args[0], cs...)
     37 	cmd.Env = []string{"GO_WANT_HELPER_PROCESS=1"}
     38 	return cmd
     39 }
     40 
     41 func TestEcho(t *testing.T) {
     42 	bs, err := helperCommand(t, "echo", "foo bar", "baz").Output()
     43 	if err != nil {
     44 		t.Errorf("echo: %v", err)
     45 	}
     46 	if g, e := string(bs), "foo bar baz\n"; g != e {
     47 		t.Errorf("echo: want %q, got %q", e, g)
     48 	}
     49 }
     50 
     51 func TestCommandRelativeName(t *testing.T) {
     52 	testenv.MustHaveExec(t)
     53 
     54 	// Run our own binary as a relative path
     55 	// (e.g. "_test/exec.test") our parent directory.
     56 	base := filepath.Base(os.Args[0]) // "exec.test"
     57 	dir := filepath.Dir(os.Args[0])   // "/tmp/go-buildNNNN/os/exec/_test"
     58 	if dir == "." {
     59 		t.Skip("skipping; running test at root somehow")
     60 	}
     61 	parentDir := filepath.Dir(dir) // "/tmp/go-buildNNNN/os/exec"
     62 	dirBase := filepath.Base(dir)  // "_test"
     63 	if dirBase == "." {
     64 		t.Skipf("skipping; unexpected shallow dir of %q", dir)
     65 	}
     66 
     67 	cmd := exec.Command(filepath.Join(dirBase, base), "-test.run=TestHelperProcess", "--", "echo", "foo")
     68 	cmd.Dir = parentDir
     69 	cmd.Env = []string{"GO_WANT_HELPER_PROCESS=1"}
     70 
     71 	out, err := cmd.Output()
     72 	if err != nil {
     73 		t.Errorf("echo: %v", err)
     74 	}
     75 	if g, e := string(out), "foo\n"; g != e {
     76 		t.Errorf("echo: want %q, got %q", e, g)
     77 	}
     78 }
     79 
     80 func TestCatStdin(t *testing.T) {
     81 	// Cat, testing stdin and stdout.
     82 	input := "Input string\nLine 2"
     83 	p := helperCommand(t, "cat")
     84 	p.Stdin = strings.NewReader(input)
     85 	bs, err := p.Output()
     86 	if err != nil {
     87 		t.Errorf("cat: %v", err)
     88 	}
     89 	s := string(bs)
     90 	if s != input {
     91 		t.Errorf("cat: want %q, got %q", input, s)
     92 	}
     93 }
     94 
     95 func TestCatGoodAndBadFile(t *testing.T) {
     96 	// Testing combined output and error values.
     97 	bs, err := helperCommand(t, "cat", "/bogus/file.foo", "exec_test.go").CombinedOutput()
     98 	if _, ok := err.(*exec.ExitError); !ok {
     99 		t.Errorf("expected *exec.ExitError from cat combined; got %T: %v", err, err)
    100 	}
    101 	s := string(bs)
    102 	sp := strings.SplitN(s, "\n", 2)
    103 	if len(sp) != 2 {
    104 		t.Fatalf("expected two lines from cat; got %q", s)
    105 	}
    106 	errLine, body := sp[0], sp[1]
    107 	if !strings.HasPrefix(errLine, "Error: open /bogus/file.foo") {
    108 		t.Errorf("expected stderr to complain about file; got %q", errLine)
    109 	}
    110 	if !strings.Contains(body, "func TestHelperProcess(t *testing.T)") {
    111 		t.Errorf("expected test code; got %q (len %d)", body, len(body))
    112 	}
    113 }
    114 
    115 func TestNoExistBinary(t *testing.T) {
    116 	// Can't run a non-existent binary
    117 	err := exec.Command("/no-exist-binary").Run()
    118 	if err == nil {
    119 		t.Error("expected error from /no-exist-binary")
    120 	}
    121 }
    122 
    123 func TestExitStatus(t *testing.T) {
    124 	// Test that exit values are returned correctly
    125 	cmd := helperCommand(t, "exit", "42")
    126 	err := cmd.Run()
    127 	want := "exit status 42"
    128 	switch runtime.GOOS {
    129 	case "plan9":
    130 		want = fmt.Sprintf("exit status: '%s %d: 42'", filepath.Base(cmd.Path), cmd.ProcessState.Pid())
    131 	}
    132 	if werr, ok := err.(*exec.ExitError); ok {
    133 		if s := werr.Error(); s != want {
    134 			t.Errorf("from exit 42 got exit %q, want %q", s, want)
    135 		}
    136 	} else {
    137 		t.Fatalf("expected *exec.ExitError from exit 42; got %T: %v", err, err)
    138 	}
    139 }
    140 
    141 func TestPipes(t *testing.T) {
    142 	check := func(what string, err error) {
    143 		if err != nil {
    144 			t.Fatalf("%s: %v", what, err)
    145 		}
    146 	}
    147 	// Cat, testing stdin and stdout.
    148 	c := helperCommand(t, "pipetest")
    149 	stdin, err := c.StdinPipe()
    150 	check("StdinPipe", err)
    151 	stdout, err := c.StdoutPipe()
    152 	check("StdoutPipe", err)
    153 	stderr, err := c.StderrPipe()
    154 	check("StderrPipe", err)
    155 
    156 	outbr := bufio.NewReader(stdout)
    157 	errbr := bufio.NewReader(stderr)
    158 	line := func(what string, br *bufio.Reader) string {
    159 		line, _, err := br.ReadLine()
    160 		if err != nil {
    161 			t.Fatalf("%s: %v", what, err)
    162 		}
    163 		return string(line)
    164 	}
    165 
    166 	err = c.Start()
    167 	check("Start", err)
    168 
    169 	_, err = stdin.Write([]byte("O:I am output\n"))
    170 	check("first stdin Write", err)
    171 	if g, e := line("first output line", outbr), "O:I am output"; g != e {
    172 		t.Errorf("got %q, want %q", g, e)
    173 	}
    174 
    175 	_, err = stdin.Write([]byte("E:I am error\n"))
    176 	check("second stdin Write", err)
    177 	if g, e := line("first error line", errbr), "E:I am error"; g != e {
    178 		t.Errorf("got %q, want %q", g, e)
    179 	}
    180 
    181 	_, err = stdin.Write([]byte("O:I am output2\n"))
    182 	check("third stdin Write 3", err)
    183 	if g, e := line("second output line", outbr), "O:I am output2"; g != e {
    184 		t.Errorf("got %q, want %q", g, e)
    185 	}
    186 
    187 	stdin.Close()
    188 	err = c.Wait()
    189 	check("Wait", err)
    190 }
    191 
    192 const stdinCloseTestString = "Some test string."
    193 
    194 // Issue 6270.
    195 func TestStdinClose(t *testing.T) {
    196 	check := func(what string, err error) {
    197 		if err != nil {
    198 			t.Fatalf("%s: %v", what, err)
    199 		}
    200 	}
    201 	cmd := helperCommand(t, "stdinClose")
    202 	stdin, err := cmd.StdinPipe()
    203 	check("StdinPipe", err)
    204 	// Check that we can access methods of the underlying os.File.`
    205 	if _, ok := stdin.(interface {
    206 		Fd() uintptr
    207 	}); !ok {
    208 		t.Error("can't access methods of underlying *os.File")
    209 	}
    210 	check("Start", cmd.Start())
    211 	go func() {
    212 		_, err := io.Copy(stdin, strings.NewReader(stdinCloseTestString))
    213 		check("Copy", err)
    214 		// Before the fix, this next line would race with cmd.Wait.
    215 		check("Close", stdin.Close())
    216 	}()
    217 	check("Wait", cmd.Wait())
    218 }
    219 
    220 // Issue 5071
    221 func TestPipeLookPathLeak(t *testing.T) {
    222 	fd0, lsof0 := numOpenFDS(t)
    223 	for i := 0; i < 4; i++ {
    224 		cmd := exec.Command("something-that-does-not-exist-binary")
    225 		cmd.StdoutPipe()
    226 		cmd.StderrPipe()
    227 		cmd.StdinPipe()
    228 		if err := cmd.Run(); err == nil {
    229 			t.Fatal("unexpected success")
    230 		}
    231 	}
    232 	for triesLeft := 3; triesLeft >= 0; triesLeft-- {
    233 		open, lsof := numOpenFDS(t)
    234 		fdGrowth := open - fd0
    235 		if fdGrowth > 2 {
    236 			if triesLeft > 0 {
    237 				// Work around what appears to be a race with Linux's
    238 				// proc filesystem (as used by lsof). It seems to only
    239 				// be eventually consistent. Give it awhile to settle.
    240 				// See golang.org/issue/7808
    241 				time.Sleep(100 * time.Millisecond)
    242 				continue
    243 			}
    244 			t.Errorf("leaked %d fds; want ~0; have:\n%s\noriginally:\n%s", fdGrowth, lsof, lsof0)
    245 		}
    246 		break
    247 	}
    248 }
    249 
    250 func numOpenFDS(t *testing.T) (n int, lsof []byte) {
    251 	if runtime.GOOS == "android" {
    252 		// Android's stock lsof does not obey the -p option,
    253 		// so extra filtering is needed. (golang.org/issue/10206)
    254 		return numOpenFDsAndroid(t)
    255 	}
    256 
    257 	lsof, err := exec.Command("lsof", "-b", "-n", "-p", strconv.Itoa(os.Getpid())).Output()
    258 	if err != nil {
    259 		t.Skip("skipping test; error finding or running lsof")
    260 	}
    261 	return bytes.Count(lsof, []byte("\n")), lsof
    262 }
    263 
    264 func numOpenFDsAndroid(t *testing.T) (n int, lsof []byte) {
    265 	raw, err := exec.Command("lsof").Output()
    266 	if err != nil {
    267 		t.Skip("skipping test; error finding or running lsof")
    268 	}
    269 
    270 	// First find the PID column index by parsing the first line, and
    271 	// select lines containing pid in the column.
    272 	pid := []byte(strconv.Itoa(os.Getpid()))
    273 	pidCol := -1
    274 
    275 	s := bufio.NewScanner(bytes.NewReader(raw))
    276 	for s.Scan() {
    277 		line := s.Bytes()
    278 		fields := bytes.Fields(line)
    279 		if pidCol < 0 {
    280 			for i, v := range fields {
    281 				if bytes.Equal(v, []byte("PID")) {
    282 					pidCol = i
    283 					break
    284 				}
    285 			}
    286 			lsof = append(lsof, line...)
    287 			continue
    288 		}
    289 		if bytes.Equal(fields[pidCol], pid) {
    290 			lsof = append(lsof, '\n')
    291 			lsof = append(lsof, line...)
    292 		}
    293 	}
    294 	if pidCol < 0 {
    295 		t.Fatal("error processing lsof output: unexpected header format")
    296 	}
    297 	if err := s.Err(); err != nil {
    298 		t.Fatalf("error processing lsof output: %v", err)
    299 	}
    300 	return bytes.Count(lsof, []byte("\n")), lsof
    301 }
    302 
    303 var testedAlreadyLeaked = false
    304 
    305 // basefds returns the number of expected file descriptors
    306 // to be present in a process at start.
    307 func basefds() uintptr {
    308 	return os.Stderr.Fd() + 1
    309 }
    310 
    311 func closeUnexpectedFds(t *testing.T, m string) {
    312 	for fd := basefds(); fd <= 101; fd++ {
    313 		err := os.NewFile(fd, "").Close()
    314 		if err == nil {
    315 			t.Logf("%s: Something already leaked - closed fd %d", m, fd)
    316 		}
    317 	}
    318 }
    319 
    320 func TestExtraFilesFDShuffle(t *testing.T) {
    321 	t.Skip("flaky test; see https://golang.org/issue/5780")
    322 	switch runtime.GOOS {
    323 	case "darwin":
    324 		// TODO(cnicolaou): https://golang.org/issue/2603
    325 		// leads to leaked file descriptors in this test when it's
    326 		// run from a builder.
    327 		closeUnexpectedFds(t, "TestExtraFilesFDShuffle")
    328 	case "netbsd":
    329 		// https://golang.org/issue/3955
    330 		closeUnexpectedFds(t, "TestExtraFilesFDShuffle")
    331 	case "windows":
    332 		t.Skip("no operating system support; skipping")
    333 	}
    334 
    335 	// syscall.StartProcess maps all the FDs passed to it in
    336 	// ProcAttr.Files (the concatenation of stdin,stdout,stderr and
    337 	// ExtraFiles) into consecutive FDs in the child, that is:
    338 	// Files{11, 12, 6, 7, 9, 3} should result in the file
    339 	// represented by FD 11 in the parent being made available as 0
    340 	// in the child, 12 as 1, etc.
    341 	//
    342 	// We want to test that FDs in the child do not get overwritten
    343 	// by one another as this shuffle occurs. The original implementation
    344 	// was buggy in that in some data dependent cases it would ovewrite
    345 	// stderr in the child with one of the ExtraFile members.
    346 	// Testing for this case is difficult because it relies on using
    347 	// the same FD values as that case. In particular, an FD of 3
    348 	// must be at an index of 4 or higher in ProcAttr.Files and
    349 	// the FD of the write end of the Stderr pipe (as obtained by
    350 	// StderrPipe()) must be the same as the size of ProcAttr.Files;
    351 	// therefore we test that the read end of this pipe (which is what
    352 	// is returned to the parent by StderrPipe() being one less than
    353 	// the size of ProcAttr.Files, i.e. 3+len(cmd.ExtraFiles).
    354 	//
    355 	// Moving this test case around within the overall tests may
    356 	// affect the FDs obtained and hence the checks to catch these cases.
    357 	npipes := 2
    358 	c := helperCommand(t, "extraFilesAndPipes", strconv.Itoa(npipes+1))
    359 	rd, wr, _ := os.Pipe()
    360 	defer rd.Close()
    361 	if rd.Fd() != 3 {
    362 		t.Errorf("bad test value for test pipe: fd %d", rd.Fd())
    363 	}
    364 	stderr, _ := c.StderrPipe()
    365 	wr.WriteString("_LAST")
    366 	wr.Close()
    367 
    368 	pipes := make([]struct {
    369 		r, w *os.File
    370 	}, npipes)
    371 	data := []string{"a", "b"}
    372 
    373 	for i := 0; i < npipes; i++ {
    374 		r, w, err := os.Pipe()
    375 		if err != nil {
    376 			t.Fatalf("unexpected error creating pipe: %s", err)
    377 		}
    378 		pipes[i].r = r
    379 		pipes[i].w = w
    380 		w.WriteString(data[i])
    381 		c.ExtraFiles = append(c.ExtraFiles, pipes[i].r)
    382 		defer func() {
    383 			r.Close()
    384 			w.Close()
    385 		}()
    386 	}
    387 	// Put fd 3 at the end.
    388 	c.ExtraFiles = append(c.ExtraFiles, rd)
    389 
    390 	stderrFd := int(stderr.(*os.File).Fd())
    391 	if stderrFd != ((len(c.ExtraFiles) + 3) - 1) {
    392 		t.Errorf("bad test value for stderr pipe")
    393 	}
    394 
    395 	expected := "child: " + strings.Join(data, "") + "_LAST"
    396 
    397 	err := c.Start()
    398 	if err != nil {
    399 		t.Fatalf("Run: %v", err)
    400 	}
    401 	ch := make(chan string, 1)
    402 	go func(ch chan string) {
    403 		buf := make([]byte, 512)
    404 		n, err := stderr.Read(buf)
    405 		if err != nil {
    406 			t.Fatalf("Read: %s", err)
    407 			ch <- err.Error()
    408 		} else {
    409 			ch <- string(buf[:n])
    410 		}
    411 		close(ch)
    412 	}(ch)
    413 	select {
    414 	case m := <-ch:
    415 		if m != expected {
    416 			t.Errorf("Read: '%s' not '%s'", m, expected)
    417 		}
    418 	case <-time.After(5 * time.Second):
    419 		t.Errorf("Read timedout")
    420 	}
    421 	c.Wait()
    422 }
    423 
    424 func TestExtraFiles(t *testing.T) {
    425 	testenv.MustHaveExec(t)
    426 
    427 	if runtime.GOOS == "windows" {
    428 		t.Skipf("skipping test on %q", runtime.GOOS)
    429 	}
    430 
    431 	// Ensure that file descriptors have not already been leaked into
    432 	// our environment.
    433 	if !testedAlreadyLeaked {
    434 		testedAlreadyLeaked = true
    435 		closeUnexpectedFds(t, "TestExtraFiles")
    436 	}
    437 
    438 	// Force network usage, to verify the epoll (or whatever) fd
    439 	// doesn't leak to the child,
    440 	ln, err := net.Listen("tcp", "127.0.0.1:0")
    441 	if err != nil {
    442 		t.Fatal(err)
    443 	}
    444 	defer ln.Close()
    445 
    446 	// Make sure duplicated fds don't leak to the child.
    447 	f, err := ln.(*net.TCPListener).File()
    448 	if err != nil {
    449 		t.Fatal(err)
    450 	}
    451 	defer f.Close()
    452 	ln2, err := net.FileListener(f)
    453 	if err != nil {
    454 		t.Fatal(err)
    455 	}
    456 	defer ln2.Close()
    457 
    458 	// Force TLS root certs to be loaded (which might involve
    459 	// cgo), to make sure none of that potential C code leaks fds.
    460 	ts := httptest.NewUnstartedServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {}))
    461 	// quiet expected TLS handshake error "remote error: bad certificate"
    462 	ts.Config.ErrorLog = log.New(ioutil.Discard, "", 0)
    463 	ts.StartTLS()
    464 	defer ts.Close()
    465 	_, err = http.Get(ts.URL)
    466 	if err == nil {
    467 		t.Errorf("success trying to fetch %s; want an error", ts.URL)
    468 	}
    469 
    470 	tf, err := ioutil.TempFile("", "")
    471 	if err != nil {
    472 		t.Fatalf("TempFile: %v", err)
    473 	}
    474 	defer os.Remove(tf.Name())
    475 	defer tf.Close()
    476 
    477 	const text = "Hello, fd 3!"
    478 	_, err = tf.Write([]byte(text))
    479 	if err != nil {
    480 		t.Fatalf("Write: %v", err)
    481 	}
    482 	_, err = tf.Seek(0, os.SEEK_SET)
    483 	if err != nil {
    484 		t.Fatalf("Seek: %v", err)
    485 	}
    486 
    487 	c := helperCommand(t, "read3")
    488 	var stdout, stderr bytes.Buffer
    489 	c.Stdout = &stdout
    490 	c.Stderr = &stderr
    491 	c.ExtraFiles = []*os.File{tf}
    492 	err = c.Run()
    493 	if err != nil {
    494 		t.Fatalf("Run: %v; stdout %q, stderr %q", err, stdout.Bytes(), stderr.Bytes())
    495 	}
    496 	if stdout.String() != text {
    497 		t.Errorf("got stdout %q, stderr %q; want %q on stdout", stdout.String(), stderr.String(), text)
    498 	}
    499 }
    500 
    501 func TestExtraFilesRace(t *testing.T) {
    502 	if runtime.GOOS == "windows" {
    503 		t.Skip("no operating system support; skipping")
    504 	}
    505 	listen := func() net.Listener {
    506 		ln, err := net.Listen("tcp", "127.0.0.1:0")
    507 		if err != nil {
    508 			t.Fatal(err)
    509 		}
    510 		return ln
    511 	}
    512 	listenerFile := func(ln net.Listener) *os.File {
    513 		f, err := ln.(*net.TCPListener).File()
    514 		if err != nil {
    515 			t.Fatal(err)
    516 		}
    517 		return f
    518 	}
    519 	runCommand := func(c *exec.Cmd, out chan<- string) {
    520 		bout, err := c.CombinedOutput()
    521 		if err != nil {
    522 			out <- "ERROR:" + err.Error()
    523 		} else {
    524 			out <- string(bout)
    525 		}
    526 	}
    527 
    528 	for i := 0; i < 10; i++ {
    529 		la := listen()
    530 		ca := helperCommand(t, "describefiles")
    531 		ca.ExtraFiles = []*os.File{listenerFile(la)}
    532 		lb := listen()
    533 		cb := helperCommand(t, "describefiles")
    534 		cb.ExtraFiles = []*os.File{listenerFile(lb)}
    535 		ares := make(chan string)
    536 		bres := make(chan string)
    537 		go runCommand(ca, ares)
    538 		go runCommand(cb, bres)
    539 		if got, want := <-ares, fmt.Sprintf("fd3: listener %s\n", la.Addr()); got != want {
    540 			t.Errorf("iteration %d, process A got:\n%s\nwant:\n%s\n", i, got, want)
    541 		}
    542 		if got, want := <-bres, fmt.Sprintf("fd3: listener %s\n", lb.Addr()); got != want {
    543 			t.Errorf("iteration %d, process B got:\n%s\nwant:\n%s\n", i, got, want)
    544 		}
    545 		la.Close()
    546 		lb.Close()
    547 		for _, f := range ca.ExtraFiles {
    548 			f.Close()
    549 		}
    550 		for _, f := range cb.ExtraFiles {
    551 			f.Close()
    552 		}
    553 
    554 	}
    555 }
    556 
    557 // TestHelperProcess isn't a real test. It's used as a helper process
    558 // for TestParameterRun.
    559 func TestHelperProcess(*testing.T) {
    560 	if os.Getenv("GO_WANT_HELPER_PROCESS") != "1" {
    561 		return
    562 	}
    563 	defer os.Exit(0)
    564 
    565 	// Determine which command to use to display open files.
    566 	ofcmd := "lsof"
    567 	switch runtime.GOOS {
    568 	case "dragonfly", "freebsd", "netbsd", "openbsd":
    569 		ofcmd = "fstat"
    570 	case "plan9":
    571 		ofcmd = "/bin/cat"
    572 	}
    573 
    574 	args := os.Args
    575 	for len(args) > 0 {
    576 		if args[0] == "--" {
    577 			args = args[1:]
    578 			break
    579 		}
    580 		args = args[1:]
    581 	}
    582 	if len(args) == 0 {
    583 		fmt.Fprintf(os.Stderr, "No command\n")
    584 		os.Exit(2)
    585 	}
    586 
    587 	cmd, args := args[0], args[1:]
    588 	switch cmd {
    589 	case "echo":
    590 		iargs := []interface{}{}
    591 		for _, s := range args {
    592 			iargs = append(iargs, s)
    593 		}
    594 		fmt.Println(iargs...)
    595 	case "cat":
    596 		if len(args) == 0 {
    597 			io.Copy(os.Stdout, os.Stdin)
    598 			return
    599 		}
    600 		exit := 0
    601 		for _, fn := range args {
    602 			f, err := os.Open(fn)
    603 			if err != nil {
    604 				fmt.Fprintf(os.Stderr, "Error: %v\n", err)
    605 				exit = 2
    606 			} else {
    607 				defer f.Close()
    608 				io.Copy(os.Stdout, f)
    609 			}
    610 		}
    611 		os.Exit(exit)
    612 	case "pipetest":
    613 		bufr := bufio.NewReader(os.Stdin)
    614 		for {
    615 			line, _, err := bufr.ReadLine()
    616 			if err == io.EOF {
    617 				break
    618 			} else if err != nil {
    619 				os.Exit(1)
    620 			}
    621 			if bytes.HasPrefix(line, []byte("O:")) {
    622 				os.Stdout.Write(line)
    623 				os.Stdout.Write([]byte{'\n'})
    624 			} else if bytes.HasPrefix(line, []byte("E:")) {
    625 				os.Stderr.Write(line)
    626 				os.Stderr.Write([]byte{'\n'})
    627 			} else {
    628 				os.Exit(1)
    629 			}
    630 		}
    631 	case "stdinClose":
    632 		b, err := ioutil.ReadAll(os.Stdin)
    633 		if err != nil {
    634 			fmt.Fprintf(os.Stderr, "Error: %v\n", err)
    635 			os.Exit(1)
    636 		}
    637 		if s := string(b); s != stdinCloseTestString {
    638 			fmt.Fprintf(os.Stderr, "Error: Read %q, want %q", s, stdinCloseTestString)
    639 			os.Exit(1)
    640 		}
    641 		os.Exit(0)
    642 	case "read3": // read fd 3
    643 		fd3 := os.NewFile(3, "fd3")
    644 		bs, err := ioutil.ReadAll(fd3)
    645 		if err != nil {
    646 			fmt.Printf("ReadAll from fd 3: %v", err)
    647 			os.Exit(1)
    648 		}
    649 		switch runtime.GOOS {
    650 		case "dragonfly":
    651 			// TODO(jsing): Determine why DragonFly is leaking
    652 			// file descriptors...
    653 		case "darwin":
    654 			// TODO(bradfitz): broken? Sometimes.
    655 			// https://golang.org/issue/2603
    656 			// Skip this additional part of the test for now.
    657 		case "netbsd":
    658 			// TODO(jsing): This currently fails on NetBSD due to
    659 			// the cloned file descriptors that result from opening
    660 			// /dev/urandom.
    661 			// https://golang.org/issue/3955
    662 		case "plan9":
    663 			// TODO(0intro): Determine why Plan 9 is leaking
    664 			// file descriptors.
    665 			// https://golang.org/issue/7118
    666 		case "solaris":
    667 			// TODO(aram): This fails on Solaris because libc opens
    668 			// its own files, as it sees fit. Darwin does the same,
    669 			// see: https://golang.org/issue/2603
    670 		default:
    671 			// Now verify that there are no other open fds.
    672 			var files []*os.File
    673 			for wantfd := basefds() + 1; wantfd <= 100; wantfd++ {
    674 				f, err := os.Open(os.Args[0])
    675 				if err != nil {
    676 					fmt.Printf("error opening file with expected fd %d: %v", wantfd, err)
    677 					os.Exit(1)
    678 				}
    679 				if got := f.Fd(); got != wantfd {
    680 					fmt.Printf("leaked parent file. fd = %d; want %d\n", got, wantfd)
    681 					var args []string
    682 					switch runtime.GOOS {
    683 					case "plan9":
    684 						args = []string{fmt.Sprintf("/proc/%d/fd", os.Getpid())}
    685 					default:
    686 						args = []string{"-p", fmt.Sprint(os.Getpid())}
    687 					}
    688 					out, _ := exec.Command(ofcmd, args...).CombinedOutput()
    689 					fmt.Print(string(out))
    690 					os.Exit(1)
    691 				}
    692 				files = append(files, f)
    693 			}
    694 			for _, f := range files {
    695 				f.Close()
    696 			}
    697 		}
    698 		// Referring to fd3 here ensures that it is not
    699 		// garbage collected, and therefore closed, while
    700 		// executing the wantfd loop above.  It doesn't matter
    701 		// what we do with fd3 as long as we refer to it;
    702 		// closing it is the easy choice.
    703 		fd3.Close()
    704 		os.Stdout.Write(bs)
    705 	case "exit":
    706 		n, _ := strconv.Atoi(args[0])
    707 		os.Exit(n)
    708 	case "describefiles":
    709 		f := os.NewFile(3, fmt.Sprintf("fd3"))
    710 		ln, err := net.FileListener(f)
    711 		if err == nil {
    712 			fmt.Printf("fd3: listener %s\n", ln.Addr())
    713 			ln.Close()
    714 		}
    715 		os.Exit(0)
    716 	case "extraFilesAndPipes":
    717 		n, _ := strconv.Atoi(args[0])
    718 		pipes := make([]*os.File, n)
    719 		for i := 0; i < n; i++ {
    720 			pipes[i] = os.NewFile(uintptr(3+i), strconv.Itoa(i))
    721 		}
    722 		response := ""
    723 		for i, r := range pipes {
    724 			ch := make(chan string, 1)
    725 			go func(c chan string) {
    726 				buf := make([]byte, 10)
    727 				n, err := r.Read(buf)
    728 				if err != nil {
    729 					fmt.Fprintf(os.Stderr, "Child: read error: %v on pipe %d\n", err, i)
    730 					os.Exit(1)
    731 				}
    732 				c <- string(buf[:n])
    733 				close(c)
    734 			}(ch)
    735 			select {
    736 			case m := <-ch:
    737 				response = response + m
    738 			case <-time.After(5 * time.Second):
    739 				fmt.Fprintf(os.Stderr, "Child: Timeout reading from pipe: %d\n", i)
    740 				os.Exit(1)
    741 			}
    742 		}
    743 		fmt.Fprintf(os.Stderr, "child: %s", response)
    744 		os.Exit(0)
    745 	case "exec":
    746 		cmd := exec.Command(args[1])
    747 		cmd.Dir = args[0]
    748 		output, err := cmd.CombinedOutput()
    749 		if err != nil {
    750 			fmt.Fprintf(os.Stderr, "Child: %s %s", err, string(output))
    751 			os.Exit(1)
    752 		}
    753 		fmt.Printf("%s", string(output))
    754 		os.Exit(0)
    755 	case "lookpath":
    756 		p, err := exec.LookPath(args[0])
    757 		if err != nil {
    758 			fmt.Fprintf(os.Stderr, "LookPath failed: %v\n", err)
    759 			os.Exit(1)
    760 		}
    761 		fmt.Print(p)
    762 		os.Exit(0)
    763 	default:
    764 		fmt.Fprintf(os.Stderr, "Unknown command %q\n", cmd)
    765 		os.Exit(2)
    766 	}
    767 }
    768 
    769 // Issue 9173: ignore stdin pipe writes if the program completes successfully.
    770 func TestIgnorePipeErrorOnSuccess(t *testing.T) {
    771 	testenv.MustHaveExec(t)
    772 
    773 	// We really only care about testing this on Unixy things.
    774 	if runtime.GOOS == "windows" || runtime.GOOS == "plan9" {
    775 		t.Skipf("skipping test on %q", runtime.GOOS)
    776 	}
    777 
    778 	cmd := helperCommand(t, "echo", "foo")
    779 	var out bytes.Buffer
    780 	cmd.Stdin = strings.NewReader(strings.Repeat("x", 10<<20))
    781 	cmd.Stdout = &out
    782 	if err := cmd.Run(); err != nil {
    783 		t.Fatal(err)
    784 	}
    785 	if got, want := out.String(), "foo\n"; got != want {
    786 		t.Errorf("output = %q; want %q", got, want)
    787 	}
    788 }
    789 
    790 type badWriter struct{}
    791 
    792 func (w *badWriter) Write(data []byte) (int, error) {
    793 	return 0, io.ErrUnexpectedEOF
    794 }
    795 
    796 func TestClosePipeOnCopyError(t *testing.T) {
    797 	testenv.MustHaveExec(t)
    798 
    799 	if runtime.GOOS == "windows" || runtime.GOOS == "plan9" {
    800 		t.Skipf("skipping test on %s - no yes command", runtime.GOOS)
    801 	}
    802 	cmd := exec.Command("yes")
    803 	cmd.Stdout = new(badWriter)
    804 	c := make(chan int, 1)
    805 	go func() {
    806 		err := cmd.Run()
    807 		if err == nil {
    808 			t.Errorf("yes completed successfully")
    809 		}
    810 		c <- 1
    811 	}()
    812 	select {
    813 	case <-c:
    814 		// ok
    815 	case <-time.After(5 * time.Second):
    816 		t.Fatalf("yes got stuck writing to bad writer")
    817 	}
    818 }
    819