diff options
Diffstat (limited to 'src/pkg/os/exec')
-rw-r--r-- | src/pkg/os/exec/example_test.go | 75 | ||||
-rw-r--r-- | src/pkg/os/exec/exec.go | 493 | ||||
-rw-r--r-- | src/pkg/os/exec/exec_test.go | 726 | ||||
-rw-r--r-- | src/pkg/os/exec/lp_plan9.go | 53 | ||||
-rw-r--r-- | src/pkg/os/exec/lp_test.go | 33 | ||||
-rw-r--r-- | src/pkg/os/exec/lp_unix.go | 60 | ||||
-rw-r--r-- | src/pkg/os/exec/lp_unix_test.go | 55 | ||||
-rw-r--r-- | src/pkg/os/exec/lp_windows.go | 123 | ||||
-rw-r--r-- | src/pkg/os/exec/lp_windows_test.go | 573 |
9 files changed, 0 insertions, 2191 deletions
diff --git a/src/pkg/os/exec/example_test.go b/src/pkg/os/exec/example_test.go deleted file mode 100644 index 55eaac8ab..000000000 --- a/src/pkg/os/exec/example_test.go +++ /dev/null @@ -1,75 +0,0 @@ -// Copyright 2012 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package exec_test - -import ( - "bytes" - "encoding/json" - "fmt" - "log" - "os/exec" - "strings" -) - -func ExampleLookPath() { - path, err := exec.LookPath("fortune") - if err != nil { - log.Fatal("installing fortune is in your future") - } - fmt.Printf("fortune is available at %s\n", path) -} - -func ExampleCommand() { - cmd := exec.Command("tr", "a-z", "A-Z") - cmd.Stdin = strings.NewReader("some input") - var out bytes.Buffer - cmd.Stdout = &out - err := cmd.Run() - if err != nil { - log.Fatal(err) - } - fmt.Printf("in all caps: %q\n", out.String()) -} - -func ExampleCmd_Output() { - out, err := exec.Command("date").Output() - if err != nil { - log.Fatal(err) - } - fmt.Printf("The date is %s\n", out) -} - -func ExampleCmd_Start() { - cmd := exec.Command("sleep", "5") - err := cmd.Start() - if err != nil { - log.Fatal(err) - } - log.Printf("Waiting for command to finish...") - err = cmd.Wait() - log.Printf("Command finished with error: %v", err) -} - -func ExampleCmd_StdoutPipe() { - cmd := exec.Command("echo", "-n", `{"Name": "Bob", "Age": 32}`) - stdout, err := cmd.StdoutPipe() - if err != nil { - log.Fatal(err) - } - if err := cmd.Start(); err != nil { - log.Fatal(err) - } - var person struct { - Name string - Age int - } - if err := json.NewDecoder(stdout).Decode(&person); err != nil { - log.Fatal(err) - } - if err := cmd.Wait(); err != nil { - log.Fatal(err) - } - fmt.Printf("%s is %d years old\n", person.Name, person.Age) -} diff --git a/src/pkg/os/exec/exec.go b/src/pkg/os/exec/exec.go deleted file mode 100644 index a70ed0d20..000000000 --- a/src/pkg/os/exec/exec.go +++ /dev/null @@ -1,493 +0,0 @@ -// Copyright 2009 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -// Package exec runs external commands. It wraps os.StartProcess to make it -// easier to remap stdin and stdout, connect I/O with pipes, and do other -// adjustments. -package exec - -import ( - "bytes" - "errors" - "io" - "os" - "path/filepath" - "runtime" - "strconv" - "strings" - "sync" - "syscall" -) - -// Error records the name of a binary that failed to be executed -// and the reason it failed. -type Error struct { - Name string - Err error -} - -func (e *Error) Error() string { - return "exec: " + strconv.Quote(e.Name) + ": " + e.Err.Error() -} - -// Cmd represents an external command being prepared or run. -type Cmd struct { - // Path is the path of the command to run. - // - // This is the only field that must be set to a non-zero - // value. If Path is relative, it is evaluated relative - // to Dir. - Path string - - // Args holds command line arguments, including the command as Args[0]. - // If the Args field is empty or nil, Run uses {Path}. - // - // In typical use, both Path and Args are set by calling Command. - Args []string - - // Env specifies the environment of the process. - // If Env is nil, Run uses the current process's environment. - Env []string - - // Dir specifies the working directory of the command. - // If Dir is the empty string, Run runs the command in the - // calling process's current directory. - Dir string - - // Stdin specifies the process's standard input. If Stdin is - // nil, the process reads from the null device (os.DevNull). - Stdin io.Reader - - // Stdout and Stderr specify the process's standard output and error. - // - // If either is nil, Run connects the corresponding file descriptor - // to the null device (os.DevNull). - // - // If Stdout and Stderr are the same writer, at most one - // goroutine at a time will call Write. - Stdout io.Writer - Stderr io.Writer - - // ExtraFiles specifies additional open files to be inherited by the - // new process. It does not include standard input, standard output, or - // standard error. If non-nil, entry i becomes file descriptor 3+i. - // - // BUG: on OS X 10.6, child processes may sometimes inherit unwanted fds. - // http://golang.org/issue/2603 - ExtraFiles []*os.File - - // SysProcAttr holds optional, operating system-specific attributes. - // Run passes it to os.StartProcess as the os.ProcAttr's Sys field. - SysProcAttr *syscall.SysProcAttr - - // Process is the underlying process, once started. - Process *os.Process - - // ProcessState contains information about an exited process, - // available after a call to Wait or Run. - ProcessState *os.ProcessState - - lookPathErr error // LookPath error, if any. - finished bool // when Wait was called - childFiles []*os.File - closeAfterStart []io.Closer - closeAfterWait []io.Closer - goroutine []func() error - errch chan error // one send per goroutine -} - -// Command returns the Cmd struct to execute the named program with -// the given arguments. -// -// It sets only the Path and Args in the returned structure. -// -// If name contains no path separators, Command uses LookPath to -// resolve the path to a complete name if possible. Otherwise it uses -// name directly. -// -// The returned Cmd's Args field is constructed from the command name -// followed by the elements of arg, so arg should not include the -// command name itself. For example, Command("echo", "hello") -func Command(name string, arg ...string) *Cmd { - cmd := &Cmd{ - Path: name, - Args: append([]string{name}, arg...), - } - if filepath.Base(name) == name { - if lp, err := LookPath(name); err != nil { - cmd.lookPathErr = err - } else { - cmd.Path = lp - } - } - return cmd -} - -// interfaceEqual protects against panics from doing equality tests on -// two interfaces with non-comparable underlying types. -func interfaceEqual(a, b interface{}) bool { - defer func() { - recover() - }() - return a == b -} - -func (c *Cmd) envv() []string { - if c.Env != nil { - return c.Env - } - return os.Environ() -} - -func (c *Cmd) argv() []string { - if len(c.Args) > 0 { - return c.Args - } - return []string{c.Path} -} - -func (c *Cmd) stdin() (f *os.File, err error) { - if c.Stdin == nil { - f, err = os.Open(os.DevNull) - if err != nil { - return - } - c.closeAfterStart = append(c.closeAfterStart, f) - return - } - - if f, ok := c.Stdin.(*os.File); ok { - return f, nil - } - - pr, pw, err := os.Pipe() - if err != nil { - return - } - - c.closeAfterStart = append(c.closeAfterStart, pr) - c.closeAfterWait = append(c.closeAfterWait, pw) - c.goroutine = append(c.goroutine, func() error { - _, err := io.Copy(pw, c.Stdin) - if err1 := pw.Close(); err == nil { - err = err1 - } - return err - }) - return pr, nil -} - -func (c *Cmd) stdout() (f *os.File, err error) { - return c.writerDescriptor(c.Stdout) -} - -func (c *Cmd) stderr() (f *os.File, err error) { - if c.Stderr != nil && interfaceEqual(c.Stderr, c.Stdout) { - return c.childFiles[1], nil - } - return c.writerDescriptor(c.Stderr) -} - -func (c *Cmd) writerDescriptor(w io.Writer) (f *os.File, err error) { - if w == nil { - f, err = os.OpenFile(os.DevNull, os.O_WRONLY, 0) - if err != nil { - return - } - c.closeAfterStart = append(c.closeAfterStart, f) - return - } - - if f, ok := w.(*os.File); ok { - return f, nil - } - - pr, pw, err := os.Pipe() - if err != nil { - return - } - - c.closeAfterStart = append(c.closeAfterStart, pw) - c.closeAfterWait = append(c.closeAfterWait, pr) - c.goroutine = append(c.goroutine, func() error { - _, err := io.Copy(w, pr) - return err - }) - return pw, nil -} - -func (c *Cmd) closeDescriptors(closers []io.Closer) { - for _, fd := range closers { - fd.Close() - } -} - -// Run starts the specified command and waits for it to complete. -// -// The returned error is nil if the command runs, has no problems -// copying stdin, stdout, and stderr, and exits with a zero exit -// status. -// -// If the command fails to run or doesn't complete successfully, the -// error is of type *ExitError. Other error types may be -// returned for I/O problems. -func (c *Cmd) Run() error { - if err := c.Start(); err != nil { - return err - } - return c.Wait() -} - -// lookExtensions finds windows executable by its dir and path. -// It uses LookPath to try appropriate extensions. -// lookExtensions does not search PATH, instead it converts `prog` into `.\prog`. -func lookExtensions(path, dir string) (string, error) { - if filepath.Base(path) == path { - path = filepath.Join(".", path) - } - if dir == "" { - return LookPath(path) - } - if filepath.VolumeName(path) != "" { - return LookPath(path) - } - if len(path) > 1 && os.IsPathSeparator(path[0]) { - return LookPath(path) - } - dirandpath := filepath.Join(dir, path) - // We assume that LookPath will only add file extension. - lp, err := LookPath(dirandpath) - if err != nil { - return "", err - } - ext := strings.TrimPrefix(lp, dirandpath) - return path + ext, nil -} - -// Start starts the specified command but does not wait for it to complete. -// -// The Wait method will return the exit code and release associated resources -// once the command exits. -func (c *Cmd) Start() error { - if c.lookPathErr != nil { - c.closeDescriptors(c.closeAfterStart) - c.closeDescriptors(c.closeAfterWait) - return c.lookPathErr - } - if runtime.GOOS == "windows" { - lp, err := lookExtensions(c.Path, c.Dir) - if err != nil { - c.closeDescriptors(c.closeAfterStart) - c.closeDescriptors(c.closeAfterWait) - return err - } - c.Path = lp - } - if c.Process != nil { - return errors.New("exec: already started") - } - - type F func(*Cmd) (*os.File, error) - for _, setupFd := range []F{(*Cmd).stdin, (*Cmd).stdout, (*Cmd).stderr} { - fd, err := setupFd(c) - if err != nil { - c.closeDescriptors(c.closeAfterStart) - c.closeDescriptors(c.closeAfterWait) - return err - } - c.childFiles = append(c.childFiles, fd) - } - c.childFiles = append(c.childFiles, c.ExtraFiles...) - - var err error - c.Process, err = os.StartProcess(c.Path, c.argv(), &os.ProcAttr{ - Dir: c.Dir, - Files: c.childFiles, - Env: c.envv(), - Sys: c.SysProcAttr, - }) - if err != nil { - c.closeDescriptors(c.closeAfterStart) - c.closeDescriptors(c.closeAfterWait) - return err - } - - c.closeDescriptors(c.closeAfterStart) - - c.errch = make(chan error, len(c.goroutine)) - for _, fn := range c.goroutine { - go func(fn func() error) { - c.errch <- fn() - }(fn) - } - - return nil -} - -// An ExitError reports an unsuccessful exit by a command. -type ExitError struct { - *os.ProcessState -} - -func (e *ExitError) Error() string { - return e.ProcessState.String() -} - -// Wait waits for the command to exit. -// It must have been started by Start. -// -// The returned error is nil if the command runs, has no problems -// copying stdin, stdout, and stderr, and exits with a zero exit -// status. -// -// If the command fails to run or doesn't complete successfully, the -// error is of type *ExitError. Other error types may be -// returned for I/O problems. -// -// Wait releases any resources associated with the Cmd. -func (c *Cmd) Wait() error { - if c.Process == nil { - return errors.New("exec: not started") - } - if c.finished { - return errors.New("exec: Wait was already called") - } - c.finished = true - state, err := c.Process.Wait() - c.ProcessState = state - - var copyError error - for _ = range c.goroutine { - if err := <-c.errch; err != nil && copyError == nil { - copyError = err - } - } - - c.closeDescriptors(c.closeAfterWait) - - if err != nil { - return err - } else if !state.Success() { - return &ExitError{state} - } - - return copyError -} - -// Output runs the command and returns its standard output. -func (c *Cmd) Output() ([]byte, error) { - if c.Stdout != nil { - return nil, errors.New("exec: Stdout already set") - } - var b bytes.Buffer - c.Stdout = &b - err := c.Run() - return b.Bytes(), err -} - -// CombinedOutput runs the command and returns its combined standard -// output and standard error. -func (c *Cmd) CombinedOutput() ([]byte, error) { - if c.Stdout != nil { - return nil, errors.New("exec: Stdout already set") - } - if c.Stderr != nil { - return nil, errors.New("exec: Stderr already set") - } - var b bytes.Buffer - c.Stdout = &b - c.Stderr = &b - err := c.Run() - return b.Bytes(), err -} - -// StdinPipe returns a pipe that will be connected to the command's -// standard input when the command starts. -// The pipe will be closed automatically after Wait sees the command exit. -// A caller need only call Close to force the pipe to close sooner. -// For example, if the command being run will not exit until standard input -// is closed, the caller must close the pipe. -func (c *Cmd) StdinPipe() (io.WriteCloser, error) { - if c.Stdin != nil { - return nil, errors.New("exec: Stdin already set") - } - if c.Process != nil { - return nil, errors.New("exec: StdinPipe after process started") - } - pr, pw, err := os.Pipe() - if err != nil { - return nil, err - } - c.Stdin = pr - c.closeAfterStart = append(c.closeAfterStart, pr) - wc := &closeOnce{File: pw} - c.closeAfterWait = append(c.closeAfterWait, wc) - return wc, nil -} - -type closeOnce struct { - *os.File - - once sync.Once - err error -} - -func (c *closeOnce) Close() error { - c.once.Do(c.close) - return c.err -} - -func (c *closeOnce) close() { - c.err = c.File.Close() -} - -// StdoutPipe returns a pipe that will be connected to the command's -// standard output when the command starts. -// -// Wait will close the pipe after seeing the command exit, so most callers -// need not close the pipe themselves; however, an implication is that -// it is incorrect to call Wait before all reads from the pipe have completed. -// For the same reason, it is incorrect to call Run when using StdoutPipe. -// See the example for idiomatic usage. -func (c *Cmd) StdoutPipe() (io.ReadCloser, error) { - if c.Stdout != nil { - return nil, errors.New("exec: Stdout already set") - } - if c.Process != nil { - return nil, errors.New("exec: StdoutPipe after process started") - } - pr, pw, err := os.Pipe() - if err != nil { - return nil, err - } - c.Stdout = pw - c.closeAfterStart = append(c.closeAfterStart, pw) - c.closeAfterWait = append(c.closeAfterWait, pr) - return pr, nil -} - -// StderrPipe returns a pipe that will be connected to the command's -// standard error when the command starts. -// -// Wait will close the pipe after seeing the command exit, so most callers -// need not close the pipe themselves; however, an implication is that -// it is incorrect to call Wait before all reads from the pipe have completed. -// For the same reason, it is incorrect to use Run when using StderrPipe. -// See the StdoutPipe example for idiomatic usage. -func (c *Cmd) StderrPipe() (io.ReadCloser, error) { - if c.Stderr != nil { - return nil, errors.New("exec: Stderr already set") - } - if c.Process != nil { - return nil, errors.New("exec: StderrPipe after process started") - } - pr, pw, err := os.Pipe() - if err != nil { - return nil, err - } - c.Stderr = pw - c.closeAfterStart = append(c.closeAfterStart, pw) - c.closeAfterWait = append(c.closeAfterWait, pr) - return pr, nil -} diff --git a/src/pkg/os/exec/exec_test.go b/src/pkg/os/exec/exec_test.go deleted file mode 100644 index 6f77ac38a..000000000 --- a/src/pkg/os/exec/exec_test.go +++ /dev/null @@ -1,726 +0,0 @@ -// Copyright 2009 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -// Use an external test to avoid os/exec -> net/http -> crypto/x509 -> os/exec -// circular dependency on non-cgo darwin. - -package exec_test - -import ( - "bufio" - "bytes" - "fmt" - "io" - "io/ioutil" - "log" - "net" - "net/http" - "net/http/httptest" - "os" - "os/exec" - "path/filepath" - "runtime" - "strconv" - "strings" - "testing" - "time" -) - -func helperCommand(t *testing.T, s ...string) *exec.Cmd { - if runtime.GOOS == "nacl" { - t.Skip("skipping on nacl") - } - cs := []string{"-test.run=TestHelperProcess", "--"} - cs = append(cs, s...) - cmd := exec.Command(os.Args[0], cs...) - cmd.Env = []string{"GO_WANT_HELPER_PROCESS=1"} - return cmd -} - -func TestEcho(t *testing.T) { - bs, err := helperCommand(t, "echo", "foo bar", "baz").Output() - if err != nil { - t.Errorf("echo: %v", err) - } - if g, e := string(bs), "foo bar baz\n"; g != e { - t.Errorf("echo: want %q, got %q", e, g) - } -} - -func TestCommandRelativeName(t *testing.T) { - // Run our own binary as a relative path - // (e.g. "_test/exec.test") our parent directory. - base := filepath.Base(os.Args[0]) // "exec.test" - dir := filepath.Dir(os.Args[0]) // "/tmp/go-buildNNNN/os/exec/_test" - if dir == "." { - t.Skip("skipping; running test at root somehow") - } - parentDir := filepath.Dir(dir) // "/tmp/go-buildNNNN/os/exec" - dirBase := filepath.Base(dir) // "_test" - if dirBase == "." { - t.Skipf("skipping; unexpected shallow dir of %q", dir) - } - - cmd := exec.Command(filepath.Join(dirBase, base), "-test.run=TestHelperProcess", "--", "echo", "foo") - cmd.Dir = parentDir - cmd.Env = []string{"GO_WANT_HELPER_PROCESS=1"} - - out, err := cmd.Output() - if err != nil { - t.Errorf("echo: %v", err) - } - if g, e := string(out), "foo\n"; g != e { - t.Errorf("echo: want %q, got %q", e, g) - } -} - -func TestCatStdin(t *testing.T) { - // Cat, testing stdin and stdout. - input := "Input string\nLine 2" - p := helperCommand(t, "cat") - p.Stdin = strings.NewReader(input) - bs, err := p.Output() - if err != nil { - t.Errorf("cat: %v", err) - } - s := string(bs) - if s != input { - t.Errorf("cat: want %q, got %q", input, s) - } -} - -func TestCatGoodAndBadFile(t *testing.T) { - // Testing combined output and error values. - bs, err := helperCommand(t, "cat", "/bogus/file.foo", "exec_test.go").CombinedOutput() - if _, ok := err.(*exec.ExitError); !ok { - t.Errorf("expected *exec.ExitError from cat combined; got %T: %v", err, err) - } - s := string(bs) - sp := strings.SplitN(s, "\n", 2) - if len(sp) != 2 { - t.Fatalf("expected two lines from cat; got %q", s) - } - errLine, body := sp[0], sp[1] - 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)") { - t.Errorf("expected test code; got %q (len %d)", body, len(body)) - } -} - -func TestNoExistBinary(t *testing.T) { - // Can't run a non-existent binary - err := exec.Command("/no-exist-binary").Run() - if err == nil { - t.Error("expected error from /no-exist-binary") - } -} - -func TestExitStatus(t *testing.T) { - // Test that exit values are returned correctly - cmd := helperCommand(t, "exit", "42") - err := cmd.Run() - want := "exit status 42" - switch runtime.GOOS { - case "plan9": - want = fmt.Sprintf("exit status: '%s %d: 42'", filepath.Base(cmd.Path), cmd.ProcessState.Pid()) - } - if werr, ok := err.(*exec.ExitError); ok { - if s := werr.Error(); s != want { - t.Errorf("from exit 42 got exit %q, want %q", s, want) - } - } else { - t.Fatalf("expected *exec.ExitError from exit 42; got %T: %v", err, err) - } -} - -func TestPipes(t *testing.T) { - check := func(what string, err error) { - if err != nil { - t.Fatalf("%s: %v", what, err) - } - } - // Cat, testing stdin and stdout. - c := helperCommand(t, "pipetest") - stdin, err := c.StdinPipe() - check("StdinPipe", err) - stdout, err := c.StdoutPipe() - check("StdoutPipe", err) - stderr, err := c.StderrPipe() - check("StderrPipe", err) - - outbr := bufio.NewReader(stdout) - errbr := bufio.NewReader(stderr) - line := func(what string, br *bufio.Reader) string { - line, _, err := br.ReadLine() - if err != nil { - t.Fatalf("%s: %v", what, err) - } - return string(line) - } - - err = c.Start() - check("Start", err) - - _, err = stdin.Write([]byte("O:I am output\n")) - check("first stdin Write", err) - if g, e := line("first output line", outbr), "O:I am output"; g != e { - t.Errorf("got %q, want %q", g, e) - } - - _, err = stdin.Write([]byte("E:I am error\n")) - check("second stdin Write", err) - if g, e := line("first error line", errbr), "E:I am error"; g != e { - t.Errorf("got %q, want %q", g, e) - } - - _, err = stdin.Write([]byte("O:I am output2\n")) - check("third stdin Write 3", err) - if g, e := line("second output line", outbr), "O:I am output2"; g != e { - t.Errorf("got %q, want %q", g, e) - } - - stdin.Close() - err = c.Wait() - check("Wait", err) -} - -const stdinCloseTestString = "Some test string." - -// Issue 6270. -func TestStdinClose(t *testing.T) { - check := func(what string, err error) { - if err != nil { - t.Fatalf("%s: %v", what, err) - } - } - cmd := helperCommand(t, "stdinClose") - stdin, err := cmd.StdinPipe() - check("StdinPipe", err) - // Check that we can access methods of the underlying os.File.` - if _, ok := stdin.(interface { - Fd() uintptr - }); !ok { - t.Error("can't access methods of underlying *os.File") - } - check("Start", cmd.Start()) - go func() { - _, err := io.Copy(stdin, strings.NewReader(stdinCloseTestString)) - check("Copy", err) - // Before the fix, this next line would race with cmd.Wait. - check("Close", stdin.Close()) - }() - check("Wait", cmd.Wait()) -} - -// Issue 5071 -func TestPipeLookPathLeak(t *testing.T) { - fd0, lsof0 := numOpenFDS(t) - for i := 0; i < 4; i++ { - cmd := exec.Command("something-that-does-not-exist-binary") - cmd.StdoutPipe() - cmd.StderrPipe() - cmd.StdinPipe() - if err := cmd.Run(); err == nil { - t.Fatal("unexpected success") - } - } - for triesLeft := 3; triesLeft >= 0; triesLeft-- { - open, lsof := numOpenFDS(t) - fdGrowth := open - fd0 - if fdGrowth > 2 { - if triesLeft > 0 { - // Work around what appears to be a race with Linux's - // proc filesystem (as used by lsof). It seems to only - // be eventually consistent. Give it awhile to settle. - // See golang.org/issue/7808 - time.Sleep(100 * time.Millisecond) - continue - } - t.Errorf("leaked %d fds; want ~0; have:\n%s\noriginally:\n%s", fdGrowth, lsof, lsof0) - } - break - } -} - -func numOpenFDS(t *testing.T) (n int, lsof []byte) { - lsof, err := exec.Command("lsof", "-n", "-p", strconv.Itoa(os.Getpid())).Output() - if err != nil { - t.Skip("skipping test; error finding or running lsof") - } - return bytes.Count(lsof, []byte("\n")), lsof -} - -var testedAlreadyLeaked = false - -// basefds returns the number of expected file descriptors -// to be present in a process at start. -func basefds() uintptr { - n := os.Stderr.Fd() + 1 - - // Go runtime for 32-bit Plan 9 requires that /dev/bintime - // be kept open. - // See ../../runtime/time_plan9_386.c:/^runtime·nanotime - if runtime.GOOS == "plan9" && runtime.GOARCH == "386" { - n++ - } - return n -} - -func closeUnexpectedFds(t *testing.T, m string) { - for fd := basefds(); fd <= 101; fd++ { - err := os.NewFile(fd, "").Close() - if err == nil { - t.Logf("%s: Something already leaked - closed fd %d", m, fd) - } - } -} - -func TestExtraFilesFDShuffle(t *testing.T) { - t.Skip("flaky test; see http://golang.org/issue/5780") - switch runtime.GOOS { - case "darwin": - // TODO(cnicolaou): http://golang.org/issue/2603 - // leads to leaked file descriptors in this test when it's - // run from a builder. - closeUnexpectedFds(t, "TestExtraFilesFDShuffle") - case "netbsd": - // http://golang.org/issue/3955 - closeUnexpectedFds(t, "TestExtraFilesFDShuffle") - case "windows": - t.Skip("no operating system support; skipping") - } - - // syscall.StartProcess maps all the FDs passed to it in - // ProcAttr.Files (the concatenation of stdin,stdout,stderr and - // ExtraFiles) into consecutive FDs in the child, that is: - // Files{11, 12, 6, 7, 9, 3} should result in the file - // represented by FD 11 in the parent being made available as 0 - // in the child, 12 as 1, etc. - // - // We want to test that FDs in the child do not get overwritten - // by one another as this shuffle occurs. The original implementation - // was buggy in that in some data dependent cases it would ovewrite - // stderr in the child with one of the ExtraFile members. - // Testing for this case is difficult because it relies on using - // the same FD values as that case. In particular, an FD of 3 - // must be at an index of 4 or higher in ProcAttr.Files and - // the FD of the write end of the Stderr pipe (as obtained by - // StderrPipe()) must be the same as the size of ProcAttr.Files; - // therefore we test that the read end of this pipe (which is what - // is returned to the parent by StderrPipe() being one less than - // the size of ProcAttr.Files, i.e. 3+len(cmd.ExtraFiles). - // - // Moving this test case around within the overall tests may - // affect the FDs obtained and hence the checks to catch these cases. - npipes := 2 - c := helperCommand(t, "extraFilesAndPipes", strconv.Itoa(npipes+1)) - rd, wr, _ := os.Pipe() - defer rd.Close() - if rd.Fd() != 3 { - t.Errorf("bad test value for test pipe: fd %d", rd.Fd()) - } - stderr, _ := c.StderrPipe() - wr.WriteString("_LAST") - wr.Close() - - pipes := make([]struct { - r, w *os.File - }, npipes) - data := []string{"a", "b"} - - for i := 0; i < npipes; i++ { - r, w, err := os.Pipe() - if err != nil { - t.Fatalf("unexpected error creating pipe: %s", err) - } - pipes[i].r = r - pipes[i].w = w - w.WriteString(data[i]) - c.ExtraFiles = append(c.ExtraFiles, pipes[i].r) - defer func() { - r.Close() - w.Close() - }() - } - // Put fd 3 at the end. - c.ExtraFiles = append(c.ExtraFiles, rd) - - stderrFd := int(stderr.(*os.File).Fd()) - if stderrFd != ((len(c.ExtraFiles) + 3) - 1) { - t.Errorf("bad test value for stderr pipe") - } - - expected := "child: " + strings.Join(data, "") + "_LAST" - - err := c.Start() - if err != nil { - t.Fatalf("Run: %v", err) - } - ch := make(chan string, 1) - go func(ch chan string) { - buf := make([]byte, 512) - n, err := stderr.Read(buf) - if err != nil { - t.Fatalf("Read: %s", err) - ch <- err.Error() - } else { - ch <- string(buf[:n]) - } - close(ch) - }(ch) - select { - case m := <-ch: - if m != expected { - t.Errorf("Read: '%s' not '%s'", m, expected) - } - case <-time.After(5 * time.Second): - t.Errorf("Read timedout") - } - c.Wait() -} - -func TestExtraFiles(t *testing.T) { - if runtime.GOOS == "windows" { - t.Skip("no operating system support; skipping") - } - - // Ensure that file descriptors have not already been leaked into - // our environment. - if !testedAlreadyLeaked { - testedAlreadyLeaked = true - closeUnexpectedFds(t, "TestExtraFiles") - } - - // Force network usage, to verify the epoll (or whatever) fd - // doesn't leak to the child, - ln, err := net.Listen("tcp", "127.0.0.1:0") - if err != nil { - t.Fatal(err) - } - defer ln.Close() - - // Make sure duplicated fds don't leak to the child. - f, err := ln.(*net.TCPListener).File() - if err != nil { - t.Fatal(err) - } - defer f.Close() - ln2, err := net.FileListener(f) - if err != nil { - t.Fatal(err) - } - defer ln2.Close() - - // Force TLS root certs to be loaded (which might involve - // cgo), to make sure none of that potential C code leaks fds. - ts := httptest.NewUnstartedServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})) - // quiet expected TLS handshake error "remote error: bad certificate" - ts.Config.ErrorLog = log.New(ioutil.Discard, "", 0) - ts.StartTLS() - defer ts.Close() - _, err = http.Get(ts.URL) - if err == nil { - t.Errorf("success trying to fetch %s; want an error", ts.URL) - } - - tf, err := ioutil.TempFile("", "") - if err != nil { - t.Fatalf("TempFile: %v", err) - } - defer os.Remove(tf.Name()) - defer tf.Close() - - const text = "Hello, fd 3!" - _, err = tf.Write([]byte(text)) - if err != nil { - t.Fatalf("Write: %v", err) - } - _, err = tf.Seek(0, os.SEEK_SET) - if err != nil { - t.Fatalf("Seek: %v", err) - } - - c := helperCommand(t, "read3") - var stdout, stderr bytes.Buffer - c.Stdout = &stdout - c.Stderr = &stderr - c.ExtraFiles = []*os.File{tf} - err = c.Run() - if err != nil { - t.Fatalf("Run: %v; stdout %q, stderr %q", err, stdout.Bytes(), stderr.Bytes()) - } - if stdout.String() != text { - t.Errorf("got stdout %q, stderr %q; want %q on stdout", stdout.String(), stderr.String(), text) - } -} - -func TestExtraFilesRace(t *testing.T) { - if runtime.GOOS == "windows" { - t.Skip("no operating system support; skipping") - } - listen := func() net.Listener { - ln, err := net.Listen("tcp", "127.0.0.1:0") - if err != nil { - t.Fatal(err) - } - return ln - } - listenerFile := func(ln net.Listener) *os.File { - f, err := ln.(*net.TCPListener).File() - if err != nil { - t.Fatal(err) - } - return f - } - runCommand := func(c *exec.Cmd, out chan<- string) { - bout, err := c.CombinedOutput() - if err != nil { - out <- "ERROR:" + err.Error() - } else { - out <- string(bout) - } - } - - for i := 0; i < 10; i++ { - la := listen() - ca := helperCommand(t, "describefiles") - ca.ExtraFiles = []*os.File{listenerFile(la)} - lb := listen() - cb := helperCommand(t, "describefiles") - cb.ExtraFiles = []*os.File{listenerFile(lb)} - ares := make(chan string) - bres := make(chan string) - go runCommand(ca, ares) - go runCommand(cb, bres) - if got, want := <-ares, fmt.Sprintf("fd3: listener %s\n", la.Addr()); got != want { - t.Errorf("iteration %d, process A got:\n%s\nwant:\n%s\n", i, got, want) - } - if got, want := <-bres, fmt.Sprintf("fd3: listener %s\n", lb.Addr()); got != want { - t.Errorf("iteration %d, process B got:\n%s\nwant:\n%s\n", i, got, want) - } - la.Close() - lb.Close() - for _, f := range ca.ExtraFiles { - f.Close() - } - for _, f := range cb.ExtraFiles { - f.Close() - } - - } -} - -// 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) - - // Determine which command to use to display open files. - ofcmd := "lsof" - switch runtime.GOOS { - case "dragonfly", "freebsd", "netbsd", "openbsd": - ofcmd = "fstat" - case "plan9": - ofcmd = "/bin/cat" - } - - 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 := []interface{}{} - for _, s := range args { - iargs = append(iargs, s) - } - fmt.Println(iargs...) - 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 := ioutil.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 "read3": // read fd 3 - fd3 := os.NewFile(3, "fd3") - bs, err := ioutil.ReadAll(fd3) - if err != nil { - fmt.Printf("ReadAll from fd 3: %v", err) - os.Exit(1) - } - switch runtime.GOOS { - case "dragonfly": - // TODO(jsing): Determine why DragonFly is leaking - // file descriptors... - case "darwin": - // TODO(bradfitz): broken? Sometimes. - // http://golang.org/issue/2603 - // Skip this additional part of the test for now. - case "netbsd": - // TODO(jsing): This currently fails on NetBSD due to - // the cloned file descriptors that result from opening - // /dev/urandom. - // http://golang.org/issue/3955 - case "plan9": - // TODO(0intro): Determine why Plan 9 is leaking - // file descriptors. - // http://golang.org/issue/7118 - case "solaris": - // TODO(aram): This fails on Solaris because libc opens - // its own files, as it sees fit. Darwin does the same, - // see: http://golang.org/issue/2603 - default: - // Now verify that there are no other open fds. - var files []*os.File - for wantfd := basefds() + 1; wantfd <= 100; wantfd++ { - f, err := os.Open(os.Args[0]) - if err != nil { - fmt.Printf("error opening file with expected fd %d: %v", wantfd, err) - os.Exit(1) - } - if got := f.Fd(); got != wantfd { - fmt.Printf("leaked parent file. fd = %d; want %d\n", got, wantfd) - var args []string - switch runtime.GOOS { - case "plan9": - args = []string{fmt.Sprintf("/proc/%d/fd", os.Getpid())} - default: - args = []string{"-p", fmt.Sprint(os.Getpid())} - } - out, _ := exec.Command(ofcmd, args...).CombinedOutput() - fmt.Print(string(out)) - os.Exit(1) - } - files = append(files, f) - } - for _, f := range files { - f.Close() - } - } - // Referring to fd3 here ensures that it is not - // garbage collected, and therefore closed, while - // executing the wantfd loop above. It doesn't matter - // what we do with fd3 as long as we refer to it; - // closing it is the easy choice. - fd3.Close() - os.Stdout.Write(bs) - 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) - default: - fmt.Fprintf(os.Stderr, "Unknown command %q\n", cmd) - os.Exit(2) - } -} diff --git a/src/pkg/os/exec/lp_plan9.go b/src/pkg/os/exec/lp_plan9.go deleted file mode 100644 index 5aa8a54ed..000000000 --- a/src/pkg/os/exec/lp_plan9.go +++ /dev/null @@ -1,53 +0,0 @@ -// Copyright 2011 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package exec - -import ( - "errors" - "os" - "strings" -) - -// ErrNotFound is the error resulting if a path search failed to find an executable file. -var ErrNotFound = errors.New("executable file not found in $path") - -func findExecutable(file string) error { - d, err := os.Stat(file) - if err != nil { - return err - } - if m := d.Mode(); !m.IsDir() && m&0111 != 0 { - return nil - } - return os.ErrPermission -} - -// LookPath searches for an executable binary named file -// in the directories named by the path environment variable. -// If file begins with "/", "#", "./", or "../", it is tried -// directly and the path is not consulted. -// The result may be an absolute path or a path relative to the current directory. -func LookPath(file string) (string, error) { - // skip the path lookup for these prefixes - skip := []string{"/", "#", "./", "../"} - - for _, p := range skip { - if strings.HasPrefix(file, p) { - err := findExecutable(file) - if err == nil { - return file, nil - } - return "", &Error{file, err} - } - } - - path := os.Getenv("path") - for _, dir := range strings.Split(path, "\000") { - if err := findExecutable(dir + "/" + file); err == nil { - return dir + "/" + file, nil - } - } - return "", &Error{file, ErrNotFound} -} diff --git a/src/pkg/os/exec/lp_test.go b/src/pkg/os/exec/lp_test.go deleted file mode 100644 index 77d8e848c..000000000 --- a/src/pkg/os/exec/lp_test.go +++ /dev/null @@ -1,33 +0,0 @@ -// Copyright 2011 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package exec - -import ( - "testing" -) - -var nonExistentPaths = []string{ - "some-non-existent-path", - "non-existent-path/slashed", -} - -func TestLookPathNotFound(t *testing.T) { - for _, name := range nonExistentPaths { - path, err := LookPath(name) - if err == nil { - t.Fatalf("LookPath found %q in $PATH", name) - } - if path != "" { - t.Fatalf("LookPath path == %q when err != nil", path) - } - perr, ok := err.(*Error) - if !ok { - t.Fatal("LookPath error is not an exec.Error") - } - if perr.Name != name { - t.Fatalf("want Error name %q, got %q", name, perr.Name) - } - } -} diff --git a/src/pkg/os/exec/lp_unix.go b/src/pkg/os/exec/lp_unix.go deleted file mode 100644 index 3f895d5b3..000000000 --- a/src/pkg/os/exec/lp_unix.go +++ /dev/null @@ -1,60 +0,0 @@ -// Copyright 2010 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -// +build darwin dragonfly freebsd linux nacl netbsd openbsd solaris - -package exec - -import ( - "errors" - "os" - "strings" -) - -// ErrNotFound is the error resulting if a path search failed to find an executable file. -var ErrNotFound = errors.New("executable file not found in $PATH") - -func findExecutable(file string) error { - d, err := os.Stat(file) - if err != nil { - return err - } - if m := d.Mode(); !m.IsDir() && m&0111 != 0 { - return nil - } - return os.ErrPermission -} - -// LookPath searches for an executable binary named file -// in the directories named by the PATH environment variable. -// If file contains a slash, it is tried directly and the PATH is not consulted. -// The result may be an absolute path or a path relative to the current directory. -func LookPath(file string) (string, error) { - // NOTE(rsc): I wish we could use the Plan 9 behavior here - // (only bypass the path if file begins with / or ./ or ../) - // but that would not match all the Unix shells. - - if strings.Contains(file, "/") { - err := findExecutable(file) - if err == nil { - return file, nil - } - return "", &Error{file, err} - } - pathenv := os.Getenv("PATH") - if pathenv == "" { - return "", &Error{file, ErrNotFound} - } - for _, dir := range strings.Split(pathenv, ":") { - if dir == "" { - // Unix shell semantics: path element "" means "." - dir = "." - } - path := dir + "/" + file - if err := findExecutable(path); err == nil { - return path, nil - } - } - return "", &Error{file, ErrNotFound} -} diff --git a/src/pkg/os/exec/lp_unix_test.go b/src/pkg/os/exec/lp_unix_test.go deleted file mode 100644 index 051db664a..000000000 --- a/src/pkg/os/exec/lp_unix_test.go +++ /dev/null @@ -1,55 +0,0 @@ -// Copyright 2013 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -// +build darwin dragonfly freebsd linux netbsd openbsd solaris - -package exec - -import ( - "io/ioutil" - "os" - "testing" -) - -func TestLookPathUnixEmptyPath(t *testing.T) { - tmp, err := ioutil.TempDir("", "TestLookPathUnixEmptyPath") - if err != nil { - t.Fatal("TempDir failed: ", err) - } - defer os.RemoveAll(tmp) - wd, err := os.Getwd() - if err != nil { - t.Fatal("Getwd failed: ", err) - } - err = os.Chdir(tmp) - if err != nil { - t.Fatal("Chdir failed: ", err) - } - defer os.Chdir(wd) - - f, err := os.OpenFile("exec_me", os.O_CREATE|os.O_EXCL, 0700) - if err != nil { - t.Fatal("OpenFile failed: ", err) - } - err = f.Close() - if err != nil { - t.Fatal("Close failed: ", err) - } - - pathenv := os.Getenv("PATH") - defer os.Setenv("PATH", pathenv) - - err = os.Setenv("PATH", "") - if err != nil { - t.Fatal("Setenv failed: ", err) - } - - path, err := LookPath("exec_me") - if err == nil { - t.Fatal("LookPath found exec_me in empty $PATH") - } - if path != "" { - t.Fatalf("LookPath path == %q when err != nil", path) - } -} diff --git a/src/pkg/os/exec/lp_windows.go b/src/pkg/os/exec/lp_windows.go deleted file mode 100644 index c3efd67e9..000000000 --- a/src/pkg/os/exec/lp_windows.go +++ /dev/null @@ -1,123 +0,0 @@ -// Copyright 2010 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package exec - -import ( - "errors" - "os" - "strings" -) - -// ErrNotFound is the error resulting if a path search failed to find an executable file. -var ErrNotFound = errors.New("executable file not found in %PATH%") - -func chkStat(file string) error { - d, err := os.Stat(file) - if err != nil { - return err - } - if d.IsDir() { - return os.ErrPermission - } - return nil -} - -func hasExt(file string) bool { - i := strings.LastIndex(file, ".") - if i < 0 { - return false - } - return strings.LastIndexAny(file, `:\/`) < i -} - -func findExecutable(file string, exts []string) (string, error) { - if len(exts) == 0 { - return file, chkStat(file) - } - if hasExt(file) { - if chkStat(file) == nil { - return file, nil - } - } - for _, e := range exts { - if f := file + e; chkStat(f) == nil { - return f, nil - } - } - return ``, os.ErrNotExist -} - -// LookPath searches for an executable binary named file -// in the directories named by the PATH environment variable. -// If file contains a slash, it is tried directly and the PATH is not consulted. -// LookPath also uses PATHEXT environment variable to match -// a suitable candidate. -// The result may be an absolute path or a path relative to the current directory. -func LookPath(file string) (f string, err error) { - x := os.Getenv(`PATHEXT`) - if x == `` { - x = `.COM;.EXE;.BAT;.CMD` - } - exts := []string{} - for _, e := range strings.Split(strings.ToLower(x), `;`) { - if e == "" { - continue - } - if e[0] != '.' { - e = "." + e - } - exts = append(exts, e) - } - if strings.IndexAny(file, `:\/`) != -1 { - if f, err = findExecutable(file, exts); err == nil { - return - } - return ``, &Error{file, err} - } - if f, err = findExecutable(`.\`+file, exts); err == nil { - return - } - if pathenv := os.Getenv(`PATH`); pathenv != `` { - for _, dir := range splitList(pathenv) { - if f, err = findExecutable(dir+`\`+file, exts); err == nil { - return - } - } - } - return ``, &Error{file, ErrNotFound} -} - -func splitList(path string) []string { - // The same implementation is used in SplitList in path/filepath; - // consider changing path/filepath when changing this. - - if path == "" { - return []string{} - } - - // Split path, respecting but preserving quotes. - list := []string{} - start := 0 - quo := false - for i := 0; i < len(path); i++ { - switch c := path[i]; { - case c == '"': - quo = !quo - case c == os.PathListSeparator && !quo: - list = append(list, path[start:i]) - start = i + 1 - } - } - list = append(list, path[start:]) - - // Remove quotes. - for i, s := range list { - if strings.Contains(s, `"`) { - list[i] = strings.Replace(s, `"`, ``, -1) - } - } - - return list -} diff --git a/src/pkg/os/exec/lp_windows_test.go b/src/pkg/os/exec/lp_windows_test.go deleted file mode 100644 index 72df03ed2..000000000 --- a/src/pkg/os/exec/lp_windows_test.go +++ /dev/null @@ -1,573 +0,0 @@ -// Copyright 2013 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package exec - -import ( - "fmt" - "io" - "io/ioutil" - "os" - "path/filepath" - "strconv" - "strings" - "testing" -) - -func installExe(t *testing.T, dest, src string) { - fsrc, err := os.Open(src) - if err != nil { - t.Fatal("os.Open failed: ", err) - } - defer fsrc.Close() - fdest, err := os.Create(dest) - if err != nil { - t.Fatal("os.Create failed: ", err) - } - defer fdest.Close() - _, err = io.Copy(fdest, fsrc) - if err != nil { - t.Fatal("io.Copy failed: ", err) - } -} - -func installBat(t *testing.T, dest string) { - f, err := os.Create(dest) - if err != nil { - t.Fatalf("failed to create batch file: %v", err) - } - defer f.Close() - fmt.Fprintf(f, "@echo %s\n", dest) -} - -func installProg(t *testing.T, dest, srcExe string) { - err := os.MkdirAll(filepath.Dir(dest), 0700) - if err != nil { - t.Fatal("os.MkdirAll failed: ", err) - } - if strings.ToLower(filepath.Ext(dest)) == ".bat" { - installBat(t, dest) - return - } - installExe(t, dest, srcExe) -} - -type lookPathTest struct { - rootDir string - PATH string - PATHEXT string - files []string - searchFor string - fails bool // test is expected to fail -} - -func (test lookPathTest) runProg(t *testing.T, env []string, args ...string) (string, error) { - cmd := Command(args[0], args[1:]...) - cmd.Env = env - cmd.Dir = test.rootDir - args[0] = filepath.Base(args[0]) - cmdText := fmt.Sprintf("%q command", strings.Join(args, " ")) - out, err := cmd.CombinedOutput() - if (err != nil) != test.fails { - if test.fails { - t.Fatalf("test=%+v: %s succeeded, but expected to fail", test, cmdText) - } - t.Fatalf("test=%+v: %s failed, but expected to succeed: %v - %v", test, cmdText, err, string(out)) - } - if err != nil { - return "", fmt.Errorf("test=%+v: %s failed: %v - %v", test, cmdText, err, string(out)) - } - // normalise program output - p := string(out) - // trim terminating \r and \n that batch file outputs - for len(p) > 0 && (p[len(p)-1] == '\n' || p[len(p)-1] == '\r') { - p = p[:len(p)-1] - } - if !filepath.IsAbs(p) { - return p, nil - } - if p[:len(test.rootDir)] != test.rootDir { - t.Fatalf("test=%+v: %s output is wrong: %q must have %q prefix", test, cmdText, p, test.rootDir) - } - return p[len(test.rootDir)+1:], nil -} - -func updateEnv(env []string, name, value string) []string { - for i, e := range env { - if strings.HasPrefix(strings.ToUpper(e), name+"=") { - env[i] = name + "=" + value - return env - } - } - return append(env, name+"="+value) -} - -func createEnv(dir, PATH, PATHEXT string) []string { - env := os.Environ() - env = updateEnv(env, "PATHEXT", PATHEXT) - // Add dir in front of every directory in the PATH. - dirs := splitList(PATH) - for i := range dirs { - dirs[i] = filepath.Join(dir, dirs[i]) - } - path := strings.Join(dirs, ";") - env = updateEnv(env, "PATH", path) - return env -} - -// createFiles copies srcPath file into multiply files. -// It uses dir as preifx for all destination files. -func createFiles(t *testing.T, dir string, files []string, srcPath string) { - for _, f := range files { - installProg(t, filepath.Join(dir, f), srcPath) - } -} - -func (test lookPathTest) run(t *testing.T, tmpdir, printpathExe string) { - test.rootDir = tmpdir - createFiles(t, test.rootDir, test.files, printpathExe) - env := createEnv(test.rootDir, test.PATH, test.PATHEXT) - // 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) - // 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) - // Compare results. - if errCmd == nil && errLP == nil { - // both succeeded - if should != have { - t.Fatalf("test=%+v failed: expected to find %q, but found %q", test, should, have) - } - return - } - if errCmd != nil && errLP != nil { - // both failed -> continue - return - } - if errCmd != nil { - t.Fatal(errCmd) - } - if errLP != nil { - t.Fatal(errLP) - } -} - -var lookPathTests = []lookPathTest{ - { - PATHEXT: `.COM;.EXE;.BAT`, - PATH: `p1;p2`, - files: []string{`p1\a.exe`, `p2\a.exe`, `p2\a`}, - searchFor: `a`, - }, - { - PATHEXT: `.COM;.EXE;.BAT`, - PATH: `p1.dir;p2.dir`, - files: []string{`p1.dir\a`, `p2.dir\a.exe`}, - searchFor: `a`, - }, - { - PATHEXT: `.COM;.EXE;.BAT`, - PATH: `p1;p2`, - files: []string{`p1\a.exe`, `p2\a.exe`}, - searchFor: `a.exe`, - }, - { - PATHEXT: `.COM;.EXE;.BAT`, - PATH: `p1;p2`, - files: []string{`p1\a.exe`, `p2\b.exe`}, - searchFor: `b`, - }, - { - PATHEXT: `.COM;.EXE;.BAT`, - PATH: `p1;p2`, - files: []string{`p1\b`, `p2\a`}, - searchFor: `a`, - fails: true, // TODO(brainman): do not know why this fails - }, - // If the command name specifies a path, the shell searches - // the specified path for an executable file matching - // the command name. If a match is found, the external - // command (the executable file) executes. - { - PATHEXT: `.COM;.EXE;.BAT`, - PATH: `p1;p2`, - files: []string{`p1\a.exe`, `p2\a.exe`}, - searchFor: `p2\a`, - }, - // If the command name specifies a path, the shell searches - // the specified path for an executable file matching the command - // name. ... If no match is found, the shell reports an error - // and command processing completes. - { - PATHEXT: `.COM;.EXE;.BAT`, - PATH: `p1;p2`, - files: []string{`p1\b.exe`, `p2\a.exe`}, - searchFor: `p2\b`, - fails: true, - }, - // If the command name does not specify a path, the shell - // searches the current directory for an executable file - // matching the command name. If a match is found, the external - // command (the executable file) executes. - { - PATHEXT: `.COM;.EXE;.BAT`, - PATH: `p1;p2`, - files: []string{`a`, `p1\a.exe`, `p2\a.exe`}, - searchFor: `a`, - }, - // The shell now searches each directory specified by the - // PATH environment variable, in the order listed, for an - // executable file matching the command name. If a match - // is found, the external command (the executable file) executes. - { - PATHEXT: `.COM;.EXE;.BAT`, - PATH: `p1;p2`, - files: []string{`p1\a.exe`, `p2\a.exe`}, - searchFor: `a`, - }, - // The shell now searches each directory specified by the - // PATH environment variable, in the order listed, for an - // executable file matching the command name. If no match - // is found, the shell reports an error and command processing - // completes. - { - PATHEXT: `.COM;.EXE;.BAT`, - PATH: `p1;p2`, - files: []string{`p1\a.exe`, `p2\a.exe`}, - searchFor: `b`, - fails: true, - }, - // If the command name includes a file extension, the shell - // searches each directory for the exact file name specified - // by the command name. - { - PATHEXT: `.COM;.EXE;.BAT`, - PATH: `p1;p2`, - files: []string{`p1\a.exe`, `p2\a.exe`}, - searchFor: `a.exe`, - }, - { - PATHEXT: `.COM;.EXE;.BAT`, - PATH: `p1;p2`, - files: []string{`p1\a.exe`, `p2\a.exe`}, - searchFor: `a.com`, - fails: true, // includes extension and not exact file name match - }, - { - PATHEXT: `.COM;.EXE;.BAT`, - PATH: `p1`, - files: []string{`p1\a.exe.exe`}, - searchFor: `a.exe`, - }, - { - PATHEXT: `.COM;.BAT`, - PATH: `p1;p2`, - files: []string{`p1\a.exe`, `p2\a.exe`}, - searchFor: `a.exe`, - }, - // If the command name does not include a file extension, the shell - // adds the extensions listed in the PATHEXT environment variable, - // one by one, and searches the directory for that file name. Note - // that the shell tries all possible file extensions in a specific - // directory before moving on to search the next directory - // (if there is one). - { - PATHEXT: `.COM;.EXE`, - PATH: `p1;p2`, - files: []string{`p1\a.bat`, `p2\a.exe`}, - searchFor: `a`, - }, - { - PATHEXT: `.COM;.EXE;.BAT`, - PATH: `p1;p2`, - files: []string{`p1\a.bat`, `p2\a.exe`}, - searchFor: `a`, - }, - { - PATHEXT: `.COM;.EXE;.BAT`, - PATH: `p1;p2`, - files: []string{`p1\a.bat`, `p1\a.exe`, `p2\a.bat`, `p2\a.exe`}, - searchFor: `a`, - }, - { - PATHEXT: `.COM`, - PATH: `p1;p2`, - files: []string{`p1\a.bat`, `p2\a.exe`}, - searchFor: `a`, - fails: true, // tried all extensions in PATHEXT, but none matches - }, -} - -func TestLookPath(t *testing.T) { - tmp, err := ioutil.TempDir("", "TestLookPath") - if err != nil { - t.Fatal("TempDir failed: ", err) - } - defer os.RemoveAll(tmp) - - printpathExe := buildPrintPathExe(t, tmp) - - // Run all tests. - for i, test := range lookPathTests { - dir := filepath.Join(tmp, "d"+strconv.Itoa(i)) - err := os.Mkdir(dir, 0700) - if err != nil { - t.Fatal("Mkdir failed: ", err) - } - test.run(t, dir, printpathExe) - } -} - -type commandTest struct { - PATH string - files []string - dir string - arg0 string - want string - fails bool // test is expected to fail -} - -func (test commandTest) isSuccess(rootDir, output string, err error) error { - if err != nil { - return fmt.Errorf("test=%+v: exec: %v %v", test, err, output) - } - path := output - if path[:len(rootDir)] != rootDir { - return fmt.Errorf("test=%+v: %q must have %q prefix", test, path, rootDir) - } - path = path[len(rootDir)+1:] - if path != test.want { - return fmt.Errorf("test=%+v: want %q, got %q", test, test.want, path) - } - return nil -} - -func (test commandTest) runOne(rootDir string, env []string, dir, arg0 string) error { - cmd := Command(os.Args[0], "-test.run=TestHelperProcess", "--", "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) - } - 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) - } -} - -var commandTests = []commandTest{ - // testing commands with no slash, like `a.exe` - { - // should find a.exe in current directory - files: []string{`a.exe`}, - arg0: `a.exe`, - want: `a.exe`, - }, - { - // like above, but add PATH in attempt to break the test - PATH: `p2;p`, - files: []string{`a.exe`, `p\a.exe`, `p2\a.exe`}, - arg0: `a.exe`, - want: `a.exe`, - }, - { - // like above, but use "a" instead of "a.exe" for command - PATH: `p2;p`, - files: []string{`a.exe`, `p\a.exe`, `p2\a.exe`}, - arg0: `a`, - want: `a.exe`, - }, - // testing commands with slash, like `.\a.exe` - { - // should find p\a.exe - files: []string{`p\a.exe`}, - arg0: `p\a.exe`, - want: `p\a.exe`, - }, - { - // like above, but adding `.` in front of executable should still be OK - files: []string{`p\a.exe`}, - arg0: `.\p\a.exe`, - want: `p\a.exe`, - }, - { - // like above, but with PATH added in attempt to break it - PATH: `p2`, - files: []string{`p\a.exe`, `p2\a.exe`}, - arg0: `p\a.exe`, - want: `p\a.exe`, - }, - { - // like above, but make sure .exe is tried even for commands with slash - PATH: `p2`, - files: []string{`p\a.exe`, `p2\a.exe`}, - arg0: `p\a`, - want: `p\a.exe`, - }, - // tests commands, like `a.exe`, with c.Dir set - { - // should not find a.exe in p, becasue LookPath(`a.exe`) will fail - files: []string{`p\a.exe`}, - dir: `p`, - arg0: `a.exe`, - want: `p\a.exe`, - fails: true, - }, - { - // LookPath(`a.exe`) will find `.\a.exe`, but prefixing that with - // dir `p\a.exe` will refer to not existant file - files: []string{`a.exe`, `p\not_important_file`}, - dir: `p`, - arg0: `a.exe`, - want: `a.exe`, - fails: true, - }, - { - // like above, but making test succeed by installing file - // in refered destination (so LookPath(`a.exe`) will still - // find `.\a.exe`, but we successfully execute `p\a.exe`) - files: []string{`a.exe`, `p\a.exe`}, - dir: `p`, - arg0: `a.exe`, - want: `p\a.exe`, - }, - { - // like above, but add PATH in attempt to break the test - PATH: `p2;p`, - files: []string{`a.exe`, `p\a.exe`, `p2\a.exe`}, - dir: `p`, - arg0: `a.exe`, - want: `p\a.exe`, - }, - { - // like above, but use "a" instead of "a.exe" for command - PATH: `p2;p`, - files: []string{`a.exe`, `p\a.exe`, `p2\a.exe`}, - dir: `p`, - arg0: `a`, - want: `p\a.exe`, - }, - { - // finds `a.exe` in the PATH regardless of dir set - // because LookPath returns full path in that case - PATH: `p2;p`, - files: []string{`p\a.exe`, `p2\a.exe`}, - dir: `p`, - arg0: `a.exe`, - want: `p2\a.exe`, - }, - // tests commands, like `.\a.exe`, with c.Dir set - { - // should use dir when command is path, like ".\a.exe" - files: []string{`p\a.exe`}, - dir: `p`, - arg0: `.\a.exe`, - want: `p\a.exe`, - }, - { - // like above, but with PATH added in attempt to break it - PATH: `p2`, - files: []string{`p\a.exe`, `p2\a.exe`}, - dir: `p`, - arg0: `.\a.exe`, - want: `p\a.exe`, - }, - { - // like above, but make sure .exe is tried even for commands with slash - PATH: `p2`, - files: []string{`p\a.exe`, `p2\a.exe`}, - dir: `p`, - arg0: `.\a`, - want: `p\a.exe`, - }, -} - -func TestCommand(t *testing.T) { - tmp, err := ioutil.TempDir("", "TestCommand") - if err != nil { - t.Fatal("TempDir failed: ", err) - } - defer os.RemoveAll(tmp) - - printpathExe := buildPrintPathExe(t, tmp) - - // Run all tests. - for i, test := range commandTests { - dir := filepath.Join(tmp, "d"+strconv.Itoa(i)) - err := os.Mkdir(dir, 0700) - if err != nil { - t.Fatal("Mkdir failed: ", err) - } - test.run(t, dir, printpathExe) - } -} - -// buildPrintPathExe creates a Go program that prints its own path. -// dir is a temp directory where executable will be created. -// The function returns full path to the created program. -func buildPrintPathExe(t *testing.T, dir string) string { - const name = "printpath" - srcname := name + ".go" - err := ioutil.WriteFile(filepath.Join(dir, srcname), []byte(printpathSrc), 0644) - if err != nil { - t.Fatalf("failed to create source: %v", err) - } - if err != nil { - t.Fatalf("failed to execute template: %v", err) - } - outname := name + ".exe" - cmd := Command("go", "build", "-o", outname, srcname) - cmd.Dir = dir - out, err := cmd.CombinedOutput() - if err != nil { - t.Fatalf("failed to build executable: %v - %v", err, string(out)) - } - return filepath.Join(dir, outname) -} - -const printpathSrc = ` -package main - -import ( - "os" - "syscall" - "unicode/utf16" - "unsafe" -) - -func getMyName() (string, error) { - var sysproc = syscall.MustLoadDLL("kernel32.dll").MustFindProc("GetModuleFileNameW") - b := make([]uint16, syscall.MAX_PATH) - r, _, err := sysproc.Call(0, uintptr(unsafe.Pointer(&b[0])), uintptr(len(b))) - n := uint32(r) - if n == 0 { - return "", err - } - return string(utf16.Decode(b[0:n])), nil -} - -func main() { - path, err := getMyName() - if err != nil { - os.Stderr.Write([]byte("getMyName failed: " + err.Error() + "\n")) - os.Exit(1) - } - os.Stdout.Write([]byte(path)) -} -` |