summaryrefslogtreecommitdiff
path: root/src/pkg/exec
diff options
context:
space:
mode:
authorOndřej Surý <ondrej@sury.org>2011-06-30 15:34:22 +0200
committerOndřej Surý <ondrej@sury.org>2011-06-30 15:34:22 +0200
commitd39f5aa373a4422f7a5f3ee764fb0f6b0b719d61 (patch)
tree1833f8b72a4b3a8f00d0d143b079a8fcad01c6ae /src/pkg/exec
parent8652e6c371b8905498d3d314491d36c58d5f68d5 (diff)
downloadgolang-upstream/58.tar.gz
Imported Upstream version 58upstream/58
Diffstat (limited to 'src/pkg/exec')
-rw-r--r--src/pkg/exec/exec.go463
-rw-r--r--src/pkg/exec/exec_test.go284
-rw-r--r--src/pkg/exec/lp_test.go6
-rw-r--r--src/pkg/exec/lp_unix.go21
-rw-r--r--src/pkg/exec/lp_windows.go38
5 files changed, 519 insertions, 293 deletions
diff --git a/src/pkg/exec/exec.go b/src/pkg/exec/exec.go
index 043f84728..935f24c21 100644
--- a/src/pkg/exec/exec.go
+++ b/src/pkg/exec/exec.go
@@ -7,198 +7,363 @@
// adjustments.
package exec
-// BUG(r): This package should be made even easier to use or merged into os.
-
import (
+ "bytes"
+ "io"
"os"
"strconv"
)
-// Arguments to Run.
-const (
- DevNull = iota
- PassThrough
- Pipe
- MergeWithStdout
-)
+// Error records the name of a binary that failed to be be executed
+// and the reason it failed.
+type Error struct {
+ Name string
+ Error os.Error
+}
-// A Cmd represents a running command.
-// Stdin, Stdout, and Stderr are Files representing pipes
-// connected to the running command's standard input, output, and error,
-// or else nil, depending on the arguments to Run.
-// Process represents the underlying operating system process.
+func (e *Error) String() string {
+ return "exec: " + strconv.Quote(e.Name) + ": " + e.Error.String()
+}
+
+// Cmd represents an external command being prepared or run.
type Cmd struct {
- Stdin *os.File
- Stdout *os.File
- Stderr *os.File
+ // Path is the path of the command to run.
+ //
+ // This is the only field that must be set to a non-zero
+ // value.
+ 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 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 /dev/null.
+ //
+ // If Stdout and Stderr are are the same writer, at most one
+ // goroutine at a time will call Write.
+ Stdout io.Writer
+ Stderr io.Writer
+
+ // Process is the underlying process, once started.
Process *os.Process
+
+ err os.Error // last error (from LookPath, stdin, stdout, stderr)
+ finished bool // when Wait was called
+ childFiles []*os.File
+ closeAfterStart []io.Closer
+ closeAfterWait []io.Closer
+ goroutine []func() os.Error
+ errch chan os.Error // one send per goroutine
}
-// PathError records the name of a binary that was not
-// found on the current $PATH.
-type PathError struct {
- Name string
+// Command returns the Cmd struct to execute the named program with
+// the given arguments.
+//
+// It sets Path and Args in the returned structure and zeroes the
+// other fields.
+//
+// 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 {
+ aname, err := LookPath(name)
+ if err != nil {
+ aname = name
+ }
+ return &Cmd{
+ Path: aname,
+ Args: append([]string{name}, arg...),
+ err: err,
+ }
}
-func (e *PathError) String() string {
- return "command " + strconv.Quote(e.Name) + " not found in $PATH"
+// 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
}
-// Given mode (DevNull, etc), return file for child
-// and file to record in Cmd structure.
-func modeToFiles(mode, fd int) (*os.File, *os.File, os.Error) {
- switch mode {
- case DevNull:
- rw := os.O_WRONLY
- if fd == 0 {
- rw = os.O_RDONLY
- }
- f, err := os.OpenFile(os.DevNull, rw, 0)
- return f, nil, err
- case PassThrough:
- switch fd {
- case 0:
- return os.Stdin, nil, nil
- case 1:
- return os.Stdout, nil, nil
- case 2:
- return os.Stderr, nil, nil
- }
- case Pipe:
- r, w, err := os.Pipe()
- if err != nil {
- return nil, nil, err
- }
- if fd == 0 {
- return r, w, nil
+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 os.Error) {
+ if c.Stdin == nil {
+ f, err = os.Open(os.DevNull)
+ 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() os.Error {
+ _, err := io.Copy(pw, c.Stdin)
+ if err1 := pw.Close(); err == nil {
+ err = err1
}
- return w, r, nil
+ return err
+ })
+ return pr, nil
+}
+
+func (c *Cmd) stdout() (f *os.File, err os.Error) {
+ return c.writerDescriptor(c.Stdout)
+}
+
+func (c *Cmd) stderr() (f *os.File, err os.Error) {
+ if c.Stderr != nil && interfaceEqual(c.Stderr, c.Stdout) {
+ return c.childFiles[1], nil
}
- return nil, nil, os.EINVAL
+ return c.writerDescriptor(c.Stderr)
}
-// Run starts the named binary running with
-// arguments argv and environment envv.
-// If the dir argument is not empty, the child changes
-// into the directory before executing the binary.
-// It returns a pointer to a new Cmd representing
-// the command or an error.
+func (c *Cmd) writerDescriptor(w io.Writer) (f *os.File, err os.Error) {
+ if w == nil {
+ f, err = os.OpenFile(os.DevNull, os.O_WRONLY, 0)
+ 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() os.Error {
+ _, err := io.Copy(w, pr)
+ return err
+ })
+ return pw, nil
+}
+
+// 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.
//
-// The arguments stdin, stdout, and stderr
-// specify how to handle standard input, output, and error.
-// The choices are DevNull (connect to /dev/null),
-// PassThrough (connect to the current process's standard stream),
-// Pipe (connect to an operating system pipe), and
-// MergeWithStdout (only for standard error; use the same
-// file descriptor as was used for standard output).
-// If an argument is Pipe, then the corresponding field (Stdin, Stdout, Stderr)
-// of the returned Cmd is the other end of the pipe.
-// Otherwise the field in Cmd is nil.
-func Run(name string, argv, envv []string, dir string, stdin, stdout, stderr int) (c *Cmd, err os.Error) {
- c = new(Cmd)
- var fd [3]*os.File
-
- if fd[0], c.Stdin, err = modeToFiles(stdin, 0); err != nil {
- goto Error
- }
- if fd[1], c.Stdout, err = modeToFiles(stdout, 1); err != nil {
- goto Error
- }
- if stderr == MergeWithStdout {
- fd[2] = fd[1]
- } else if fd[2], c.Stderr, err = modeToFiles(stderr, 2); err != nil {
- goto Error
- }
-
- // Run command.
- c.Process, err = os.StartProcess(name, argv, &os.ProcAttr{Dir: dir, Files: fd[:], Env: envv})
+// If the command fails to run or doesn't complete successfully, the
+// error is of type *os.Waitmsg. Other error types may be
+// returned for I/O problems.
+func (c *Cmd) Run() os.Error {
+ if err := c.Start(); err != nil {
+ return err
+ }
+ return c.Wait()
+}
+
+// Start starts the specified command but does not wait for it to complete.
+func (c *Cmd) Start() os.Error {
+ if c.err != nil {
+ return c.err
+ }
+ if c.Process != nil {
+ return os.NewError("exec: already started")
+ }
+
+ type F func(*Cmd) (*os.File, os.Error)
+ for _, setupFd := range []F{(*Cmd).stdin, (*Cmd).stdout, (*Cmd).stderr} {
+ fd, err := setupFd(c)
+ if err != nil {
+ return err
+ }
+ c.childFiles = append(c.childFiles, fd)
+ }
+
+ var err os.Error
+ c.Process, err = os.StartProcess(c.Path, c.argv(), &os.ProcAttr{
+ Dir: c.Dir,
+ Files: c.childFiles,
+ Env: c.envv(),
+ })
if err != nil {
- goto Error
+ return err
+ }
+
+ for _, fd := range c.closeAfterStart {
+ fd.Close()
}
- if fd[0] != os.Stdin {
- fd[0].Close()
+
+ c.errch = make(chan os.Error, len(c.goroutine))
+ for _, fn := range c.goroutine {
+ go func(fn func() os.Error) {
+ c.errch <- fn()
+ }(fn)
}
- if fd[1] != os.Stdout {
- fd[1].Close()
+
+ return nil
+}
+
+// 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 *os.Waitmsg. Other error types may be
+// returned for I/O problems.
+func (c *Cmd) Wait() os.Error {
+ if c.Process == nil {
+ return os.NewError("exec: not started")
}
- if fd[2] != os.Stderr && fd[2] != fd[1] {
- fd[2].Close()
+ if c.finished {
+ return os.NewError("exec: Wait was already called")
}
- return c, nil
+ c.finished = true
+ msg, err := c.Process.Wait(0)
-Error:
- if fd[0] != os.Stdin && fd[0] != nil {
- fd[0].Close()
+ var copyError os.Error
+ for _ = range c.goroutine {
+ if err := <-c.errch; err != nil && copyError == nil {
+ copyError = err
+ }
}
- if fd[1] != os.Stdout && fd[1] != nil {
- fd[1].Close()
+
+ for _, fd := range c.closeAfterWait {
+ fd.Close()
}
- if fd[2] != os.Stderr && fd[2] != nil && fd[2] != fd[1] {
- fd[2].Close()
+
+ if err != nil {
+ return err
+ } else if !msg.Exited() || msg.ExitStatus() != 0 {
+ return msg
}
- if c.Stdin != nil {
- c.Stdin.Close()
+
+ return copyError
+}
+
+// Output runs the command and returns its standard output.
+func (c *Cmd) Output() ([]byte, os.Error) {
+ if c.Stdout != nil {
+ return nil, os.NewError("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, os.Error) {
if c.Stdout != nil {
- c.Stdout.Close()
+ return nil, os.NewError("exec: Stdout already set")
}
if c.Stderr != nil {
- c.Stderr.Close()
+ return nil, os.NewError("exec: Stderr already set")
}
- if c.Process != nil {
- c.Process.Release()
- }
- return nil, err
+ var b bytes.Buffer
+ c.Stdout = &b
+ c.Stderr = &b
+ err := c.Run()
+ return b.Bytes(), err
}
-// Wait waits for the running command c,
-// returning the Waitmsg returned when the process exits.
-// The options are passed to the process's Wait method.
-// Setting options to 0 waits for c to exit;
-// other options cause Wait to return for other
-// process events; see package os for details.
-func (c *Cmd) Wait(options int) (*os.Waitmsg, os.Error) {
- if c.Process == nil {
- return nil, os.ErrorString("exec: invalid use of Cmd.Wait")
+// StdinPipe returns a pipe that will be connected to the command's
+// standard input when the command starts.
+func (c *Cmd) StdinPipe() (io.WriteCloser, os.Error) {
+ if c.Stdin != nil {
+ return nil, os.NewError("exec: Stdin already set")
}
- w, err := c.Process.Wait(options)
- if w != nil && (w.Exited() || w.Signaled()) {
- c.Process.Release()
- c.Process = nil
+ if c.Process != nil {
+ return nil, os.NewError("exec: StdinPipe after process started")
+ }
+ pr, pw, err := os.Pipe()
+ if err != nil {
+ return nil, err
}
- return w, err
+ c.Stdin = pr
+ c.closeAfterStart = append(c.closeAfterStart, pr)
+ c.closeAfterWait = append(c.closeAfterStart, pw)
+ return pw, nil
}
-// Close waits for the running command c to exit,
-// if it hasn't already, and then closes the non-nil file descriptors
-// c.Stdin, c.Stdout, and c.Stderr.
-func (c *Cmd) Close() os.Error {
+// StdoutPipe returns a pipe that will be connected to the command's
+// standard output when the command starts.
+func (c *Cmd) StdoutPipe() (io.Reader, os.Error) {
+ if c.Stdout != nil {
+ return nil, os.NewError("exec: Stdout already set")
+ }
if c.Process != nil {
- // Loop on interrupt, but
- // ignore other errors -- maybe
- // caller has already waited for pid.
- _, err := c.Wait(0)
- for err == os.EINTR {
- _, err = c.Wait(0)
- }
+ return nil, os.NewError("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.closeAfterStart, pr)
+ return pr, nil
+}
- // Close the FDs that are still open.
- var err os.Error
- if c.Stdin != nil && c.Stdin.Fd() >= 0 {
- if err1 := c.Stdin.Close(); err1 != nil {
- err = err1
- }
+// StderrPipe returns a pipe that will be connected to the command's
+// standard error when the command starts.
+func (c *Cmd) StderrPipe() (io.Reader, os.Error) {
+ if c.Stderr != nil {
+ return nil, os.NewError("exec: Stderr already set")
}
- if c.Stdout != nil && c.Stdout.Fd() >= 0 {
- if err1 := c.Stdout.Close(); err1 != nil && err != nil {
- err = err1
- }
+ if c.Process != nil {
+ return nil, os.NewError("exec: StderrPipe after process started")
}
- if c.Stderr != nil && c.Stderr != c.Stdout && c.Stderr.Fd() >= 0 {
- if err1 := c.Stderr.Close(); err1 != nil && err != nil {
- err = err1
- }
+ pr, pw, err := os.Pipe()
+ if err != nil {
+ return nil, err
}
- return err
+ c.Stderr = pw
+ c.closeAfterStart = append(c.closeAfterStart, pw)
+ c.closeAfterWait = append(c.closeAfterStart, pr)
+ return pr, nil
}
diff --git a/src/pkg/exec/exec_test.go b/src/pkg/exec/exec_test.go
index eb8cd5fec..c45a7d70a 100644
--- a/src/pkg/exec/exec_test.go
+++ b/src/pkg/exec/exec_test.go
@@ -5,163 +5,211 @@
package exec
import (
+ "bufio"
+ "bytes"
+ "fmt"
"io"
- "io/ioutil"
"testing"
"os"
+ "strconv"
+ "strings"
)
-func run(argv []string, stdin, stdout, stderr int) (p *Cmd, err os.Error) {
- exe, err := LookPath(argv[0])
- if err != nil {
- return nil, err
- }
- return Run(exe, argv, nil, "", stdin, stdout, stderr)
+func helperCommand(s ...string) *Cmd {
+ cs := []string{"-test.run=exec.TestHelperProcess", "--"}
+ cs = append(cs, s...)
+ cmd := Command(os.Args[0], cs...)
+ cmd.Env = append([]string{"GO_WANT_HELPER_PROCESS=1"}, os.Environ()...)
+ return cmd
}
-func TestRunCat(t *testing.T) {
- cmd, err := run([]string{"cat"}, Pipe, Pipe, DevNull)
+func TestEcho(t *testing.T) {
+ bs, err := helperCommand("echo", "foo bar", "baz").Output()
if err != nil {
- t.Fatal("run:", err)
- }
- io.WriteString(cmd.Stdin, "hello, world\n")
- cmd.Stdin.Close()
- buf, err := ioutil.ReadAll(cmd.Stdout)
- if err != nil {
- t.Fatal("read:", err)
- }
- if string(buf) != "hello, world\n" {
- t.Fatalf("read: got %q", buf)
+ t.Errorf("echo: %v", err)
}
- if err = cmd.Close(); err != nil {
- t.Fatal("close:", err)
+ if g, e := string(bs), "foo bar baz\n"; g != e {
+ t.Errorf("echo: want %q, got %q", e, g)
}
}
-func TestRunEcho(t *testing.T) {
- cmd, err := run([]string{"sh", "-c", "echo hello world"},
- DevNull, Pipe, DevNull)
+func TestCatStdin(t *testing.T) {
+ // Cat, testing stdin and stdout.
+ input := "Input string\nLine 2"
+ p := helperCommand("cat")
+ p.Stdin = strings.NewReader(input)
+ bs, err := p.Output()
if err != nil {
- t.Fatal("run:", err)
+ t.Errorf("cat: %v", err)
}
- buf, err := ioutil.ReadAll(cmd.Stdout)
- if err != nil {
- t.Fatal("read:", err)
- }
- if string(buf) != "hello world\n" {
- t.Fatalf("read: got %q", buf)
- }
- if err = cmd.Close(); err != nil {
- t.Fatal("close:", err)
+ s := string(bs)
+ if s != input {
+ t.Errorf("cat: want %q, got %q", input, s)
}
}
-func TestStderr(t *testing.T) {
- cmd, err := run([]string{"sh", "-c", "echo hello world 1>&2"},
- DevNull, DevNull, Pipe)
- if err != nil {
- t.Fatal("run:", err)
+func TestCatGoodAndBadFile(t *testing.T) {
+ // Testing combined output and error values.
+ bs, err := helperCommand("cat", "/bogus/file.foo", "exec_test.go").CombinedOutput()
+ if _, ok := err.(*os.Waitmsg); !ok {
+ t.Errorf("expected Waitmsg from cat combined; got %T: %v", err, err)
}
- buf, err := ioutil.ReadAll(cmd.Stderr)
- if err != nil {
- t.Fatal("read:", err)
+ s := string(bs)
+ sp := strings.Split(s, "\n", 2)
+ if len(sp) != 2 {
+ t.Fatalf("expected two lines from cat; got %q", s)
}
- if string(buf) != "hello world\n" {
- t.Fatalf("read: got %q", buf)
+ 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 err = cmd.Close(); err != nil {
- t.Fatal("close:", err)
+ if !strings.Contains(body, "func TestHelperProcess(t *testing.T)") {
+ t.Errorf("expected test code; got %q (len %d)", body, len(body))
}
}
-func TestMergeWithStdout(t *testing.T) {
- cmd, err := run([]string{"sh", "-c", "echo hello world 1>&2"},
- DevNull, Pipe, MergeWithStdout)
- if err != nil {
- t.Fatal("run:", err)
- }
- buf, err := ioutil.ReadAll(cmd.Stdout)
- if err != nil {
- t.Fatal("read:", err)
- }
- if string(buf) != "hello world\n" {
- t.Fatalf("read: got %q", buf)
+
+func TestNoExistBinary(t *testing.T) {
+ // Can't run a non-existent binary
+ err := Command("/no-exist-binary").Run()
+ if err == nil {
+ t.Error("expected error from /no-exist-binary")
}
- if err = cmd.Close(); err != nil {
- t.Fatal("close:", err)
+}
+
+func TestExitStatus(t *testing.T) {
+ // Test that exit values are returned correctly
+ err := helperCommand("exit", "42").Run()
+ if werr, ok := err.(*os.Waitmsg); ok {
+ if s, e := werr.String(), "exit status 42"; s != e {
+ t.Errorf("from exit 42 got exit %q, want %q", s, e)
+ }
+ } else {
+ t.Fatalf("expected Waitmsg from exit 42; got %T: %v", err, err)
}
}
-func TestAddEnvVar(t *testing.T) {
- err := os.Setenv("NEWVAR", "hello world")
- if err != nil {
- t.Fatal("setenv:", err)
+func TestPipes(t *testing.T) {
+ check := func(what string, err os.Error) {
+ if err != nil {
+ t.Fatalf("%s: %v", what, err)
+ }
}
- cmd, err := run([]string{"sh", "-c", "echo $NEWVAR"},
- DevNull, Pipe, DevNull)
- if err != nil {
- t.Fatal("run:", err)
+ // Cat, testing stdin and stdout.
+ c := helperCommand("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)
}
- buf, err := ioutil.ReadAll(cmd.Stdout)
- if err != nil {
- t.Fatal("read:", err)
+
+ 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)
}
- if string(buf) != "hello world\n" {
- t.Fatalf("read: got %q", buf)
+
+ _, 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)
}
- if err = cmd.Close(); err != nil {
- t.Fatal("close:", err)
+
+ _, 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)
}
-}
-var tryargs = []string{
- `2`,
- `2 `,
- "2 \t",
- `2" "`,
- `2 ab `,
- `2 "ab" `,
- `2 \ `,
- `2 \\ `,
- `2 \" `,
- `2 \`,
- `2\`,
- `2"`,
- `2\"`,
- `2 "`,
- `2 \"`,
- ``,
- `2 ^ `,
- `2 \^`,
+ stdin.Close()
+ err = c.Wait()
+ check("Wait", err)
}
-func TestArgs(t *testing.T) {
- for _, a := range tryargs {
- argv := []string{
- "awk",
- `BEGIN{printf("%s|%s|%s",ARGV[1],ARGV[2],ARGV[3])}`,
- "/dev/null",
- a,
- "EOF",
- }
- exe, err := LookPath(argv[0])
- if err != nil {
- t.Fatal("run:", err)
+// 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
}
- cmd, err := Run(exe, argv, nil, "", DevNull, Pipe, DevNull)
- if err != nil {
- t.Fatal("run:", err)
+ 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)
}
- buf, err := ioutil.ReadAll(cmd.Stdout)
- if err != nil {
- t.Fatal("read:", err)
+ fmt.Println(iargs...)
+ case "cat":
+ if len(args) == 0 {
+ io.Copy(os.Stdout, os.Stdin)
+ return
}
- expect := "/dev/null|" + a + "|EOF"
- if string(buf) != expect {
- t.Errorf("read: got %q expect %q", buf, expect)
+ 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)
+ }
}
- if err = cmd.Close(); err != nil {
- t.Fatal("close:", err)
+ os.Exit(exit)
+ case "pipetest":
+ bufr := bufio.NewReader(os.Stdin)
+ for {
+ line, _, err := bufr.ReadLine()
+ if err == os.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 "exit":
+ n, _ := strconv.Atoi(args[0])
+ os.Exit(n)
+ default:
+ fmt.Fprintf(os.Stderr, "Unknown command %q\n", cmd)
+ os.Exit(2)
}
}
diff --git a/src/pkg/exec/lp_test.go b/src/pkg/exec/lp_test.go
index 54081771e..77d8e848c 100644
--- a/src/pkg/exec/lp_test.go
+++ b/src/pkg/exec/lp_test.go
@@ -22,12 +22,12 @@ func TestLookPathNotFound(t *testing.T) {
if path != "" {
t.Fatalf("LookPath path == %q when err != nil", path)
}
- perr, ok := err.(*PathError)
+ perr, ok := err.(*Error)
if !ok {
- t.Fatal("LookPath error is not a PathError")
+ t.Fatal("LookPath error is not an exec.Error")
}
if perr.Name != name {
- t.Fatalf("want PathError name %q, got %q", name, perr.Name)
+ t.Fatalf("want Error name %q, got %q", name, perr.Name)
}
}
}
diff --git a/src/pkg/exec/lp_unix.go b/src/pkg/exec/lp_unix.go
index 44f84347b..3fc3be832 100644
--- a/src/pkg/exec/lp_unix.go
+++ b/src/pkg/exec/lp_unix.go
@@ -9,12 +9,18 @@ import (
"strings"
)
-func canExec(file string) bool {
+// ErrNotFound is the error resulting if a path search failed to find an executable file.
+var ErrNotFound = os.ErrorString("executable file not found in $PATH")
+
+func findExecutable(file string) os.Error {
d, err := os.Stat(file)
if err != nil {
- return false
+ return err
+ }
+ if d.IsRegular() && d.Permission()&0111 != 0 {
+ return nil
}
- return d.IsRegular() && d.Permission()&0111 != 0
+ return os.EPERM
}
// LookPath searches for an executable binary named file
@@ -26,10 +32,11 @@ func LookPath(file string) (string, os.Error) {
// but that would not match all the Unix shells.
if strings.Contains(file, "/") {
- if canExec(file) {
+ err := findExecutable(file)
+ if err == nil {
return file, nil
}
- return "", &PathError{file}
+ return "", &Error{file, err}
}
pathenv := os.Getenv("PATH")
for _, dir := range strings.Split(pathenv, ":", -1) {
@@ -37,9 +44,9 @@ func LookPath(file string) (string, os.Error) {
// Unix shell semantics: path element "" means "."
dir = "."
}
- if canExec(dir + "/" + file) {
+ if err := findExecutable(dir + "/" + file); err == nil {
return dir + "/" + file, nil
}
}
- return "", &PathError{file}
+ return "", &Error{file, ErrNotFound}
}
diff --git a/src/pkg/exec/lp_windows.go b/src/pkg/exec/lp_windows.go
index d357575fd..758861021 100644
--- a/src/pkg/exec/lp_windows.go
+++ b/src/pkg/exec/lp_windows.go
@@ -9,15 +9,21 @@ import (
"strings"
)
-func chkStat(file string) bool {
+// ErrNotFound is the error resulting if a path search failed to find an executable file.
+var ErrNotFound = os.ErrorString("executable file not found in %PATH%")
+
+func chkStat(file string) os.Error {
d, err := os.Stat(file)
if err != nil {
- return false
+ return err
+ }
+ if d.IsRegular() {
+ return nil
}
- return d.IsRegular()
+ return os.EPERM
}
-func canExec(file string, exts []string) (string, bool) {
+func findExecutable(file string, exts []string) (string, os.Error) {
if len(exts) == 0 {
return file, chkStat(file)
}
@@ -28,14 +34,14 @@ func canExec(file string, exts []string) (string, bool) {
}
}
for _, e := range exts {
- if f := file + e; chkStat(f) {
- return f, true
+ if f := file + e; chkStat(f) == nil {
+ return f, nil
}
}
- return ``, false
+ return ``, ErrNotFound
}
-func LookPath(file string) (string, os.Error) {
+func LookPath(file string) (f string, err os.Error) {
exts := []string{}
if x := os.Getenv(`PATHEXT`); x != `` {
exts = strings.Split(strings.ToLower(x), `;`, -1)
@@ -46,21 +52,21 @@ func LookPath(file string) (string, os.Error) {
}
}
if strings.Contains(file, `\`) || strings.Contains(file, `/`) {
- if f, ok := canExec(file, exts); ok {
- return f, nil
+ if f, err = findExecutable(file, exts); err == nil {
+ return
}
- return ``, &PathError{file}
+ return ``, &Error{file, err}
}
if pathenv := os.Getenv(`PATH`); pathenv == `` {
- if f, ok := canExec(`.\`+file, exts); ok {
- return f, nil
+ if f, err = findExecutable(`.\`+file, exts); err == nil {
+ return
}
} else {
for _, dir := range strings.Split(pathenv, `;`, -1) {
- if f, ok := canExec(dir+`\`+file, exts); ok {
- return f, nil
+ if f, err = findExecutable(dir+`\`+file, exts); err == nil {
+ return
}
}
}
- return ``, &PathError{file}
+ return ``, &Error{file, ErrNotFound}
}