aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/os/exec/exec_linux_test.go2
-rw-r--r--src/os/exec/exec_posix_test.go45
-rw-r--r--src/os/exec/exec_test.go470
-rw-r--r--src/os/exec/exec_windows_test.go42
-rw-r--r--src/os/exec/lp_windows_test.go50
-rw-r--r--src/os/exec/read3.go2
6 files changed, 359 insertions, 252 deletions
diff --git a/src/os/exec/exec_linux_test.go b/src/os/exec/exec_linux_test.go
index 4a37c96e63a..b9f6b7b7672 100644
--- a/src/os/exec/exec_linux_test.go
+++ b/src/os/exec/exec_linux_test.go
@@ -22,7 +22,7 @@ import (
)
func init() {
- if os.Getenv("GO_WANT_HELPER_PROCESS") != "1" {
+ if os.Getenv("GO_EXEC_TEST_PID") == "" {
return
}
diff --git a/src/os/exec/exec_posix_test.go b/src/os/exec/exec_posix_test.go
index e5830394533..f0401377e8e 100644
--- a/src/os/exec/exec_posix_test.go
+++ b/src/os/exec/exec_posix_test.go
@@ -7,9 +7,9 @@
package exec_test
import (
+ "fmt"
"internal/testenv"
"os"
- "os/exec"
"os/user"
"path/filepath"
"reflect"
@@ -21,8 +21,32 @@ import (
"time"
)
+func init() {
+ registerHelperCommand("pwd", cmdPwd)
+ registerHelperCommand("sleep", cmdSleep)
+}
+
+func cmdPwd(...string) {
+ pwd, err := os.Getwd()
+ if err != nil {
+ fmt.Fprintln(os.Stderr, err)
+ os.Exit(1)
+ }
+ fmt.Println(pwd)
+}
+
+func cmdSleep(args ...string) {
+ n, err := strconv.Atoi(args[0])
+ if err != nil {
+ fmt.Println(err)
+ os.Exit(1)
+ }
+ time.Sleep(time.Duration(n) * time.Second)
+}
+
func TestCredentialNoSetGroups(t *testing.T) {
if runtime.GOOS == "android" {
+ maySkipHelperCommand("echo")
t.Skip("unsupported on Android")
}
@@ -61,7 +85,7 @@ func TestCredentialNoSetGroups(t *testing.T) {
func TestWaitid(t *testing.T) {
t.Parallel()
- cmd := helperCommand(t, "sleep")
+ cmd := helperCommand(t, "sleep", "3")
if err := cmd.Start(); err != nil {
t.Fatal(err)
}
@@ -97,9 +121,6 @@ func TestWaitid(t *testing.T) {
// implicitly update PWD to the correct path, and Environ should list the
// updated value.
func TestImplicitPWD(t *testing.T) {
- testenv.MustHaveExec(t)
- _, pwdErr := exec.LookPath("pwd")
-
t.Parallel()
cwd, err := os.Getwd()
@@ -124,12 +145,10 @@ func TestImplicitPWD(t *testing.T) {
t.Run(tc.name, func(t *testing.T) {
t.Parallel()
- // Note: we're using the actual "pwd" command here (instead of helperCommand)
- // because the implementation of helperCommand requires a non-empty Env.
- // (We could perhaps refactor helperCommand to use a flag or switch on the
- // value of argv[0] instead, but that doesn't seem worth the trouble at
- // the moment.)
- cmd := exec.Command("pwd", "-L")
+ cmd := helperCommand(t, "pwd")
+ if cmd.Env != nil {
+ t.Fatalf("test requires helperCommand not to set Env field")
+ }
cmd.Dir = tc.dir
var pwds []string
@@ -149,9 +168,6 @@ func TestImplicitPWD(t *testing.T) {
t.Errorf("PWD entries in cmd.Environ():\n\t%s\nwant:\n\t%s", strings.Join(pwds, "\n\t"), strings.Join(wantPWDs, "\n\t"))
}
- if pwdErr != nil {
- t.Skipf("not running `pwd` because it was not found: %v", pwdErr)
- }
cmd.Stderr = new(strings.Builder)
out, err := cmd.Output()
if err != nil {
@@ -170,6 +186,7 @@ func TestImplicitPWD(t *testing.T) {
// (This checks that the implementation for https://go.dev/issue/50599 doesn't
// break existing users who may have explicitly mismatched the PWD variable.)
func TestExplicitPWD(t *testing.T) {
+ maySkipHelperCommand("pwd")
testenv.MustHaveSymlink(t)
cwd, err := os.Getwd()
diff --git a/src/os/exec/exec_test.go b/src/os/exec/exec_test.go
index f90066cea31..c593cbd11d1 100644
--- a/src/os/exec/exec_test.go
+++ b/src/os/exec/exec_test.go
@@ -11,6 +11,7 @@ import (
"bufio"
"bytes"
"context"
+ "flag"
"fmt"
"internal/poll"
"internal/testenv"
@@ -27,6 +28,7 @@ import (
"runtime"
"strconv"
"strings"
+ "sync"
"testing"
"time"
)
@@ -36,7 +38,7 @@ import (
var haveUnexpectedFDs bool
func init() {
- if os.Getenv("GO_WANT_HELPER_PROCESS") == "1" {
+ if os.Getenv("GO_EXEC_TEST_PID") != "" {
return
}
if runtime.GOOS == "windows" {
@@ -54,30 +56,253 @@ func init() {
}
}
-func helperCommandContext(t *testing.T, ctx context.Context, s ...string) (cmd *exec.Cmd) {
- testenv.MustHaveExec(t)
+// TestMain allows the test binary to impersonate many other binaries,
+// some of which may manipulate os.Stdin, os.Stdout, and/or os.Stderr
+// (and thus cannot run as an ordinary Test function, since the testing
+// package monkey-patches those variables before running tests).
+func TestMain(m *testing.M) {
+ flag.Parse()
+
+ pid := os.Getpid()
+ if os.Getenv("GO_EXEC_TEST_PID") == "" {
+ os.Setenv("GO_EXEC_TEST_PID", strconv.Itoa(pid))
+
+ code := m.Run()
+ if code == 0 && flag.Lookup("test.run").Value.String() == "" && flag.Lookup("test.list").Value.String() == "" {
+ for cmd := range helperCommands {
+ if _, ok := helperCommandUsed.Load(cmd); !ok {
+ fmt.Fprintf(os.Stderr, "helper command unused: %q\n", cmd)
+ code = 1
+ }
+ }
+ }
+ os.Exit(code)
+ }
- // Use os.Executable instead of os.Args[0] in case the caller modifies
- // cmd.Dir: if the test binary is invoked like "./exec.test", it should
- // not fail spuriously.
- exe, err := os.Executable()
- if err != nil {
- t.Fatal(err)
+ args := flag.Args()
+ if len(args) == 0 {
+ fmt.Fprintf(os.Stderr, "No command\n")
+ os.Exit(2)
}
- cs := []string{"-test.run=TestHelperProcess", "--"}
- cs = append(cs, s...)
+ cmd, args := args[0], args[1:]
+ f, ok := helperCommands[cmd]
+ if !ok {
+ fmt.Fprintf(os.Stderr, "Unknown command %q\n", cmd)
+ os.Exit(2)
+ }
+ f(args...)
+ os.Exit(0)
+}
+
+// registerHelperCommand registers a command that the test process can impersonate.
+// A command should be registered in the same source file in which it is used.
+// If all tests are run and pass, all registered commands must be used.
+// (This prevents stale commands from accreting if tests are removed or
+// refactored over time.)
+func registerHelperCommand(name string, f func(...string)) {
+ if helperCommands[name] != nil {
+ panic("duplicate command registered: " + name)
+ }
+ helperCommands[name] = f
+}
+
+// maySkipHelperCommand records that the test that uses the named helper command
+// was invoked, but may call Skip on the test before actually calling
+// helperCommand.
+func maySkipHelperCommand(name string) {
+ helperCommandUsed.Store(name, true)
+}
+
+// helperCommand returns an exec.Cmd that will run the named helper command.
+func helperCommand(t *testing.T, name string, args ...string) *exec.Cmd {
+ t.Helper()
+ return helperCommandContext(t, nil, name, args...)
+}
+
+// helperCommandContext is like helperCommand, but also accepts a Context under
+// which to run the command.
+func helperCommandContext(t *testing.T, ctx context.Context, name string, args ...string) (cmd *exec.Cmd) {
+ helperCommandUsed.LoadOrStore(name, true)
+
+ t.Helper()
+ testenv.MustHaveExec(t)
+
+ cs := append([]string{name}, args...)
if ctx != nil {
- cmd = exec.CommandContext(ctx, exe, cs...)
+ cmd = exec.CommandContext(ctx, exePath(t), cs...)
} else {
- cmd = exec.Command(exe, cs...)
+ cmd = exec.Command(exePath(t), cs...)
}
- cmd.Env = append(os.Environ(), "GO_WANT_HELPER_PROCESS=1")
return cmd
}
-func helperCommand(t *testing.T, s ...string) *exec.Cmd {
- return helperCommandContext(t, nil, s...)
+// exePath returns the path to the running executable.
+func exePath(t testing.TB) string {
+ exeOnce.Do(func() {
+ // Use os.Executable instead of os.Args[0] in case the caller modifies
+ // cmd.Dir: if the test binary is invoked like "./exec.test", it should
+ // not fail spuriously.
+ exeOnce.path, exeOnce.err = os.Executable()
+ })
+
+ if exeOnce.err != nil {
+ if t == nil {
+ panic(exeOnce.err)
+ }
+ t.Fatal(exeOnce.err)
+ }
+
+ return exeOnce.path
+}
+
+var exeOnce struct {
+ path string
+ err error
+ sync.Once
+}
+
+var helperCommandUsed sync.Map
+
+var helperCommands = map[string]func(...string){
+ "echo": cmdEcho,
+ "echoenv": cmdEchoEnv,
+ "cat": cmdCat,
+ "pipetest": cmdPipeTest,
+ "stdinClose": cmdStdinClose,
+ "exit": cmdExit,
+ "describefiles": cmdDescribeFiles,
+ "extraFilesAndPipes": cmdExtraFilesAndPipes,
+ "stderrfail": cmdStderrFail,
+ "yes": cmdYes,
+}
+
+func cmdEcho(args ...string) {
+ iargs := []any{}
+ for _, s := range args {
+ iargs = append(iargs, s)
+ }
+ fmt.Println(iargs...)
+}
+
+func cmdEchoEnv(args ...string) {
+ for _, s := range args {
+ fmt.Println(os.Getenv(s))
+ }
+}
+
+func cmdCat(args ...string) {
+ if len(args) == 0 {
+ io.Copy(os.Stdout, os.Stdin)
+ return
+ }
+ exit := 0
+ for _, fn := range args {
+ f, err := os.Open(fn)
+ if err != nil {
+ fmt.Fprintf(os.Stderr, "Error: %v\n", err)
+ exit = 2
+ } else {
+ defer f.Close()
+ io.Copy(os.Stdout, f)
+ }
+ }
+ os.Exit(exit)
+}
+
+func cmdPipeTest(...string) {
+ bufr := bufio.NewReader(os.Stdin)
+ for {
+ line, _, err := bufr.ReadLine()
+ if err == io.EOF {
+ break
+ } else if err != nil {
+ os.Exit(1)
+ }
+ if bytes.HasPrefix(line, []byte("O:")) {
+ os.Stdout.Write(line)
+ os.Stdout.Write([]byte{'\n'})
+ } else if bytes.HasPrefix(line, []byte("E:")) {
+ os.Stderr.Write(line)
+ os.Stderr.Write([]byte{'\n'})
+ } else {
+ os.Exit(1)
+ }
+ }
+}
+
+func cmdStdinClose(...string) {
+ b, err := io.ReadAll(os.Stdin)
+ if err != nil {
+ fmt.Fprintf(os.Stderr, "Error: %v\n", err)
+ os.Exit(1)
+ }
+ if s := string(b); s != stdinCloseTestString {
+ fmt.Fprintf(os.Stderr, "Error: Read %q, want %q", s, stdinCloseTestString)
+ os.Exit(1)
+ }
+}
+
+func cmdExit(args ...string) {
+ n, _ := strconv.Atoi(args[0])
+ os.Exit(n)
+}
+
+func cmdDescribeFiles(args ...string) {
+ f := os.NewFile(3, fmt.Sprintf("fd3"))
+ ln, err := net.FileListener(f)
+ if err == nil {
+ fmt.Printf("fd3: listener %s\n", ln.Addr())
+ ln.Close()
+ }
+}
+
+func cmdExtraFilesAndPipes(args ...string) {
+ n, _ := strconv.Atoi(args[0])
+ pipes := make([]*os.File, n)
+ for i := 0; i < n; i++ {
+ pipes[i] = os.NewFile(uintptr(3+i), strconv.Itoa(i))
+ }
+ response := ""
+ for i, r := range pipes {
+ ch := make(chan string, 1)
+ go func(c chan string) {
+ buf := make([]byte, 10)
+ n, err := r.Read(buf)
+ if err != nil {
+ fmt.Fprintf(os.Stderr, "Child: read error: %v on pipe %d\n", err, i)
+ os.Exit(1)
+ }
+ c <- string(buf[:n])
+ close(c)
+ }(ch)
+ select {
+ case m := <-ch:
+ response = response + m
+ case <-time.After(5 * time.Second):
+ fmt.Fprintf(os.Stderr, "Child: Timeout reading from pipe: %d\n", i)
+ os.Exit(1)
+ }
+ }
+ fmt.Fprintf(os.Stderr, "child: %s", response)
+}
+
+func cmdStderrFail(...string) {
+ fmt.Fprintf(os.Stderr, "some stderr text\n")
+ os.Exit(1)
+}
+
+func cmdYes(args ...string) {
+ if len(args) == 0 {
+ args = []string{"y"}
+ }
+ s := strings.Join(args, " ") + "\n"
+ for {
+ _, err := os.Stdout.WriteString(s)
+ if err != nil {
+ os.Exit(1)
+ }
+ }
}
func TestEcho(t *testing.T) {
@@ -91,7 +316,7 @@ func TestEcho(t *testing.T) {
}
func TestCommandRelativeName(t *testing.T) {
- testenv.MustHaveExec(t)
+ cmd := helperCommand(t, "echo", "foo")
// Run our own binary as a relative path
// (e.g. "_test/exec.test") our parent directory.
@@ -106,9 +331,8 @@ func TestCommandRelativeName(t *testing.T) {
t.Skipf("skipping; unexpected shallow dir of %q", dir)
}
- cmd := exec.Command(filepath.Join(dirBase, base), "-test.run=TestHelperProcess", "--", "echo", "foo")
+ cmd.Path = filepath.Join(dirBase, base)
cmd.Dir = parentDir
- cmd.Env = []string{"GO_WANT_HELPER_PROCESS=1"}
out, err := cmd.Output()
if err != nil {
@@ -167,7 +391,7 @@ func TestCatGoodAndBadFile(t *testing.T) {
if !strings.HasPrefix(errLine, "Error: open /bogus/file.foo") {
t.Errorf("expected stderr to complain about file; got %q", errLine)
}
- if !strings.Contains(body, "func TestHelperProcess(t *testing.T)") {
+ if !strings.Contains(body, "func TestCatGoodAndBadFile(t *testing.T)") {
t.Errorf("expected test code; got %q (len %d)", body, len(body))
}
}
@@ -402,6 +626,7 @@ func TestPipeLookPathLeak(t *testing.T) {
}
func TestExtraFilesFDShuffle(t *testing.T) {
+ maySkipHelperCommand("extraFilesAndPipes")
testenv.SkipFlaky(t, 5780)
switch runtime.GOOS {
case "windows":
@@ -627,6 +852,7 @@ func TestExtraFiles(t *testing.T) {
func TestExtraFilesRace(t *testing.T) {
if runtime.GOOS == "windows" {
+ maySkipHelperCommand("describefiles")
t.Skip("no operating system support; skipping")
}
listen := func() net.Listener {
@@ -684,175 +910,6 @@ func TestExtraFilesRace(t *testing.T) {
}
}
-// TestHelperProcess isn't a real test. It's used as a helper process
-// for TestParameterRun.
-func TestHelperProcess(*testing.T) {
- if os.Getenv("GO_WANT_HELPER_PROCESS") != "1" {
- return
- }
- defer os.Exit(0)
-
- args := os.Args
- for len(args) > 0 {
- if args[0] == "--" {
- args = args[1:]
- break
- }
- args = args[1:]
- }
- if len(args) == 0 {
- fmt.Fprintf(os.Stderr, "No command\n")
- os.Exit(2)
- }
-
- cmd, args := args[0], args[1:]
- switch cmd {
- case "echo":
- iargs := []any{}
- for _, s := range args {
- iargs = append(iargs, s)
- }
- fmt.Println(iargs...)
- case "echoenv":
- for _, s := range args {
- fmt.Println(os.Getenv(s))
- }
- os.Exit(0)
- case "cat":
- if len(args) == 0 {
- io.Copy(os.Stdout, os.Stdin)
- return
- }
- exit := 0
- for _, fn := range args {
- f, err := os.Open(fn)
- if err != nil {
- fmt.Fprintf(os.Stderr, "Error: %v\n", err)
- exit = 2
- } else {
- defer f.Close()
- io.Copy(os.Stdout, f)
- }
- }
- os.Exit(exit)
- case "pipetest":
- bufr := bufio.NewReader(os.Stdin)
- for {
- line, _, err := bufr.ReadLine()
- if err == io.EOF {
- break
- } else if err != nil {
- os.Exit(1)
- }
- if bytes.HasPrefix(line, []byte("O:")) {
- os.Stdout.Write(line)
- os.Stdout.Write([]byte{'\n'})
- } else if bytes.HasPrefix(line, []byte("E:")) {
- os.Stderr.Write(line)
- os.Stderr.Write([]byte{'\n'})
- } else {
- os.Exit(1)
- }
- }
- case "stdinClose":
- b, err := io.ReadAll(os.Stdin)
- if err != nil {
- fmt.Fprintf(os.Stderr, "Error: %v\n", err)
- os.Exit(1)
- }
- if s := string(b); s != stdinCloseTestString {
- fmt.Fprintf(os.Stderr, "Error: Read %q, want %q", s, stdinCloseTestString)
- os.Exit(1)
- }
- os.Exit(0)
- case "exit":
- n, _ := strconv.Atoi(args[0])
- os.Exit(n)
- case "describefiles":
- f := os.NewFile(3, fmt.Sprintf("fd3"))
- ln, err := net.FileListener(f)
- if err == nil {
- fmt.Printf("fd3: listener %s\n", ln.Addr())
- ln.Close()
- }
- os.Exit(0)
- case "extraFilesAndPipes":
- n, _ := strconv.Atoi(args[0])
- pipes := make([]*os.File, n)
- for i := 0; i < n; i++ {
- pipes[i] = os.NewFile(uintptr(3+i), strconv.Itoa(i))
- }
- response := ""
- for i, r := range pipes {
- ch := make(chan string, 1)
- go func(c chan string) {
- buf := make([]byte, 10)
- n, err := r.Read(buf)
- if err != nil {
- fmt.Fprintf(os.Stderr, "Child: read error: %v on pipe %d\n", err, i)
- os.Exit(1)
- }
- c <- string(buf[:n])
- close(c)
- }(ch)
- select {
- case m := <-ch:
- response = response + m
- case <-time.After(5 * time.Second):
- fmt.Fprintf(os.Stderr, "Child: Timeout reading from pipe: %d\n", i)
- os.Exit(1)
- }
- }
- fmt.Fprintf(os.Stderr, "child: %s", response)
- os.Exit(0)
- case "exec":
- cmd := exec.Command(args[1])
- cmd.Dir = args[0]
- output, err := cmd.CombinedOutput()
- if err != nil {
- fmt.Fprintf(os.Stderr, "Child: %s %s", err, string(output))
- os.Exit(1)
- }
- fmt.Printf("%s", string(output))
- os.Exit(0)
- case "lookpath":
- p, err := exec.LookPath(args[0])
- if err != nil {
- fmt.Fprintf(os.Stderr, "LookPath failed: %v\n", err)
- os.Exit(1)
- }
- fmt.Print(p)
- os.Exit(0)
- case "stderrfail":
- fmt.Fprintf(os.Stderr, "some stderr text\n")
- os.Exit(1)
- case "sleep":
- time.Sleep(3 * time.Second)
- os.Exit(0)
- case "pipehandle":
- handle, _ := strconv.ParseUint(args[0], 16, 64)
- pipe := os.NewFile(uintptr(handle), "")
- _, err := fmt.Fprint(pipe, args[1])
- if err != nil {
- fmt.Fprintf(os.Stderr, "writing to pipe failed: %v\n", err)
- os.Exit(1)
- }
- pipe.Close()
- os.Exit(0)
- case "pwd":
- pwd, err := os.Getwd()
- if err != nil {
- fmt.Fprintln(os.Stderr, err)
- os.Exit(1)
- }
- fmt.Println(pwd)
- os.Exit(0)
- default:
- fmt.Fprintf(os.Stderr, "Unknown command %q\n", cmd)
- os.Exit(2)
- }
-}
-
type delayedInfiniteReader struct{}
func (delayedInfiniteReader) Read(b []byte) (int, error) {
@@ -865,8 +922,6 @@ func (delayedInfiniteReader) Read(b []byte) (int, error) {
// Issue 9173: ignore stdin pipe writes if the program completes successfully.
func TestIgnorePipeErrorOnSuccess(t *testing.T) {
- testenv.MustHaveExec(t)
-
testWith := func(r io.Reader) func(*testing.T) {
return func(t *testing.T) {
cmd := helperCommand(t, "echo", "foo")
@@ -892,12 +947,7 @@ func (w *badWriter) Write(data []byte) (int, error) {
}
func TestClosePipeOnCopyError(t *testing.T) {
- testenv.MustHaveExec(t)
-
- if runtime.GOOS == "windows" || runtime.GOOS == "plan9" {
- t.Skipf("skipping test on %s - no yes command", runtime.GOOS)
- }
- cmd := exec.Command("yes")
+ cmd := helperCommand(t, "yes")
cmd.Stdout = new(badWriter)
c := make(chan int, 1)
go func() {
@@ -916,8 +966,6 @@ func TestClosePipeOnCopyError(t *testing.T) {
}
func TestOutputStderrCapture(t *testing.T) {
- testenv.MustHaveExec(t)
-
cmd := helperCommand(t, "stderrfail")
_, err := cmd.Output()
ee, ok := err.(*exec.ExitError)
@@ -971,6 +1019,7 @@ func TestContext(t *testing.T) {
func TestContextCancel(t *testing.T) {
if runtime.GOOS == "netbsd" && runtime.GOARCH == "arm64" {
+ maySkipHelperCommand("cat")
testenv.SkipFlaky(t, 42061)
}
@@ -1032,10 +1081,8 @@ func TestContextCancel(t *testing.T) {
// test that environment variables are de-duped.
func TestDedupEnvEcho(t *testing.T) {
- testenv.MustHaveExec(t)
-
cmd := helperCommand(t, "echoenv", "FOO")
- cmd.Env = append(cmd.Env, "FOO=bad", "FOO=good")
+ cmd.Env = append(cmd.Environ(), "FOO=bad", "FOO=good")
out, err := cmd.CombinedOutput()
if err != nil {
t.Fatal(err)
@@ -1078,22 +1125,3 @@ func TestStringPathNotResolved(t *testing.T) {
t.Errorf("String(%q, %q) = %q, want %q", "makemeasandwich", "-lettuce", got, want)
}
}
-
-// start a child process without the user code explicitly starting
-// with a copy of the parent's. (The Windows SYSTEMROOT issue: Issue
-// 25210)
-func TestChildCriticalEnv(t *testing.T) {
- testenv.MustHaveExec(t)
- if runtime.GOOS != "windows" {
- t.Skip("only testing on Windows")
- }
- cmd := helperCommand(t, "echoenv", "SYSTEMROOT")
- cmd.Env = []string{"GO_WANT_HELPER_PROCESS=1"}
- out, err := cmd.CombinedOutput()
- if err != nil {
- t.Fatal(err)
- }
- if strings.TrimSpace(string(out)) == "" {
- t.Error("no SYSTEMROOT found")
- }
-}
diff --git a/src/os/exec/exec_windows_test.go b/src/os/exec/exec_windows_test.go
index 8e31e47190f..35ae0b0b8af 100644
--- a/src/os/exec/exec_windows_test.go
+++ b/src/os/exec/exec_windows_test.go
@@ -7,14 +7,31 @@
package exec_test
import (
+ "fmt"
"io"
"os"
"os/exec"
"strconv"
+ "strings"
"syscall"
"testing"
)
+func init() {
+ registerHelperCommand("pipehandle", cmdPipeHandle)
+}
+
+func cmdPipeHandle(args ...string) {
+ handle, _ := strconv.ParseUint(args[0], 16, 64)
+ pipe := os.NewFile(uintptr(handle), "")
+ _, err := fmt.Fprint(pipe, args[1])
+ if err != nil {
+ fmt.Fprintf(os.Stderr, "writing to pipe failed: %v\n", err)
+ os.Exit(1)
+ }
+ pipe.Close()
+}
+
func TestPipePassing(t *testing.T) {
r, w, err := os.Pipe()
if err != nil {
@@ -54,3 +71,28 @@ func TestNoInheritHandles(t *testing.T) {
t.Fatalf("got exit code %d; want 88", exitError.ExitCode())
}
}
+
+// start a child process without the user code explicitly starting
+// with a copy of the parent's SYSTEMROOT.
+// (See issue 25210.)
+func TestChildCriticalEnv(t *testing.T) {
+ cmd := helperCommand(t, "echoenv", "SYSTEMROOT")
+
+ // Explicitly remove SYSTEMROOT from the command's environment.
+ var env []string
+ for _, kv := range cmd.Environ() {
+ k, _, ok := strings.Cut(kv, "=")
+ if !ok || !strings.EqualFold(k, "SYSTEMROOT") {
+ env = append(env, kv)
+ }
+ }
+ cmd.Env = env
+
+ out, err := cmd.CombinedOutput()
+ if err != nil {
+ t.Fatal(err)
+ }
+ if strings.TrimSpace(string(out)) == "" {
+ t.Error("no SYSTEMROOT found")
+ }
+}
diff --git a/src/os/exec/lp_windows_test.go b/src/os/exec/lp_windows_test.go
index bbf6a9b7f13..34abe09d049 100644
--- a/src/os/exec/lp_windows_test.go
+++ b/src/os/exec/lp_windows_test.go
@@ -19,6 +19,31 @@ import (
"testing"
)
+func init() {
+ registerHelperCommand("exec", cmdExec)
+ registerHelperCommand("lookpath", cmdLookPath)
+}
+
+func cmdLookPath(args ...string) {
+ p, err := exec.LookPath(args[0])
+ if err != nil {
+ fmt.Fprintf(os.Stderr, "LookPath failed: %v\n", err)
+ os.Exit(1)
+ }
+ fmt.Print(p)
+}
+
+func cmdExec(args ...string) {
+ cmd := exec.Command(args[1])
+ cmd.Dir = args[0]
+ output, err := cmd.CombinedOutput()
+ if err != nil {
+ fmt.Fprintf(os.Stderr, "Child: %s %s", err, string(output))
+ os.Exit(1)
+ }
+ fmt.Printf("%s", string(output))
+}
+
func installExe(t *testing.T, dest, src string) {
fsrc, err := os.Open(src)
if err != nil {
@@ -66,10 +91,10 @@ type lookPathTest struct {
fails bool // test is expected to fail
}
-func (test lookPathTest) runProg(t *testing.T, env []string, args ...string) (string, error) {
- cmd := exec.Command(args[0], args[1:]...)
+func (test lookPathTest) runProg(t *testing.T, env []string, cmd *exec.Cmd) (string, error) {
cmd.Env = env
cmd.Dir = test.rootDir
+ args := append([]string(nil), cmd.Args...)
args[0] = filepath.Base(args[0])
cmdText := fmt.Sprintf("%q command", strings.Join(args, " "))
out, err := cmd.CombinedOutput()
@@ -135,10 +160,9 @@ func (test lookPathTest) run(t *testing.T, tmpdir, printpathExe string) {
// Run "cmd.exe /c test.searchFor" with new environment and
// work directory set. All candidates are copies of printpath.exe.
// These will output their program paths when run.
- should, errCmd := test.runProg(t, env, "cmd", "/c", test.searchFor)
+ should, errCmd := test.runProg(t, env, exec.Command("cmd", "/c", test.searchFor))
// Run the lookpath program with new environment and work directory set.
- env = append(env, "GO_WANT_HELPER_PROCESS=1")
- have, errLP := test.runProg(t, env, os.Args[0], "-test.run=TestHelperProcess", "--", "lookpath", test.searchFor)
+ have, errLP := test.runProg(t, env, helperCommand(t, "lookpath", test.searchFor))
// Compare results.
if errCmd == nil && errLP == nil {
// both succeeded
@@ -346,30 +370,26 @@ func (test commandTest) isSuccess(rootDir, output string, err error) error {
return nil
}
-func (test commandTest) runOne(rootDir string, env []string, dir, arg0 string) error {
- cmd := exec.Command(os.Args[0], "-test.run=TestHelperProcess", "--", "exec", dir, arg0)
+func (test commandTest) runOne(t *testing.T, rootDir string, env []string, dir, arg0 string) {
+ cmd := helperCommand(t, "exec", dir, arg0)
cmd.Dir = rootDir
cmd.Env = env
output, err := cmd.CombinedOutput()
err = test.isSuccess(rootDir, string(output), err)
if (err != nil) != test.fails {
if test.fails {
- return fmt.Errorf("test=%+v: succeeded, but expected to fail", test)
+ t.Errorf("test=%+v: succeeded, but expected to fail", test)
+ } else {
+ t.Error(err)
}
- return err
}
- return nil
}
func (test commandTest) run(t *testing.T, rootDir, printpathExe string) {
createFiles(t, rootDir, test.files, printpathExe)
PATHEXT := `.COM;.EXE;.BAT`
env := createEnv(rootDir, test.PATH, PATHEXT)
- env = append(env, "GO_WANT_HELPER_PROCESS=1")
- err := test.runOne(rootDir, env, test.dir, test.arg0)
- if err != nil {
- t.Error(err)
- }
+ test.runOne(t, rootDir, env, test.dir, test.arg0)
}
var commandTests = []commandTest{
diff --git a/src/os/exec/read3.go b/src/os/exec/read3.go
index 10cbfbd54ab..8327d73e514 100644
--- a/src/os/exec/read3.go
+++ b/src/os/exec/read3.go
@@ -6,7 +6,7 @@
// This is a test program that verifies that it can read from
// descriptor 3 and that no other descriptors are open.
-// This is not done via TestHelperProcess and GO_WANT_HELPER_PROCESS
+// This is not done via TestHelperProcess and GO_EXEC_TEST_PID
// because we want to ensure that this program does not use cgo,
// because C libraries can open file descriptors behind our backs
// and confuse the test. See issue 25628.