Source file src/os/exec/exec_test.go

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

View as plain text