lib: v23test: more migrations
Also adds "Cmd.ExitErrorIsOk" option, handy for tests where
commands are expected to fail.
MultiPart: 1/2
Change-Id: If7a7558c574bdc0f4561f9e425d0fd9424e32abb
diff --git a/gosh/.api b/gosh/.api
index 61d44e5..3206ab5 100644
--- a/gosh/.api
+++ b/gosh/.api
@@ -36,6 +36,8 @@
pkg gosh, method (*Shell) Wait()
pkg gosh, type Cmd struct
pkg gosh, type Cmd struct, Args []string
+pkg gosh, type Cmd struct, Err error
+pkg gosh, type Cmd struct, ExitErrorIsOk bool
pkg gosh, type Cmd struct, OutputDir string
pkg gosh, type Cmd struct, Stdin io.Reader
pkg gosh, type Cmd struct, SuppressOutput bool
diff --git a/gosh/cmd.go b/gosh/cmd.go
index a3d2a76..2337696 100644
--- a/gosh/cmd.go
+++ b/gosh/cmd.go
@@ -26,17 +26,22 @@
// Cmd represents a command. Not thread-safe.
// Public fields should not be modified after calling Start.
type Cmd struct {
+ // Err is the most recent error from this Cmd (may be nil).
+ Err error
// Vars is the map of env vars for this Cmd.
Vars map[string]string
// Args is the list of args for this Cmd.
Args []string
+ // Stdin specifies this Cmd's stdin. See comments in exec.Cmd for detailed
+ // semantics.
+ Stdin io.Reader
// SuppressOutput is inherited from Shell.Opts.SuppressChildOutput.
SuppressOutput bool
// OutputDir is inherited from Shell.Opts.ChildOutputDir.
OutputDir string
- // Stdin specifies this Cmd's stdin. See comments in exec.Cmd for detailed
- // semantics.
- Stdin io.Reader
+ // ExitErrorIsOk specifies whether an *exec.ExitError should be reported via
+ // Shell.HandleError.
+ ExitErrorIsOk bool
// Internal state.
sh *Shell
c *exec.Cmd
@@ -51,37 +56,37 @@
recvVars map[string]string // protected by condVars.L
}
-// StdoutPipe returns a Reader backed by a buffered pipe for this command's
+// StdoutPipe returns a Reader backed by a buffered pipe for the command's
// stdout. Must be called before Start. May be called more than once; each
// invocation creates a new pipe.
func (c *Cmd) StdoutPipe() io.Reader {
c.sh.Ok()
res, err := c.stdoutPipe()
- c.sh.HandleError(err)
+ c.handleError(err)
return res
}
-// StderrPipe returns a Reader backed by a buffered pipe for this command's
+// StderrPipe returns a Reader backed by a buffered pipe for the command's
// stderr. Must be called before Start. May be called more than once; each
// invocation creates a new pipe.
func (c *Cmd) StderrPipe() io.Reader {
c.sh.Ok()
res, err := c.stderrPipe()
- c.sh.HandleError(err)
+ c.handleError(err)
return res
}
-// Start starts this command.
+// Start starts the command.
func (c *Cmd) Start() {
c.sh.Ok()
- c.sh.HandleError(c.start())
+ c.handleError(c.start())
}
// AwaitReady waits for the child process to call SendReady. Must not be called
// before Start or after Wait.
func (c *Cmd) AwaitReady() {
c.sh.Ok()
- c.sh.HandleError(c.awaitReady())
+ c.handleError(c.awaitReady())
}
// AwaitVars waits for the child process to send values for the given vars
@@ -89,54 +94,54 @@
func (c *Cmd) AwaitVars(keys ...string) map[string]string {
c.sh.Ok()
res, err := c.awaitVars(keys...)
- c.sh.HandleError(err)
+ c.handleError(err)
return res
}
-// Wait waits for this command to exit.
+// Wait waits for the command to exit.
func (c *Cmd) Wait() {
c.sh.Ok()
- c.sh.HandleError(c.wait())
+ c.handleError(c.wait())
}
// TODO(sadovsky): Maybe add a method to send SIGINT, wait for a bit, then send
// SIGKILL if the process hasn't exited.
-// Shutdown sends the given signal to this command, then waits for it to exit.
+// Shutdown sends the given signal to the command, then waits for it to exit.
func (c *Cmd) Shutdown(sig os.Signal) {
c.sh.Ok()
- c.sh.HandleError(c.shutdown(sig))
+ c.handleError(c.shutdown(sig))
}
// Run calls Start followed by Wait.
func (c *Cmd) Run() {
c.sh.Ok()
- c.sh.HandleError(c.run())
+ c.handleError(c.run())
}
-// Output calls Start followed by Wait, then returns this command's stdout and
+// Output calls Start followed by Wait, then returns the command's stdout and
// stderr.
func (c *Cmd) Output() (string, string) {
c.sh.Ok()
stdout, stderr, err := c.output()
- c.sh.HandleError(err)
+ c.handleError(err)
return stdout, stderr
}
-// CombinedOutput calls Start followed by Wait, then returns this command's
+// CombinedOutput calls Start followed by Wait, then returns the command's
// combined stdout and stderr.
func (c *Cmd) CombinedOutput() string {
c.sh.Ok()
res, err := c.combinedOutput()
- c.sh.HandleError(err)
+ c.handleError(err)
return res
}
-// Process returns the underlying process handle for this command.
+// Process returns the underlying process handle for the command.
func (c *Cmd) Process() *os.Process {
c.sh.Ok()
res, err := c.process()
- c.sh.HandleError(err)
+ c.handleError(err)
return res
}
@@ -171,6 +176,16 @@
return c, nil
}
+func (c *Cmd) handleError(err error) {
+ c.Err = err
+ if c.ExitErrorIsOk {
+ if _, ok := err.(*exec.ExitError); ok {
+ return
+ }
+ }
+ c.sh.HandleError(err)
+}
+
func (c *Cmd) calledStart() bool {
return c.c != nil
}
@@ -296,7 +311,7 @@
c.c = exec.Command(c.name, c.Args...)
c.c.Env = mapToSlice(c.Vars)
c.c.Stdin = c.Stdin
- t := time.Now().UTC().Format("20060102.150405.000000")
+ t := time.Now().Format("20060102.150405.000000")
var err error
if c.c.Stdout, err = c.initMultiWriter(os.Stdout, t); err != nil {
return err
diff --git a/gosh/shell.go b/gosh/shell.go
index 1c0c3ce..370f4e3 100644
--- a/gosh/shell.go
+++ b/gosh/shell.go
@@ -43,7 +43,8 @@
// Shell represents a shell. Not thread-safe.
type Shell struct {
- // Err is the most recent error (may be nil).
+ // Err is the most recent error from this Shell or any of its Cmds (may be
+ // nil).
Err error
// Opts is the Opts struct for this Shell, with default values filled in.
Opts Opts
diff --git a/gosh/shell_test.go b/gosh/shell_test.go
index c583987..80fcfc2 100644
--- a/gosh/shell_test.go
+++ b/gosh/shell_test.go
@@ -242,6 +242,35 @@
}
}
+var exit = gosh.Register("exit", func(code int) {
+ os.Exit(code)
+})
+
+func TestExitErrorIsOk(t *testing.T) {
+ sh := gosh.NewShell(gosh.Opts{Errorf: makeErrorf(t), Logf: t.Logf})
+ defer sh.Cleanup()
+
+ // Exit code 0 is not an error.
+ c := sh.Fn(exit, 0)
+ c.Run()
+ ok(t, c.Err)
+ ok(t, sh.Err)
+
+ // Exit code 1 is an error.
+ c = sh.Fn(exit, 1)
+ c.ExitErrorIsOk = true
+ c.Run()
+ nok(t, c.Err)
+ ok(t, sh.Err)
+
+ // If ExitErrorIsOk is false, exit code 1 triggers sh.HandleError.
+ sh.Opts.Errorf = func(string, ...interface{}) {}
+ c = sh.Fn(exit, 1)
+ c.Run()
+ nok(t, c.Err)
+ nok(t, sh.Err)
+}
+
// Tests that sh.Ok panics under various conditions.
func TestOkPanics(t *testing.T) {
func() { // errDidNotCallNewShell