github.com/mtsmfm/go/src@v0.0.0-20221020090648-44bdcb9f8fde/os/exec/exec_test.go (about)

     1  // Copyright 2009 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  // Use an external test to avoid os/exec -> net/http -> crypto/x509 -> os/exec
     6  // circular dependency on non-cgo darwin.
     7  
     8  package exec_test
     9  
    10  import (
    11  	"bufio"
    12  	"bytes"
    13  	"context"
    14  	"errors"
    15  	"flag"
    16  	"fmt"
    17  	"internal/poll"
    18  	"internal/testenv"
    19  	"io"
    20  	"log"
    21  	"net"
    22  	"net/http"
    23  	"net/http/httptest"
    24  	"os"
    25  	"os/exec"
    26  	"os/exec/internal/fdtest"
    27  	"path/filepath"
    28  	"reflect"
    29  	"runtime"
    30  	"runtime/debug"
    31  	"strconv"
    32  	"strings"
    33  	"sync"
    34  	"testing"
    35  	"time"
    36  )
    37  
    38  // haveUnexpectedFDs is set at init time to report whether any file descriptors
    39  // were open at program start.
    40  var haveUnexpectedFDs bool
    41  
    42  func init() {
    43  	godebug := os.Getenv("GODEBUG")
    44  	if godebug != "" {
    45  		godebug += ","
    46  	}
    47  	godebug += "execwait=2"
    48  	os.Setenv("GODEBUG", godebug)
    49  
    50  	if os.Getenv("GO_EXEC_TEST_PID") != "" {
    51  		return
    52  	}
    53  	if runtime.GOOS == "windows" {
    54  		return
    55  	}
    56  	for fd := uintptr(3); fd <= 100; fd++ {
    57  		if poll.IsPollDescriptor(fd) {
    58  			continue
    59  		}
    60  
    61  		if fdtest.Exists(fd) {
    62  			haveUnexpectedFDs = true
    63  			return
    64  		}
    65  	}
    66  }
    67  
    68  // TestMain allows the test binary to impersonate many other binaries,
    69  // some of which may manipulate os.Stdin, os.Stdout, and/or os.Stderr
    70  // (and thus cannot run as an ordinary Test function, since the testing
    71  // package monkey-patches those variables before running tests).
    72  func TestMain(m *testing.M) {
    73  	flag.Parse()
    74  
    75  	pid := os.Getpid()
    76  	if os.Getenv("GO_EXEC_TEST_PID") == "" {
    77  		os.Setenv("GO_EXEC_TEST_PID", strconv.Itoa(pid))
    78  
    79  		code := m.Run()
    80  		if code == 0 && flag.Lookup("test.run").Value.String() == "" && flag.Lookup("test.list").Value.String() == "" {
    81  			for cmd := range helperCommands {
    82  				if _, ok := helperCommandUsed.Load(cmd); !ok {
    83  					fmt.Fprintf(os.Stderr, "helper command unused: %q\n", cmd)
    84  					code = 1
    85  				}
    86  			}
    87  		}
    88  
    89  		if !testing.Short() {
    90  			// Run a couple of GC cycles to increase the odds of detecting
    91  			// process leaks using the finalizers installed by GODEBUG=execwait=2.
    92  			runtime.GC()
    93  			runtime.GC()
    94  		}
    95  
    96  		os.Exit(code)
    97  	}
    98  
    99  	args := flag.Args()
   100  	if len(args) == 0 {
   101  		fmt.Fprintf(os.Stderr, "No command\n")
   102  		os.Exit(2)
   103  	}
   104  
   105  	cmd, args := args[0], args[1:]
   106  	f, ok := helperCommands[cmd]
   107  	if !ok {
   108  		fmt.Fprintf(os.Stderr, "Unknown command %q\n", cmd)
   109  		os.Exit(2)
   110  	}
   111  	f(args...)
   112  	os.Exit(0)
   113  }
   114  
   115  // registerHelperCommand registers a command that the test process can impersonate.
   116  // A command should be registered in the same source file in which it is used.
   117  // If all tests are run and pass, all registered commands must be used.
   118  // (This prevents stale commands from accreting if tests are removed or
   119  // refactored over time.)
   120  func registerHelperCommand(name string, f func(...string)) {
   121  	if helperCommands[name] != nil {
   122  		panic("duplicate command registered: " + name)
   123  	}
   124  	helperCommands[name] = f
   125  }
   126  
   127  // maySkipHelperCommand records that the test that uses the named helper command
   128  // was invoked, but may call Skip on the test before actually calling
   129  // helperCommand.
   130  func maySkipHelperCommand(name string) {
   131  	helperCommandUsed.Store(name, true)
   132  }
   133  
   134  // helperCommand returns an exec.Cmd that will run the named helper command.
   135  func helperCommand(t *testing.T, name string, args ...string) *exec.Cmd {
   136  	t.Helper()
   137  	return helperCommandContext(t, nil, name, args...)
   138  }
   139  
   140  // helperCommandContext is like helperCommand, but also accepts a Context under
   141  // which to run the command.
   142  func helperCommandContext(t *testing.T, ctx context.Context, name string, args ...string) (cmd *exec.Cmd) {
   143  	helperCommandUsed.LoadOrStore(name, true)
   144  
   145  	t.Helper()
   146  	testenv.MustHaveExec(t)
   147  
   148  	cs := append([]string{name}, args...)
   149  	if ctx != nil {
   150  		cmd = exec.CommandContext(ctx, exePath(t), cs...)
   151  	} else {
   152  		cmd = exec.Command(exePath(t), cs...)
   153  	}
   154  	return cmd
   155  }
   156  
   157  // exePath returns the path to the running executable.
   158  func exePath(t testing.TB) string {
   159  	exeOnce.Do(func() {
   160  		// Use os.Executable instead of os.Args[0] in case the caller modifies
   161  		// cmd.Dir: if the test binary is invoked like "./exec.test", it should
   162  		// not fail spuriously.
   163  		exeOnce.path, exeOnce.err = os.Executable()
   164  	})
   165  
   166  	if exeOnce.err != nil {
   167  		if t == nil {
   168  			panic(exeOnce.err)
   169  		}
   170  		t.Fatal(exeOnce.err)
   171  	}
   172  
   173  	return exeOnce.path
   174  }
   175  
   176  var exeOnce struct {
   177  	path string
   178  	err  error
   179  	sync.Once
   180  }
   181  
   182  var helperCommandUsed sync.Map
   183  
   184  var helperCommands = map[string]func(...string){
   185  	"echo":          cmdEcho,
   186  	"echoenv":       cmdEchoEnv,
   187  	"cat":           cmdCat,
   188  	"pipetest":      cmdPipeTest,
   189  	"stdinClose":    cmdStdinClose,
   190  	"exit":          cmdExit,
   191  	"describefiles": cmdDescribeFiles,
   192  	"stderrfail":    cmdStderrFail,
   193  	"yes":           cmdYes,
   194  }
   195  
   196  func cmdEcho(args ...string) {
   197  	iargs := []any{}
   198  	for _, s := range args {
   199  		iargs = append(iargs, s)
   200  	}
   201  	fmt.Println(iargs...)
   202  }
   203  
   204  func cmdEchoEnv(args ...string) {
   205  	for _, s := range args {
   206  		fmt.Println(os.Getenv(s))
   207  	}
   208  }
   209  
   210  func cmdCat(args ...string) {
   211  	if len(args) == 0 {
   212  		io.Copy(os.Stdout, os.Stdin)
   213  		return
   214  	}
   215  	exit := 0
   216  	for _, fn := range args {
   217  		f, err := os.Open(fn)
   218  		if err != nil {
   219  			fmt.Fprintf(os.Stderr, "Error: %v\n", err)
   220  			exit = 2
   221  		} else {
   222  			defer f.Close()
   223  			io.Copy(os.Stdout, f)
   224  		}
   225  	}
   226  	os.Exit(exit)
   227  }
   228  
   229  func cmdPipeTest(...string) {
   230  	bufr := bufio.NewReader(os.Stdin)
   231  	for {
   232  		line, _, err := bufr.ReadLine()
   233  		if err == io.EOF {
   234  			break
   235  		} else if err != nil {
   236  			os.Exit(1)
   237  		}
   238  		if bytes.HasPrefix(line, []byte("O:")) {
   239  			os.Stdout.Write(line)
   240  			os.Stdout.Write([]byte{'\n'})
   241  		} else if bytes.HasPrefix(line, []byte("E:")) {
   242  			os.Stderr.Write(line)
   243  			os.Stderr.Write([]byte{'\n'})
   244  		} else {
   245  			os.Exit(1)
   246  		}
   247  	}
   248  }
   249  
   250  func cmdStdinClose(...string) {
   251  	b, err := io.ReadAll(os.Stdin)
   252  	if err != nil {
   253  		fmt.Fprintf(os.Stderr, "Error: %v\n", err)
   254  		os.Exit(1)
   255  	}
   256  	if s := string(b); s != stdinCloseTestString {
   257  		fmt.Fprintf(os.Stderr, "Error: Read %q, want %q", s, stdinCloseTestString)
   258  		os.Exit(1)
   259  	}
   260  }
   261  
   262  func cmdExit(args ...string) {
   263  	n, _ := strconv.Atoi(args[0])
   264  	os.Exit(n)
   265  }
   266  
   267  func cmdDescribeFiles(args ...string) {
   268  	f := os.NewFile(3, fmt.Sprintf("fd3"))
   269  	ln, err := net.FileListener(f)
   270  	if err == nil {
   271  		fmt.Printf("fd3: listener %s\n", ln.Addr())
   272  		ln.Close()
   273  	}
   274  }
   275  
   276  func cmdStderrFail(...string) {
   277  	fmt.Fprintf(os.Stderr, "some stderr text\n")
   278  	os.Exit(1)
   279  }
   280  
   281  func cmdYes(args ...string) {
   282  	if len(args) == 0 {
   283  		args = []string{"y"}
   284  	}
   285  	s := strings.Join(args, " ") + "\n"
   286  	for {
   287  		_, err := os.Stdout.WriteString(s)
   288  		if err != nil {
   289  			os.Exit(1)
   290  		}
   291  	}
   292  }
   293  
   294  func TestEcho(t *testing.T) {
   295  	t.Parallel()
   296  
   297  	bs, err := helperCommand(t, "echo", "foo bar", "baz").Output()
   298  	if err != nil {
   299  		t.Errorf("echo: %v", err)
   300  	}
   301  	if g, e := string(bs), "foo bar baz\n"; g != e {
   302  		t.Errorf("echo: want %q, got %q", e, g)
   303  	}
   304  }
   305  
   306  func TestCommandRelativeName(t *testing.T) {
   307  	t.Parallel()
   308  
   309  	cmd := helperCommand(t, "echo", "foo")
   310  
   311  	// Run our own binary as a relative path
   312  	// (e.g. "_test/exec.test") our parent directory.
   313  	base := filepath.Base(os.Args[0]) // "exec.test"
   314  	dir := filepath.Dir(os.Args[0])   // "/tmp/go-buildNNNN/os/exec/_test"
   315  	if dir == "." {
   316  		t.Skip("skipping; running test at root somehow")
   317  	}
   318  	parentDir := filepath.Dir(dir) // "/tmp/go-buildNNNN/os/exec"
   319  	dirBase := filepath.Base(dir)  // "_test"
   320  	if dirBase == "." {
   321  		t.Skipf("skipping; unexpected shallow dir of %q", dir)
   322  	}
   323  
   324  	cmd.Path = filepath.Join(dirBase, base)
   325  	cmd.Dir = parentDir
   326  
   327  	out, err := cmd.Output()
   328  	if err != nil {
   329  		t.Errorf("echo: %v", err)
   330  	}
   331  	if g, e := string(out), "foo\n"; g != e {
   332  		t.Errorf("echo: want %q, got %q", e, g)
   333  	}
   334  }
   335  
   336  func TestCatStdin(t *testing.T) {
   337  	t.Parallel()
   338  
   339  	// Cat, testing stdin and stdout.
   340  	input := "Input string\nLine 2"
   341  	p := helperCommand(t, "cat")
   342  	p.Stdin = strings.NewReader(input)
   343  	bs, err := p.Output()
   344  	if err != nil {
   345  		t.Errorf("cat: %v", err)
   346  	}
   347  	s := string(bs)
   348  	if s != input {
   349  		t.Errorf("cat: want %q, got %q", input, s)
   350  	}
   351  }
   352  
   353  func TestEchoFileRace(t *testing.T) {
   354  	t.Parallel()
   355  
   356  	cmd := helperCommand(t, "echo")
   357  	stdin, err := cmd.StdinPipe()
   358  	if err != nil {
   359  		t.Fatalf("StdinPipe: %v", err)
   360  	}
   361  	if err := cmd.Start(); err != nil {
   362  		t.Fatalf("Start: %v", err)
   363  	}
   364  	wrote := make(chan bool)
   365  	go func() {
   366  		defer close(wrote)
   367  		fmt.Fprint(stdin, "echo\n")
   368  	}()
   369  	if err := cmd.Wait(); err != nil {
   370  		t.Fatalf("Wait: %v", err)
   371  	}
   372  	<-wrote
   373  }
   374  
   375  func TestCatGoodAndBadFile(t *testing.T) {
   376  	t.Parallel()
   377  
   378  	// Testing combined output and error values.
   379  	bs, err := helperCommand(t, "cat", "/bogus/file.foo", "exec_test.go").CombinedOutput()
   380  	if _, ok := err.(*exec.ExitError); !ok {
   381  		t.Errorf("expected *exec.ExitError from cat combined; got %T: %v", err, err)
   382  	}
   383  	errLine, body, ok := strings.Cut(string(bs), "\n")
   384  	if !ok {
   385  		t.Fatalf("expected two lines from cat; got %q", bs)
   386  	}
   387  	if !strings.HasPrefix(errLine, "Error: open /bogus/file.foo") {
   388  		t.Errorf("expected stderr to complain about file; got %q", errLine)
   389  	}
   390  	if !strings.Contains(body, "func TestCatGoodAndBadFile(t *testing.T)") {
   391  		t.Errorf("expected test code; got %q (len %d)", body, len(body))
   392  	}
   393  }
   394  
   395  func TestNoExistExecutable(t *testing.T) {
   396  	t.Parallel()
   397  
   398  	// Can't run a non-existent executable
   399  	err := exec.Command("/no-exist-executable").Run()
   400  	if err == nil {
   401  		t.Error("expected error from /no-exist-executable")
   402  	}
   403  }
   404  
   405  func TestExitStatus(t *testing.T) {
   406  	t.Parallel()
   407  
   408  	// Test that exit values are returned correctly
   409  	cmd := helperCommand(t, "exit", "42")
   410  	err := cmd.Run()
   411  	want := "exit status 42"
   412  	switch runtime.GOOS {
   413  	case "plan9":
   414  		want = fmt.Sprintf("exit status: '%s %d: 42'", filepath.Base(cmd.Path), cmd.ProcessState.Pid())
   415  	}
   416  	if werr, ok := err.(*exec.ExitError); ok {
   417  		if s := werr.Error(); s != want {
   418  			t.Errorf("from exit 42 got exit %q, want %q", s, want)
   419  		}
   420  	} else {
   421  		t.Fatalf("expected *exec.ExitError from exit 42; got %T: %v", err, err)
   422  	}
   423  }
   424  
   425  func TestExitCode(t *testing.T) {
   426  	t.Parallel()
   427  
   428  	// Test that exit code are returned correctly
   429  	cmd := helperCommand(t, "exit", "42")
   430  	cmd.Run()
   431  	want := 42
   432  	if runtime.GOOS == "plan9" {
   433  		want = 1
   434  	}
   435  	got := cmd.ProcessState.ExitCode()
   436  	if want != got {
   437  		t.Errorf("ExitCode got %d, want %d", got, want)
   438  	}
   439  
   440  	cmd = helperCommand(t, "/no-exist-executable")
   441  	cmd.Run()
   442  	want = 2
   443  	if runtime.GOOS == "plan9" {
   444  		want = 1
   445  	}
   446  	got = cmd.ProcessState.ExitCode()
   447  	if want != got {
   448  		t.Errorf("ExitCode got %d, want %d", got, want)
   449  	}
   450  
   451  	cmd = helperCommand(t, "exit", "255")
   452  	cmd.Run()
   453  	want = 255
   454  	if runtime.GOOS == "plan9" {
   455  		want = 1
   456  	}
   457  	got = cmd.ProcessState.ExitCode()
   458  	if want != got {
   459  		t.Errorf("ExitCode got %d, want %d", got, want)
   460  	}
   461  
   462  	cmd = helperCommand(t, "cat")
   463  	cmd.Run()
   464  	want = 0
   465  	got = cmd.ProcessState.ExitCode()
   466  	if want != got {
   467  		t.Errorf("ExitCode got %d, want %d", got, want)
   468  	}
   469  
   470  	// Test when command does not call Run().
   471  	cmd = helperCommand(t, "cat")
   472  	want = -1
   473  	got = cmd.ProcessState.ExitCode()
   474  	if want != got {
   475  		t.Errorf("ExitCode got %d, want %d", got, want)
   476  	}
   477  }
   478  
   479  func TestPipes(t *testing.T) {
   480  	t.Parallel()
   481  
   482  	check := func(what string, err error) {
   483  		if err != nil {
   484  			t.Fatalf("%s: %v", what, err)
   485  		}
   486  	}
   487  	// Cat, testing stdin and stdout.
   488  	c := helperCommand(t, "pipetest")
   489  	stdin, err := c.StdinPipe()
   490  	check("StdinPipe", err)
   491  	stdout, err := c.StdoutPipe()
   492  	check("StdoutPipe", err)
   493  	stderr, err := c.StderrPipe()
   494  	check("StderrPipe", err)
   495  
   496  	outbr := bufio.NewReader(stdout)
   497  	errbr := bufio.NewReader(stderr)
   498  	line := func(what string, br *bufio.Reader) string {
   499  		line, _, err := br.ReadLine()
   500  		if err != nil {
   501  			t.Fatalf("%s: %v", what, err)
   502  		}
   503  		return string(line)
   504  	}
   505  
   506  	err = c.Start()
   507  	check("Start", err)
   508  
   509  	_, err = stdin.Write([]byte("O:I am output\n"))
   510  	check("first stdin Write", err)
   511  	if g, e := line("first output line", outbr), "O:I am output"; g != e {
   512  		t.Errorf("got %q, want %q", g, e)
   513  	}
   514  
   515  	_, err = stdin.Write([]byte("E:I am error\n"))
   516  	check("second stdin Write", err)
   517  	if g, e := line("first error line", errbr), "E:I am error"; g != e {
   518  		t.Errorf("got %q, want %q", g, e)
   519  	}
   520  
   521  	_, err = stdin.Write([]byte("O:I am output2\n"))
   522  	check("third stdin Write 3", err)
   523  	if g, e := line("second output line", outbr), "O:I am output2"; g != e {
   524  		t.Errorf("got %q, want %q", g, e)
   525  	}
   526  
   527  	stdin.Close()
   528  	err = c.Wait()
   529  	check("Wait", err)
   530  }
   531  
   532  const stdinCloseTestString = "Some test string."
   533  
   534  // Issue 6270.
   535  func TestStdinClose(t *testing.T) {
   536  	t.Parallel()
   537  
   538  	check := func(what string, err error) {
   539  		if err != nil {
   540  			t.Fatalf("%s: %v", what, err)
   541  		}
   542  	}
   543  	cmd := helperCommand(t, "stdinClose")
   544  	stdin, err := cmd.StdinPipe()
   545  	check("StdinPipe", err)
   546  	// Check that we can access methods of the underlying os.File.`
   547  	if _, ok := stdin.(interface {
   548  		Fd() uintptr
   549  	}); !ok {
   550  		t.Error("can't access methods of underlying *os.File")
   551  	}
   552  	check("Start", cmd.Start())
   553  
   554  	var wg sync.WaitGroup
   555  	wg.Add(1)
   556  	defer wg.Wait()
   557  	go func() {
   558  		defer wg.Done()
   559  
   560  		_, err := io.Copy(stdin, strings.NewReader(stdinCloseTestString))
   561  		check("Copy", err)
   562  
   563  		// Before the fix, this next line would race with cmd.Wait.
   564  		if err := stdin.Close(); err != nil && !errors.Is(err, os.ErrClosed) {
   565  			t.Errorf("Close: %v", err)
   566  		}
   567  	}()
   568  
   569  	check("Wait", cmd.Wait())
   570  }
   571  
   572  // Issue 17647.
   573  // It used to be the case that TestStdinClose, above, would fail when
   574  // run under the race detector. This test is a variant of TestStdinClose
   575  // that also used to fail when run under the race detector.
   576  // This test is run by cmd/dist under the race detector to verify that
   577  // the race detector no longer reports any problems.
   578  func TestStdinCloseRace(t *testing.T) {
   579  	t.Parallel()
   580  
   581  	cmd := helperCommand(t, "stdinClose")
   582  	stdin, err := cmd.StdinPipe()
   583  	if err != nil {
   584  		t.Fatalf("StdinPipe: %v", err)
   585  	}
   586  	if err := cmd.Start(); err != nil {
   587  		t.Fatalf("Start: %v", err)
   588  
   589  	}
   590  
   591  	var wg sync.WaitGroup
   592  	wg.Add(2)
   593  	defer wg.Wait()
   594  
   595  	go func() {
   596  		defer wg.Done()
   597  		// We don't check the error return of Kill. It is
   598  		// possible that the process has already exited, in
   599  		// which case Kill will return an error "process
   600  		// already finished". The purpose of this test is to
   601  		// see whether the race detector reports an error; it
   602  		// doesn't matter whether this Kill succeeds or not.
   603  		cmd.Process.Kill()
   604  	}()
   605  
   606  	go func() {
   607  		defer wg.Done()
   608  		// Send the wrong string, so that the child fails even
   609  		// if the other goroutine doesn't manage to kill it first.
   610  		// This test is to check that the race detector does not
   611  		// falsely report an error, so it doesn't matter how the
   612  		// child process fails.
   613  		io.Copy(stdin, strings.NewReader("unexpected string"))
   614  		if err := stdin.Close(); err != nil && !errors.Is(err, os.ErrClosed) {
   615  			t.Errorf("stdin.Close: %v", err)
   616  		}
   617  	}()
   618  
   619  	if err := cmd.Wait(); err == nil {
   620  		t.Fatalf("Wait: succeeded unexpectedly")
   621  	}
   622  }
   623  
   624  // Issue 5071
   625  func TestPipeLookPathLeak(t *testing.T) {
   626  	if runtime.GOOS == "windows" {
   627  		t.Skip("we don't currently suppore counting open handles on windows")
   628  	}
   629  	// Not parallel: checks for leaked file descriptors
   630  
   631  	openFDs := func() []uintptr {
   632  		var fds []uintptr
   633  		for i := uintptr(0); i < 100; i++ {
   634  			if fdtest.Exists(i) {
   635  				fds = append(fds, i)
   636  			}
   637  		}
   638  		return fds
   639  	}
   640  
   641  	want := openFDs()
   642  	for i := 0; i < 6; i++ {
   643  		cmd := exec.Command("something-that-does-not-exist-executable")
   644  		cmd.StdoutPipe()
   645  		cmd.StderrPipe()
   646  		cmd.StdinPipe()
   647  		if err := cmd.Run(); err == nil {
   648  			t.Fatal("unexpected success")
   649  		}
   650  	}
   651  	got := openFDs()
   652  	if !reflect.DeepEqual(got, want) {
   653  		t.Errorf("set of open file descriptors changed: got %v, want %v", got, want)
   654  	}
   655  }
   656  
   657  func TestExtraFiles(t *testing.T) {
   658  	if testing.Short() {
   659  		t.Skipf("skipping test in short mode that would build a helper binary")
   660  	}
   661  
   662  	if haveUnexpectedFDs {
   663  		// The point of this test is to make sure that any
   664  		// descriptors we open are marked close-on-exec.
   665  		// If haveUnexpectedFDs is true then there were other
   666  		// descriptors open when we started the test,
   667  		// so those descriptors are clearly not close-on-exec,
   668  		// and they will confuse the test. We could modify
   669  		// the test to expect those descriptors to remain open,
   670  		// but since we don't know where they came from or what
   671  		// they are doing, that seems fragile. For example,
   672  		// perhaps they are from the startup code on this
   673  		// system for some reason. Also, this test is not
   674  		// system-specific; as long as most systems do not skip
   675  		// the test, we will still be testing what we care about.
   676  		t.Skip("skipping test because test was run with FDs open")
   677  	}
   678  
   679  	testenv.MustHaveExec(t)
   680  	testenv.MustHaveGoBuild(t)
   681  
   682  	// This test runs with cgo disabled. External linking needs cgo, so
   683  	// it doesn't work if external linking is required.
   684  	testenv.MustInternalLink(t)
   685  
   686  	if runtime.GOOS == "windows" {
   687  		t.Skipf("skipping test on %q", runtime.GOOS)
   688  	}
   689  
   690  	// Force network usage, to verify the epoll (or whatever) fd
   691  	// doesn't leak to the child,
   692  	ln, err := net.Listen("tcp", "127.0.0.1:0")
   693  	if err != nil {
   694  		t.Fatal(err)
   695  	}
   696  	defer ln.Close()
   697  
   698  	// Make sure duplicated fds don't leak to the child.
   699  	f, err := ln.(*net.TCPListener).File()
   700  	if err != nil {
   701  		t.Fatal(err)
   702  	}
   703  	defer f.Close()
   704  	ln2, err := net.FileListener(f)
   705  	if err != nil {
   706  		t.Fatal(err)
   707  	}
   708  	defer ln2.Close()
   709  
   710  	// Force TLS root certs to be loaded (which might involve
   711  	// cgo), to make sure none of that potential C code leaks fds.
   712  	ts := httptest.NewUnstartedServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {}))
   713  	// quiet expected TLS handshake error "remote error: bad certificate"
   714  	ts.Config.ErrorLog = log.New(io.Discard, "", 0)
   715  	ts.StartTLS()
   716  	defer ts.Close()
   717  	_, err = http.Get(ts.URL)
   718  	if err == nil {
   719  		t.Errorf("success trying to fetch %s; want an error", ts.URL)
   720  	}
   721  
   722  	tf, err := os.CreateTemp("", "")
   723  	if err != nil {
   724  		t.Fatalf("TempFile: %v", err)
   725  	}
   726  	defer os.Remove(tf.Name())
   727  	defer tf.Close()
   728  
   729  	const text = "Hello, fd 3!"
   730  	_, err = tf.Write([]byte(text))
   731  	if err != nil {
   732  		t.Fatalf("Write: %v", err)
   733  	}
   734  	_, err = tf.Seek(0, io.SeekStart)
   735  	if err != nil {
   736  		t.Fatalf("Seek: %v", err)
   737  	}
   738  
   739  	tempdir := t.TempDir()
   740  	exe := filepath.Join(tempdir, "read3.exe")
   741  
   742  	c := exec.Command(testenv.GoToolPath(t), "build", "-o", exe, "read3.go")
   743  	// Build the test without cgo, so that C library functions don't
   744  	// open descriptors unexpectedly. See issue 25628.
   745  	c.Env = append(os.Environ(), "CGO_ENABLED=0")
   746  	if output, err := c.CombinedOutput(); err != nil {
   747  		t.Logf("go build -o %s read3.go\n%s", exe, output)
   748  		t.Fatalf("go build failed: %v", err)
   749  	}
   750  
   751  	// Use a deadline to try to get some output even if the program hangs.
   752  	ctx := context.Background()
   753  	if deadline, ok := t.Deadline(); ok {
   754  		// Leave a 20% grace period to flush output, which may be large on the
   755  		// linux/386 builders because we're running the subprocess under strace.
   756  		deadline = deadline.Add(-time.Until(deadline) / 5)
   757  
   758  		var cancel context.CancelFunc
   759  		ctx, cancel = context.WithDeadline(ctx, deadline)
   760  		defer cancel()
   761  	}
   762  
   763  	c = exec.CommandContext(ctx, exe)
   764  	var stdout, stderr strings.Builder
   765  	c.Stdout = &stdout
   766  	c.Stderr = &stderr
   767  	c.ExtraFiles = []*os.File{tf}
   768  	if runtime.GOOS == "illumos" {
   769  		// Some facilities in illumos are implemented via access
   770  		// to /proc by libc; such accesses can briefly occupy a
   771  		// low-numbered fd.  If this occurs concurrently with the
   772  		// test that checks for leaked descriptors, the check can
   773  		// become confused and report a spurious leaked descriptor.
   774  		// (See issue #42431 for more detailed analysis.)
   775  		//
   776  		// Attempt to constrain the use of additional threads in the
   777  		// child process to make this test less flaky:
   778  		c.Env = append(os.Environ(), "GOMAXPROCS=1")
   779  	}
   780  	err = c.Run()
   781  	if err != nil {
   782  		t.Fatalf("Run: %v\n--- stdout:\n%s--- stderr:\n%s", err, stdout.String(), stderr.String())
   783  	}
   784  	if stdout.String() != text {
   785  		t.Errorf("got stdout %q, stderr %q; want %q on stdout", stdout.String(), stderr.String(), text)
   786  	}
   787  }
   788  
   789  func TestExtraFilesRace(t *testing.T) {
   790  	if runtime.GOOS == "windows" {
   791  		maySkipHelperCommand("describefiles")
   792  		t.Skip("no operating system support; skipping")
   793  	}
   794  	t.Parallel()
   795  
   796  	listen := func() net.Listener {
   797  		ln, err := net.Listen("tcp", "127.0.0.1:0")
   798  		if err != nil {
   799  			t.Fatal(err)
   800  		}
   801  		return ln
   802  	}
   803  	listenerFile := func(ln net.Listener) *os.File {
   804  		f, err := ln.(*net.TCPListener).File()
   805  		if err != nil {
   806  			t.Fatal(err)
   807  		}
   808  		return f
   809  	}
   810  	runCommand := func(c *exec.Cmd, out chan<- string) {
   811  		bout, err := c.CombinedOutput()
   812  		if err != nil {
   813  			out <- "ERROR:" + err.Error()
   814  		} else {
   815  			out <- string(bout)
   816  		}
   817  	}
   818  
   819  	for i := 0; i < 10; i++ {
   820  		if testing.Short() && i >= 3 {
   821  			break
   822  		}
   823  		la := listen()
   824  		ca := helperCommand(t, "describefiles")
   825  		ca.ExtraFiles = []*os.File{listenerFile(la)}
   826  		lb := listen()
   827  		cb := helperCommand(t, "describefiles")
   828  		cb.ExtraFiles = []*os.File{listenerFile(lb)}
   829  		ares := make(chan string)
   830  		bres := make(chan string)
   831  		go runCommand(ca, ares)
   832  		go runCommand(cb, bres)
   833  		if got, want := <-ares, fmt.Sprintf("fd3: listener %s\n", la.Addr()); got != want {
   834  			t.Errorf("iteration %d, process A got:\n%s\nwant:\n%s\n", i, got, want)
   835  		}
   836  		if got, want := <-bres, fmt.Sprintf("fd3: listener %s\n", lb.Addr()); got != want {
   837  			t.Errorf("iteration %d, process B got:\n%s\nwant:\n%s\n", i, got, want)
   838  		}
   839  		la.Close()
   840  		lb.Close()
   841  		for _, f := range ca.ExtraFiles {
   842  			f.Close()
   843  		}
   844  		for _, f := range cb.ExtraFiles {
   845  			f.Close()
   846  		}
   847  	}
   848  }
   849  
   850  type delayedInfiniteReader struct{}
   851  
   852  func (delayedInfiniteReader) Read(b []byte) (int, error) {
   853  	time.Sleep(100 * time.Millisecond)
   854  	for i := range b {
   855  		b[i] = 'x'
   856  	}
   857  	return len(b), nil
   858  }
   859  
   860  // Issue 9173: ignore stdin pipe writes if the program completes successfully.
   861  func TestIgnorePipeErrorOnSuccess(t *testing.T) {
   862  	t.Parallel()
   863  
   864  	testWith := func(r io.Reader) func(*testing.T) {
   865  		return func(t *testing.T) {
   866  			t.Parallel()
   867  
   868  			cmd := helperCommand(t, "echo", "foo")
   869  			var out strings.Builder
   870  			cmd.Stdin = r
   871  			cmd.Stdout = &out
   872  			if err := cmd.Run(); err != nil {
   873  				t.Fatal(err)
   874  			}
   875  			if got, want := out.String(), "foo\n"; got != want {
   876  				t.Errorf("output = %q; want %q", got, want)
   877  			}
   878  		}
   879  	}
   880  	t.Run("10MB", testWith(strings.NewReader(strings.Repeat("x", 10<<20))))
   881  	t.Run("Infinite", testWith(delayedInfiniteReader{}))
   882  }
   883  
   884  type badWriter struct{}
   885  
   886  func (w *badWriter) Write(data []byte) (int, error) {
   887  	return 0, io.ErrUnexpectedEOF
   888  }
   889  
   890  func TestClosePipeOnCopyError(t *testing.T) {
   891  	t.Parallel()
   892  
   893  	cmd := helperCommand(t, "yes")
   894  	cmd.Stdout = new(badWriter)
   895  	err := cmd.Run()
   896  	if err == nil {
   897  		t.Errorf("yes unexpectedly completed successfully")
   898  	}
   899  }
   900  
   901  func TestOutputStderrCapture(t *testing.T) {
   902  	t.Parallel()
   903  
   904  	cmd := helperCommand(t, "stderrfail")
   905  	_, err := cmd.Output()
   906  	ee, ok := err.(*exec.ExitError)
   907  	if !ok {
   908  		t.Fatalf("Output error type = %T; want ExitError", err)
   909  	}
   910  	got := string(ee.Stderr)
   911  	want := "some stderr text\n"
   912  	if got != want {
   913  		t.Errorf("ExitError.Stderr = %q; want %q", got, want)
   914  	}
   915  }
   916  
   917  func TestContext(t *testing.T) {
   918  	t.Parallel()
   919  
   920  	ctx, cancel := context.WithCancel(context.Background())
   921  	c := helperCommandContext(t, ctx, "pipetest")
   922  	stdin, err := c.StdinPipe()
   923  	if err != nil {
   924  		t.Fatal(err)
   925  	}
   926  	stdout, err := c.StdoutPipe()
   927  	if err != nil {
   928  		t.Fatal(err)
   929  	}
   930  	if err := c.Start(); err != nil {
   931  		t.Fatal(err)
   932  	}
   933  
   934  	if _, err := stdin.Write([]byte("O:hi\n")); err != nil {
   935  		t.Fatal(err)
   936  	}
   937  	buf := make([]byte, 5)
   938  	n, err := io.ReadFull(stdout, buf)
   939  	if n != len(buf) || err != nil || string(buf) != "O:hi\n" {
   940  		t.Fatalf("ReadFull = %d, %v, %q", n, err, buf[:n])
   941  	}
   942  	go cancel()
   943  
   944  	if err := c.Wait(); err == nil {
   945  		t.Fatal("expected Wait failure")
   946  	}
   947  }
   948  
   949  func TestContextCancel(t *testing.T) {
   950  	if runtime.GOOS == "netbsd" && runtime.GOARCH == "arm64" {
   951  		maySkipHelperCommand("cat")
   952  		testenv.SkipFlaky(t, 42061)
   953  	}
   954  
   955  	// To reduce noise in the final goroutine dump,
   956  	// let other parallel tests complete if possible.
   957  	t.Parallel()
   958  
   959  	ctx, cancel := context.WithCancel(context.Background())
   960  	defer cancel()
   961  	c := helperCommandContext(t, ctx, "cat")
   962  
   963  	stdin, err := c.StdinPipe()
   964  	if err != nil {
   965  		t.Fatal(err)
   966  	}
   967  	defer stdin.Close()
   968  
   969  	if err := c.Start(); err != nil {
   970  		t.Fatal(err)
   971  	}
   972  
   973  	// At this point the process is alive. Ensure it by sending data to stdin.
   974  	if _, err := io.WriteString(stdin, "echo"); err != nil {
   975  		t.Fatal(err)
   976  	}
   977  
   978  	cancel()
   979  
   980  	// Calling cancel should have killed the process, so writes
   981  	// should now fail.  Give the process a little while to die.
   982  	start := time.Now()
   983  	delay := 1 * time.Millisecond
   984  	for {
   985  		if _, err := io.WriteString(stdin, "echo"); err != nil {
   986  			break
   987  		}
   988  
   989  		if time.Since(start) > time.Minute {
   990  			// Panic instead of calling t.Fatal so that we get a goroutine dump.
   991  			// We want to know exactly what the os/exec goroutines got stuck on.
   992  			debug.SetTraceback("system")
   993  			panic("canceling context did not stop program")
   994  		}
   995  
   996  		// Back off exponentially (up to 1-second sleeps) to give the OS time to
   997  		// terminate the process.
   998  		delay *= 2
   999  		if delay > 1*time.Second {
  1000  			delay = 1 * time.Second
  1001  		}
  1002  		time.Sleep(delay)
  1003  	}
  1004  
  1005  	if err := c.Wait(); err == nil {
  1006  		t.Error("program unexpectedly exited successfully")
  1007  	} else {
  1008  		t.Logf("exit status: %v", err)
  1009  	}
  1010  }
  1011  
  1012  // test that environment variables are de-duped.
  1013  func TestDedupEnvEcho(t *testing.T) {
  1014  	t.Parallel()
  1015  
  1016  	cmd := helperCommand(t, "echoenv", "FOO")
  1017  	cmd.Env = append(cmd.Environ(), "FOO=bad", "FOO=good")
  1018  	out, err := cmd.CombinedOutput()
  1019  	if err != nil {
  1020  		t.Fatal(err)
  1021  	}
  1022  	if got, want := strings.TrimSpace(string(out)), "good"; got != want {
  1023  		t.Errorf("output = %q; want %q", got, want)
  1024  	}
  1025  }
  1026  
  1027  func TestString(t *testing.T) {
  1028  	t.Parallel()
  1029  
  1030  	echoPath, err := exec.LookPath("echo")
  1031  	if err != nil {
  1032  		t.Skip(err)
  1033  	}
  1034  	tests := [...]struct {
  1035  		path string
  1036  		args []string
  1037  		want string
  1038  	}{
  1039  		{"echo", nil, echoPath},
  1040  		{"echo", []string{"a"}, echoPath + " a"},
  1041  		{"echo", []string{"a", "b"}, echoPath + " a b"},
  1042  	}
  1043  	for _, test := range tests {
  1044  		cmd := exec.Command(test.path, test.args...)
  1045  		if got := cmd.String(); got != test.want {
  1046  			t.Errorf("String(%q, %q) = %q, want %q", test.path, test.args, got, test.want)
  1047  		}
  1048  	}
  1049  }
  1050  
  1051  func TestStringPathNotResolved(t *testing.T) {
  1052  	t.Parallel()
  1053  
  1054  	_, err := exec.LookPath("makemeasandwich")
  1055  	if err == nil {
  1056  		t.Skip("wow, thanks")
  1057  	}
  1058  
  1059  	cmd := exec.Command("makemeasandwich", "-lettuce")
  1060  	want := "makemeasandwich -lettuce"
  1061  	if got := cmd.String(); got != want {
  1062  		t.Errorf("String(%q, %q) = %q, want %q", "makemeasandwich", "-lettuce", got, want)
  1063  	}
  1064  }
  1065  
  1066  func TestNoPath(t *testing.T) {
  1067  	err := new(exec.Cmd).Start()
  1068  	want := "exec: no command"
  1069  	if err == nil || err.Error() != want {
  1070  		t.Errorf("new(Cmd).Start() = %v, want %q", err, want)
  1071  	}
  1072  }
  1073  
  1074  // TestDoubleStartLeavesPipesOpen checks for a regression in which calling
  1075  // Start twice, which returns an error on the second call, would spuriously
  1076  // close the pipes established in the first call.
  1077  func TestDoubleStartLeavesPipesOpen(t *testing.T) {
  1078  	t.Parallel()
  1079  
  1080  	cmd := helperCommand(t, "pipetest")
  1081  	in, err := cmd.StdinPipe()
  1082  	if err != nil {
  1083  		t.Fatal(err)
  1084  	}
  1085  	out, err := cmd.StdoutPipe()
  1086  	if err != nil {
  1087  		t.Fatal(err)
  1088  	}
  1089  
  1090  	if err := cmd.Start(); err != nil {
  1091  		t.Fatal(err)
  1092  	}
  1093  	t.Cleanup(func() {
  1094  		if err := cmd.Wait(); err != nil {
  1095  			t.Error(err)
  1096  		}
  1097  	})
  1098  
  1099  	if err := cmd.Start(); err == nil || !strings.HasSuffix(err.Error(), "already started") {
  1100  		t.Fatalf("second call to Start returned a nil; want an 'already started' error")
  1101  	}
  1102  
  1103  	outc := make(chan []byte, 1)
  1104  	go func() {
  1105  		b, err := io.ReadAll(out)
  1106  		if err != nil {
  1107  			t.Error(err)
  1108  		}
  1109  		outc <- b
  1110  	}()
  1111  
  1112  	const msg = "O:Hello, pipe!\n"
  1113  
  1114  	_, err = io.WriteString(in, msg)
  1115  	if err != nil {
  1116  		t.Fatal(err)
  1117  	}
  1118  	in.Close()
  1119  
  1120  	b := <-outc
  1121  	if !bytes.Equal(b, []byte(msg)) {
  1122  		t.Fatalf("read %q from stdout pipe; want %q", b, msg)
  1123  	}
  1124  }