github.com/m10x/go/src@v0.0.0-20220112094212-ba61592315da/os/exec/exec_test.go (about) 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 "context" 14 "fmt" 15 "internal/poll" 16 "internal/testenv" 17 "io" 18 "log" 19 "net" 20 "net/http" 21 "net/http/httptest" 22 "os" 23 "os/exec" 24 "os/exec/internal/fdtest" 25 "path/filepath" 26 "reflect" 27 "runtime" 28 "strconv" 29 "strings" 30 "testing" 31 "time" 32 ) 33 34 // haveUnexpectedFDs is set at init time to report whether any file descriptors 35 // were open at program start. 36 var haveUnexpectedFDs bool 37 38 func init() { 39 if os.Getenv("GO_WANT_HELPER_PROCESS") == "1" { 40 return 41 } 42 if runtime.GOOS == "windows" { 43 return 44 } 45 for fd := uintptr(3); fd <= 100; fd++ { 46 if poll.IsPollDescriptor(fd) { 47 continue 48 } 49 50 if fdtest.Exists(fd) { 51 haveUnexpectedFDs = true 52 return 53 } 54 } 55 } 56 57 func helperCommandContext(t *testing.T, ctx context.Context, s ...string) (cmd *exec.Cmd) { 58 testenv.MustHaveExec(t) 59 60 cs := []string{"-test.run=TestHelperProcess", "--"} 61 cs = append(cs, s...) 62 if ctx != nil { 63 cmd = exec.CommandContext(ctx, os.Args[0], cs...) 64 } else { 65 cmd = exec.Command(os.Args[0], cs...) 66 } 67 cmd.Env = append(os.Environ(), "GO_WANT_HELPER_PROCESS=1") 68 return cmd 69 } 70 71 func helperCommand(t *testing.T, s ...string) *exec.Cmd { 72 return helperCommandContext(t, nil, s...) 73 } 74 75 func TestEcho(t *testing.T) { 76 bs, err := helperCommand(t, "echo", "foo bar", "baz").Output() 77 if err != nil { 78 t.Errorf("echo: %v", err) 79 } 80 if g, e := string(bs), "foo bar baz\n"; g != e { 81 t.Errorf("echo: want %q, got %q", e, g) 82 } 83 } 84 85 func TestCommandRelativeName(t *testing.T) { 86 testenv.MustHaveExec(t) 87 88 // Run our own binary as a relative path 89 // (e.g. "_test/exec.test") our parent directory. 90 base := filepath.Base(os.Args[0]) // "exec.test" 91 dir := filepath.Dir(os.Args[0]) // "/tmp/go-buildNNNN/os/exec/_test" 92 if dir == "." { 93 t.Skip("skipping; running test at root somehow") 94 } 95 parentDir := filepath.Dir(dir) // "/tmp/go-buildNNNN/os/exec" 96 dirBase := filepath.Base(dir) // "_test" 97 if dirBase == "." { 98 t.Skipf("skipping; unexpected shallow dir of %q", dir) 99 } 100 101 cmd := exec.Command(filepath.Join(dirBase, base), "-test.run=TestHelperProcess", "--", "echo", "foo") 102 cmd.Dir = parentDir 103 cmd.Env = []string{"GO_WANT_HELPER_PROCESS=1"} 104 105 out, err := cmd.Output() 106 if err != nil { 107 t.Errorf("echo: %v", err) 108 } 109 if g, e := string(out), "foo\n"; g != e { 110 t.Errorf("echo: want %q, got %q", e, g) 111 } 112 } 113 114 func TestCatStdin(t *testing.T) { 115 // Cat, testing stdin and stdout. 116 input := "Input string\nLine 2" 117 p := helperCommand(t, "cat") 118 p.Stdin = strings.NewReader(input) 119 bs, err := p.Output() 120 if err != nil { 121 t.Errorf("cat: %v", err) 122 } 123 s := string(bs) 124 if s != input { 125 t.Errorf("cat: want %q, got %q", input, s) 126 } 127 } 128 129 func TestEchoFileRace(t *testing.T) { 130 cmd := helperCommand(t, "echo") 131 stdin, err := cmd.StdinPipe() 132 if err != nil { 133 t.Fatalf("StdinPipe: %v", err) 134 } 135 if err := cmd.Start(); err != nil { 136 t.Fatalf("Start: %v", err) 137 } 138 wrote := make(chan bool) 139 go func() { 140 defer close(wrote) 141 fmt.Fprint(stdin, "echo\n") 142 }() 143 if err := cmd.Wait(); err != nil { 144 t.Fatalf("Wait: %v", err) 145 } 146 <-wrote 147 } 148 149 func TestCatGoodAndBadFile(t *testing.T) { 150 // Testing combined output and error values. 151 bs, err := helperCommand(t, "cat", "/bogus/file.foo", "exec_test.go").CombinedOutput() 152 if _, ok := err.(*exec.ExitError); !ok { 153 t.Errorf("expected *exec.ExitError from cat combined; got %T: %v", err, err) 154 } 155 errLine, body, ok := strings.Cut(string(bs), "\n") 156 if !ok { 157 t.Fatalf("expected two lines from cat; got %q", bs) 158 } 159 if !strings.HasPrefix(errLine, "Error: open /bogus/file.foo") { 160 t.Errorf("expected stderr to complain about file; got %q", errLine) 161 } 162 if !strings.Contains(body, "func TestHelperProcess(t *testing.T)") { 163 t.Errorf("expected test code; got %q (len %d)", body, len(body)) 164 } 165 } 166 167 func TestNoExistExecutable(t *testing.T) { 168 // Can't run a non-existent executable 169 err := exec.Command("/no-exist-executable").Run() 170 if err == nil { 171 t.Error("expected error from /no-exist-executable") 172 } 173 } 174 175 func TestExitStatus(t *testing.T) { 176 // Test that exit values are returned correctly 177 cmd := helperCommand(t, "exit", "42") 178 err := cmd.Run() 179 want := "exit status 42" 180 switch runtime.GOOS { 181 case "plan9": 182 want = fmt.Sprintf("exit status: '%s %d: 42'", filepath.Base(cmd.Path), cmd.ProcessState.Pid()) 183 } 184 if werr, ok := err.(*exec.ExitError); ok { 185 if s := werr.Error(); s != want { 186 t.Errorf("from exit 42 got exit %q, want %q", s, want) 187 } 188 } else { 189 t.Fatalf("expected *exec.ExitError from exit 42; got %T: %v", err, err) 190 } 191 } 192 193 func TestExitCode(t *testing.T) { 194 // Test that exit code are returned correctly 195 cmd := helperCommand(t, "exit", "42") 196 cmd.Run() 197 want := 42 198 if runtime.GOOS == "plan9" { 199 want = 1 200 } 201 got := cmd.ProcessState.ExitCode() 202 if want != got { 203 t.Errorf("ExitCode got %d, want %d", got, want) 204 } 205 206 cmd = helperCommand(t, "/no-exist-executable") 207 cmd.Run() 208 want = 2 209 if runtime.GOOS == "plan9" { 210 want = 1 211 } 212 got = cmd.ProcessState.ExitCode() 213 if want != got { 214 t.Errorf("ExitCode got %d, want %d", got, want) 215 } 216 217 cmd = helperCommand(t, "exit", "255") 218 cmd.Run() 219 want = 255 220 if runtime.GOOS == "plan9" { 221 want = 1 222 } 223 got = cmd.ProcessState.ExitCode() 224 if want != got { 225 t.Errorf("ExitCode got %d, want %d", got, want) 226 } 227 228 cmd = helperCommand(t, "cat") 229 cmd.Run() 230 want = 0 231 got = cmd.ProcessState.ExitCode() 232 if want != got { 233 t.Errorf("ExitCode got %d, want %d", got, want) 234 } 235 236 // Test when command does not call Run(). 237 cmd = helperCommand(t, "cat") 238 want = -1 239 got = cmd.ProcessState.ExitCode() 240 if want != got { 241 t.Errorf("ExitCode got %d, want %d", got, want) 242 } 243 } 244 245 func TestPipes(t *testing.T) { 246 check := func(what string, err error) { 247 if err != nil { 248 t.Fatalf("%s: %v", what, err) 249 } 250 } 251 // Cat, testing stdin and stdout. 252 c := helperCommand(t, "pipetest") 253 stdin, err := c.StdinPipe() 254 check("StdinPipe", err) 255 stdout, err := c.StdoutPipe() 256 check("StdoutPipe", err) 257 stderr, err := c.StderrPipe() 258 check("StderrPipe", err) 259 260 outbr := bufio.NewReader(stdout) 261 errbr := bufio.NewReader(stderr) 262 line := func(what string, br *bufio.Reader) string { 263 line, _, err := br.ReadLine() 264 if err != nil { 265 t.Fatalf("%s: %v", what, err) 266 } 267 return string(line) 268 } 269 270 err = c.Start() 271 check("Start", err) 272 273 _, err = stdin.Write([]byte("O:I am output\n")) 274 check("first stdin Write", err) 275 if g, e := line("first output line", outbr), "O:I am output"; g != e { 276 t.Errorf("got %q, want %q", g, e) 277 } 278 279 _, err = stdin.Write([]byte("E:I am error\n")) 280 check("second stdin Write", err) 281 if g, e := line("first error line", errbr), "E:I am error"; g != e { 282 t.Errorf("got %q, want %q", g, e) 283 } 284 285 _, err = stdin.Write([]byte("O:I am output2\n")) 286 check("third stdin Write 3", err) 287 if g, e := line("second output line", outbr), "O:I am output2"; g != e { 288 t.Errorf("got %q, want %q", g, e) 289 } 290 291 stdin.Close() 292 err = c.Wait() 293 check("Wait", err) 294 } 295 296 const stdinCloseTestString = "Some test string." 297 298 // Issue 6270. 299 func TestStdinClose(t *testing.T) { 300 check := func(what string, err error) { 301 if err != nil { 302 t.Fatalf("%s: %v", what, err) 303 } 304 } 305 cmd := helperCommand(t, "stdinClose") 306 stdin, err := cmd.StdinPipe() 307 check("StdinPipe", err) 308 // Check that we can access methods of the underlying os.File.` 309 if _, ok := stdin.(interface { 310 Fd() uintptr 311 }); !ok { 312 t.Error("can't access methods of underlying *os.File") 313 } 314 check("Start", cmd.Start()) 315 go func() { 316 _, err := io.Copy(stdin, strings.NewReader(stdinCloseTestString)) 317 check("Copy", err) 318 // Before the fix, this next line would race with cmd.Wait. 319 check("Close", stdin.Close()) 320 }() 321 check("Wait", cmd.Wait()) 322 } 323 324 // Issue 17647. 325 // It used to be the case that TestStdinClose, above, would fail when 326 // run under the race detector. This test is a variant of TestStdinClose 327 // that also used to fail when run under the race detector. 328 // This test is run by cmd/dist under the race detector to verify that 329 // the race detector no longer reports any problems. 330 func TestStdinCloseRace(t *testing.T) { 331 cmd := helperCommand(t, "stdinClose") 332 stdin, err := cmd.StdinPipe() 333 if err != nil { 334 t.Fatalf("StdinPipe: %v", err) 335 } 336 if err := cmd.Start(); err != nil { 337 t.Fatalf("Start: %v", err) 338 } 339 go func() { 340 // We don't check the error return of Kill. It is 341 // possible that the process has already exited, in 342 // which case Kill will return an error "process 343 // already finished". The purpose of this test is to 344 // see whether the race detector reports an error; it 345 // doesn't matter whether this Kill succeeds or not. 346 cmd.Process.Kill() 347 }() 348 go func() { 349 // Send the wrong string, so that the child fails even 350 // if the other goroutine doesn't manage to kill it first. 351 // This test is to check that the race detector does not 352 // falsely report an error, so it doesn't matter how the 353 // child process fails. 354 io.Copy(stdin, strings.NewReader("unexpected string")) 355 if err := stdin.Close(); err != nil { 356 t.Errorf("stdin.Close: %v", err) 357 } 358 }() 359 if err := cmd.Wait(); err == nil { 360 t.Fatalf("Wait: succeeded unexpectedly") 361 } 362 } 363 364 // Issue 5071 365 func TestPipeLookPathLeak(t *testing.T) { 366 if runtime.GOOS == "windows" { 367 t.Skip("we don't currently suppore counting open handles on windows") 368 } 369 370 openFDs := func() []uintptr { 371 var fds []uintptr 372 for i := uintptr(0); i < 100; i++ { 373 if fdtest.Exists(i) { 374 fds = append(fds, i) 375 } 376 } 377 return fds 378 } 379 380 want := openFDs() 381 for i := 0; i < 6; i++ { 382 cmd := exec.Command("something-that-does-not-exist-executable") 383 cmd.StdoutPipe() 384 cmd.StderrPipe() 385 cmd.StdinPipe() 386 if err := cmd.Run(); err == nil { 387 t.Fatal("unexpected success") 388 } 389 } 390 got := openFDs() 391 if !reflect.DeepEqual(got, want) { 392 t.Errorf("set of open file descriptors changed: got %v, want %v", got, want) 393 } 394 } 395 396 func TestExtraFilesFDShuffle(t *testing.T) { 397 testenv.SkipFlaky(t, 5780) 398 switch runtime.GOOS { 399 case "windows": 400 t.Skip("no operating system support; skipping") 401 } 402 403 // syscall.StartProcess maps all the FDs passed to it in 404 // ProcAttr.Files (the concatenation of stdin,stdout,stderr and 405 // ExtraFiles) into consecutive FDs in the child, that is: 406 // Files{11, 12, 6, 7, 9, 3} should result in the file 407 // represented by FD 11 in the parent being made available as 0 408 // in the child, 12 as 1, etc. 409 // 410 // We want to test that FDs in the child do not get overwritten 411 // by one another as this shuffle occurs. The original implementation 412 // was buggy in that in some data dependent cases it would overwrite 413 // stderr in the child with one of the ExtraFile members. 414 // Testing for this case is difficult because it relies on using 415 // the same FD values as that case. In particular, an FD of 3 416 // must be at an index of 4 or higher in ProcAttr.Files and 417 // the FD of the write end of the Stderr pipe (as obtained by 418 // StderrPipe()) must be the same as the size of ProcAttr.Files; 419 // therefore we test that the read end of this pipe (which is what 420 // is returned to the parent by StderrPipe() being one less than 421 // the size of ProcAttr.Files, i.e. 3+len(cmd.ExtraFiles). 422 // 423 // Moving this test case around within the overall tests may 424 // affect the FDs obtained and hence the checks to catch these cases. 425 npipes := 2 426 c := helperCommand(t, "extraFilesAndPipes", strconv.Itoa(npipes+1)) 427 rd, wr, _ := os.Pipe() 428 defer rd.Close() 429 if rd.Fd() != 3 { 430 t.Errorf("bad test value for test pipe: fd %d", rd.Fd()) 431 } 432 stderr, _ := c.StderrPipe() 433 wr.WriteString("_LAST") 434 wr.Close() 435 436 pipes := make([]struct { 437 r, w *os.File 438 }, npipes) 439 data := []string{"a", "b"} 440 441 for i := 0; i < npipes; i++ { 442 r, w, err := os.Pipe() 443 if err != nil { 444 t.Fatalf("unexpected error creating pipe: %s", err) 445 } 446 pipes[i].r = r 447 pipes[i].w = w 448 w.WriteString(data[i]) 449 c.ExtraFiles = append(c.ExtraFiles, pipes[i].r) 450 defer func() { 451 r.Close() 452 w.Close() 453 }() 454 } 455 // Put fd 3 at the end. 456 c.ExtraFiles = append(c.ExtraFiles, rd) 457 458 stderrFd := int(stderr.(*os.File).Fd()) 459 if stderrFd != ((len(c.ExtraFiles) + 3) - 1) { 460 t.Errorf("bad test value for stderr pipe") 461 } 462 463 expected := "child: " + strings.Join(data, "") + "_LAST" 464 465 err := c.Start() 466 if err != nil { 467 t.Fatalf("Run: %v", err) 468 } 469 ch := make(chan string, 1) 470 go func(ch chan string) { 471 buf := make([]byte, 512) 472 n, err := stderr.Read(buf) 473 if err != nil { 474 t.Errorf("Read: %s", err) 475 ch <- err.Error() 476 } else { 477 ch <- string(buf[:n]) 478 } 479 close(ch) 480 }(ch) 481 select { 482 case m := <-ch: 483 if m != expected { 484 t.Errorf("Read: '%s' not '%s'", m, expected) 485 } 486 case <-time.After(5 * time.Second): 487 t.Errorf("Read timedout") 488 } 489 c.Wait() 490 } 491 492 func TestExtraFiles(t *testing.T) { 493 if haveUnexpectedFDs { 494 // The point of this test is to make sure that any 495 // descriptors we open are marked close-on-exec. 496 // If haveUnexpectedFDs is true then there were other 497 // descriptors open when we started the test, 498 // so those descriptors are clearly not close-on-exec, 499 // and they will confuse the test. We could modify 500 // the test to expect those descriptors to remain open, 501 // but since we don't know where they came from or what 502 // they are doing, that seems fragile. For example, 503 // perhaps they are from the startup code on this 504 // system for some reason. Also, this test is not 505 // system-specific; as long as most systems do not skip 506 // the test, we will still be testing what we care about. 507 t.Skip("skipping test because test was run with FDs open") 508 } 509 510 testenv.MustHaveExec(t) 511 testenv.MustHaveGoBuild(t) 512 513 // This test runs with cgo disabled. External linking needs cgo, so 514 // it doesn't work if external linking is required. 515 testenv.MustInternalLink(t) 516 517 if runtime.GOOS == "windows" { 518 t.Skipf("skipping test on %q", runtime.GOOS) 519 } 520 521 // Force network usage, to verify the epoll (or whatever) fd 522 // doesn't leak to the child, 523 ln, err := net.Listen("tcp", "127.0.0.1:0") 524 if err != nil { 525 t.Fatal(err) 526 } 527 defer ln.Close() 528 529 // Make sure duplicated fds don't leak to the child. 530 f, err := ln.(*net.TCPListener).File() 531 if err != nil { 532 t.Fatal(err) 533 } 534 defer f.Close() 535 ln2, err := net.FileListener(f) 536 if err != nil { 537 t.Fatal(err) 538 } 539 defer ln2.Close() 540 541 // Force TLS root certs to be loaded (which might involve 542 // cgo), to make sure none of that potential C code leaks fds. 543 ts := httptest.NewUnstartedServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})) 544 // quiet expected TLS handshake error "remote error: bad certificate" 545 ts.Config.ErrorLog = log.New(io.Discard, "", 0) 546 ts.StartTLS() 547 defer ts.Close() 548 _, err = http.Get(ts.URL) 549 if err == nil { 550 t.Errorf("success trying to fetch %s; want an error", ts.URL) 551 } 552 553 tf, err := os.CreateTemp("", "") 554 if err != nil { 555 t.Fatalf("TempFile: %v", err) 556 } 557 defer os.Remove(tf.Name()) 558 defer tf.Close() 559 560 const text = "Hello, fd 3!" 561 _, err = tf.Write([]byte(text)) 562 if err != nil { 563 t.Fatalf("Write: %v", err) 564 } 565 _, err = tf.Seek(0, io.SeekStart) 566 if err != nil { 567 t.Fatalf("Seek: %v", err) 568 } 569 570 tempdir := t.TempDir() 571 exe := filepath.Join(tempdir, "read3.exe") 572 573 c := exec.Command(testenv.GoToolPath(t), "build", "-o", exe, "read3.go") 574 // Build the test without cgo, so that C library functions don't 575 // open descriptors unexpectedly. See issue 25628. 576 c.Env = append(os.Environ(), "CGO_ENABLED=0") 577 if output, err := c.CombinedOutput(); err != nil { 578 t.Logf("go build -o %s read3.go\n%s", exe, output) 579 t.Fatalf("go build failed: %v", err) 580 } 581 582 // Use a deadline to try to get some output even if the program hangs. 583 ctx := context.Background() 584 if deadline, ok := t.Deadline(); ok { 585 // Leave a 20% grace period to flush output, which may be large on the 586 // linux/386 builders because we're running the subprocess under strace. 587 deadline = deadline.Add(-time.Until(deadline) / 5) 588 589 var cancel context.CancelFunc 590 ctx, cancel = context.WithDeadline(ctx, deadline) 591 defer cancel() 592 } 593 594 c = exec.CommandContext(ctx, exe) 595 var stdout, stderr bytes.Buffer 596 c.Stdout = &stdout 597 c.Stderr = &stderr 598 c.ExtraFiles = []*os.File{tf} 599 if runtime.GOOS == "illumos" { 600 // Some facilities in illumos are implemented via access 601 // to /proc by libc; such accesses can briefly occupy a 602 // low-numbered fd. If this occurs concurrently with the 603 // test that checks for leaked descriptors, the check can 604 // become confused and report a spurious leaked descriptor. 605 // (See issue #42431 for more detailed analysis.) 606 // 607 // Attempt to constrain the use of additional threads in the 608 // child process to make this test less flaky: 609 c.Env = append(os.Environ(), "GOMAXPROCS=1") 610 } 611 err = c.Run() 612 if err != nil { 613 t.Fatalf("Run: %v\n--- stdout:\n%s--- stderr:\n%s", err, stdout.Bytes(), stderr.Bytes()) 614 } 615 if stdout.String() != text { 616 t.Errorf("got stdout %q, stderr %q; want %q on stdout", stdout.String(), stderr.String(), text) 617 } 618 } 619 620 func TestExtraFilesRace(t *testing.T) { 621 if runtime.GOOS == "windows" { 622 t.Skip("no operating system support; skipping") 623 } 624 listen := func() net.Listener { 625 ln, err := net.Listen("tcp", "127.0.0.1:0") 626 if err != nil { 627 t.Fatal(err) 628 } 629 return ln 630 } 631 listenerFile := func(ln net.Listener) *os.File { 632 f, err := ln.(*net.TCPListener).File() 633 if err != nil { 634 t.Fatal(err) 635 } 636 return f 637 } 638 runCommand := func(c *exec.Cmd, out chan<- string) { 639 bout, err := c.CombinedOutput() 640 if err != nil { 641 out <- "ERROR:" + err.Error() 642 } else { 643 out <- string(bout) 644 } 645 } 646 647 for i := 0; i < 10; i++ { 648 if testing.Short() && i >= 3 { 649 break 650 } 651 la := listen() 652 ca := helperCommand(t, "describefiles") 653 ca.ExtraFiles = []*os.File{listenerFile(la)} 654 lb := listen() 655 cb := helperCommand(t, "describefiles") 656 cb.ExtraFiles = []*os.File{listenerFile(lb)} 657 ares := make(chan string) 658 bres := make(chan string) 659 go runCommand(ca, ares) 660 go runCommand(cb, bres) 661 if got, want := <-ares, fmt.Sprintf("fd3: listener %s\n", la.Addr()); got != want { 662 t.Errorf("iteration %d, process A got:\n%s\nwant:\n%s\n", i, got, want) 663 } 664 if got, want := <-bres, fmt.Sprintf("fd3: listener %s\n", lb.Addr()); got != want { 665 t.Errorf("iteration %d, process B got:\n%s\nwant:\n%s\n", i, got, want) 666 } 667 la.Close() 668 lb.Close() 669 for _, f := range ca.ExtraFiles { 670 f.Close() 671 } 672 for _, f := range cb.ExtraFiles { 673 f.Close() 674 } 675 676 } 677 } 678 679 // TestHelperProcess isn't a real test. It's used as a helper process 680 // for TestParameterRun. 681 func TestHelperProcess(*testing.T) { 682 if os.Getenv("GO_WANT_HELPER_PROCESS") != "1" { 683 return 684 } 685 defer os.Exit(0) 686 687 args := os.Args 688 for len(args) > 0 { 689 if args[0] == "--" { 690 args = args[1:] 691 break 692 } 693 args = args[1:] 694 } 695 if len(args) == 0 { 696 fmt.Fprintf(os.Stderr, "No command\n") 697 os.Exit(2) 698 } 699 700 cmd, args := args[0], args[1:] 701 switch cmd { 702 case "echo": 703 iargs := []any{} 704 for _, s := range args { 705 iargs = append(iargs, s) 706 } 707 fmt.Println(iargs...) 708 case "echoenv": 709 for _, s := range args { 710 fmt.Println(os.Getenv(s)) 711 } 712 os.Exit(0) 713 case "cat": 714 if len(args) == 0 { 715 io.Copy(os.Stdout, os.Stdin) 716 return 717 } 718 exit := 0 719 for _, fn := range args { 720 f, err := os.Open(fn) 721 if err != nil { 722 fmt.Fprintf(os.Stderr, "Error: %v\n", err) 723 exit = 2 724 } else { 725 defer f.Close() 726 io.Copy(os.Stdout, f) 727 } 728 } 729 os.Exit(exit) 730 case "pipetest": 731 bufr := bufio.NewReader(os.Stdin) 732 for { 733 line, _, err := bufr.ReadLine() 734 if err == io.EOF { 735 break 736 } else if err != nil { 737 os.Exit(1) 738 } 739 if bytes.HasPrefix(line, []byte("O:")) { 740 os.Stdout.Write(line) 741 os.Stdout.Write([]byte{'\n'}) 742 } else if bytes.HasPrefix(line, []byte("E:")) { 743 os.Stderr.Write(line) 744 os.Stderr.Write([]byte{'\n'}) 745 } else { 746 os.Exit(1) 747 } 748 } 749 case "stdinClose": 750 b, err := io.ReadAll(os.Stdin) 751 if err != nil { 752 fmt.Fprintf(os.Stderr, "Error: %v\n", err) 753 os.Exit(1) 754 } 755 if s := string(b); s != stdinCloseTestString { 756 fmt.Fprintf(os.Stderr, "Error: Read %q, want %q", s, stdinCloseTestString) 757 os.Exit(1) 758 } 759 os.Exit(0) 760 case "exit": 761 n, _ := strconv.Atoi(args[0]) 762 os.Exit(n) 763 case "describefiles": 764 f := os.NewFile(3, fmt.Sprintf("fd3")) 765 ln, err := net.FileListener(f) 766 if err == nil { 767 fmt.Printf("fd3: listener %s\n", ln.Addr()) 768 ln.Close() 769 } 770 os.Exit(0) 771 case "extraFilesAndPipes": 772 n, _ := strconv.Atoi(args[0]) 773 pipes := make([]*os.File, n) 774 for i := 0; i < n; i++ { 775 pipes[i] = os.NewFile(uintptr(3+i), strconv.Itoa(i)) 776 } 777 response := "" 778 for i, r := range pipes { 779 ch := make(chan string, 1) 780 go func(c chan string) { 781 buf := make([]byte, 10) 782 n, err := r.Read(buf) 783 if err != nil { 784 fmt.Fprintf(os.Stderr, "Child: read error: %v on pipe %d\n", err, i) 785 os.Exit(1) 786 } 787 c <- string(buf[:n]) 788 close(c) 789 }(ch) 790 select { 791 case m := <-ch: 792 response = response + m 793 case <-time.After(5 * time.Second): 794 fmt.Fprintf(os.Stderr, "Child: Timeout reading from pipe: %d\n", i) 795 os.Exit(1) 796 } 797 } 798 fmt.Fprintf(os.Stderr, "child: %s", response) 799 os.Exit(0) 800 case "exec": 801 cmd := exec.Command(args[1]) 802 cmd.Dir = args[0] 803 output, err := cmd.CombinedOutput() 804 if err != nil { 805 fmt.Fprintf(os.Stderr, "Child: %s %s", err, string(output)) 806 os.Exit(1) 807 } 808 fmt.Printf("%s", string(output)) 809 os.Exit(0) 810 case "lookpath": 811 p, err := exec.LookPath(args[0]) 812 if err != nil { 813 fmt.Fprintf(os.Stderr, "LookPath failed: %v\n", err) 814 os.Exit(1) 815 } 816 fmt.Print(p) 817 os.Exit(0) 818 case "stderrfail": 819 fmt.Fprintf(os.Stderr, "some stderr text\n") 820 os.Exit(1) 821 case "sleep": 822 time.Sleep(3 * time.Second) 823 os.Exit(0) 824 case "pipehandle": 825 handle, _ := strconv.ParseUint(args[0], 16, 64) 826 pipe := os.NewFile(uintptr(handle), "") 827 _, err := fmt.Fprint(pipe, args[1]) 828 if err != nil { 829 fmt.Fprintf(os.Stderr, "writing to pipe failed: %v\n", err) 830 os.Exit(1) 831 } 832 pipe.Close() 833 os.Exit(0) 834 default: 835 fmt.Fprintf(os.Stderr, "Unknown command %q\n", cmd) 836 os.Exit(2) 837 } 838 } 839 840 type delayedInfiniteReader struct{} 841 842 func (delayedInfiniteReader) Read(b []byte) (int, error) { 843 time.Sleep(100 * time.Millisecond) 844 for i := range b { 845 b[i] = 'x' 846 } 847 return len(b), nil 848 } 849 850 // Issue 9173: ignore stdin pipe writes if the program completes successfully. 851 func TestIgnorePipeErrorOnSuccess(t *testing.T) { 852 testenv.MustHaveExec(t) 853 854 testWith := func(r io.Reader) func(*testing.T) { 855 return func(t *testing.T) { 856 cmd := helperCommand(t, "echo", "foo") 857 var out bytes.Buffer 858 cmd.Stdin = r 859 cmd.Stdout = &out 860 if err := cmd.Run(); err != nil { 861 t.Fatal(err) 862 } 863 if got, want := out.String(), "foo\n"; got != want { 864 t.Errorf("output = %q; want %q", got, want) 865 } 866 } 867 } 868 t.Run("10MB", testWith(strings.NewReader(strings.Repeat("x", 10<<20)))) 869 t.Run("Infinite", testWith(delayedInfiniteReader{})) 870 } 871 872 type badWriter struct{} 873 874 func (w *badWriter) Write(data []byte) (int, error) { 875 return 0, io.ErrUnexpectedEOF 876 } 877 878 func TestClosePipeOnCopyError(t *testing.T) { 879 testenv.MustHaveExec(t) 880 881 if runtime.GOOS == "windows" || runtime.GOOS == "plan9" { 882 t.Skipf("skipping test on %s - no yes command", runtime.GOOS) 883 } 884 cmd := exec.Command("yes") 885 cmd.Stdout = new(badWriter) 886 c := make(chan int, 1) 887 go func() { 888 err := cmd.Run() 889 if err == nil { 890 t.Errorf("yes completed successfully") 891 } 892 c <- 1 893 }() 894 select { 895 case <-c: 896 // ok 897 case <-time.After(5 * time.Second): 898 t.Fatalf("yes got stuck writing to bad writer") 899 } 900 } 901 902 func TestOutputStderrCapture(t *testing.T) { 903 testenv.MustHaveExec(t) 904 905 cmd := helperCommand(t, "stderrfail") 906 _, err := cmd.Output() 907 ee, ok := err.(*exec.ExitError) 908 if !ok { 909 t.Fatalf("Output error type = %T; want ExitError", err) 910 } 911 got := string(ee.Stderr) 912 want := "some stderr text\n" 913 if got != want { 914 t.Errorf("ExitError.Stderr = %q; want %q", got, want) 915 } 916 } 917 918 func TestContext(t *testing.T) { 919 ctx, cancel := context.WithCancel(context.Background()) 920 c := helperCommandContext(t, ctx, "pipetest") 921 stdin, err := c.StdinPipe() 922 if err != nil { 923 t.Fatal(err) 924 } 925 stdout, err := c.StdoutPipe() 926 if err != nil { 927 t.Fatal(err) 928 } 929 if err := c.Start(); err != nil { 930 t.Fatal(err) 931 } 932 933 if _, err := stdin.Write([]byte("O:hi\n")); err != nil { 934 t.Fatal(err) 935 } 936 buf := make([]byte, 5) 937 n, err := io.ReadFull(stdout, buf) 938 if n != len(buf) || err != nil || string(buf) != "O:hi\n" { 939 t.Fatalf("ReadFull = %d, %v, %q", n, err, buf[:n]) 940 } 941 waitErr := make(chan error, 1) 942 go func() { 943 waitErr <- c.Wait() 944 }() 945 cancel() 946 select { 947 case err := <-waitErr: 948 if err == nil { 949 t.Fatal("expected Wait failure") 950 } 951 case <-time.After(3 * time.Second): 952 t.Fatal("timeout waiting for child process death") 953 } 954 } 955 956 func TestContextCancel(t *testing.T) { 957 if runtime.GOOS == "netbsd" && runtime.GOARCH == "arm64" { 958 testenv.SkipFlaky(t, 42061) 959 } 960 961 // To reduce noise in the final goroutine dump, 962 // let other parallel tests complete if possible. 963 t.Parallel() 964 965 ctx, cancel := context.WithCancel(context.Background()) 966 defer cancel() 967 c := helperCommandContext(t, ctx, "cat") 968 969 stdin, err := c.StdinPipe() 970 if err != nil { 971 t.Fatal(err) 972 } 973 defer stdin.Close() 974 975 if err := c.Start(); err != nil { 976 t.Fatal(err) 977 } 978 979 // At this point the process is alive. Ensure it by sending data to stdin. 980 if _, err := io.WriteString(stdin, "echo"); err != nil { 981 t.Fatal(err) 982 } 983 984 cancel() 985 986 // Calling cancel should have killed the process, so writes 987 // should now fail. Give the process a little while to die. 988 start := time.Now() 989 delay := 1 * time.Millisecond 990 for { 991 if _, err := io.WriteString(stdin, "echo"); err != nil { 992 break 993 } 994 995 if time.Since(start) > time.Minute { 996 // Panic instead of calling t.Fatal so that we get a goroutine dump. 997 // We want to know exactly what the os/exec goroutines got stuck on. 998 panic("canceling context did not stop program") 999 } 1000 1001 // Back off exponentially (up to 1-second sleeps) to give the OS time to 1002 // terminate the process. 1003 delay *= 2 1004 if delay > 1*time.Second { 1005 delay = 1 * time.Second 1006 } 1007 time.Sleep(delay) 1008 } 1009 1010 if err := c.Wait(); err == nil { 1011 t.Error("program unexpectedly exited successfully") 1012 } else { 1013 t.Logf("exit status: %v", err) 1014 } 1015 } 1016 1017 // test that environment variables are de-duped. 1018 func TestDedupEnvEcho(t *testing.T) { 1019 testenv.MustHaveExec(t) 1020 1021 cmd := helperCommand(t, "echoenv", "FOO") 1022 cmd.Env = append(cmd.Env, "FOO=bad", "FOO=good") 1023 out, err := cmd.CombinedOutput() 1024 if err != nil { 1025 t.Fatal(err) 1026 } 1027 if got, want := strings.TrimSpace(string(out)), "good"; got != want { 1028 t.Errorf("output = %q; want %q", got, want) 1029 } 1030 } 1031 1032 func TestString(t *testing.T) { 1033 echoPath, err := exec.LookPath("echo") 1034 if err != nil { 1035 t.Skip(err) 1036 } 1037 tests := [...]struct { 1038 path string 1039 args []string 1040 want string 1041 }{ 1042 {"echo", nil, echoPath}, 1043 {"echo", []string{"a"}, echoPath + " a"}, 1044 {"echo", []string{"a", "b"}, echoPath + " a b"}, 1045 } 1046 for _, test := range tests { 1047 cmd := exec.Command(test.path, test.args...) 1048 if got := cmd.String(); got != test.want { 1049 t.Errorf("String(%q, %q) = %q, want %q", test.path, test.args, got, test.want) 1050 } 1051 } 1052 } 1053 1054 func TestStringPathNotResolved(t *testing.T) { 1055 _, err := exec.LookPath("makemeasandwich") 1056 if err == nil { 1057 t.Skip("wow, thanks") 1058 } 1059 cmd := exec.Command("makemeasandwich", "-lettuce") 1060 want := "makemeasandwich -lettuce" 1061 if got := cmd.String(); got != want { 1062 t.Errorf("String(%q, %q) = %q, want %q", "makemeasandwich", "-lettuce", got, want) 1063 } 1064 } 1065 1066 // start a child process without the user code explicitly starting 1067 // with a copy of the parent's. (The Windows SYSTEMROOT issue: Issue 1068 // 25210) 1069 func TestChildCriticalEnv(t *testing.T) { 1070 testenv.MustHaveExec(t) 1071 if runtime.GOOS != "windows" { 1072 t.Skip("only testing on Windows") 1073 } 1074 cmd := helperCommand(t, "echoenv", "SYSTEMROOT") 1075 cmd.Env = []string{"GO_WANT_HELPER_PROCESS=1"} 1076 out, err := cmd.CombinedOutput() 1077 if err != nil { 1078 t.Fatal(err) 1079 } 1080 if strings.TrimSpace(string(out)) == "" { 1081 t.Error("no SYSTEMROOT found") 1082 } 1083 }