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  	//
   687  	// N.B. go build below explictly doesn't pass through
   688  	// -asan/-msan/-race, so we don't care about those.
   689  	testenv.MustInternalLink(t, testenv.NoSpecialBuildTypes)
   690  
   691  	if runtime.GOOS == "windows" {
   692  		t.Skipf("skipping test on %q", runtime.GOOS)
   693  	}
   694  
   695  	// Force network usage, to verify the epoll (or whatever) fd
   696  	// doesn't leak to the child,
   697  	ln, err := net.Listen("tcp", "127.0.0.1:0")
   698  	if err != nil {
   699  		t.Fatal(err)
   700  	}
   701  	defer ln.Close()
   702  
   703  	// Make sure duplicated fds don't leak to the child.
   704  	f, err := ln.(*net.TCPListener).File()
   705  	if err != nil {
   706  		t.Fatal(err)
   707  	}
   708  	defer f.Close()
   709  	ln2, err := net.FileListener(f)
   710  	if err != nil {
   711  		t.Fatal(err)
   712  	}
   713  	defer ln2.Close()
   714  
   715  	// Force TLS root certs to be loaded (which might involve
   716  	// cgo), to make sure none of that potential C code leaks fds.
   717  	ts := httptest.NewUnstartedServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {}))
   718  	// quiet expected TLS handshake error "remote error: bad certificate"
   719  	ts.Config.ErrorLog = log.New(io.Discard, "", 0)
   720  	ts.StartTLS()
   721  	defer ts.Close()
   722  	_, err = http.Get(ts.URL)
   723  	if err == nil {
   724  		t.Errorf("success trying to fetch %s; want an error", ts.URL)
   725  	}
   726  
   727  	tf, err := os.CreateTemp("", "")
   728  	if err != nil {
   729  		t.Fatalf("TempFile: %v", err)
   730  	}
   731  	defer os.Remove(tf.Name())
   732  	defer tf.Close()
   733  
   734  	const text = "Hello, fd 3!"
   735  	_, err = tf.Write([]byte(text))
   736  	if err != nil {
   737  		t.Fatalf("Write: %v", err)
   738  	}
   739  	_, err = tf.Seek(0, io.SeekStart)
   740  	if err != nil {
   741  		t.Fatalf("Seek: %v", err)
   742  	}
   743  
   744  	tempdir := t.TempDir()
   745  	exe := filepath.Join(tempdir, "read3.exe")
   746  
   747  	c := testenv.Command(t, testenv.GoToolPath(t), "build", "-o", exe, "read3.go")
   748  	// Build the test without cgo, so that C library functions don't
   749  	// open descriptors unexpectedly. See issue 25628.
   750  	c.Env = append(os.Environ(), "CGO_ENABLED=0")
   751  	if output, err := c.CombinedOutput(); err != nil {
   752  		t.Logf("go build -o %s read3.go\n%s", exe, output)
   753  		t.Fatalf("go build failed: %v", err)
   754  	}
   755  
   756  	// Use a deadline to try to get some output even if the program hangs.
   757  	ctx := context.Background()
   758  	if deadline, ok := t.Deadline(); ok {
   759  		// Leave a 20% grace period to flush output, which may be large on the
   760  		// linux/386 builders because we're running the subprocess under strace.
   761  		deadline = deadline.Add(-time.Until(deadline) / 5)
   762  
   763  		var cancel context.CancelFunc
   764  		ctx, cancel = context.WithDeadline(ctx, deadline)
   765  		defer cancel()
   766  	}
   767  
   768  	c = exec.CommandContext(ctx, exe)
   769  	var stdout, stderr strings.Builder
   770  	c.Stdout = &stdout
   771  	c.Stderr = &stderr
   772  	c.ExtraFiles = []*os.File{tf}
   773  	if runtime.GOOS == "illumos" {
   774  		// Some facilities in illumos are implemented via access
   775  		// to /proc by libc; such accesses can briefly occupy a
   776  		// low-numbered fd.  If this occurs concurrently with the
   777  		// test that checks for leaked descriptors, the check can
   778  		// become confused and report a spurious leaked descriptor.
   779  		// (See issue #42431 for more detailed analysis.)
   780  		//
   781  		// Attempt to constrain the use of additional threads in the
   782  		// child process to make this test less flaky:
   783  		c.Env = append(os.Environ(), "GOMAXPROCS=1")
   784  	}
   785  	err = c.Run()
   786  	if err != nil {
   787  		t.Fatalf("Run: %v\n--- stdout:\n%s--- stderr:\n%s", err, stdout.String(), stderr.String())
   788  	}
   789  	if stdout.String() != text {
   790  		t.Errorf("got stdout %q, stderr %q; want %q on stdout", stdout.String(), stderr.String(), text)
   791  	}
   792  }
   793  
   794  func TestExtraFilesRace(t *testing.T) {
   795  	if runtime.GOOS == "windows" {
   796  		maySkipHelperCommand("describefiles")
   797  		t.Skip("no operating system support; skipping")
   798  	}
   799  	t.Parallel()
   800  
   801  	listen := func() net.Listener {
   802  		ln, err := net.Listen("tcp", "127.0.0.1:0")
   803  		if err != nil {
   804  			t.Fatal(err)
   805  		}
   806  		return ln
   807  	}
   808  	listenerFile := func(ln net.Listener) *os.File {
   809  		f, err := ln.(*net.TCPListener).File()
   810  		if err != nil {
   811  			t.Fatal(err)
   812  		}
   813  		return f
   814  	}
   815  	runCommand := func(c *exec.Cmd, out chan<- string) {
   816  		bout, err := c.CombinedOutput()
   817  		if err != nil {
   818  			out <- "ERROR:" + err.Error()
   819  		} else {
   820  			out <- string(bout)
   821  		}
   822  	}
   823  
   824  	for i := 0; i < 10; i++ {
   825  		if testing.Short() && i >= 3 {
   826  			break
   827  		}
   828  		la := listen()
   829  		ca := helperCommand(t, "describefiles")
   830  		ca.ExtraFiles = []*os.File{listenerFile(la)}
   831  		lb := listen()
   832  		cb := helperCommand(t, "describefiles")
   833  		cb.ExtraFiles = []*os.File{listenerFile(lb)}
   834  		ares := make(chan string)
   835  		bres := make(chan string)
   836  		go runCommand(ca, ares)
   837  		go runCommand(cb, bres)
   838  		if got, want := <-ares, fmt.Sprintf("fd3: listener %s\n", la.Addr()); got != want {
   839  			t.Errorf("iteration %d, process A got:\n%s\nwant:\n%s\n", i, got, want)
   840  		}
   841  		if got, want := <-bres, fmt.Sprintf("fd3: listener %s\n", lb.Addr()); got != want {
   842  			t.Errorf("iteration %d, process B got:\n%s\nwant:\n%s\n", i, got, want)
   843  		}
   844  		la.Close()
   845  		lb.Close()
   846  		for _, f := range ca.ExtraFiles {
   847  			f.Close()
   848  		}
   849  		for _, f := range cb.ExtraFiles {
   850  			f.Close()
   851  		}
   852  	}
   853  }
   854  
   855  type delayedInfiniteReader struct{}
   856  
   857  func (delayedInfiniteReader) Read(b []byte) (int, error) {
   858  	time.Sleep(100 * time.Millisecond)
   859  	for i := range b {
   860  		b[i] = 'x'
   861  	}
   862  	return len(b), nil
   863  }
   864  
   865  // Issue 9173: ignore stdin pipe writes if the program completes successfully.
   866  func TestIgnorePipeErrorOnSuccess(t *testing.T) {
   867  	t.Parallel()
   868  
   869  	testWith := func(r io.Reader) func(*testing.T) {
   870  		return func(t *testing.T) {
   871  			t.Parallel()
   872  
   873  			cmd := helperCommand(t, "echo", "foo")
   874  			var out strings.Builder
   875  			cmd.Stdin = r
   876  			cmd.Stdout = &out
   877  			if err := cmd.Run(); err != nil {
   878  				t.Fatal(err)
   879  			}
   880  			if got, want := out.String(), "foo\n"; got != want {
   881  				t.Errorf("output = %q; want %q", got, want)
   882  			}
   883  		}
   884  	}
   885  	t.Run("10MB", testWith(strings.NewReader(strings.Repeat("x", 10<<20))))
   886  	t.Run("Infinite", testWith(delayedInfiniteReader{}))
   887  }
   888  
   889  type badWriter struct{}
   890  
   891  func (w *badWriter) Write(data []byte) (int, error) {
   892  	return 0, io.ErrUnexpectedEOF
   893  }
   894  
   895  func TestClosePipeOnCopyError(t *testing.T) {
   896  	t.Parallel()
   897  
   898  	cmd := helperCommand(t, "yes")
   899  	cmd.Stdout = new(badWriter)
   900  	err := cmd.Run()
   901  	if err == nil {
   902  		t.Errorf("yes unexpectedly completed successfully")
   903  	}
   904  }
   905  
   906  func TestOutputStderrCapture(t *testing.T) {
   907  	t.Parallel()
   908  
   909  	cmd := helperCommand(t, "stderrfail")
   910  	_, err := cmd.Output()
   911  	ee, ok := err.(*exec.ExitError)
   912  	if !ok {
   913  		t.Fatalf("Output error type = %T; want ExitError", err)
   914  	}
   915  	got := string(ee.Stderr)
   916  	want := "some stderr text\n"
   917  	if got != want {
   918  		t.Errorf("ExitError.Stderr = %q; want %q", got, want)
   919  	}
   920  }
   921  
   922  func TestContext(t *testing.T) {
   923  	t.Parallel()
   924  
   925  	ctx, cancel := context.WithCancel(context.Background())
   926  	c := helperCommandContext(t, ctx, "pipetest")
   927  	stdin, err := c.StdinPipe()
   928  	if err != nil {
   929  		t.Fatal(err)
   930  	}
   931  	stdout, err := c.StdoutPipe()
   932  	if err != nil {
   933  		t.Fatal(err)
   934  	}
   935  	if err := c.Start(); err != nil {
   936  		t.Fatal(err)
   937  	}
   938  
   939  	if _, err := stdin.Write([]byte("O:hi\n")); err != nil {
   940  		t.Fatal(err)
   941  	}
   942  	buf := make([]byte, 5)
   943  	n, err := io.ReadFull(stdout, buf)
   944  	if n != len(buf) || err != nil || string(buf) != "O:hi\n" {
   945  		t.Fatalf("ReadFull = %d, %v, %q", n, err, buf[:n])
   946  	}
   947  	go cancel()
   948  
   949  	if err := c.Wait(); err == nil {
   950  		t.Fatal("expected Wait failure")
   951  	}
   952  }
   953  
   954  func TestContextCancel(t *testing.T) {
   955  	if runtime.GOOS == "netbsd" && runtime.GOARCH == "arm64" {
   956  		maySkipHelperCommand("cat")
   957  		testenv.SkipFlaky(t, 42061)
   958  	}
   959  
   960  	// To reduce noise in the final goroutine dump,
   961  	// let other parallel tests complete if possible.
   962  	t.Parallel()
   963  
   964  	ctx, cancel := context.WithCancel(context.Background())
   965  	defer cancel()
   966  	c := helperCommandContext(t, ctx, "cat")
   967  
   968  	stdin, err := c.StdinPipe()
   969  	if err != nil {
   970  		t.Fatal(err)
   971  	}
   972  	defer stdin.Close()
   973  
   974  	if err := c.Start(); err != nil {
   975  		t.Fatal(err)
   976  	}
   977  
   978  	// At this point the process is alive. Ensure it by sending data to stdin.
   979  	if _, err := io.WriteString(stdin, "echo"); err != nil {
   980  		t.Fatal(err)
   981  	}
   982  
   983  	cancel()
   984  
   985  	// Calling cancel should have killed the process, so writes
   986  	// should now fail.  Give the process a little while to die.
   987  	start := time.Now()
   988  	delay := 1 * time.Millisecond
   989  	for {
   990  		if _, err := io.WriteString(stdin, "echo"); err != nil {
   991  			break
   992  		}
   993  
   994  		if time.Since(start) > time.Minute {
   995  			// Panic instead of calling t.Fatal so that we get a goroutine dump.
   996  			// We want to know exactly what the os/exec goroutines got stuck on.
   997  			debug.SetTraceback("system")
   998  			panic("canceling context did not stop program")
   999  		}
  1000  
  1001  		// Back off exponentially (up to 1-second sleeps) to give the OS time to
  1002  		// terminate the process.
  1003  		delay *= 2
  1004  		if delay > 1*time.Second {
  1005  			delay = 1 * time.Second
  1006  		}
  1007  		time.Sleep(delay)
  1008  	}
  1009  
  1010  	if err := c.Wait(); err == nil {
  1011  		t.Error("program unexpectedly exited successfully")
  1012  	} else {
  1013  		t.Logf("exit status: %v", err)
  1014  	}
  1015  }
  1016  
  1017  // test that environment variables are de-duped.
  1018  func TestDedupEnvEcho(t *testing.T) {
  1019  	t.Parallel()
  1020  
  1021  	cmd := helperCommand(t, "echoenv", "FOO")
  1022  	cmd.Env = append(cmd.Environ(), "FOO=bad", "FOO=good")
  1023  	out, err := cmd.CombinedOutput()
  1024  	if err != nil {
  1025  		t.Fatal(err)
  1026  	}
  1027  	if got, want := strings.TrimSpace(string(out)), "good"; got != want {
  1028  		t.Errorf("output = %q; want %q", got, want)
  1029  	}
  1030  }
  1031  
  1032  func TestEnvNULCharacter(t *testing.T) {
  1033  	if runtime.GOOS == "plan9" {
  1034  		t.Skip("plan9 explicitly allows NUL in the environment")
  1035  	}
  1036  	cmd := helperCommand(t, "echoenv", "FOO", "BAR")
  1037  	cmd.Env = append(cmd.Environ(), "FOO=foo\x00BAR=bar")
  1038  	out, err := cmd.CombinedOutput()
  1039  	if err == nil {
  1040  		t.Errorf("output = %q; want error", string(out))
  1041  	}
  1042  }
  1043  
  1044  func TestString(t *testing.T) {
  1045  	t.Parallel()
  1046  
  1047  	echoPath, err := exec.LookPath("echo")
  1048  	if err != nil {
  1049  		t.Skip(err)
  1050  	}
  1051  	tests := [...]struct {
  1052  		path string
  1053  		args []string
  1054  		want string
  1055  	}{
  1056  		{"echo", nil, echoPath},
  1057  		{"echo", []string{"a"}, echoPath + " a"},
  1058  		{"echo", []string{"a", "b"}, echoPath + " a b"},
  1059  	}
  1060  	for _, test := range tests {
  1061  		cmd := exec.Command(test.path, test.args...)
  1062  		if got := cmd.String(); got != test.want {
  1063  			t.Errorf("String(%q, %q) = %q, want %q", test.path, test.args, got, test.want)
  1064  		}
  1065  	}
  1066  }
  1067  
  1068  func TestStringPathNotResolved(t *testing.T) {
  1069  	t.Parallel()
  1070  
  1071  	_, err := exec.LookPath("makemeasandwich")
  1072  	if err == nil {
  1073  		t.Skip("wow, thanks")
  1074  	}
  1075  
  1076  	cmd := exec.Command("makemeasandwich", "-lettuce")
  1077  	want := "makemeasandwich -lettuce"
  1078  	if got := cmd.String(); got != want {
  1079  		t.Errorf("String(%q, %q) = %q, want %q", "makemeasandwich", "-lettuce", got, want)
  1080  	}
  1081  }
  1082  
  1083  func TestNoPath(t *testing.T) {
  1084  	err := new(exec.Cmd).Start()
  1085  	want := "exec: no command"
  1086  	if err == nil || err.Error() != want {
  1087  		t.Errorf("new(Cmd).Start() = %v, want %q", err, want)
  1088  	}
  1089  }
  1090  
  1091  // TestDoubleStartLeavesPipesOpen checks for a regression in which calling
  1092  // Start twice, which returns an error on the second call, would spuriously
  1093  // close the pipes established in the first call.
  1094  func TestDoubleStartLeavesPipesOpen(t *testing.T) {
  1095  	t.Parallel()
  1096  
  1097  	cmd := helperCommand(t, "pipetest")
  1098  	in, err := cmd.StdinPipe()
  1099  	if err != nil {
  1100  		t.Fatal(err)
  1101  	}
  1102  	out, err := cmd.StdoutPipe()
  1103  	if err != nil {
  1104  		t.Fatal(err)
  1105  	}
  1106  
  1107  	if err := cmd.Start(); err != nil {
  1108  		t.Fatal(err)
  1109  	}
  1110  	t.Cleanup(func() {
  1111  		if err := cmd.Wait(); err != nil {
  1112  			t.Error(err)
  1113  		}
  1114  	})
  1115  
  1116  	if err := cmd.Start(); err == nil || !strings.HasSuffix(err.Error(), "already started") {
  1117  		t.Fatalf("second call to Start returned a nil; want an 'already started' error")
  1118  	}
  1119  
  1120  	outc := make(chan []byte, 1)
  1121  	go func() {
  1122  		b, err := io.ReadAll(out)
  1123  		if err != nil {
  1124  			t.Error(err)
  1125  		}
  1126  		outc <- b
  1127  	}()
  1128  
  1129  	const msg = "O:Hello, pipe!\n"
  1130  
  1131  	_, err = io.WriteString(in, msg)
  1132  	if err != nil {
  1133  		t.Fatal(err)
  1134  	}
  1135  	in.Close()
  1136  
  1137  	b := <-outc
  1138  	if !bytes.Equal(b, []byte(msg)) {
  1139  		t.Fatalf("read %q from stdout pipe; want %q", b, msg)
  1140  	}
  1141  }
  1142  
  1143  func cmdHang(args ...string) {
  1144  	sleep, err := time.ParseDuration(args[0])
  1145  	if err != nil {
  1146  		panic(err)
  1147  	}
  1148  
  1149  	fs := flag.NewFlagSet("hang", flag.ExitOnError)
  1150  	exitOnInterrupt := fs.Bool("interrupt", false, "if true, commands should exit 0 on os.Interrupt")
  1151  	subsleep := fs.Duration("subsleep", 0, "amount of time for the 'hang' helper to leave an orphaned subprocess sleeping with stderr open")
  1152  	probe := fs.Duration("probe", 0, "if nonzero, the 'hang' helper should write to stderr at this interval, and exit nonzero if a write fails")
  1153  	read := fs.Bool("read", false, "if true, the 'hang' helper should read stdin to completion before sleeping")
  1154  	fs.Parse(args[1:])
  1155  
  1156  	pid := os.Getpid()
  1157  
  1158  	if *subsleep != 0 {
  1159  		cmd := exec.Command(testenv.Executable(nil), "hang", subsleep.String(), "-read=true", "-probe="+probe.String())
  1160  		cmd.Stdin = os.Stdin
  1161  		cmd.Stderr = os.Stderr
  1162  		out, err := cmd.StdoutPipe()
  1163  		if err != nil {
  1164  			fmt.Fprintln(os.Stderr, err)
  1165  			os.Exit(1)
  1166  		}
  1167  		cmd.Start()
  1168  
  1169  		buf := new(strings.Builder)
  1170  		if _, err := io.Copy(buf, out); err != nil {
  1171  			fmt.Fprintln(os.Stderr, err)
  1172  			cmd.Process.Kill()
  1173  			cmd.Wait()
  1174  			os.Exit(1)
  1175  		}
  1176  		fmt.Fprintf(os.Stderr, "%d: started %d: %v\n", pid, cmd.Process.Pid, cmd)
  1177  		go cmd.Wait() // Release resources if cmd happens not to outlive this process.
  1178  	}
  1179  
  1180  	if *exitOnInterrupt {
  1181  		c := make(chan os.Signal, 1)
  1182  		signal.Notify(c, os.Interrupt)
  1183  		go func() {
  1184  			sig := <-c
  1185  			fmt.Fprintf(os.Stderr, "%d: received %v\n", pid, sig)
  1186  			os.Exit(0)
  1187  		}()
  1188  	} else {
  1189  		signal.Ignore(os.Interrupt)
  1190  	}
  1191  
  1192  	// Signal that the process is set up by closing stdout.
  1193  	os.Stdout.Close()
  1194  
  1195  	if *read {
  1196  		if pipeSignal != nil {
  1197  			signal.Ignore(pipeSignal)
  1198  		}
  1199  		r := bufio.NewReader(os.Stdin)
  1200  		for {
  1201  			line, err := r.ReadBytes('\n')
  1202  			if len(line) > 0 {
  1203  				// Ignore write errors: we want to keep reading even if stderr is closed.
  1204  				fmt.Fprintf(os.Stderr, "%d: read %s", pid, line)
  1205  			}
  1206  			if err != nil {
  1207  				fmt.Fprintf(os.Stderr, "%d: finished read: %v", pid, err)
  1208  				break
  1209  			}
  1210  		}
  1211  	}
  1212  
  1213  	if *probe != 0 {
  1214  		ticker := time.NewTicker(*probe)
  1215  		go func() {
  1216  			for range ticker.C {
  1217  				if _, err := fmt.Fprintf(os.Stderr, "%d: ok\n", pid); err != nil {
  1218  					os.Exit(1)
  1219  				}
  1220  			}
  1221  		}()
  1222  	}
  1223  
  1224  	if sleep != 0 {
  1225  		time.Sleep(sleep)
  1226  		fmt.Fprintf(os.Stderr, "%d: slept %v\n", pid, sleep)
  1227  	}
  1228  }
  1229  
  1230  // A tickReader reads an unbounded sequence of timestamps at no more than a
  1231  // fixed interval.
  1232  type tickReader struct {
  1233  	interval time.Duration
  1234  	lastTick time.Time
  1235  	s        string
  1236  }
  1237  
  1238  func newTickReader(interval time.Duration) *tickReader {
  1239  	return &tickReader{interval: interval}
  1240  }
  1241  
  1242  func (r *tickReader) Read(p []byte) (n int, err error) {
  1243  	if len(r.s) == 0 {
  1244  		if d := r.interval - time.Since(r.lastTick); d > 0 {
  1245  			time.Sleep(d)
  1246  		}
  1247  		r.lastTick = time.Now()
  1248  		r.s = r.lastTick.Format(time.RFC3339Nano + "\n")
  1249  	}
  1250  
  1251  	n = copy(p, r.s)
  1252  	r.s = r.s[n:]
  1253  	return n, nil
  1254  }
  1255  
  1256  func startHang(t *testing.T, ctx context.Context, hangTime time.Duration, interrupt os.Signal, waitDelay time.Duration, flags ...string) *exec.Cmd {
  1257  	t.Helper()
  1258  
  1259  	args := append([]string{hangTime.String()}, flags...)
  1260  	cmd := helperCommandContext(t, ctx, "hang", args...)
  1261  	cmd.Stdin = newTickReader(1 * time.Millisecond)
  1262  	cmd.Stderr = new(strings.Builder)
  1263  	if interrupt == nil {
  1264  		cmd.Cancel = nil
  1265  	} else {
  1266  		cmd.Cancel = func() error {
  1267  			return cmd.Process.Signal(interrupt)
  1268  		}
  1269  	}
  1270  	cmd.WaitDelay = waitDelay
  1271  	out, err := cmd.StdoutPipe()
  1272  	if err != nil {
  1273  		t.Fatal(err)
  1274  	}
  1275  
  1276  	t.Log(cmd)
  1277  	if err := cmd.Start(); err != nil {
  1278  		t.Fatal(err)
  1279  	}
  1280  
  1281  	// Wait for cmd to close stdout to signal that its handlers are installed.
  1282  	buf := new(strings.Builder)
  1283  	if _, err := io.Copy(buf, out); err != nil {
  1284  		t.Error(err)
  1285  		cmd.Process.Kill()
  1286  		cmd.Wait()
  1287  		t.FailNow()
  1288  	}
  1289  	if buf.Len() > 0 {
  1290  		t.Logf("stdout %v:\n%s", cmd.Args, buf)
  1291  	}
  1292  
  1293  	return cmd
  1294  }
  1295  
  1296  func TestWaitInterrupt(t *testing.T) {
  1297  	t.Parallel()
  1298  
  1299  	// tooLong is an arbitrary duration that is expected to be much longer than
  1300  	// the test runs, but short enough that leaked processes will eventually exit
  1301  	// on their own.
  1302  	const tooLong = 10 * time.Minute
  1303  
  1304  	// Control case: with no cancellation and no WaitDelay, we should wait for the
  1305  	// process to exit.
  1306  	t.Run("Wait", func(t *testing.T) {
  1307  		t.Parallel()
  1308  		cmd := startHang(t, context.Background(), 1*time.Millisecond, os.Kill, 0)
  1309  		err := cmd.Wait()
  1310  		t.Logf("stderr:\n%s", cmd.Stderr)
  1311  		t.Logf("[%d] %v", cmd.Process.Pid, err)
  1312  
  1313  		if err != nil {
  1314  			t.Errorf("Wait: %v; want <nil>", err)
  1315  		}
  1316  		if ps := cmd.ProcessState; !ps.Exited() {
  1317  			t.Errorf("cmd did not exit: %v", ps)
  1318  		} else if code := ps.ExitCode(); code != 0 {
  1319  			t.Errorf("cmd.ProcessState.ExitCode() = %v; want 0", code)
  1320  		}
  1321  	})
  1322  
  1323  	// With a very long WaitDelay and no Cancel function, we should wait for the
  1324  	// process to exit even if the command's Context is canceled.
  1325  	t.Run("WaitDelay", func(t *testing.T) {
  1326  		if runtime.GOOS == "windows" {
  1327  			t.Skipf("skipping: os.Interrupt is not implemented on Windows")
  1328  		}
  1329  		t.Parallel()
  1330  
  1331  		ctx, cancel := context.WithCancel(context.Background())
  1332  		cmd := startHang(t, ctx, tooLong, nil, tooLong, "-interrupt=true")
  1333  		cancel()
  1334  
  1335  		time.Sleep(1 * time.Millisecond)
  1336  		// At this point cmd should still be running (because we passed nil to
  1337  		// startHang for the cancel signal). Sending it an explicit Interrupt signal
  1338  		// should succeed.
  1339  		if err := cmd.Process.Signal(os.Interrupt); err != nil {
  1340  			t.Error(err)
  1341  		}
  1342  
  1343  		err := cmd.Wait()
  1344  		t.Logf("stderr:\n%s", cmd.Stderr)
  1345  		t.Logf("[%d] %v", cmd.Process.Pid, err)
  1346  
  1347  		// This program exits with status 0,
  1348  		// but pretty much always does so during the wait delay.
  1349  		// Since the Cmd itself didn't do anything to stop the process when the
  1350  		// context expired, a successful exit is valid (even if late) and does
  1351  		// not merit a non-nil error.
  1352  		if err != nil {
  1353  			t.Errorf("Wait: %v; want nil", err)
  1354  		}
  1355  		if ps := cmd.ProcessState; !ps.Exited() {
  1356  			t.Errorf("cmd did not exit: %v", ps)
  1357  		} else if code := ps.ExitCode(); code != 0 {
  1358  			t.Errorf("cmd.ProcessState.ExitCode() = %v; want 0", code)
  1359  		}
  1360  	})
  1361  
  1362  	// If the context is canceled and the Cancel function sends os.Kill,
  1363  	// the process should be terminated immediately, and its output
  1364  	// pipes should be closed (causing Wait to return) after WaitDelay
  1365  	// even if a child process is still writing to them.
  1366  	t.Run("SIGKILL-hang", func(t *testing.T) {
  1367  		t.Parallel()
  1368  
  1369  		ctx, cancel := context.WithCancel(context.Background())
  1370  		cmd := startHang(t, ctx, tooLong, os.Kill, 10*time.Millisecond, "-subsleep=10m", "-probe=1ms")
  1371  		cancel()
  1372  		err := cmd.Wait()
  1373  		t.Logf("stderr:\n%s", cmd.Stderr)
  1374  		t.Logf("[%d] %v", cmd.Process.Pid, err)
  1375  
  1376  		// This test should kill the child process after 10ms,
  1377  		// leaving a grandchild process writing probes in a loop.
  1378  		// The child process should be reported as failed,
  1379  		// and the grandchild will exit (or die by SIGPIPE) once the
  1380  		// stderr pipe is closed.
  1381  		if ee := new(*exec.ExitError); !errors.As(err, ee) {
  1382  			t.Errorf("Wait error = %v; want %T", err, *ee)
  1383  		}
  1384  	})
  1385  
  1386  	// If the process exits with status 0 but leaves a child behind writing
  1387  	// to its output pipes, Wait should only wait for WaitDelay before
  1388  	// closing the pipes and returning.  Wait should return ErrWaitDelay
  1389  	// to indicate that the piped output may be incomplete even though the
  1390  	// command returned a “success” code.
  1391  	t.Run("Exit-hang", func(t *testing.T) {
  1392  		t.Parallel()
  1393  
  1394  		cmd := startHang(t, context.Background(), 1*time.Millisecond, nil, 10*time.Millisecond, "-subsleep=10m", "-probe=1ms")
  1395  		err := cmd.Wait()
  1396  		t.Logf("stderr:\n%s", cmd.Stderr)
  1397  		t.Logf("[%d] %v", cmd.Process.Pid, err)
  1398  
  1399  		// This child process should exit immediately,
  1400  		// leaving a grandchild process writing probes in a loop.
  1401  		// Since the child has no ExitError to report but we did not
  1402  		// read all of its output, Wait should return ErrWaitDelay.
  1403  		if !errors.Is(err, exec.ErrWaitDelay) {
  1404  			t.Errorf("Wait error = %v; want %T", err, exec.ErrWaitDelay)
  1405  		}
  1406  	})
  1407  
  1408  	// If the Cancel function sends a signal that the process can handle, and it
  1409  	// handles that signal without actually exiting, then it should be terminated
  1410  	// after the WaitDelay.
  1411  	t.Run("SIGINT-ignored", func(t *testing.T) {
  1412  		if runtime.GOOS == "windows" {
  1413  			t.Skipf("skipping: os.Interrupt is not implemented on Windows")
  1414  		}
  1415  		t.Parallel()
  1416  
  1417  		ctx, cancel := context.WithCancel(context.Background())
  1418  		cmd := startHang(t, ctx, tooLong, os.Interrupt, 10*time.Millisecond, "-interrupt=false")
  1419  		cancel()
  1420  		err := cmd.Wait()
  1421  		t.Logf("stderr:\n%s", cmd.Stderr)
  1422  		t.Logf("[%d] %v", cmd.Process.Pid, err)
  1423  
  1424  		// This command ignores SIGINT, sleeping until it is killed.
  1425  		// Wait should return the usual error for a killed process.
  1426  		if ee := new(*exec.ExitError); !errors.As(err, ee) {
  1427  			t.Errorf("Wait error = %v; want %T", err, *ee)
  1428  		}
  1429  	})
  1430  
  1431  	// If the process handles the cancellation signal and exits with status 0,
  1432  	// Wait should report a non-nil error (because the process had to be
  1433  	// interrupted), and it should be a context error (because there is no error
  1434  	// to report from the child process itself).
  1435  	t.Run("SIGINT-handled", func(t *testing.T) {
  1436  		if runtime.GOOS == "windows" {
  1437  			t.Skipf("skipping: os.Interrupt is not implemented on Windows")
  1438  		}
  1439  		t.Parallel()
  1440  
  1441  		ctx, cancel := context.WithCancel(context.Background())
  1442  		cmd := startHang(t, ctx, tooLong, os.Interrupt, 0, "-interrupt=true")
  1443  		cancel()
  1444  		err := cmd.Wait()
  1445  		t.Logf("stderr:\n%s", cmd.Stderr)
  1446  		t.Logf("[%d] %v", cmd.Process.Pid, err)
  1447  
  1448  		if !errors.Is(err, ctx.Err()) {
  1449  			t.Errorf("Wait error = %v; want %v", err, ctx.Err())
  1450  		}
  1451  		if ps := cmd.ProcessState; !ps.Exited() {
  1452  			t.Errorf("cmd did not exit: %v", ps)
  1453  		} else if code := ps.ExitCode(); code != 0 {
  1454  			t.Errorf("cmd.ProcessState.ExitCode() = %v; want 0", code)
  1455  		}
  1456  	})
  1457  
  1458  	// If the Cancel function sends SIGQUIT, it should be handled in the usual
  1459  	// way: a Go program should dump its goroutines and exit with non-success
  1460  	// status. (We expect SIGQUIT to be a common pattern in real-world use.)
  1461  	t.Run("SIGQUIT", func(t *testing.T) {
  1462  		if quitSignal == nil {
  1463  			t.Skipf("skipping: SIGQUIT is not supported on %v", runtime.GOOS)
  1464  		}
  1465  		t.Parallel()
  1466  
  1467  		ctx, cancel := context.WithCancel(context.Background())
  1468  		cmd := startHang(t, ctx, tooLong, quitSignal, 0)
  1469  		cancel()
  1470  		err := cmd.Wait()
  1471  		t.Logf("stderr:\n%s", cmd.Stderr)
  1472  		t.Logf("[%d] %v", cmd.Process.Pid, err)
  1473  
  1474  		if ee := new(*exec.ExitError); !errors.As(err, ee) {
  1475  			t.Errorf("Wait error = %v; want %v", err, ctx.Err())
  1476  		}
  1477  
  1478  		if ps := cmd.ProcessState; !ps.Exited() {
  1479  			t.Errorf("cmd did not exit: %v", ps)
  1480  		} else if code := ps.ExitCode(); code != 2 {
  1481  			// The default os/signal handler exits with code 2.
  1482  			t.Errorf("cmd.ProcessState.ExitCode() = %v; want 2", code)
  1483  		}
  1484  
  1485  		if !strings.Contains(fmt.Sprint(cmd.Stderr), "\n\ngoroutine ") {
  1486  			t.Errorf("cmd.Stderr does not contain a goroutine dump")
  1487  		}
  1488  	})
  1489  }
  1490  
  1491  func TestCancelErrors(t *testing.T) {
  1492  	t.Parallel()
  1493  
  1494  	// If Cancel returns a non-ErrProcessDone error and the process
  1495  	// exits successfully, Wait should wrap the error from Cancel.
  1496  	t.Run("success after error", func(t *testing.T) {
  1497  		t.Parallel()
  1498  
  1499  		ctx, cancel := context.WithCancel(context.Background())
  1500  		defer cancel()
  1501  
  1502  		cmd := helperCommandContext(t, ctx, "pipetest")
  1503  		stdin, err := cmd.StdinPipe()
  1504  		if err != nil {
  1505  			t.Fatal(err)
  1506  		}
  1507  
  1508  		errArbitrary := errors.New("arbitrary error")
  1509  		cmd.Cancel = func() error {
  1510  			stdin.Close()
  1511  			t.Logf("Cancel returning %v", errArbitrary)
  1512  			return errArbitrary
  1513  		}
  1514  		if err := cmd.Start(); err != nil {
  1515  			t.Fatal(err)
  1516  		}
  1517  		cancel()
  1518  
  1519  		err = cmd.Wait()
  1520  		t.Logf("[%d] %v", cmd.Process.Pid, err)
  1521  		if !errors.Is(err, errArbitrary) || err == errArbitrary {
  1522  			t.Errorf("Wait error = %v; want an error wrapping %v", err, errArbitrary)
  1523  		}
  1524  	})
  1525  
  1526  	// If Cancel returns an error equivalent to ErrProcessDone,
  1527  	// Wait should ignore that error. (ErrProcessDone indicates that the
  1528  	// process was already done before we tried to interrupt it — maybe we
  1529  	// just didn't notice because Wait hadn't been called yet.)
  1530  	t.Run("success after ErrProcessDone", func(t *testing.T) {
  1531  		t.Parallel()
  1532  
  1533  		ctx, cancel := context.WithCancel(context.Background())
  1534  		defer cancel()
  1535  
  1536  		cmd := helperCommandContext(t, ctx, "pipetest")
  1537  		stdin, err := cmd.StdinPipe()
  1538  		if err != nil {
  1539  			t.Fatal(err)
  1540  		}
  1541  
  1542  		stdout, err := cmd.StdoutPipe()
  1543  		if err != nil {
  1544  			t.Fatal(err)
  1545  		}
  1546  
  1547  		// We intentionally race Cancel against the process exiting,
  1548  		// but ensure that the process wins the race (and return ErrProcessDone
  1549  		// from Cancel to report that).
  1550  		interruptCalled := make(chan struct{})
  1551  		done := make(chan struct{})
  1552  		cmd.Cancel = func() error {
  1553  			close(interruptCalled)
  1554  			<-done
  1555  			t.Logf("Cancel returning an error wrapping ErrProcessDone")
  1556  			return fmt.Errorf("%w: stdout closed", os.ErrProcessDone)
  1557  		}
  1558  
  1559  		if err := cmd.Start(); err != nil {
  1560  			t.Fatal(err)
  1561  		}
  1562  
  1563  		cancel()
  1564  		<-interruptCalled
  1565  		stdin.Close()
  1566  		io.Copy(io.Discard, stdout) // reaches EOF when the process exits
  1567  		close(done)
  1568  
  1569  		err = cmd.Wait()
  1570  		t.Logf("[%d] %v", cmd.Process.Pid, err)
  1571  		if err != nil {
  1572  			t.Errorf("Wait error = %v; want nil", err)
  1573  		}
  1574  	})
  1575  
  1576  	// If Cancel returns an error and the process is killed after
  1577  	// WaitDelay, Wait should report the usual SIGKILL ExitError, not the
  1578  	// error from Cancel.
  1579  	t.Run("killed after error", func(t *testing.T) {
  1580  		t.Parallel()
  1581  
  1582  		ctx, cancel := context.WithCancel(context.Background())
  1583  		defer cancel()
  1584  
  1585  		cmd := helperCommandContext(t, ctx, "pipetest")
  1586  		stdin, err := cmd.StdinPipe()
  1587  		if err != nil {
  1588  			t.Fatal(err)
  1589  		}
  1590  		defer stdin.Close()
  1591  
  1592  		errArbitrary := errors.New("arbitrary error")
  1593  		var interruptCalled atomic.Bool
  1594  		cmd.Cancel = func() error {
  1595  			t.Logf("Cancel called")
  1596  			interruptCalled.Store(true)
  1597  			return errArbitrary
  1598  		}
  1599  		cmd.WaitDelay = 1 * time.Millisecond
  1600  		if err := cmd.Start(); err != nil {
  1601  			t.Fatal(err)
  1602  		}
  1603  		cancel()
  1604  
  1605  		err = cmd.Wait()
  1606  		t.Logf("[%d] %v", cmd.Process.Pid, err)
  1607  
  1608  		// Ensure that Cancel actually had the opportunity to
  1609  		// return the error.
  1610  		if !interruptCalled.Load() {
  1611  			t.Errorf("Cancel was not called when the context was canceled")
  1612  		}
  1613  
  1614  		// This test should kill the child process after 1ms,
  1615  		// To maximize compatibility with existing uses of exec.CommandContext, the
  1616  		// resulting error should be an exec.ExitError without additional wrapping.
  1617  		if _, ok := err.(*exec.ExitError); !ok {
  1618  			t.Errorf("Wait error = %v; want *exec.ExitError", err)
  1619  		}
  1620  	})
  1621  
  1622  	// If Cancel returns ErrProcessDone but the process is not actually done
  1623  	// (and has to be killed), Wait should report the usual SIGKILL ExitError,
  1624  	// not the error from Cancel.
  1625  	t.Run("killed after spurious ErrProcessDone", func(t *testing.T) {
  1626  		t.Parallel()
  1627  
  1628  		ctx, cancel := context.WithCancel(context.Background())
  1629  		defer cancel()
  1630  
  1631  		cmd := helperCommandContext(t, ctx, "pipetest")
  1632  		stdin, err := cmd.StdinPipe()
  1633  		if err != nil {
  1634  			t.Fatal(err)
  1635  		}
  1636  		defer stdin.Close()
  1637  
  1638  		var interruptCalled atomic.Bool
  1639  		cmd.Cancel = func() error {
  1640  			t.Logf("Cancel returning an error wrapping ErrProcessDone")
  1641  			interruptCalled.Store(true)
  1642  			return fmt.Errorf("%w: stdout closed", os.ErrProcessDone)
  1643  		}
  1644  		cmd.WaitDelay = 1 * time.Millisecond
  1645  		if err := cmd.Start(); err != nil {
  1646  			t.Fatal(err)
  1647  		}
  1648  		cancel()
  1649  
  1650  		err = cmd.Wait()
  1651  		t.Logf("[%d] %v", cmd.Process.Pid, err)
  1652  
  1653  		// Ensure that Cancel actually had the opportunity to
  1654  		// return the error.
  1655  		if !interruptCalled.Load() {
  1656  			t.Errorf("Cancel was not called when the context was canceled")
  1657  		}
  1658  
  1659  		// This test should kill the child process after 1ms,
  1660  		// To maximize compatibility with existing uses of exec.CommandContext, the
  1661  		// resulting error should be an exec.ExitError without additional wrapping.
  1662  		if ee, ok := err.(*exec.ExitError); !ok {
  1663  			t.Errorf("Wait error of type %T; want %T", err, ee)
  1664  		}
  1665  	})
  1666  
  1667  	// If Cancel returns an error and the process exits with an
  1668  	// unsuccessful exit code, the process error should take precedence over the
  1669  	// Cancel error.
  1670  	t.Run("nonzero exit after error", func(t *testing.T) {
  1671  		t.Parallel()
  1672  
  1673  		ctx, cancel := context.WithCancel(context.Background())
  1674  		defer cancel()
  1675  
  1676  		cmd := helperCommandContext(t, ctx, "stderrfail")
  1677  		stderr, err := cmd.StderrPipe()
  1678  		if err != nil {
  1679  			t.Fatal(err)
  1680  		}
  1681  
  1682  		errArbitrary := errors.New("arbitrary error")
  1683  		interrupted := make(chan struct{})
  1684  		cmd.Cancel = func() error {
  1685  			close(interrupted)
  1686  			return errArbitrary
  1687  		}
  1688  		if err := cmd.Start(); err != nil {
  1689  			t.Fatal(err)
  1690  		}
  1691  		cancel()
  1692  		<-interrupted
  1693  		io.Copy(io.Discard, stderr)
  1694  
  1695  		err = cmd.Wait()
  1696  		t.Logf("[%d] %v", cmd.Process.Pid, err)
  1697  
  1698  		if ee, ok := err.(*exec.ExitError); !ok || ee.ProcessState.ExitCode() != 1 {
  1699  			t.Errorf("Wait error = %v; want exit status 1", err)
  1700  		}
  1701  	})
  1702  }
  1703  
  1704  // TestConcurrentExec is a regression test for https://go.dev/issue/61080.
  1705  //
  1706  // Forking multiple child processes concurrently would sometimes hang on darwin.
  1707  // (This test hung on a gomote with -count=100 after only a few iterations.)
  1708  func TestConcurrentExec(t *testing.T) {
  1709  	ctx, cancel := context.WithCancel(context.Background())
  1710  
  1711  	// This test will spawn nHangs subprocesses that hang reading from stdin,
  1712  	// and nExits subprocesses that exit immediately.
  1713  	//
  1714  	// When issue #61080 was present, a long-lived "hang" subprocess would
  1715  	// occasionally inherit the fork/exec status pipe from an "exit" subprocess,
  1716  	// causing the parent process (which expects to see an EOF on that pipe almost
  1717  	// immediately) to unexpectedly block on reading from the pipe.
  1718  	var (
  1719  		nHangs       = runtime.GOMAXPROCS(0)
  1720  		nExits       = runtime.GOMAXPROCS(0)
  1721  		hangs, exits sync.WaitGroup
  1722  	)
  1723  	hangs.Add(nHangs)
  1724  	exits.Add(nExits)
  1725  
  1726  	// ready is done when the goroutines have done as much work as possible to
  1727  	// prepare to create subprocesses. It isn't strictly necessary for the test,
  1728  	// but helps to increase the repro rate by making it more likely that calls to
  1729  	// syscall.StartProcess for the "hang" and "exit" goroutines overlap.
  1730  	var ready sync.WaitGroup
  1731  	ready.Add(nHangs + nExits)
  1732  
  1733  	for i := 0; i < nHangs; i++ {
  1734  		go func() {
  1735  			defer hangs.Done()
  1736  
  1737  			cmd := helperCommandContext(t, ctx, "pipetest")
  1738  			stdin, err := cmd.StdinPipe()
  1739  			if err != nil {
  1740  				ready.Done()
  1741  				t.Error(err)
  1742  				return
  1743  			}
  1744  			cmd.Cancel = stdin.Close
  1745  			ready.Done()
  1746  
  1747  			ready.Wait()
  1748  			if err := cmd.Start(); err != nil {
  1749  				if !errors.Is(err, context.Canceled) {
  1750  					t.Error(err)
  1751  				}
  1752  				return
  1753  			}
  1754  
  1755  			cmd.Wait()
  1756  		}()
  1757  	}
  1758  
  1759  	for i := 0; i < nExits; i++ {
  1760  		go func() {
  1761  			defer exits.Done()
  1762  
  1763  			cmd := helperCommandContext(t, ctx, "exit", "0")
  1764  			ready.Done()
  1765  
  1766  			ready.Wait()
  1767  			if err := cmd.Run(); err != nil {
  1768  				t.Error(err)
  1769  			}
  1770  		}()
  1771  	}
  1772  
  1773  	exits.Wait()
  1774  	cancel()
  1775  	hangs.Wait()
  1776  }
  1777  
  1778  // TestPathRace tests that [Cmd.String] can be called concurrently
  1779  // with [Cmd.Start].
  1780  func TestPathRace(t *testing.T) {
  1781  	cmd := helperCommand(t, "exit", "0")
  1782  
  1783  	done := make(chan struct{})
  1784  	go func() {
  1785  		out, err := cmd.CombinedOutput()
  1786  		t.Logf("%v: %v\n%s", cmd, err, out)
  1787  		close(done)
  1788  	}()
  1789  
  1790  	t.Logf("running in background: %v", cmd)
  1791  	<-done
  1792  }
  1793  
  1794  func TestAbsPathExec(t *testing.T) {
  1795  	testenv.MustHaveExec(t)
  1796  	testenv.MustHaveGoBuild(t) // must have GOROOT/bin/{go,gofmt}
  1797  
  1798  	// A simple exec of a full path should work.
  1799  	// Go 1.22 broke this on Windows, requiring ".exe"; see #66586.
  1800  	exe := filepath.Join(testenv.GOROOT(t), "bin/gofmt")
  1801  	cmd := exec.Command(exe)
  1802  	if cmd.Path != exe {
  1803  		t.Errorf("exec.Command(%#q) set Path=%#q", exe, cmd.Path)
  1804  	}
  1805  	err := cmd.Run()
  1806  	if err != nil {
  1807  		t.Errorf("using exec.Command(%#q): %v", exe, err)
  1808  	}
  1809  
  1810  	cmd = &exec.Cmd{Path: exe}
  1811  	err = cmd.Run()
  1812  	if err != nil {
  1813  		t.Errorf("using exec.Cmd{Path: %#q}: %v", cmd.Path, err)
  1814  	}
  1815  
  1816  	cmd = &exec.Cmd{Path: "gofmt", Dir: "/"}
  1817  	err = cmd.Run()
  1818  	if err == nil {
  1819  		t.Errorf("using exec.Cmd{Path: %#q}: unexpected success", cmd.Path)
  1820  	}
  1821  
  1822  	// A simple exec after modifying Cmd.Path should work.
  1823  	// This broke on Windows. See go.dev/issue/68314.
  1824  	t.Run("modified", func(t *testing.T) {
  1825  		if exec.Command(filepath.Join(testenv.GOROOT(t), "bin/go")).Run() == nil {
  1826  			// The implementation of the test case below relies on the go binary
  1827  			// exiting with a non-zero exit code when run without any arguments.
  1828  			// In the unlikely case that changes, we need to use another binary.
  1829  			t.Fatal("test case needs updating to verify fix for go.dev/issue/68314")
  1830  		}
  1831  		exe1 := filepath.Join(testenv.GOROOT(t), "bin/go")
  1832  		exe2 := filepath.Join(testenv.GOROOT(t), "bin/gofmt")
  1833  		cmd := exec.Command(exe1)
  1834  		cmd.Path = exe2
  1835  		cmd.Args = []string{cmd.Path}
  1836  		err := cmd.Run()
  1837  		if err != nil {
  1838  			t.Error("ran wrong binary")
  1839  		}
  1840  	})
  1841  }
  1842  

View as plain text