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