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 "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 "os/signal" 28 "path/filepath" 29 "runtime" 30 "runtime/debug" 31 "strconv" 32 "strings" 33 "sync" 34 "sync/atomic" 35 "testing" 36 "time" 37) 38 39// haveUnexpectedFDs is set at init time to report whether any file descriptors 40// were open at program start. 41var haveUnexpectedFDs bool 42 43func init() { 44 godebug := os.Getenv("GODEBUG") 45 if godebug != "" { 46 godebug += "," 47 } 48 godebug += "execwait=2" 49 os.Setenv("GODEBUG", godebug) 50 51 if os.Getenv("GO_EXEC_TEST_PID") != "" { 52 return 53 } 54 if runtime.GOOS == "windows" { 55 return 56 } 57 for fd := uintptr(3); fd <= 100; fd++ { 58 if poll.IsPollDescriptor(fd) { 59 continue 60 } 61 62 if fdtest.Exists(fd) { 63 haveUnexpectedFDs = true 64 return 65 } 66 } 67} 68 69// TestMain allows the test binary to impersonate many other binaries, 70// some of which may manipulate os.Stdin, os.Stdout, and/or os.Stderr 71// (and thus cannot run as an ordinary Test function, since the testing 72// package monkey-patches those variables before running tests). 73func TestMain(m *testing.M) { 74 flag.Parse() 75 76 pid := os.Getpid() 77 if os.Getenv("GO_EXEC_TEST_PID") == "" { 78 os.Setenv("GO_EXEC_TEST_PID", strconv.Itoa(pid)) 79 80 if runtime.GOOS == "windows" { 81 // Normalize environment so that test behavior is consistent. 82 // (The behavior of LookPath varies depending on this variable.) 83 // 84 // Ideally we would test both with the variable set and with it cleared, 85 // but I (bcmills) am not sure that that's feasible: it may already be set 86 // in the Windows registry, and I'm not sure if it is possible to remove 87 // a registry variable in a program's environment. 88 // 89 // Per https://learn.microsoft.com/en-us/windows/win32/api/processenv/nf-processenv-needcurrentdirectoryforexepathw#remarks, 90 // “the existence of the NoDefaultCurrentDirectoryInExePath environment 91 // variable is checked, and not its value.” 92 os.Setenv("NoDefaultCurrentDirectoryInExePath", "TRUE") 93 } 94 95 code := m.Run() 96 if code == 0 && flag.Lookup("test.run").Value.String() == "" && flag.Lookup("test.list").Value.String() == "" { 97 for cmd := range helperCommands { 98 if _, ok := helperCommandUsed.Load(cmd); !ok { 99 fmt.Fprintf(os.Stderr, "helper command unused: %q\n", cmd) 100 code = 1 101 } 102 } 103 } 104 105 if !testing.Short() { 106 // Run a couple of GC cycles to increase the odds of detecting 107 // process leaks using the finalizers installed by GODEBUG=execwait=2. 108 runtime.GC() 109 runtime.GC() 110 } 111 112 os.Exit(code) 113 } 114 115 args := flag.Args() 116 if len(args) == 0 { 117 fmt.Fprintf(os.Stderr, "No command\n") 118 os.Exit(2) 119 } 120 121 cmd, args := args[0], args[1:] 122 f, ok := helperCommands[cmd] 123 if !ok { 124 fmt.Fprintf(os.Stderr, "Unknown command %q\n", cmd) 125 os.Exit(2) 126 } 127 f(args...) 128 os.Exit(0) 129} 130 131// registerHelperCommand registers a command that the test process can impersonate. 132// A command should be registered in the same source file in which it is used. 133// If all tests are run and pass, all registered commands must be used. 134// (This prevents stale commands from accreting if tests are removed or 135// refactored over time.) 136func registerHelperCommand(name string, f func(...string)) { 137 if helperCommands[name] != nil { 138 panic("duplicate command registered: " + name) 139 } 140 helperCommands[name] = f 141} 142 143// maySkipHelperCommand records that the test that uses the named helper command 144// was invoked, but may call Skip on the test before actually calling 145// helperCommand. 146func maySkipHelperCommand(name string) { 147 helperCommandUsed.Store(name, true) 148} 149 150// helperCommand returns an exec.Cmd that will run the named helper command. 151func helperCommand(t *testing.T, name string, args ...string) *exec.Cmd { 152 t.Helper() 153 return helperCommandContext(t, nil, name, args...) 154} 155 156// helperCommandContext is like helperCommand, but also accepts a Context under 157// which to run the command. 158func helperCommandContext(t *testing.T, ctx context.Context, name string, args ...string) (cmd *exec.Cmd) { 159 helperCommandUsed.LoadOrStore(name, true) 160 161 t.Helper() 162 testenv.MustHaveExec(t) 163 164 cs := append([]string{name}, args...) 165 if ctx != nil { 166 cmd = exec.CommandContext(ctx, exePath(t), cs...) 167 } else { 168 cmd = exec.Command(exePath(t), cs...) 169 } 170 return cmd 171} 172 173// exePath returns the path to the running executable. 174func exePath(t testing.TB) string { 175 exeOnce.Do(func() { 176 // Use os.Executable instead of os.Args[0] in case the caller modifies 177 // cmd.Dir: if the test binary is invoked like "./exec.test", it should 178 // not fail spuriously. 179 exeOnce.path, exeOnce.err = os.Executable() 180 }) 181 182 if exeOnce.err != nil { 183 if t == nil { 184 panic(exeOnce.err) 185 } 186 t.Fatal(exeOnce.err) 187 } 188 189 return exeOnce.path 190} 191 192var exeOnce struct { 193 path string 194 err error 195 sync.Once 196} 197 198func chdir(t *testing.T, dir string) { 199 t.Helper() 200 201 prev, err := os.Getwd() 202 if err != nil { 203 t.Fatal(err) 204 } 205 if err := os.Chdir(dir); err != nil { 206 t.Fatal(err) 207 } 208 t.Logf("Chdir(%#q)", dir) 209 210 t.Cleanup(func() { 211 if err := os.Chdir(prev); err != nil { 212 // Couldn't chdir back to the original working directory. 213 // panic instead of t.Fatal so that we don't run other tests 214 // in an unexpected location. 215 panic("couldn't restore working directory: " + err.Error()) 216 } 217 }) 218} 219 220var helperCommandUsed sync.Map 221 222var helperCommands = map[string]func(...string){ 223 "echo": cmdEcho, 224 "echoenv": cmdEchoEnv, 225 "cat": cmdCat, 226 "pipetest": cmdPipeTest, 227 "stdinClose": cmdStdinClose, 228 "exit": cmdExit, 229 "describefiles": cmdDescribeFiles, 230 "stderrfail": cmdStderrFail, 231 "yes": cmdYes, 232 "hang": cmdHang, 233} 234 235func cmdEcho(args ...string) { 236 iargs := []any{} 237 for _, s := range args { 238 iargs = append(iargs, s) 239 } 240 fmt.Println(iargs...) 241} 242 243func cmdEchoEnv(args ...string) { 244 for _, s := range args { 245 fmt.Println(os.Getenv(s)) 246 } 247} 248 249func cmdCat(args ...string) { 250 if len(args) == 0 { 251 io.Copy(os.Stdout, os.Stdin) 252 return 253 } 254 exit := 0 255 for _, fn := range args { 256 f, err := os.Open(fn) 257 if err != nil { 258 fmt.Fprintf(os.Stderr, "Error: %v\n", err) 259 exit = 2 260 } else { 261 defer f.Close() 262 io.Copy(os.Stdout, f) 263 } 264 } 265 os.Exit(exit) 266} 267 268func cmdPipeTest(...string) { 269 bufr := bufio.NewReader(os.Stdin) 270 for { 271 line, _, err := bufr.ReadLine() 272 if err == io.EOF { 273 break 274 } else if err != nil { 275 os.Exit(1) 276 } 277 if bytes.HasPrefix(line, []byte("O:")) { 278 os.Stdout.Write(line) 279 os.Stdout.Write([]byte{'\n'}) 280 } else if bytes.HasPrefix(line, []byte("E:")) { 281 os.Stderr.Write(line) 282 os.Stderr.Write([]byte{'\n'}) 283 } else { 284 os.Exit(1) 285 } 286 } 287} 288 289func cmdStdinClose(...string) { 290 b, err := io.ReadAll(os.Stdin) 291 if err != nil { 292 fmt.Fprintf(os.Stderr, "Error: %v\n", err) 293 os.Exit(1) 294 } 295 if s := string(b); s != stdinCloseTestString { 296 fmt.Fprintf(os.Stderr, "Error: Read %q, want %q", s, stdinCloseTestString) 297 os.Exit(1) 298 } 299} 300 301func cmdExit(args ...string) { 302 n, _ := strconv.Atoi(args[0]) 303 os.Exit(n) 304} 305 306func cmdDescribeFiles(args ...string) { 307 f := os.NewFile(3, "fd3") 308 ln, err := net.FileListener(f) 309 if err == nil { 310 fmt.Printf("fd3: listener %s\n", ln.Addr()) 311 ln.Close() 312 } 313} 314 315func cmdStderrFail(...string) { 316 fmt.Fprintf(os.Stderr, "some stderr text\n") 317 os.Exit(1) 318} 319 320func cmdYes(args ...string) { 321 if len(args) == 0 { 322 args = []string{"y"} 323 } 324 s := strings.Join(args, " ") + "\n" 325 for { 326 _, err := os.Stdout.WriteString(s) 327 if err != nil { 328 os.Exit(1) 329 } 330 } 331} 332 333func TestEcho(t *testing.T) { 334 t.Parallel() 335 336 bs, err := helperCommand(t, "echo", "foo bar", "baz").Output() 337 if err != nil { 338 t.Errorf("echo: %v", err) 339 } 340 if g, e := string(bs), "foo bar baz\n"; g != e { 341 t.Errorf("echo: want %q, got %q", e, g) 342 } 343} 344 345func TestCommandRelativeName(t *testing.T) { 346 t.Parallel() 347 348 cmd := helperCommand(t, "echo", "foo") 349 350 // Run our own binary as a relative path 351 // (e.g. "_test/exec.test") our parent directory. 352 base := filepath.Base(os.Args[0]) // "exec.test" 353 dir := filepath.Dir(os.Args[0]) // "/tmp/go-buildNNNN/os/exec/_test" 354 if dir == "." { 355 t.Skip("skipping; running test at root somehow") 356 } 357 parentDir := filepath.Dir(dir) // "/tmp/go-buildNNNN/os/exec" 358 dirBase := filepath.Base(dir) // "_test" 359 if dirBase == "." { 360 t.Skipf("skipping; unexpected shallow dir of %q", dir) 361 } 362 363 cmd.Path = filepath.Join(dirBase, base) 364 cmd.Dir = parentDir 365 366 out, err := cmd.Output() 367 if err != nil { 368 t.Errorf("echo: %v", err) 369 } 370 if g, e := string(out), "foo\n"; g != e { 371 t.Errorf("echo: want %q, got %q", e, g) 372 } 373} 374 375func TestCatStdin(t *testing.T) { 376 t.Parallel() 377 378 // Cat, testing stdin and stdout. 379 input := "Input string\nLine 2" 380 p := helperCommand(t, "cat") 381 p.Stdin = strings.NewReader(input) 382 bs, err := p.Output() 383 if err != nil { 384 t.Errorf("cat: %v", err) 385 } 386 s := string(bs) 387 if s != input { 388 t.Errorf("cat: want %q, got %q", input, s) 389 } 390} 391 392func TestEchoFileRace(t *testing.T) { 393 t.Parallel() 394 395 cmd := helperCommand(t, "echo") 396 stdin, err := cmd.StdinPipe() 397 if err != nil { 398 t.Fatalf("StdinPipe: %v", err) 399 } 400 if err := cmd.Start(); err != nil { 401 t.Fatalf("Start: %v", err) 402 } 403 wrote := make(chan bool) 404 go func() { 405 defer close(wrote) 406 fmt.Fprint(stdin, "echo\n") 407 }() 408 if err := cmd.Wait(); err != nil { 409 t.Fatalf("Wait: %v", err) 410 } 411 <-wrote 412} 413 414func TestCatGoodAndBadFile(t *testing.T) { 415 t.Parallel() 416 417 // Testing combined output and error values. 418 bs, err := helperCommand(t, "cat", "/bogus/file.foo", "exec_test.go").CombinedOutput() 419 if _, ok := err.(*exec.ExitError); !ok { 420 t.Errorf("expected *exec.ExitError from cat combined; got %T: %v", err, err) 421 } 422 errLine, body, ok := strings.Cut(string(bs), "\n") 423 if !ok { 424 t.Fatalf("expected two lines from cat; got %q", bs) 425 } 426 if !strings.HasPrefix(errLine, "Error: open /bogus/file.foo") { 427 t.Errorf("expected stderr to complain about file; got %q", errLine) 428 } 429 if !strings.Contains(body, "func TestCatGoodAndBadFile(t *testing.T)") { 430 t.Errorf("expected test code; got %q (len %d)", body, len(body)) 431 } 432} 433 434func TestNoExistExecutable(t *testing.T) { 435 t.Parallel() 436 437 // Can't run a non-existent executable 438 err := exec.Command("/no-exist-executable").Run() 439 if err == nil { 440 t.Error("expected error from /no-exist-executable") 441 } 442} 443 444func TestExitStatus(t *testing.T) { 445 t.Parallel() 446 447 // Test that exit values are returned correctly 448 cmd := helperCommand(t, "exit", "42") 449 err := cmd.Run() 450 want := "exit status 42" 451 switch runtime.GOOS { 452 case "plan9": 453 want = fmt.Sprintf("exit status: '%s %d: 42'", filepath.Base(cmd.Path), cmd.ProcessState.Pid()) 454 } 455 if werr, ok := err.(*exec.ExitError); ok { 456 if s := werr.Error(); s != want { 457 t.Errorf("from exit 42 got exit %q, want %q", s, want) 458 } 459 } else { 460 t.Fatalf("expected *exec.ExitError from exit 42; got %T: %v", err, err) 461 } 462} 463 464func TestExitCode(t *testing.T) { 465 t.Parallel() 466 467 // Test that exit code are returned correctly 468 cmd := helperCommand(t, "exit", "42") 469 cmd.Run() 470 want := 42 471 if runtime.GOOS == "plan9" { 472 want = 1 473 } 474 got := cmd.ProcessState.ExitCode() 475 if want != got { 476 t.Errorf("ExitCode got %d, want %d", got, want) 477 } 478 479 cmd = helperCommand(t, "/no-exist-executable") 480 cmd.Run() 481 want = 2 482 if runtime.GOOS == "plan9" { 483 want = 1 484 } 485 got = cmd.ProcessState.ExitCode() 486 if want != got { 487 t.Errorf("ExitCode got %d, want %d", got, want) 488 } 489 490 cmd = helperCommand(t, "exit", "255") 491 cmd.Run() 492 want = 255 493 if runtime.GOOS == "plan9" { 494 want = 1 495 } 496 got = cmd.ProcessState.ExitCode() 497 if want != got { 498 t.Errorf("ExitCode got %d, want %d", got, want) 499 } 500 501 cmd = helperCommand(t, "cat") 502 cmd.Run() 503 want = 0 504 got = cmd.ProcessState.ExitCode() 505 if want != got { 506 t.Errorf("ExitCode got %d, want %d", got, want) 507 } 508 509 // Test when command does not call Run(). 510 cmd = helperCommand(t, "cat") 511 want = -1 512 got = cmd.ProcessState.ExitCode() 513 if want != got { 514 t.Errorf("ExitCode got %d, want %d", got, want) 515 } 516} 517 518func TestPipes(t *testing.T) { 519 t.Parallel() 520 521 check := func(what string, err error) { 522 if err != nil { 523 t.Fatalf("%s: %v", what, err) 524 } 525 } 526 // Cat, testing stdin and stdout. 527 c := helperCommand(t, "pipetest") 528 stdin, err := c.StdinPipe() 529 check("StdinPipe", err) 530 stdout, err := c.StdoutPipe() 531 check("StdoutPipe", err) 532 stderr, err := c.StderrPipe() 533 check("StderrPipe", err) 534 535 outbr := bufio.NewReader(stdout) 536 errbr := bufio.NewReader(stderr) 537 line := func(what string, br *bufio.Reader) string { 538 line, _, err := br.ReadLine() 539 if err != nil { 540 t.Fatalf("%s: %v", what, err) 541 } 542 return string(line) 543 } 544 545 err = c.Start() 546 check("Start", err) 547 548 _, err = stdin.Write([]byte("O:I am output\n")) 549 check("first stdin Write", err) 550 if g, e := line("first output line", outbr), "O:I am output"; g != e { 551 t.Errorf("got %q, want %q", g, e) 552 } 553 554 _, err = stdin.Write([]byte("E:I am error\n")) 555 check("second stdin Write", err) 556 if g, e := line("first error line", errbr), "E:I am error"; g != e { 557 t.Errorf("got %q, want %q", g, e) 558 } 559 560 _, err = stdin.Write([]byte("O:I am output2\n")) 561 check("third stdin Write 3", err) 562 if g, e := line("second output line", outbr), "O:I am output2"; g != e { 563 t.Errorf("got %q, want %q", g, e) 564 } 565 566 stdin.Close() 567 err = c.Wait() 568 check("Wait", err) 569} 570 571const stdinCloseTestString = "Some test string." 572 573// Issue 6270. 574func TestStdinClose(t *testing.T) { 575 t.Parallel() 576 577 check := func(what string, err error) { 578 if err != nil { 579 t.Fatalf("%s: %v", what, err) 580 } 581 } 582 cmd := helperCommand(t, "stdinClose") 583 stdin, err := cmd.StdinPipe() 584 check("StdinPipe", err) 585 // Check that we can access methods of the underlying os.File.` 586 if _, ok := stdin.(interface { 587 Fd() uintptr 588 }); !ok { 589 t.Error("can't access methods of underlying *os.File") 590 } 591 check("Start", cmd.Start()) 592 593 var wg sync.WaitGroup 594 wg.Add(1) 595 defer wg.Wait() 596 go func() { 597 defer wg.Done() 598 599 _, err := io.Copy(stdin, strings.NewReader(stdinCloseTestString)) 600 check("Copy", err) 601 602 // Before the fix, this next line would race with cmd.Wait. 603 if err := stdin.Close(); err != nil && !errors.Is(err, os.ErrClosed) { 604 t.Errorf("Close: %v", err) 605 } 606 }() 607 608 check("Wait", cmd.Wait()) 609} 610 611// Issue 17647. 612// It used to be the case that TestStdinClose, above, would fail when 613// run under the race detector. This test is a variant of TestStdinClose 614// that also used to fail when run under the race detector. 615// This test is run by cmd/dist under the race detector to verify that 616// the race detector no longer reports any problems. 617func TestStdinCloseRace(t *testing.T) { 618 t.Parallel() 619 620 cmd := helperCommand(t, "stdinClose") 621 stdin, err := cmd.StdinPipe() 622 if err != nil { 623 t.Fatalf("StdinPipe: %v", err) 624 } 625 if err := cmd.Start(); err != nil { 626 t.Fatalf("Start: %v", err) 627 628 } 629 630 var wg sync.WaitGroup 631 wg.Add(2) 632 defer wg.Wait() 633 634 go func() { 635 defer wg.Done() 636 // We don't check the error return of Kill. It is 637 // possible that the process has already exited, in 638 // which case Kill will return an error "process 639 // already finished". The purpose of this test is to 640 // see whether the race detector reports an error; it 641 // doesn't matter whether this Kill succeeds or not. 642 cmd.Process.Kill() 643 }() 644 645 go func() { 646 defer wg.Done() 647 // Send the wrong string, so that the child fails even 648 // if the other goroutine doesn't manage to kill it first. 649 // This test is to check that the race detector does not 650 // falsely report an error, so it doesn't matter how the 651 // child process fails. 652 io.Copy(stdin, strings.NewReader("unexpected string")) 653 if err := stdin.Close(); err != nil && !errors.Is(err, os.ErrClosed) { 654 t.Errorf("stdin.Close: %v", err) 655 } 656 }() 657 658 if err := cmd.Wait(); err == nil { 659 t.Fatalf("Wait: succeeded unexpectedly") 660 } 661} 662 663// Issue 5071 664func TestPipeLookPathLeak(t *testing.T) { 665 if runtime.GOOS == "windows" { 666 t.Skip("we don't currently suppore counting open handles on windows") 667 } 668 // Not parallel: checks for leaked file descriptors 669 670 openFDs := func() []uintptr { 671 var fds []uintptr 672 for i := uintptr(0); i < 100; i++ { 673 if fdtest.Exists(i) { 674 fds = append(fds, i) 675 } 676 } 677 return fds 678 } 679 680 old := map[uintptr]bool{} 681 for _, fd := range openFDs() { 682 old[fd] = true 683 } 684 685 for i := 0; i < 6; i++ { 686 cmd := exec.Command("something-that-does-not-exist-executable") 687 cmd.StdoutPipe() 688 cmd.StderrPipe() 689 cmd.StdinPipe() 690 if err := cmd.Run(); err == nil { 691 t.Fatal("unexpected success") 692 } 693 } 694 695 // Since this test is not running in parallel, we don't expect any new file 696 // descriptors to be opened while it runs. However, if there are additional 697 // FDs present at the start of the test (for example, opened by libc), those 698 // may be closed due to a timeout of some sort. Allow those to go away, but 699 // check that no new FDs are added. 700 for _, fd := range openFDs() { 701 if !old[fd] { 702 t.Errorf("leaked file descriptor %v", fd) 703 } 704 } 705} 706 707func TestExtraFiles(t *testing.T) { 708 if testing.Short() { 709 t.Skipf("skipping test in short mode that would build a helper binary") 710 } 711 712 if haveUnexpectedFDs { 713 // The point of this test is to make sure that any 714 // descriptors we open are marked close-on-exec. 715 // If haveUnexpectedFDs is true then there were other 716 // descriptors open when we started the test, 717 // so those descriptors are clearly not close-on-exec, 718 // and they will confuse the test. We could modify 719 // the test to expect those descriptors to remain open, 720 // but since we don't know where they came from or what 721 // they are doing, that seems fragile. For example, 722 // perhaps they are from the startup code on this 723 // system for some reason. Also, this test is not 724 // system-specific; as long as most systems do not skip 725 // the test, we will still be testing what we care about. 726 t.Skip("skipping test because test was run with FDs open") 727 } 728 729 testenv.MustHaveExec(t) 730 testenv.MustHaveGoBuild(t) 731 732 // This test runs with cgo disabled. External linking needs cgo, so 733 // it doesn't work if external linking is required. 734 testenv.MustInternalLink(t, false) 735 736 if runtime.GOOS == "windows" { 737 t.Skipf("skipping test on %q", runtime.GOOS) 738 } 739 740 // Force network usage, to verify the epoll (or whatever) fd 741 // doesn't leak to the child, 742 ln, err := net.Listen("tcp", "127.0.0.1:0") 743 if err != nil { 744 t.Fatal(err) 745 } 746 defer ln.Close() 747 748 // Make sure duplicated fds don't leak to the child. 749 f, err := ln.(*net.TCPListener).File() 750 if err != nil { 751 t.Fatal(err) 752 } 753 defer f.Close() 754 ln2, err := net.FileListener(f) 755 if err != nil { 756 t.Fatal(err) 757 } 758 defer ln2.Close() 759 760 // Force TLS root certs to be loaded (which might involve 761 // cgo), to make sure none of that potential C code leaks fds. 762 ts := httptest.NewUnstartedServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})) 763 // quiet expected TLS handshake error "remote error: bad certificate" 764 ts.Config.ErrorLog = log.New(io.Discard, "", 0) 765 ts.StartTLS() 766 defer ts.Close() 767 _, err = http.Get(ts.URL) 768 if err == nil { 769 t.Errorf("success trying to fetch %s; want an error", ts.URL) 770 } 771 772 tf, err := os.CreateTemp("", "") 773 if err != nil { 774 t.Fatalf("TempFile: %v", err) 775 } 776 defer os.Remove(tf.Name()) 777 defer tf.Close() 778 779 const text = "Hello, fd 3!" 780 _, err = tf.Write([]byte(text)) 781 if err != nil { 782 t.Fatalf("Write: %v", err) 783 } 784 _, err = tf.Seek(0, io.SeekStart) 785 if err != nil { 786 t.Fatalf("Seek: %v", err) 787 } 788 789 tempdir := t.TempDir() 790 exe := filepath.Join(tempdir, "read3.exe") 791 792 c := testenv.Command(t, testenv.GoToolPath(t), "build", "-o", exe, "read3.go") 793 // Build the test without cgo, so that C library functions don't 794 // open descriptors unexpectedly. See issue 25628. 795 c.Env = append(os.Environ(), "CGO_ENABLED=0") 796 if output, err := c.CombinedOutput(); err != nil { 797 t.Logf("go build -o %s read3.go\n%s", exe, output) 798 t.Fatalf("go build failed: %v", err) 799 } 800 801 // Use a deadline to try to get some output even if the program hangs. 802 ctx := context.Background() 803 if deadline, ok := t.Deadline(); ok { 804 // Leave a 20% grace period to flush output, which may be large on the 805 // linux/386 builders because we're running the subprocess under strace. 806 deadline = deadline.Add(-time.Until(deadline) / 5) 807 808 var cancel context.CancelFunc 809 ctx, cancel = context.WithDeadline(ctx, deadline) 810 defer cancel() 811 } 812 813 c = exec.CommandContext(ctx, exe) 814 var stdout, stderr strings.Builder 815 c.Stdout = &stdout 816 c.Stderr = &stderr 817 c.ExtraFiles = []*os.File{tf} 818 if runtime.GOOS == "illumos" { 819 // Some facilities in illumos are implemented via access 820 // to /proc by libc; such accesses can briefly occupy a 821 // low-numbered fd. If this occurs concurrently with the 822 // test that checks for leaked descriptors, the check can 823 // become confused and report a spurious leaked descriptor. 824 // (See issue #42431 for more detailed analysis.) 825 // 826 // Attempt to constrain the use of additional threads in the 827 // child process to make this test less flaky: 828 c.Env = append(os.Environ(), "GOMAXPROCS=1") 829 } 830 err = c.Run() 831 if err != nil { 832 t.Fatalf("Run: %v\n--- stdout:\n%s--- stderr:\n%s", err, stdout.String(), stderr.String()) 833 } 834 if stdout.String() != text { 835 t.Errorf("got stdout %q, stderr %q; want %q on stdout", stdout.String(), stderr.String(), text) 836 } 837} 838 839func TestExtraFilesRace(t *testing.T) { 840 if runtime.GOOS == "windows" { 841 maySkipHelperCommand("describefiles") 842 t.Skip("no operating system support; skipping") 843 } 844 t.Parallel() 845 846 listen := func() net.Listener { 847 ln, err := net.Listen("tcp", "127.0.0.1:0") 848 if err != nil { 849 t.Fatal(err) 850 } 851 return ln 852 } 853 listenerFile := func(ln net.Listener) *os.File { 854 f, err := ln.(*net.TCPListener).File() 855 if err != nil { 856 t.Fatal(err) 857 } 858 return f 859 } 860 runCommand := func(c *exec.Cmd, out chan<- string) { 861 bout, err := c.CombinedOutput() 862 if err != nil { 863 out <- "ERROR:" + err.Error() 864 } else { 865 out <- string(bout) 866 } 867 } 868 869 for i := 0; i < 10; i++ { 870 if testing.Short() && i >= 3 { 871 break 872 } 873 la := listen() 874 ca := helperCommand(t, "describefiles") 875 ca.ExtraFiles = []*os.File{listenerFile(la)} 876 lb := listen() 877 cb := helperCommand(t, "describefiles") 878 cb.ExtraFiles = []*os.File{listenerFile(lb)} 879 ares := make(chan string) 880 bres := make(chan string) 881 go runCommand(ca, ares) 882 go runCommand(cb, bres) 883 if got, want := <-ares, fmt.Sprintf("fd3: listener %s\n", la.Addr()); got != want { 884 t.Errorf("iteration %d, process A got:\n%s\nwant:\n%s\n", i, got, want) 885 } 886 if got, want := <-bres, fmt.Sprintf("fd3: listener %s\n", lb.Addr()); got != want { 887 t.Errorf("iteration %d, process B got:\n%s\nwant:\n%s\n", i, got, want) 888 } 889 la.Close() 890 lb.Close() 891 for _, f := range ca.ExtraFiles { 892 f.Close() 893 } 894 for _, f := range cb.ExtraFiles { 895 f.Close() 896 } 897 } 898} 899 900type delayedInfiniteReader struct{} 901 902func (delayedInfiniteReader) Read(b []byte) (int, error) { 903 time.Sleep(100 * time.Millisecond) 904 for i := range b { 905 b[i] = 'x' 906 } 907 return len(b), nil 908} 909 910// Issue 9173: ignore stdin pipe writes if the program completes successfully. 911func TestIgnorePipeErrorOnSuccess(t *testing.T) { 912 t.Parallel() 913 914 testWith := func(r io.Reader) func(*testing.T) { 915 return func(t *testing.T) { 916 t.Parallel() 917 918 cmd := helperCommand(t, "echo", "foo") 919 var out strings.Builder 920 cmd.Stdin = r 921 cmd.Stdout = &out 922 if err := cmd.Run(); err != nil { 923 t.Fatal(err) 924 } 925 if got, want := out.String(), "foo\n"; got != want { 926 t.Errorf("output = %q; want %q", got, want) 927 } 928 } 929 } 930 t.Run("10MB", testWith(strings.NewReader(strings.Repeat("x", 10<<20)))) 931 t.Run("Infinite", testWith(delayedInfiniteReader{})) 932} 933 934type badWriter struct{} 935 936func (w *badWriter) Write(data []byte) (int, error) { 937 return 0, io.ErrUnexpectedEOF 938} 939 940func TestClosePipeOnCopyError(t *testing.T) { 941 t.Parallel() 942 943 cmd := helperCommand(t, "yes") 944 cmd.Stdout = new(badWriter) 945 err := cmd.Run() 946 if err == nil { 947 t.Errorf("yes unexpectedly completed successfully") 948 } 949} 950 951func TestOutputStderrCapture(t *testing.T) { 952 t.Parallel() 953 954 cmd := helperCommand(t, "stderrfail") 955 _, err := cmd.Output() 956 ee, ok := err.(*exec.ExitError) 957 if !ok { 958 t.Fatalf("Output error type = %T; want ExitError", err) 959 } 960 got := string(ee.Stderr) 961 want := "some stderr text\n" 962 if got != want { 963 t.Errorf("ExitError.Stderr = %q; want %q", got, want) 964 } 965} 966 967func TestContext(t *testing.T) { 968 t.Parallel() 969 970 ctx, cancel := context.WithCancel(context.Background()) 971 c := helperCommandContext(t, ctx, "pipetest") 972 stdin, err := c.StdinPipe() 973 if err != nil { 974 t.Fatal(err) 975 } 976 stdout, err := c.StdoutPipe() 977 if err != nil { 978 t.Fatal(err) 979 } 980 if err := c.Start(); err != nil { 981 t.Fatal(err) 982 } 983 984 if _, err := stdin.Write([]byte("O:hi\n")); err != nil { 985 t.Fatal(err) 986 } 987 buf := make([]byte, 5) 988 n, err := io.ReadFull(stdout, buf) 989 if n != len(buf) || err != nil || string(buf) != "O:hi\n" { 990 t.Fatalf("ReadFull = %d, %v, %q", n, err, buf[:n]) 991 } 992 go cancel() 993 994 if err := c.Wait(); err == nil { 995 t.Fatal("expected Wait failure") 996 } 997} 998 999func TestContextCancel(t *testing.T) { 1000 if runtime.GOOS == "netbsd" && runtime.GOARCH == "arm64" { 1001 maySkipHelperCommand("cat") 1002 testenv.SkipFlaky(t, 42061) 1003 } 1004 1005 // To reduce noise in the final goroutine dump, 1006 // let other parallel tests complete if possible. 1007 t.Parallel() 1008 1009 ctx, cancel := context.WithCancel(context.Background()) 1010 defer cancel() 1011 c := helperCommandContext(t, ctx, "cat") 1012 1013 stdin, err := c.StdinPipe() 1014 if err != nil { 1015 t.Fatal(err) 1016 } 1017 defer stdin.Close() 1018 1019 if err := c.Start(); err != nil { 1020 t.Fatal(err) 1021 } 1022 1023 // At this point the process is alive. Ensure it by sending data to stdin. 1024 if _, err := io.WriteString(stdin, "echo"); err != nil { 1025 t.Fatal(err) 1026 } 1027 1028 cancel() 1029 1030 // Calling cancel should have killed the process, so writes 1031 // should now fail. Give the process a little while to die. 1032 start := time.Now() 1033 delay := 1 * time.Millisecond 1034 for { 1035 if _, err := io.WriteString(stdin, "echo"); err != nil { 1036 break 1037 } 1038 1039 if time.Since(start) > time.Minute { 1040 // Panic instead of calling t.Fatal so that we get a goroutine dump. 1041 // We want to know exactly what the os/exec goroutines got stuck on. 1042 debug.SetTraceback("system") 1043 panic("canceling context did not stop program") 1044 } 1045 1046 // Back off exponentially (up to 1-second sleeps) to give the OS time to 1047 // terminate the process. 1048 delay *= 2 1049 if delay > 1*time.Second { 1050 delay = 1 * time.Second 1051 } 1052 time.Sleep(delay) 1053 } 1054 1055 if err := c.Wait(); err == nil { 1056 t.Error("program unexpectedly exited successfully") 1057 } else { 1058 t.Logf("exit status: %v", err) 1059 } 1060} 1061 1062// test that environment variables are de-duped. 1063func TestDedupEnvEcho(t *testing.T) { 1064 t.Parallel() 1065 1066 cmd := helperCommand(t, "echoenv", "FOO") 1067 cmd.Env = append(cmd.Environ(), "FOO=bad", "FOO=good") 1068 out, err := cmd.CombinedOutput() 1069 if err != nil { 1070 t.Fatal(err) 1071 } 1072 if got, want := strings.TrimSpace(string(out)), "good"; got != want { 1073 t.Errorf("output = %q; want %q", got, want) 1074 } 1075} 1076 1077func TestEnvNULCharacter(t *testing.T) { 1078 if runtime.GOOS == "plan9" { 1079 t.Skip("plan9 explicitly allows NUL in the environment") 1080 } 1081 cmd := helperCommand(t, "echoenv", "FOO", "BAR") 1082 cmd.Env = append(cmd.Environ(), "FOO=foo\x00BAR=bar") 1083 out, err := cmd.CombinedOutput() 1084 if err == nil { 1085 t.Errorf("output = %q; want error", string(out)) 1086 } 1087} 1088 1089func TestString(t *testing.T) { 1090 t.Parallel() 1091 1092 echoPath, err := exec.LookPath("echo") 1093 if err != nil { 1094 t.Skip(err) 1095 } 1096 tests := [...]struct { 1097 path string 1098 args []string 1099 want string 1100 }{ 1101 {"echo", nil, echoPath}, 1102 {"echo", []string{"a"}, echoPath + " a"}, 1103 {"echo", []string{"a", "b"}, echoPath + " a b"}, 1104 } 1105 for _, test := range tests { 1106 cmd := exec.Command(test.path, test.args...) 1107 if got := cmd.String(); got != test.want { 1108 t.Errorf("String(%q, %q) = %q, want %q", test.path, test.args, got, test.want) 1109 } 1110 } 1111} 1112 1113func TestStringPathNotResolved(t *testing.T) { 1114 t.Parallel() 1115 1116 _, err := exec.LookPath("makemeasandwich") 1117 if err == nil { 1118 t.Skip("wow, thanks") 1119 } 1120 1121 cmd := exec.Command("makemeasandwich", "-lettuce") 1122 want := "makemeasandwich -lettuce" 1123 if got := cmd.String(); got != want { 1124 t.Errorf("String(%q, %q) = %q, want %q", "makemeasandwich", "-lettuce", got, want) 1125 } 1126} 1127 1128func TestNoPath(t *testing.T) { 1129 err := new(exec.Cmd).Start() 1130 want := "exec: no command" 1131 if err == nil || err.Error() != want { 1132 t.Errorf("new(Cmd).Start() = %v, want %q", err, want) 1133 } 1134} 1135 1136// TestDoubleStartLeavesPipesOpen checks for a regression in which calling 1137// Start twice, which returns an error on the second call, would spuriously 1138// close the pipes established in the first call. 1139func TestDoubleStartLeavesPipesOpen(t *testing.T) { 1140 t.Parallel() 1141 1142 cmd := helperCommand(t, "pipetest") 1143 in, err := cmd.StdinPipe() 1144 if err != nil { 1145 t.Fatal(err) 1146 } 1147 out, err := cmd.StdoutPipe() 1148 if err != nil { 1149 t.Fatal(err) 1150 } 1151 1152 if err := cmd.Start(); err != nil { 1153 t.Fatal(err) 1154 } 1155 t.Cleanup(func() { 1156 if err := cmd.Wait(); err != nil { 1157 t.Error(err) 1158 } 1159 }) 1160 1161 if err := cmd.Start(); err == nil || !strings.HasSuffix(err.Error(), "already started") { 1162 t.Fatalf("second call to Start returned a nil; want an 'already started' error") 1163 } 1164 1165 outc := make(chan []byte, 1) 1166 go func() { 1167 b, err := io.ReadAll(out) 1168 if err != nil { 1169 t.Error(err) 1170 } 1171 outc <- b 1172 }() 1173 1174 const msg = "O:Hello, pipe!\n" 1175 1176 _, err = io.WriteString(in, msg) 1177 if err != nil { 1178 t.Fatal(err) 1179 } 1180 in.Close() 1181 1182 b := <-outc 1183 if !bytes.Equal(b, []byte(msg)) { 1184 t.Fatalf("read %q from stdout pipe; want %q", b, msg) 1185 } 1186} 1187 1188func cmdHang(args ...string) { 1189 sleep, err := time.ParseDuration(args[0]) 1190 if err != nil { 1191 panic(err) 1192 } 1193 1194 fs := flag.NewFlagSet("hang", flag.ExitOnError) 1195 exitOnInterrupt := fs.Bool("interrupt", false, "if true, commands should exit 0 on os.Interrupt") 1196 subsleep := fs.Duration("subsleep", 0, "amount of time for the 'hang' helper to leave an orphaned subprocess sleeping with stderr open") 1197 probe := fs.Duration("probe", 0, "if nonzero, the 'hang' helper should write to stderr at this interval, and exit nonzero if a write fails") 1198 read := fs.Bool("read", false, "if true, the 'hang' helper should read stdin to completion before sleeping") 1199 fs.Parse(args[1:]) 1200 1201 pid := os.Getpid() 1202 1203 if *subsleep != 0 { 1204 cmd := exec.Command(exePath(nil), "hang", subsleep.String(), "-read=true", "-probe="+probe.String()) 1205 cmd.Stdin = os.Stdin 1206 cmd.Stderr = os.Stderr 1207 out, err := cmd.StdoutPipe() 1208 if err != nil { 1209 fmt.Fprintln(os.Stderr, err) 1210 os.Exit(1) 1211 } 1212 cmd.Start() 1213 1214 buf := new(strings.Builder) 1215 if _, err := io.Copy(buf, out); err != nil { 1216 fmt.Fprintln(os.Stderr, err) 1217 cmd.Process.Kill() 1218 cmd.Wait() 1219 os.Exit(1) 1220 } 1221 fmt.Fprintf(os.Stderr, "%d: started %d: %v\n", pid, cmd.Process.Pid, cmd) 1222 go cmd.Wait() // Release resources if cmd happens not to outlive this process. 1223 } 1224 1225 if *exitOnInterrupt { 1226 c := make(chan os.Signal, 1) 1227 signal.Notify(c, os.Interrupt) 1228 go func() { 1229 sig := <-c 1230 fmt.Fprintf(os.Stderr, "%d: received %v\n", pid, sig) 1231 os.Exit(0) 1232 }() 1233 } else { 1234 signal.Ignore(os.Interrupt) 1235 } 1236 1237 // Signal that the process is set up by closing stdout. 1238 os.Stdout.Close() 1239 1240 if *read { 1241 if pipeSignal != nil { 1242 signal.Ignore(pipeSignal) 1243 } 1244 r := bufio.NewReader(os.Stdin) 1245 for { 1246 line, err := r.ReadBytes('\n') 1247 if len(line) > 0 { 1248 // Ignore write errors: we want to keep reading even if stderr is closed. 1249 fmt.Fprintf(os.Stderr, "%d: read %s", pid, line) 1250 } 1251 if err != nil { 1252 fmt.Fprintf(os.Stderr, "%d: finished read: %v", pid, err) 1253 break 1254 } 1255 } 1256 } 1257 1258 if *probe != 0 { 1259 ticker := time.NewTicker(*probe) 1260 go func() { 1261 for range ticker.C { 1262 if _, err := fmt.Fprintf(os.Stderr, "%d: ok\n", pid); err != nil { 1263 os.Exit(1) 1264 } 1265 } 1266 }() 1267 } 1268 1269 if sleep != 0 { 1270 time.Sleep(sleep) 1271 fmt.Fprintf(os.Stderr, "%d: slept %v\n", pid, sleep) 1272 } 1273} 1274 1275// A tickReader reads an unbounded sequence of timestamps at no more than a 1276// fixed interval. 1277type tickReader struct { 1278 interval time.Duration 1279 lastTick time.Time 1280 s string 1281} 1282 1283func newTickReader(interval time.Duration) *tickReader { 1284 return &tickReader{interval: interval} 1285} 1286 1287func (r *tickReader) Read(p []byte) (n int, err error) { 1288 if len(r.s) == 0 { 1289 if d := r.interval - time.Since(r.lastTick); d > 0 { 1290 time.Sleep(d) 1291 } 1292 r.lastTick = time.Now() 1293 r.s = r.lastTick.Format(time.RFC3339Nano + "\n") 1294 } 1295 1296 n = copy(p, r.s) 1297 r.s = r.s[n:] 1298 return n, nil 1299} 1300 1301func startHang(t *testing.T, ctx context.Context, hangTime time.Duration, interrupt os.Signal, waitDelay time.Duration, flags ...string) *exec.Cmd { 1302 t.Helper() 1303 1304 args := append([]string{hangTime.String()}, flags...) 1305 cmd := helperCommandContext(t, ctx, "hang", args...) 1306 cmd.Stdin = newTickReader(1 * time.Millisecond) 1307 cmd.Stderr = new(strings.Builder) 1308 if interrupt == nil { 1309 cmd.Cancel = nil 1310 } else { 1311 cmd.Cancel = func() error { 1312 return cmd.Process.Signal(interrupt) 1313 } 1314 } 1315 cmd.WaitDelay = waitDelay 1316 out, err := cmd.StdoutPipe() 1317 if err != nil { 1318 t.Fatal(err) 1319 } 1320 1321 t.Log(cmd) 1322 if err := cmd.Start(); err != nil { 1323 t.Fatal(err) 1324 } 1325 1326 // Wait for cmd to close stdout to signal that its handlers are installed. 1327 buf := new(strings.Builder) 1328 if _, err := io.Copy(buf, out); err != nil { 1329 t.Error(err) 1330 cmd.Process.Kill() 1331 cmd.Wait() 1332 t.FailNow() 1333 } 1334 if buf.Len() > 0 { 1335 t.Logf("stdout %v:\n%s", cmd.Args, buf) 1336 } 1337 1338 return cmd 1339} 1340 1341func TestWaitInterrupt(t *testing.T) { 1342 t.Parallel() 1343 1344 // tooLong is an arbitrary duration that is expected to be much longer than 1345 // the test runs, but short enough that leaked processes will eventually exit 1346 // on their own. 1347 const tooLong = 10 * time.Minute 1348 1349 // Control case: with no cancellation and no WaitDelay, we should wait for the 1350 // process to exit. 1351 t.Run("Wait", func(t *testing.T) { 1352 t.Parallel() 1353 cmd := startHang(t, context.Background(), 1*time.Millisecond, os.Kill, 0) 1354 err := cmd.Wait() 1355 t.Logf("stderr:\n%s", cmd.Stderr) 1356 t.Logf("[%d] %v", cmd.Process.Pid, err) 1357 1358 if err != nil { 1359 t.Errorf("Wait: %v; want <nil>", err) 1360 } 1361 if ps := cmd.ProcessState; !ps.Exited() { 1362 t.Errorf("cmd did not exit: %v", ps) 1363 } else if code := ps.ExitCode(); code != 0 { 1364 t.Errorf("cmd.ProcessState.ExitCode() = %v; want 0", code) 1365 } 1366 }) 1367 1368 // With a very long WaitDelay and no Cancel function, we should wait for the 1369 // process to exit even if the command's Context is canceled. 1370 t.Run("WaitDelay", func(t *testing.T) { 1371 if runtime.GOOS == "windows" { 1372 t.Skipf("skipping: os.Interrupt is not implemented on Windows") 1373 } 1374 t.Parallel() 1375 1376 ctx, cancel := context.WithCancel(context.Background()) 1377 cmd := startHang(t, ctx, tooLong, nil, tooLong, "-interrupt=true") 1378 cancel() 1379 1380 time.Sleep(1 * time.Millisecond) 1381 // At this point cmd should still be running (because we passed nil to 1382 // startHang for the cancel signal). Sending it an explicit Interrupt signal 1383 // should succeed. 1384 if err := cmd.Process.Signal(os.Interrupt); err != nil { 1385 t.Error(err) 1386 } 1387 1388 err := cmd.Wait() 1389 t.Logf("stderr:\n%s", cmd.Stderr) 1390 t.Logf("[%d] %v", cmd.Process.Pid, err) 1391 1392 // This program exits with status 0, 1393 // but pretty much always does so during the wait delay. 1394 // Since the Cmd itself didn't do anything to stop the process when the 1395 // context expired, a successful exit is valid (even if late) and does 1396 // not merit a non-nil error. 1397 if err != nil { 1398 t.Errorf("Wait: %v; want nil", err) 1399 } 1400 if ps := cmd.ProcessState; !ps.Exited() { 1401 t.Errorf("cmd did not exit: %v", ps) 1402 } else if code := ps.ExitCode(); code != 0 { 1403 t.Errorf("cmd.ProcessState.ExitCode() = %v; want 0", code) 1404 } 1405 }) 1406 1407 // If the context is canceled and the Cancel function sends os.Kill, 1408 // the process should be terminated immediately, and its output 1409 // pipes should be closed (causing Wait to return) after WaitDelay 1410 // even if a child process is still writing to them. 1411 t.Run("SIGKILL-hang", func(t *testing.T) { 1412 t.Parallel() 1413 1414 ctx, cancel := context.WithCancel(context.Background()) 1415 cmd := startHang(t, ctx, tooLong, os.Kill, 10*time.Millisecond, "-subsleep=10m", "-probe=1ms") 1416 cancel() 1417 err := cmd.Wait() 1418 t.Logf("stderr:\n%s", cmd.Stderr) 1419 t.Logf("[%d] %v", cmd.Process.Pid, err) 1420 1421 // This test should kill the child process after 10ms, 1422 // leaving a grandchild process writing probes in a loop. 1423 // The child process should be reported as failed, 1424 // and the grandchild will exit (or die by SIGPIPE) once the 1425 // stderr pipe is closed. 1426 if ee := new(*exec.ExitError); !errors.As(err, ee) { 1427 t.Errorf("Wait error = %v; want %T", err, *ee) 1428 } 1429 }) 1430 1431 // If the process exits with status 0 but leaves a child behind writing 1432 // to its output pipes, Wait should only wait for WaitDelay before 1433 // closing the pipes and returning. Wait should return ErrWaitDelay 1434 // to indicate that the piped output may be incomplete even though the 1435 // command returned a “success” code. 1436 t.Run("Exit-hang", func(t *testing.T) { 1437 t.Parallel() 1438 1439 cmd := startHang(t, context.Background(), 1*time.Millisecond, nil, 10*time.Millisecond, "-subsleep=10m", "-probe=1ms") 1440 err := cmd.Wait() 1441 t.Logf("stderr:\n%s", cmd.Stderr) 1442 t.Logf("[%d] %v", cmd.Process.Pid, err) 1443 1444 // This child process should exit immediately, 1445 // leaving a grandchild process writing probes in a loop. 1446 // Since the child has no ExitError to report but we did not 1447 // read all of its output, Wait should return ErrWaitDelay. 1448 if !errors.Is(err, exec.ErrWaitDelay) { 1449 t.Errorf("Wait error = %v; want %T", err, exec.ErrWaitDelay) 1450 } 1451 }) 1452 1453 // If the Cancel function sends a signal that the process can handle, and it 1454 // handles that signal without actually exiting, then it should be terminated 1455 // after the WaitDelay. 1456 t.Run("SIGINT-ignored", func(t *testing.T) { 1457 if runtime.GOOS == "windows" { 1458 t.Skipf("skipping: os.Interrupt is not implemented on Windows") 1459 } 1460 t.Parallel() 1461 1462 ctx, cancel := context.WithCancel(context.Background()) 1463 cmd := startHang(t, ctx, tooLong, os.Interrupt, 10*time.Millisecond, "-interrupt=false") 1464 cancel() 1465 err := cmd.Wait() 1466 t.Logf("stderr:\n%s", cmd.Stderr) 1467 t.Logf("[%d] %v", cmd.Process.Pid, err) 1468 1469 // This command ignores SIGINT, sleeping until it is killed. 1470 // Wait should return the usual error for a killed process. 1471 if ee := new(*exec.ExitError); !errors.As(err, ee) { 1472 t.Errorf("Wait error = %v; want %T", err, *ee) 1473 } 1474 }) 1475 1476 // If the process handles the cancellation signal and exits with status 0, 1477 // Wait should report a non-nil error (because the process had to be 1478 // interrupted), and it should be a context error (because there is no error 1479 // to report from the child process itself). 1480 t.Run("SIGINT-handled", func(t *testing.T) { 1481 if runtime.GOOS == "windows" { 1482 t.Skipf("skipping: os.Interrupt is not implemented on Windows") 1483 } 1484 t.Parallel() 1485 1486 ctx, cancel := context.WithCancel(context.Background()) 1487 cmd := startHang(t, ctx, tooLong, os.Interrupt, 0, "-interrupt=true") 1488 cancel() 1489 err := cmd.Wait() 1490 t.Logf("stderr:\n%s", cmd.Stderr) 1491 t.Logf("[%d] %v", cmd.Process.Pid, err) 1492 1493 if !errors.Is(err, ctx.Err()) { 1494 t.Errorf("Wait error = %v; want %v", err, ctx.Err()) 1495 } 1496 if ps := cmd.ProcessState; !ps.Exited() { 1497 t.Errorf("cmd did not exit: %v", ps) 1498 } else if code := ps.ExitCode(); code != 0 { 1499 t.Errorf("cmd.ProcessState.ExitCode() = %v; want 0", code) 1500 } 1501 }) 1502 1503 // If the Cancel function sends SIGQUIT, it should be handled in the usual 1504 // way: a Go program should dump its goroutines and exit with non-success 1505 // status. (We expect SIGQUIT to be a common pattern in real-world use.) 1506 t.Run("SIGQUIT", func(t *testing.T) { 1507 if quitSignal == nil { 1508 t.Skipf("skipping: SIGQUIT is not supported on %v", runtime.GOOS) 1509 } 1510 t.Parallel() 1511 1512 ctx, cancel := context.WithCancel(context.Background()) 1513 cmd := startHang(t, ctx, tooLong, quitSignal, 0) 1514 cancel() 1515 err := cmd.Wait() 1516 t.Logf("stderr:\n%s", cmd.Stderr) 1517 t.Logf("[%d] %v", cmd.Process.Pid, err) 1518 1519 if ee := new(*exec.ExitError); !errors.As(err, ee) { 1520 t.Errorf("Wait error = %v; want %v", err, ctx.Err()) 1521 } 1522 1523 if ps := cmd.ProcessState; !ps.Exited() { 1524 t.Errorf("cmd did not exit: %v", ps) 1525 } else if code := ps.ExitCode(); code != 2 { 1526 // The default os/signal handler exits with code 2. 1527 t.Errorf("cmd.ProcessState.ExitCode() = %v; want 2", code) 1528 } 1529 1530 if !strings.Contains(fmt.Sprint(cmd.Stderr), "\n\ngoroutine ") { 1531 t.Errorf("cmd.Stderr does not contain a goroutine dump") 1532 } 1533 }) 1534} 1535 1536func TestCancelErrors(t *testing.T) { 1537 t.Parallel() 1538 1539 // If Cancel returns a non-ErrProcessDone error and the process 1540 // exits successfully, Wait should wrap the error from Cancel. 1541 t.Run("success after error", func(t *testing.T) { 1542 t.Parallel() 1543 1544 ctx, cancel := context.WithCancel(context.Background()) 1545 defer cancel() 1546 1547 cmd := helperCommandContext(t, ctx, "pipetest") 1548 stdin, err := cmd.StdinPipe() 1549 if err != nil { 1550 t.Fatal(err) 1551 } 1552 1553 errArbitrary := errors.New("arbitrary error") 1554 cmd.Cancel = func() error { 1555 stdin.Close() 1556 t.Logf("Cancel returning %v", errArbitrary) 1557 return errArbitrary 1558 } 1559 if err := cmd.Start(); err != nil { 1560 t.Fatal(err) 1561 } 1562 cancel() 1563 1564 err = cmd.Wait() 1565 t.Logf("[%d] %v", cmd.Process.Pid, err) 1566 if !errors.Is(err, errArbitrary) || err == errArbitrary { 1567 t.Errorf("Wait error = %v; want an error wrapping %v", err, errArbitrary) 1568 } 1569 }) 1570 1571 // If Cancel returns an error equivalent to ErrProcessDone, 1572 // Wait should ignore that error. (ErrProcessDone indicates that the 1573 // process was already done before we tried to interrupt it — maybe we 1574 // just didn't notice because Wait hadn't been called yet.) 1575 t.Run("success after ErrProcessDone", func(t *testing.T) { 1576 t.Parallel() 1577 1578 ctx, cancel := context.WithCancel(context.Background()) 1579 defer cancel() 1580 1581 cmd := helperCommandContext(t, ctx, "pipetest") 1582 stdin, err := cmd.StdinPipe() 1583 if err != nil { 1584 t.Fatal(err) 1585 } 1586 1587 stdout, err := cmd.StdoutPipe() 1588 if err != nil { 1589 t.Fatal(err) 1590 } 1591 1592 // We intentionally race Cancel against the process exiting, 1593 // but ensure that the process wins the race (and return ErrProcessDone 1594 // from Cancel to report that). 1595 interruptCalled := make(chan struct{}) 1596 done := make(chan struct{}) 1597 cmd.Cancel = func() error { 1598 close(interruptCalled) 1599 <-done 1600 t.Logf("Cancel returning an error wrapping ErrProcessDone") 1601 return fmt.Errorf("%w: stdout closed", os.ErrProcessDone) 1602 } 1603 1604 if err := cmd.Start(); err != nil { 1605 t.Fatal(err) 1606 } 1607 1608 cancel() 1609 <-interruptCalled 1610 stdin.Close() 1611 io.Copy(io.Discard, stdout) // reaches EOF when the process exits 1612 close(done) 1613 1614 err = cmd.Wait() 1615 t.Logf("[%d] %v", cmd.Process.Pid, err) 1616 if err != nil { 1617 t.Errorf("Wait error = %v; want nil", err) 1618 } 1619 }) 1620 1621 // If Cancel returns an error and the process is killed after 1622 // WaitDelay, Wait should report the usual SIGKILL ExitError, not the 1623 // error from Cancel. 1624 t.Run("killed after error", func(t *testing.T) { 1625 t.Parallel() 1626 1627 ctx, cancel := context.WithCancel(context.Background()) 1628 defer cancel() 1629 1630 cmd := helperCommandContext(t, ctx, "pipetest") 1631 stdin, err := cmd.StdinPipe() 1632 if err != nil { 1633 t.Fatal(err) 1634 } 1635 defer stdin.Close() 1636 1637 errArbitrary := errors.New("arbitrary error") 1638 var interruptCalled atomic.Bool 1639 cmd.Cancel = func() error { 1640 t.Logf("Cancel called") 1641 interruptCalled.Store(true) 1642 return errArbitrary 1643 } 1644 cmd.WaitDelay = 1 * time.Millisecond 1645 if err := cmd.Start(); err != nil { 1646 t.Fatal(err) 1647 } 1648 cancel() 1649 1650 err = cmd.Wait() 1651 t.Logf("[%d] %v", cmd.Process.Pid, err) 1652 1653 // Ensure that Cancel actually had the opportunity to 1654 // return the error. 1655 if !interruptCalled.Load() { 1656 t.Errorf("Cancel was not called when the context was canceled") 1657 } 1658 1659 // This test should kill the child process after 1ms, 1660 // To maximize compatibility with existing uses of exec.CommandContext, the 1661 // resulting error should be an exec.ExitError without additional wrapping. 1662 if _, ok := err.(*exec.ExitError); !ok { 1663 t.Errorf("Wait error = %v; want *exec.ExitError", err) 1664 } 1665 }) 1666 1667 // If Cancel returns ErrProcessDone but the process is not actually done 1668 // (and has to be killed), Wait should report the usual SIGKILL ExitError, 1669 // not the error from Cancel. 1670 t.Run("killed after spurious ErrProcessDone", func(t *testing.T) { 1671 t.Parallel() 1672 1673 ctx, cancel := context.WithCancel(context.Background()) 1674 defer cancel() 1675 1676 cmd := helperCommandContext(t, ctx, "pipetest") 1677 stdin, err := cmd.StdinPipe() 1678 if err != nil { 1679 t.Fatal(err) 1680 } 1681 defer stdin.Close() 1682 1683 var interruptCalled atomic.Bool 1684 cmd.Cancel = func() error { 1685 t.Logf("Cancel returning an error wrapping ErrProcessDone") 1686 interruptCalled.Store(true) 1687 return fmt.Errorf("%w: stdout closed", os.ErrProcessDone) 1688 } 1689 cmd.WaitDelay = 1 * time.Millisecond 1690 if err := cmd.Start(); err != nil { 1691 t.Fatal(err) 1692 } 1693 cancel() 1694 1695 err = cmd.Wait() 1696 t.Logf("[%d] %v", cmd.Process.Pid, err) 1697 1698 // Ensure that Cancel actually had the opportunity to 1699 // return the error. 1700 if !interruptCalled.Load() { 1701 t.Errorf("Cancel was not called when the context was canceled") 1702 } 1703 1704 // This test should kill the child process after 1ms, 1705 // To maximize compatibility with existing uses of exec.CommandContext, the 1706 // resulting error should be an exec.ExitError without additional wrapping. 1707 if ee, ok := err.(*exec.ExitError); !ok { 1708 t.Errorf("Wait error of type %T; want %T", err, ee) 1709 } 1710 }) 1711 1712 // If Cancel returns an error and the process exits with an 1713 // unsuccessful exit code, the process error should take precedence over the 1714 // Cancel error. 1715 t.Run("nonzero exit after error", func(t *testing.T) { 1716 t.Parallel() 1717 1718 ctx, cancel := context.WithCancel(context.Background()) 1719 defer cancel() 1720 1721 cmd := helperCommandContext(t, ctx, "stderrfail") 1722 stderr, err := cmd.StderrPipe() 1723 if err != nil { 1724 t.Fatal(err) 1725 } 1726 1727 errArbitrary := errors.New("arbitrary error") 1728 interrupted := make(chan struct{}) 1729 cmd.Cancel = func() error { 1730 close(interrupted) 1731 return errArbitrary 1732 } 1733 if err := cmd.Start(); err != nil { 1734 t.Fatal(err) 1735 } 1736 cancel() 1737 <-interrupted 1738 io.Copy(io.Discard, stderr) 1739 1740 err = cmd.Wait() 1741 t.Logf("[%d] %v", cmd.Process.Pid, err) 1742 1743 if ee, ok := err.(*exec.ExitError); !ok || ee.ProcessState.ExitCode() != 1 { 1744 t.Errorf("Wait error = %v; want exit status 1", err) 1745 } 1746 }) 1747} 1748 1749// TestConcurrentExec is a regression test for https://go.dev/issue/61080. 1750// 1751// Forking multiple child processes concurrently would sometimes hang on darwin. 1752// (This test hung on a gomote with -count=100 after only a few iterations.) 1753func TestConcurrentExec(t *testing.T) { 1754 ctx, cancel := context.WithCancel(context.Background()) 1755 1756 // This test will spawn nHangs subprocesses that hang reading from stdin, 1757 // and nExits subprocesses that exit immediately. 1758 // 1759 // When issue #61080 was present, a long-lived "hang" subprocess would 1760 // occasionally inherit the fork/exec status pipe from an "exit" subprocess, 1761 // causing the parent process (which expects to see an EOF on that pipe almost 1762 // immediately) to unexpectedly block on reading from the pipe. 1763 var ( 1764 nHangs = runtime.GOMAXPROCS(0) 1765 nExits = runtime.GOMAXPROCS(0) 1766 hangs, exits sync.WaitGroup 1767 ) 1768 hangs.Add(nHangs) 1769 exits.Add(nExits) 1770 1771 // ready is done when the goroutines have done as much work as possible to 1772 // prepare to create subprocesses. It isn't strictly necessary for the test, 1773 // but helps to increase the repro rate by making it more likely that calls to 1774 // syscall.StartProcess for the "hang" and "exit" goroutines overlap. 1775 var ready sync.WaitGroup 1776 ready.Add(nHangs + nExits) 1777 1778 for i := 0; i < nHangs; i++ { 1779 go func() { 1780 defer hangs.Done() 1781 1782 cmd := helperCommandContext(t, ctx, "pipetest") 1783 stdin, err := cmd.StdinPipe() 1784 if err != nil { 1785 ready.Done() 1786 t.Error(err) 1787 return 1788 } 1789 cmd.Cancel = stdin.Close 1790 ready.Done() 1791 1792 ready.Wait() 1793 if err := cmd.Start(); err != nil { 1794 if !errors.Is(err, context.Canceled) { 1795 t.Error(err) 1796 } 1797 return 1798 } 1799 1800 cmd.Wait() 1801 }() 1802 } 1803 1804 for i := 0; i < nExits; i++ { 1805 go func() { 1806 defer exits.Done() 1807 1808 cmd := helperCommandContext(t, ctx, "exit", "0") 1809 ready.Done() 1810 1811 ready.Wait() 1812 if err := cmd.Run(); err != nil { 1813 t.Error(err) 1814 } 1815 }() 1816 } 1817 1818 exits.Wait() 1819 cancel() 1820 hangs.Wait() 1821} 1822 1823// TestPathRace tests that [Cmd.String] can be called concurrently 1824// with [Cmd.Start]. 1825func TestPathRace(t *testing.T) { 1826 cmd := helperCommand(t, "exit", "0") 1827 1828 done := make(chan struct{}) 1829 go func() { 1830 out, err := cmd.CombinedOutput() 1831 t.Logf("%v: %v\n%s", cmd, err, out) 1832 close(done) 1833 }() 1834 1835 t.Logf("running in background: %v", cmd) 1836 <-done 1837} 1838 1839func TestAbsPathExec(t *testing.T) { 1840 testenv.MustHaveExec(t) 1841 testenv.MustHaveGoBuild(t) // must have GOROOT/bin/{go,gofmt} 1842 1843 // A simple exec of a full path should work. 1844 // Go 1.22 broke this on Windows, requiring ".exe"; see #66586. 1845 exe := filepath.Join(testenv.GOROOT(t), "bin/gofmt") 1846 cmd := exec.Command(exe) 1847 if cmd.Path != exe { 1848 t.Errorf("exec.Command(%#q) set Path=%#q", exe, cmd.Path) 1849 } 1850 err := cmd.Run() 1851 if err != nil { 1852 t.Errorf("using exec.Command(%#q): %v", exe, err) 1853 } 1854 1855 cmd = &exec.Cmd{Path: exe} 1856 err = cmd.Run() 1857 if err != nil { 1858 t.Errorf("using exec.Cmd{Path: %#q}: %v", cmd.Path, err) 1859 } 1860 1861 cmd = &exec.Cmd{Path: "gofmt", Dir: "/"} 1862 err = cmd.Run() 1863 if err == nil { 1864 t.Errorf("using exec.Cmd{Path: %#q}: unexpected success", cmd.Path) 1865 } 1866 1867 // A simple exec after modifying Cmd.Path should work. 1868 // This broke on Windows. See go.dev/issue/68314. 1869 t.Run("modified", func(t *testing.T) { 1870 if exec.Command(filepath.Join(testenv.GOROOT(t), "bin/go")).Run() == nil { 1871 // The implementation of the test case below relies on the go binary 1872 // exiting with a non-zero exit code when run without any arguments. 1873 // In the unlikely case that changes, we need to use another binary. 1874 t.Fatal("test case needs updating to verify fix for go.dev/issue/68314") 1875 } 1876 exe1 := filepath.Join(testenv.GOROOT(t), "bin/go") 1877 exe2 := filepath.Join(testenv.GOROOT(t), "bin/gofmt") 1878 cmd := exec.Command(exe1) 1879 cmd.Path = exe2 1880 cmd.Args = []string{cmd.Path} 1881 err := cmd.Run() 1882 if err != nil { 1883 t.Error("ran wrong binary") 1884 } 1885 }) 1886} 1887