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