lib: gosh: switch from Logf/Fatalf to TB, and related
This change makes most of the changes described in the final
proposal in https://v.io/i/1128 - namely, it makes all the
requisite API changes.
Things not done in this change:
- "Good PC" logging
- Printing Cmd stdout/stderr on failure
Those will be done in subsequent changes.
MultiPart: 4/9
Change-Id: I04b0fa0af8aa358a42db055a9c8503ffa4e88904
diff --git a/gosh/.api b/gosh/.api
index 10ee7c1..0dd3f73 100644
--- a/gosh/.api
+++ b/gosh/.api
@@ -2,7 +2,7 @@
pkg gosh, func InitChildMain()
pkg gosh, func InitMain()
pkg gosh, func NewPipeline(*Cmd, ...*Cmd) *Pipeline
-pkg gosh, func NewShell(Opts) *Shell
+pkg gosh, func NewShell(TB) *Shell
pkg gosh, func RegisterFunc(string, interface{}) *Func
pkg gosh, func SendVars(map[string]string)
pkg gosh, method (*Cmd) AddStderrWriter(io.Writer)
@@ -61,14 +61,14 @@
pkg gosh, type Cmd struct, PropagateOutput bool
pkg gosh, type Cmd struct, Vars map[string]string
pkg gosh, type Func struct
-pkg gosh, type Opts struct
-pkg gosh, type Opts struct, ChildOutputDir string
-pkg gosh, type Opts struct, Fatalf func(string, ...interface{})
-pkg gosh, type Opts struct, Logf func(string, ...interface{})
-pkg gosh, type Opts struct, PropagateChildOutput bool
pkg gosh, type Pipeline struct
pkg gosh, type Shell struct
pkg gosh, type Shell struct, Args []string
+pkg gosh, type Shell struct, ChildOutputDir string
+pkg gosh, type Shell struct, ContinueOnError bool
pkg gosh, type Shell struct, Err error
-pkg gosh, type Shell struct, Opts Opts
+pkg gosh, type Shell struct, PropagateChildOutput bool
pkg gosh, type Shell struct, Vars map[string]string
+pkg gosh, type TB interface { FailNow, Logf }
+pkg gosh, type TB interface, FailNow()
+pkg gosh, type TB interface, Logf(string, ...interface{})
diff --git a/gosh/cmd.go b/gosh/cmd.go
index 9357094..e72b4d6 100644
--- a/gosh/cmd.go
+++ b/gosh/cmd.go
@@ -50,9 +50,9 @@
// the given duration has elapsed. Only takes effect if the child process was
// spawned via Shell.FuncCmd or explicitly calls InitChildMain.
ExitAfter time.Duration
- // PropagateOutput is inherited from Shell.Opts.PropagateChildOutput.
+ // PropagateOutput is inherited from Shell.PropagateChildOutput.
PropagateOutput bool
- // OutputDir is inherited from Shell.Opts.ChildOutputDir.
+ // OutputDir is inherited from Shell.ChildOutputDir.
OutputDir string
// ExitErrorIsOk specifies whether an *exec.ExitError should be reported via
// Shell.HandleError.
@@ -446,12 +446,12 @@
case c.c.Stdin != nil:
return nil, errAlreadySetStdin
}
- // We want to provide an unlimited-size pipe to the user. If we set
- // c.c.Stdin directly to the newBufferedPipe, the os/exec package will
- // create an os.Pipe for us, along with a goroutine to copy data over. And
- // exec.Cmd.Wait will wait for this goroutine to exit before returning, even
- // if the process has already exited. That means the user will be forced to
- // call Close on the returned WriteCloser, which is annoying.
+ // We want to provide an unlimited-size pipe to the user. If we set c.c.Stdin
+ // directly to the newBufferedPipe, the os/exec package will create an os.Pipe
+ // for us, along with a goroutine to copy data over. And exec.Cmd.Wait will
+ // wait for this goroutine to exit before returning, even if the process has
+ // already exited. That means the user will be forced to call Close on the
+ // returned WriteCloser, which is annoying.
//
// Instead, we set c.c.Stdin to our own os.Pipe, so that os/exec won't create
// the pipe nor the goroutine. We chain our newBufferedPipe in front of this,
diff --git a/gosh/internal/gosh_example/main.go b/gosh/internal/gosh_example/main.go
index a3072ad..18d00f4 100644
--- a/gosh/internal/gosh_example/main.go
+++ b/gosh/internal/gosh_example/main.go
@@ -13,7 +13,7 @@
// Mirrors TestCmd in shell_test.go.
func ExampleCmd() {
- sh := gosh.NewShell(gosh.Opts{})
+ sh := gosh.NewShell(nil)
defer sh.Cleanup()
// Start server.
@@ -37,7 +37,7 @@
// Mirrors TestFuncCmd in shell_test.go.
func ExampleFuncCmd() {
- sh := gosh.NewShell(gosh.Opts{})
+ sh := gosh.NewShell(nil)
defer sh.Cleanup()
// Start server.
diff --git a/gosh/pipeline.go b/gosh/pipeline.go
index c9aec89..296f2eb 100644
--- a/gosh/pipeline.go
+++ b/gosh/pipeline.go
@@ -163,15 +163,15 @@
// handleError is used instead of direct calls to Shell.HandleError throughout
// the pipeline implementation. This is needed to handle the case where the user
-// has set Shell.Opts.Fatalf to a non-fatal function.
+// has set Shell.ContinueOnError to true.
//
// The general pattern is that after each Shell or Cmd method is called, we
-// check p.sh.Err. If there was an error it is wrapped with errAlreadyHandled,
-// indicating that Shell.HandleError has already been called with this error,
-// and should not be called again.
+// check p.sh.Err; if it's non-nil, we wrap it with errAlreadyHandled to
+// indicate that Shell.HandleError has already been called with this error and
+// should not be called again.
func handleError(sh *Shell, err error) {
if _, ok := err.(errAlreadyHandled); ok {
- return // the shell already handled this error
+ return // the shell has already handled this error
}
sh.HandleError(err)
}
@@ -253,12 +253,12 @@
}
// TODO(toddw): Clean up resources in Shell.Cleanup. E.g. we'll currently leak
-// the os.Pipe fds if the user sets up a pipeline, but never calls Start (or
+// the os.Pipe fds if the user sets up a pipeline but never calls Start (or
// Wait, Terminate).
func (p *Pipeline) start() error {
// Start all commands in the pipeline, capturing the first error.
- // Ensure all commands are processed, by avoiding early-exit.
+ // Ensure all commands are processed by avoiding early-exit.
var shErr, closeErr error
for i, c := range p.cmds {
p.sh.Err = nil
@@ -288,7 +288,7 @@
func (p *Pipeline) wait() error {
// Wait for all commands in the pipeline, capturing the first error.
- // Ensure all commands are processed, by avoiding early-exit.
+ // Ensure all commands are processed by avoiding early-exit.
var shErr, closeErr error
for i, c := range p.cmds {
p.sh.Err = nil
diff --git a/gosh/pipeline_test.go b/gosh/pipeline_test.go
index 25901e2..95dbf4a 100644
--- a/gosh/pipeline_test.go
+++ b/gosh/pipeline_test.go
@@ -14,7 +14,7 @@
)
func TestPipeline(t *testing.T) {
- sh := gosh.NewShell(gosh.Opts{Fatalf: makeFatalf(t), Logf: t.Logf})
+ sh := gosh.NewShell(t)
defer sh.Cleanup()
echo := sh.FuncCmd(echoFunc)
@@ -55,9 +55,9 @@
}
func TestPipelineDifferentShells(t *testing.T) {
- sh1 := gosh.NewShell(gosh.Opts{Fatalf: makeFatalf(t), Logf: t.Logf})
+ sh1 := gosh.NewShell(t)
defer sh1.Cleanup()
- sh2 := gosh.NewShell(gosh.Opts{Fatalf: makeFatalf(t), Logf: t.Logf})
+ sh2 := gosh.NewShell(t)
defer sh2.Cleanup()
setsErr(t, sh1, func() { gosh.NewPipeline(sh1.FuncCmd(echoFunc), sh2.FuncCmd(catFunc)) })
@@ -71,7 +71,7 @@
}
func TestPipelineClosedPipe(t *testing.T) {
- sh := gosh.NewShell(gosh.Opts{Fatalf: makeFatalf(t), Logf: t.Logf})
+ sh := gosh.NewShell(t)
defer sh.Cleanup()
writeLoop, readLine := sh.FuncCmd(writeLoopFunc), sh.FuncCmd(readFunc)
@@ -88,7 +88,7 @@
}
func TestPipelineCmdFailure(t *testing.T) {
- sh := gosh.NewShell(gosh.Opts{Fatalf: makeFatalf(t), Logf: t.Logf})
+ sh := gosh.NewShell(t)
defer sh.Cleanup()
cat := sh.FuncCmd(catFunc)
exit1 := sh.FuncCmd(exitFunc, 1)
@@ -170,7 +170,7 @@
}
func TestPipelineSignal(t *testing.T) {
- sh := gosh.NewShell(gosh.Opts{Fatalf: makeFatalf(t), Logf: t.Logf})
+ sh := gosh.NewShell(t)
defer sh.Cleanup()
for _, d := range []time.Duration{0, time.Hour} {
@@ -204,7 +204,7 @@
}
func TestPipelineTerminate(t *testing.T) {
- sh := gosh.NewShell(gosh.Opts{Fatalf: makeFatalf(t), Logf: t.Logf})
+ sh := gosh.NewShell(t)
defer sh.Cleanup()
for _, d := range []time.Duration{0, time.Hour} {
diff --git a/gosh/registry.go b/gosh/registry.go
index 64453a8..2202722 100644
--- a/gosh/registry.go
+++ b/gosh/registry.go
@@ -51,7 +51,7 @@
// Register the function's args with gob. Needed because Shell.Func takes
// interface{} arguments.
for i := 0; i < t.NumIn(); i++ {
- // Note: Clients are responsible for registering any concrete types stored
+ // Note: Users are responsible for registering any concrete types stored
// inside interface{} arguments.
if t.In(i).Kind() == reflect.Interface {
continue
@@ -92,7 +92,7 @@
if arg != nil {
av = reflect.ValueOf(arg)
} else {
- // Client passed nil; construct the zero value for this argument based on
+ // User passed nil; construct the zero value for this argument based on
// the function signature.
av = reflect.Zero(argType(t, i))
}
diff --git a/gosh/shell.go b/gosh/shell.go
index c318b57..a42d69c 100644
--- a/gosh/shell.go
+++ b/gosh/shell.go
@@ -24,16 +24,16 @@
"os/signal"
"path"
"path/filepath"
+ "runtime/debug"
"sync"
"syscall"
"time"
)
const (
- envChildOutputDir = "GOSH_CHILD_OUTPUT_DIR"
- envExitAfter = "GOSH_EXIT_AFTER"
- envInvocation = "GOSH_INVOCATION"
- envWatchParent = "GOSH_WATCH_PARENT"
+ envExitAfter = "GOSH_EXIT_AFTER"
+ envInvocation = "GOSH_INVOCATION"
+ envWatchParent = "GOSH_WATCH_PARENT"
)
var (
@@ -42,19 +42,35 @@
errDidNotCallNewShell = errors.New("gosh: did not call gosh.NewShell")
)
+// TB is a subset of the testing.TB interface, defined here to avoid depending
+// on the testing package.
+type TB interface {
+ FailNow()
+ Logf(format string, args ...interface{})
+}
+
// Shell represents a shell. Not thread-safe.
type Shell struct {
- // Err is the most recent error from this Shell or any of its Cmds (may be
- // nil).
+ // Err is the most recent error from this Shell or any of its child Cmds (may
+ // be nil).
Err error
- // Opts is the Opts struct for this Shell, with default values filled in.
- Opts Opts
+ // PropagateChildOutput specifies whether to propagate child stdout and stderr
+ // up to the parent's stdout and stderr.
+ PropagateChildOutput bool
+ // ChildOutputDir, if non-empty, makes it so child stdout and stderr are tee'd
+ // to files in the specified directory.
+ ChildOutputDir string
+ // ContinueOnError specifies whether to invoke TB.FailNow on error, i.e.
+ // whether to panic on error. Users that set ContinueOnError to true should
+ // inspect sh.Err after each Shell method invocation.
+ ContinueOnError bool
// Vars is the map of env vars for this Shell.
Vars map[string]string
// Args is the list of args to append to subsequent command invocations.
Args []string
// Internal state.
calledNewShell bool
+ tb TB
cleanupDone chan struct{}
cleanupMu sync.Mutex // protects the fields below; held during cleanup
calledCleanup bool
@@ -65,36 +81,24 @@
cleanupHandlers []func()
}
-// Opts configures Shell.
-type Opts struct {
- // Fatalf is called whenever an error is encountered.
- // If not specified, defaults to panic(fmt.Sprintf(format, v...)).
- Fatalf func(format string, v ...interface{})
- // Logf is called to log things.
- // If not specified, defaults to log.Printf(format, v...).
- Logf func(format string, v ...interface{})
- // Child stdout and stderr are propagated up to the parent's stdout and stderr
- // iff PropagateChildOutput is true.
- PropagateChildOutput bool
- // If specified, each child's stdout and stderr streams are also piped to
- // files in this directory.
- // If not specified, defaults to GOSH_CHILD_OUTPUT_DIR.
- ChildOutputDir string
-}
-
-// NewShell returns a new Shell.
-func NewShell(opts Opts) *Shell {
- sh, err := newShell(opts)
+// NewShell returns a new Shell. Tests and benchmarks should pass their
+// testing.TB instance; non-tests should pass nil.
+func NewShell(tb TB) *Shell {
+ sh, err := newShell(tb)
sh.HandleError(err)
return sh
}
-// HandleError sets sh.Err. If err is not nil, it also calls sh.Opts.Fatalf.
+// HandleError sets sh.Err. If err is not nil and sh.ContinueOnError is false,
+// it also calls TB.FailNow.
func (sh *Shell) HandleError(err error) {
sh.Ok()
sh.Err = err
- if err != nil && sh.Opts.Fatalf != nil {
- sh.Opts.Fatalf("%v", err)
+ if err != nil && !sh.ContinueOnError {
+ if sh.tb != pkgLevelDefaultTB {
+ sh.tb.Logf(string(debug.Stack()))
+ }
+ sh.tb.FailNow()
}
}
@@ -204,32 +208,31 @@
////////////////////////////////////////
// Internals
-// Note: On error, newShell returns a *Shell with Opts.Fatalf initialized to
-// simplify things for the caller.
-func newShell(opts Opts) (*Shell, error) {
- osVars := sliceToMap(os.Environ())
- if opts.Fatalf == nil {
- opts.Fatalf = func(format string, v ...interface{}) {
- panic(fmt.Sprintf(format, v...))
- }
- }
- if opts.Logf == nil {
- opts.Logf = func(format string, v ...interface{}) {
- log.Printf(format, v...)
- }
- }
- if opts.ChildOutputDir == "" {
- opts.ChildOutputDir = osVars[envChildOutputDir]
+type defaultTB struct{}
+
+func (*defaultTB) FailNow() {
+ panic(nil)
+}
+
+func (*defaultTB) Logf(format string, args ...interface{}) {
+ log.Printf(format, args...)
+}
+
+var pkgLevelDefaultTB *defaultTB = &defaultTB{}
+
+func newShell(tb TB) (*Shell, error) {
+ if tb == nil {
+ tb = pkgLevelDefaultTB
}
// Filter out any gosh env vars coming from outside.
- shVars := copyMap(osVars)
- for _, key := range []string{envChildOutputDir, envExitAfter, envInvocation, envWatchParent} {
+ shVars := sliceToMap(os.Environ())
+ for _, key := range []string{envExitAfter, envInvocation, envWatchParent} {
delete(shVars, key)
}
sh := &Shell{
- Opts: opts,
Vars: shVars,
calledNewShell: true,
+ tb: tb,
cleanupDone: make(chan struct{}),
}
sh.cleanupOnSignal()
@@ -245,7 +248,7 @@
select {
case sig := <-ch:
// A termination signal was received; the process will exit.
- sh.logf("Received signal: %v\n", sig)
+ sh.tb.Logf("Received signal: %v\n", sig)
sh.cleanupMu.Lock()
defer sh.cleanupMu.Unlock()
if !sh.calledCleanup {
@@ -262,12 +265,6 @@
}()
}
-func (sh *Shell) logf(format string, v ...interface{}) {
- if sh.Opts.Logf != nil {
- sh.Opts.Logf(format, v...)
- }
-}
-
func (sh *Shell) cmd(vars map[string]string, name string, args ...string) (*Cmd, error) {
if vars == nil {
vars = make(map[string]string)
@@ -276,8 +273,8 @@
if err != nil {
return nil, err
}
- c.PropagateOutput = sh.Opts.PropagateChildOutput
- c.OutputDir = sh.Opts.ChildOutputDir
+ c.PropagateOutput = sh.PropagateChildOutput
+ c.OutputDir = sh.ChildOutputDir
return c, nil
}
@@ -313,7 +310,7 @@
continue
}
if err := c.wait(); !c.errorIsOk(err) {
- sh.logf("%s (PID %d) failed: %v\n", c.Path, c.Pid(), err)
+ sh.tb.Logf("%s (PID %d) failed: %v\n", c.Path, c.Pid(), err)
res = err
}
}
@@ -454,14 +451,14 @@
// Send os.Interrupt first; if that doesn't work, send os.Kill.
anyRunning := sh.forEachRunningCmd(func(c *Cmd) {
if err := c.signal(os.Interrupt); err != nil {
- sh.logf("%d.Signal(os.Interrupt) failed: %v\n", c.Pid(), err)
+ sh.tb.Logf("%d.Signal(os.Interrupt) failed: %v\n", c.Pid(), err)
}
})
// If any child is still running, wait for 100ms.
if anyRunning {
time.Sleep(100 * time.Millisecond)
anyRunning = sh.forEachRunningCmd(func(c *Cmd) {
- sh.logf("%s (PID %d) did not die\n", c.Path, c.Pid())
+ sh.tb.Logf("%s (PID %d) did not die\n", c.Path, c.Pid())
})
}
// If any child is still running, wait for another second, then send os.Kill
@@ -470,10 +467,10 @@
time.Sleep(time.Second)
sh.forEachRunningCmd(func(c *Cmd) {
if err := c.signal(os.Kill); err != nil {
- sh.logf("%d.Signal(os.Kill) failed: %v\n", c.Pid(), err)
+ sh.tb.Logf("%d.Signal(os.Kill) failed: %v\n", c.Pid(), err)
}
})
- sh.logf("Killed all remaining child processes\n")
+ sh.tb.Logf("Killed all remaining child processes\n")
}
}
@@ -485,23 +482,23 @@
for _, tempFile := range sh.tempFiles {
name := tempFile.Name()
if err := tempFile.Close(); err != nil {
- sh.logf("%q.Close() failed: %v\n", name, err)
+ sh.tb.Logf("%q.Close() failed: %v\n", name, err)
}
if err := os.RemoveAll(name); err != nil {
- sh.logf("os.RemoveAll(%q) failed: %v\n", name, err)
+ sh.tb.Logf("os.RemoveAll(%q) failed: %v\n", name, err)
}
}
// Delete all temporary directories.
for _, tempDir := range sh.tempDirs {
if err := os.RemoveAll(tempDir); err != nil {
- sh.logf("os.RemoveAll(%q) failed: %v\n", tempDir, err)
+ sh.tb.Logf("os.RemoveAll(%q) failed: %v\n", tempDir, err)
}
}
// Change back to the top of the dir stack.
if len(sh.dirStack) > 0 {
dir := sh.dirStack[0]
if err := os.Chdir(dir); err != nil {
- sh.logf("os.Chdir(%q) failed: %v\n", dir, err)
+ sh.tb.Logf("os.Chdir(%q) failed: %v\n", dir, err)
}
}
// Call cleanup handlers in LIFO order.
diff --git a/gosh/shell_test.go b/gosh/shell_test.go
index 1cb124f..00147c4 100644
--- a/gosh/shell_test.go
+++ b/gosh/shell_test.go
@@ -6,10 +6,10 @@
// TODO(sadovsky): Add more tests:
// - effects of Shell.Cleanup
-// - Shell.{Vars,Args,Rename,MakeTempFile,MakeTempDir}
-// - Shell.Opts.{PropagateChildOutput,ChildOutputDir}
+// - Shell.{PropagateChildOutput,ChildOutputDir,Vars,Args}
+// - Shell.{Move,MakeTempFile}
+// - Cmd.{IgnoreParentExit,ExitAfter,PropagateOutput}
// - Cmd.Clone
-// - Cmd.Opts.{IgnoreParentExit,ExitAfter,PropagateOutput}
import (
"bufio"
@@ -73,21 +73,13 @@
return string(b)
}
-func makeFatalf(t *testing.T) func(string, ...interface{}) {
- return func(format string, v ...interface{}) {
- debug.PrintStack()
- t.Fatalf(format, v...)
- }
-}
-
func setsErr(t *testing.T, sh *gosh.Shell, f func()) {
- calledFatalf := false
- sh.Opts.Fatalf = func(string, ...interface{}) { calledFatalf = true }
+ continueOnError := sh.ContinueOnError
+ sh.ContinueOnError = true
f()
nok(t, sh.Err)
- eq(t, calledFatalf, true)
sh.Err = nil
- sh.Opts.Fatalf = makeFatalf(t)
+ sh.ContinueOnError = continueOnError
}
////////////////////////////////////////
@@ -138,20 +130,32 @@
////////////////////////////////////////
// Tests
-func TestCustomFatalf(t *testing.T) {
- sh := gosh.NewShell(gosh.Opts{Fatalf: makeFatalf(t), Logf: t.Logf})
+type customTB struct {
+ t *testing.T
+ calledFailNow bool
+}
+
+func (tb *customTB) FailNow() {
+ tb.calledFailNow = true
+}
+
+func (tb *customTB) Logf(format string, args ...interface{}) {
+ tb.t.Logf(format, args...)
+}
+
+func TestCustomTB(t *testing.T) {
+ tb := &customTB{t: t}
+ sh := gosh.NewShell(tb)
defer sh.Cleanup()
- var calledFatalf bool
- sh.Opts.Fatalf = func(string, ...interface{}) { calledFatalf = true }
sh.HandleError(fakeError)
// Note, our deferred sh.Cleanup() should succeed despite this error.
nok(t, sh.Err)
- eq(t, calledFatalf, true)
+ eq(t, tb.calledFailNow, true)
}
func TestPushdPopd(t *testing.T) {
- sh := gosh.NewShell(gosh.Opts{Fatalf: makeFatalf(t), Logf: t.Logf})
+ sh := gosh.NewShell(t)
defer sh.Cleanup()
startDir, err := os.Getwd()
@@ -193,7 +197,7 @@
func TestPushdNoPopdCleanup(t *testing.T) {
startDir := getwdEvalSymlinks(t)
- sh := gosh.NewShell(gosh.Opts{Fatalf: makeFatalf(t), Logf: t.Logf})
+ sh := gosh.NewShell(t)
tmpDir := sh.MakeTempDir()
sh.Pushd(tmpDir)
eq(t, getwdEvalSymlinks(t), evalSymlinks(t, tmpDir))
@@ -211,7 +215,7 @@
// Mirrors ExampleCmd in internal/gosh_example/main.go.
func TestCmd(t *testing.T) {
- sh := gosh.NewShell(gosh.Opts{Fatalf: makeFatalf(t), Logf: t.Logf})
+ sh := gosh.NewShell(t)
defer sh.Cleanup()
// Start server.
@@ -235,7 +239,7 @@
// Mirrors ExampleFuncCmd in internal/gosh_example/main.go.
func TestFuncCmd(t *testing.T) {
- sh := gosh.NewShell(gosh.Opts{Fatalf: makeFatalf(t), Logf: t.Logf})
+ sh := gosh.NewShell(t)
defer sh.Cleanup()
// Start server.
@@ -254,7 +258,7 @@
if testing.Short() {
t.Skip()
}
- sh := gosh.NewShell(gosh.Opts{Fatalf: makeFatalf(t), Logf: t.Logf})
+ sh := gosh.NewShell(t)
defer sh.Cleanup()
// Set -o to an absolute name.
@@ -297,7 +301,7 @@
// Tests that Shell.Cmd uses Shell.Vars["PATH"] to locate executables with
// relative names.
func TestLookPath(t *testing.T) {
- sh := gosh.NewShell(gosh.Opts{Fatalf: makeFatalf(t), Logf: t.Logf})
+ sh := gosh.NewShell(t)
defer sh.Cleanup()
binDir := sh.MakeTempDir()
@@ -326,7 +330,7 @@
// Tests that AwaitVars works under various conditions.
func TestAwaitVars(t *testing.T) {
- sh := gosh.NewShell(gosh.Opts{Fatalf: makeFatalf(t), Logf: t.Logf})
+ sh := gosh.NewShell(t)
defer sh.Cleanup()
c := sh.FuncCmd(sendVarsFunc, map[string]string{"a": "1"})
@@ -371,7 +375,7 @@
// Tests that AwaitVars returns immediately when the process exits.
func TestAwaitProcessExit(t *testing.T) {
- sh := gosh.NewShell(gosh.Opts{Fatalf: makeFatalf(t), Logf: t.Logf})
+ sh := gosh.NewShell(t)
defer sh.Cleanup()
c := sh.FuncCmd(exitFunc, 0)
@@ -399,7 +403,7 @@
// Tests function signature-checking and execution.
func TestRegistry(t *testing.T) {
- sh := gosh.NewShell(gosh.Opts{Fatalf: makeFatalf(t), Logf: t.Logf})
+ sh := gosh.NewShell(t)
defer sh.Cleanup()
// Variadic functions. Non-variadic functions are sufficiently covered in
@@ -441,7 +445,7 @@
}
func TestStdin(t *testing.T) {
- sh := gosh.NewShell(gosh.Opts{Fatalf: makeFatalf(t), Logf: t.Logf})
+ sh := gosh.NewShell(t)
defer sh.Cleanup()
// The "cat" command exits after the reader returns EOF.
@@ -487,7 +491,7 @@
}
func TestStdinPipeWriteUntilExit(t *testing.T) {
- sh := gosh.NewShell(gosh.Opts{Fatalf: makeFatalf(t), Logf: t.Logf})
+ sh := gosh.NewShell(t)
defer sh.Cleanup()
// Ensure that Write calls on stdin fail after the process exits. Note that we
@@ -532,7 +536,7 @@
})
func TestStdoutStderr(t *testing.T) {
- sh := gosh.NewShell(gosh.Opts{Fatalf: makeFatalf(t), Logf: t.Logf})
+ sh := gosh.NewShell(t)
defer sh.Cleanup()
// Write to stdout only.
@@ -564,7 +568,7 @@
}
var writeMoreFunc = gosh.RegisterFunc("writeMoreFunc", func() {
- sh := gosh.NewShell(gosh.Opts{})
+ sh := gosh.NewShell(nil)
defer sh.Cleanup()
c := sh.FuncCmd(writeFunc, true, true)
@@ -578,7 +582,7 @@
// Tests that it's safe to add os.Stdout and os.Stderr as writers.
func TestAddStdoutStderrWriter(t *testing.T) {
- sh := gosh.NewShell(gosh.Opts{Fatalf: makeFatalf(t), Logf: t.Logf})
+ sh := gosh.NewShell(t)
defer sh.Cleanup()
stdout, stderr := sh.FuncCmd(writeMoreFunc).StdoutStderr()
@@ -587,7 +591,7 @@
}
func TestCombinedOutput(t *testing.T) {
- sh := gosh.NewShell(gosh.Opts{Fatalf: makeFatalf(t), Logf: t.Logf})
+ sh := gosh.NewShell(t)
defer sh.Cleanup()
c := sh.FuncCmd(writeFunc, true, true)
@@ -604,7 +608,7 @@
}
func TestOutputDir(t *testing.T) {
- sh := gosh.NewShell(gosh.Opts{Fatalf: makeFatalf(t), Logf: t.Logf})
+ sh := gosh.NewShell(t)
defer sh.Cleanup()
dir := sh.MakeTempDir()
@@ -645,7 +649,7 @@
})
func TestSignal(t *testing.T) {
- sh := gosh.NewShell(gosh.Opts{Fatalf: makeFatalf(t), Logf: t.Logf})
+ sh := gosh.NewShell(t)
defer sh.Cleanup()
for _, d := range []time.Duration{0, time.Hour} {
@@ -677,7 +681,7 @@
}
func TestTerminate(t *testing.T) {
- sh := gosh.NewShell(gosh.Opts{Fatalf: makeFatalf(t), Logf: t.Logf})
+ sh := gosh.NewShell(t)
defer sh.Cleanup()
for _, d := range []time.Duration{0, time.Hour} {
@@ -701,7 +705,7 @@
}
func TestShellWait(t *testing.T) {
- sh := gosh.NewShell(gosh.Opts{Fatalf: makeFatalf(t), Logf: t.Logf})
+ sh := gosh.NewShell(t)
defer sh.Cleanup()
d0 := time.Duration(0)
@@ -745,7 +749,7 @@
}
func TestExitErrorIsOk(t *testing.T) {
- sh := gosh.NewShell(gosh.Opts{Fatalf: makeFatalf(t), Logf: t.Logf})
+ sh := gosh.NewShell(t)
defer sh.Cleanup()
// Exit code 0 is not an error.
@@ -768,7 +772,7 @@
}
func TestIgnoreClosedPipeError(t *testing.T) {
- sh := gosh.NewShell(gosh.Opts{Fatalf: makeFatalf(t), Logf: t.Logf})
+ sh := gosh.NewShell(t)
defer sh.Cleanup()
// Since writeLoopFunc will only finish if it receives a write error, it's
@@ -814,14 +818,16 @@
sh.Ok()
}()
func() { // errShellErrIsNotNil
- sh := gosh.NewShell(gosh.Opts{Fatalf: t.Logf})
+ sh := gosh.NewShell(t)
+ sh.ContinueOnError = true
defer sh.Cleanup()
sh.Err = fakeError
defer func() { neq(t, recover(), nil) }()
sh.Ok()
}()
func() { // errAlreadyCalledCleanup
- sh := gosh.NewShell(gosh.Opts{Fatalf: t.Logf})
+ sh := gosh.NewShell(t)
+ sh.ContinueOnError = true
sh.Cleanup()
defer func() { neq(t, recover(), nil) }()
sh.Ok()
@@ -836,14 +842,16 @@
sh.HandleError(fakeError)
}()
func() { // errShellErrIsNotNil
- sh := gosh.NewShell(gosh.Opts{Fatalf: t.Logf})
+ sh := gosh.NewShell(t)
+ sh.ContinueOnError = true
defer sh.Cleanup()
sh.Err = fakeError
defer func() { neq(t, recover(), nil) }()
sh.HandleError(fakeError)
}()
func() { // errAlreadyCalledCleanup
- sh := gosh.NewShell(gosh.Opts{Fatalf: t.Logf})
+ sh := gosh.NewShell(t)
+ sh.ContinueOnError = true
sh.Cleanup()
defer func() { neq(t, recover(), nil) }()
sh.HandleError(fakeError)
@@ -861,14 +869,14 @@
// Tests that sh.Cleanup succeeds even if sh.Err is not nil.
func TestCleanupAfterError(t *testing.T) {
- sh := gosh.NewShell(gosh.Opts{Fatalf: makeFatalf(t), Logf: t.Logf})
+ sh := gosh.NewShell(t)
sh.Err = fakeError
sh.Cleanup()
}
// Tests that sh.Cleanup can be called multiple times.
func TestMultipleCleanup(t *testing.T) {
- sh := gosh.NewShell(gosh.Opts{Fatalf: makeFatalf(t), Logf: t.Logf})
+ sh := gosh.NewShell(t)
sh.Cleanup()
sh.Cleanup()
}
diff --git a/vlog/flags_test.go b/vlog/flags_test.go
index f22ed59..707ac55 100644
--- a/vlog/flags_test.go
+++ b/vlog/flags_test.go
@@ -42,7 +42,7 @@
})
func TestFlags(t *testing.T) {
- sh := gosh.NewShell(gosh.Opts{Fatalf: t.Fatalf, Logf: t.Logf})
+ sh := gosh.NewShell(t)
defer sh.Cleanup()
sh.FuncCmd(child).Run()
}