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  }