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

View as plain text