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