lib: gosh: address TODOs
More specifically:
- Eliminates Shell.Main, since it's easy enough to use
Shell.Fn and set the returned Cmd's Args
- Renames Shell.Fn to Shell.FuncCmd
- Extends Shell.{Cmd,FuncCmd} comments to explain what's
done with the given arguments
- Makes it so NewShell takes a snapshot of os.Environ() and
uses that henceforth
- Makes it so NewShell filters out any gosh env vars coming
from outside
- Renames registry.go's Fn to Func, Register to
RegisterFunc, and Call to CallFunc
- Adjusts the behavior of RegisterFunc so that names are
augmented to produce collision-resistant handles of the
form "file:line:name"
- Makes callFunc and Func.call private for now, until
there's a clear need for them to be exported
- Eliminates gosh.Run
- Renames Cmd.Shutdown (which we decided to keep) to
Cmd.Terminate
- Changes Cmd.Signal and Cmd.Terminate to fail if Wait
has been called
- Drops Cmd.Kill; if it proves necessary, we'll add a
gosh.Kill implementation of os.Signal that tells
Cmd.Signal and Cmd.Terminate to issue Process.Kill
Also, updates v23test:
- Same changes as in gosh (Fn->FuncCmd, no more Main)
- Replaces v23test.Run with TestMain (simple case) and
InitTestMain (advanced case)
- Eliminates the hack with credentials env vars
MultiPart: 3/8
Change-Id: I43458981706f0bd5c694c898a1213f075d208282
diff --git a/gosh/.api b/gosh/.api
index 687f358..d8cead2 100644
--- a/gosh/.api
+++ b/gosh/.api
@@ -1,11 +1,9 @@
-pkg gosh, func Call(string, ...interface{}) error
pkg gosh, func InitChildMain()
pkg gosh, func InitMain()
pkg gosh, func NewBufferedPipe() io.ReadWriteCloser
pkg gosh, func NewShell(Opts) *Shell
pkg gosh, func NopWriteCloser(io.Writer) io.WriteCloser
-pkg gosh, func Register(string, interface{}) *Fn
-pkg gosh, func Run(func() int) int
+pkg gosh, func RegisterFunc(string, interface{}) *Func
pkg gosh, func SendReady()
pkg gosh, func SendVars(map[string]string)
pkg gosh, method (*Cmd) AddStderrWriter(io.WriteCloser)
@@ -14,10 +12,8 @@
pkg gosh, method (*Cmd) AwaitVars(...string) map[string]string
pkg gosh, method (*Cmd) Clone() *Cmd
pkg gosh, method (*Cmd) CombinedOutput() string
-pkg gosh, method (*Cmd) Kill()
pkg gosh, method (*Cmd) Pid() int
pkg gosh, method (*Cmd) Run()
-pkg gosh, method (*Cmd) Shutdown(os.Signal)
pkg gosh, method (*Cmd) Signal(os.Signal)
pkg gosh, method (*Cmd) Start()
pkg gosh, method (*Cmd) StderrPipe() io.Reader
@@ -25,15 +21,14 @@
pkg gosh, method (*Cmd) Stdout() string
pkg gosh, method (*Cmd) StdoutPipe() io.Reader
pkg gosh, method (*Cmd) StdoutStderr() (string, string)
+pkg gosh, method (*Cmd) Terminate(os.Signal)
pkg gosh, method (*Cmd) Wait()
-pkg gosh, method (*Fn) Call(...interface{}) error
pkg gosh, method (*Shell) AddToCleanup(func())
pkg gosh, method (*Shell) BuildGoPkg(string, ...string) string
pkg gosh, method (*Shell) Cleanup()
pkg gosh, method (*Shell) Cmd(string, ...string) *Cmd
-pkg gosh, method (*Shell) Fn(*Fn, ...interface{}) *Cmd
+pkg gosh, method (*Shell) FuncCmd(*Func, ...interface{}) *Cmd
pkg gosh, method (*Shell) HandleError(error)
-pkg gosh, method (*Shell) Main(*Fn, ...string) *Cmd
pkg gosh, method (*Shell) MakeTempDir() string
pkg gosh, method (*Shell) MakeTempFile() *os.File
pkg gosh, method (*Shell) Ok()
@@ -52,7 +47,7 @@
pkg gosh, type Cmd struct, PropagateOutput bool
pkg gosh, type Cmd struct, Stdin string
pkg gosh, type Cmd struct, Vars map[string]string
-pkg gosh, type Fn struct
+pkg gosh, type Func struct
pkg gosh, type Opts struct
pkg gosh, type Opts struct, BinDir string
pkg gosh, type Opts struct, ChildOutputDir string
diff --git a/gosh/cmd.go b/gosh/cmd.go
index 6ed9682..9fa6d1f 100644
--- a/gosh/cmd.go
+++ b/gosh/cmd.go
@@ -43,11 +43,11 @@
Args []string
// IgnoreParentExit, if true, makes it so the child process does not exit when
// its parent exits. Only takes effect if the child process was spawned via
- // Shell.Fn or Shell.Main, or explicitly calls InitChildMain.
+ // Shell.FuncCmd or explicitly calls InitChildMain.
IgnoreParentExit bool
// ExitAfter, if non-zero, specifies that the child process should exit after
// the given duration has elapsed. Only takes effect if the child process was
- // spawned via Shell.Fn or Shell.Main, or explicitly calls InitChildMain.
+ // spawned via Shell.FuncCmd or explicitly calls InitChildMain.
ExitAfter time.Duration
// PropagateOutput is inherited from Shell.Opts.PropagateChildOutput.
PropagateOutput bool
@@ -174,22 +174,18 @@
c.handleError(c.wait())
}
-// Kill causes the process to exit immediately.
-func (c *Cmd) Kill() {
- c.sh.Ok()
- c.handleError(c.kill())
-}
-
// Signal sends a signal to the process.
func (c *Cmd) Signal(sig os.Signal) {
c.sh.Ok()
c.handleError(c.signal(sig))
}
-// Shutdown sends a signal to the process, then waits for it to exit.
-func (c *Cmd) Shutdown(sig os.Signal) {
+// Terminate sends a signal to the process, then waits for it to exit. Terminate
+// is different from Signal followed by Wait: Terminate succeeds as long as the
+// process exits, whereas Wait fails if the exit code isn't 0.
+func (c *Cmd) Terminate(sig os.Signal) {
c.sh.Ok()
- c.handleError(c.shutdown(sig))
+ c.handleError(c.terminate(sig))
}
// Run calls Start followed by Wait.
@@ -408,10 +404,9 @@
}
func (c *Cmd) clone() (*Cmd, error) {
- vars := copyMap(c.Vars)
args := make([]string, len(c.Args))
copy(args, c.Args)
- res, err := newCmdInternal(c.sh, vars, c.Path, args[1:])
+ res, err := newCmdInternal(c.sh, copyMap(c.Vars), c.Path, args[1:])
if err != nil {
return nil, err
}
@@ -617,22 +612,16 @@
// https://golang.org/src/os/exec_windows.go
const errFinished = "os: process already finished"
-func (c *Cmd) kill() error {
- if !c.started {
- return errDidNotCallStart
- }
- if !c.isRunning() {
- return nil
- }
- if err := c.c.Process.Kill(); err != nil && err.Error() != errFinished {
- return err
- }
- return nil
-}
-
+// NOTE(sadovsky): Technically speaking, Process.Signal(os.Kill) is different
+// from Process.Kill. Currently, gosh.Cmd does not provide a way to trigger
+// Process.Kill. If it proves necessary, we'll add a "gosh.Kill" implementation
+// of the os.Signal interface, and have the signal and terminate methods map
+// that to Process.Kill.
func (c *Cmd) signal(sig os.Signal) error {
if !c.started {
return errDidNotCallStart
+ } else if c.calledWait {
+ return errAlreadyCalledWait
}
if !c.isRunning() {
return nil
@@ -643,11 +632,12 @@
return nil
}
-func (c *Cmd) shutdown(sig os.Signal) error {
+func (c *Cmd) terminate(sig os.Signal) error {
if err := c.signal(sig); err != nil {
return err
}
if err := c.wait(); err != nil {
+ // Succeed as long as the process exited, regardless of the exit code.
if _, ok := err.(*exec.ExitError); !ok {
return err
}
diff --git a/gosh/internal/gosh_example/main.go b/gosh/internal/gosh_example/main.go
index d79d680..d3ecab2 100644
--- a/gosh/internal/gosh_example/main.go
+++ b/gosh/internal/gosh_example/main.go
@@ -11,7 +11,7 @@
"v.io/x/lib/gosh/internal/gosh_example_lib"
)
-func ExampleCmds() {
+func ExampleCmd() {
sh := gosh.NewShell(gosh.Opts{})
defer sh.Cleanup()
@@ -30,37 +30,28 @@
}
var (
- getFn = gosh.Register("getFn", lib.Get)
- serveFn = gosh.Register("serveFn", lib.Serve)
+ getFunc = gosh.RegisterFunc("getFunc", lib.Get)
+ serveFunc = gosh.RegisterFunc("serveFunc", lib.Serve)
)
-func ExampleFns() {
+func ExampleFuncCmd() {
sh := gosh.NewShell(gosh.Opts{})
defer sh.Cleanup()
// Start server.
- c := sh.Fn(serveFn)
+ c := sh.FuncCmd(serveFunc)
c.Start()
c.AwaitReady()
addr := c.AwaitVars("Addr")["Addr"]
fmt.Println(addr)
// Run client.
- c = sh.Fn(getFn, addr)
- fmt.Print(c.Stdout())
-}
-
-func ExampleShellMain() {
- sh := gosh.NewShell(gosh.Opts{})
- defer sh.Cleanup()
-
- c := sh.Main(lib.HelloWorldMain)
+ c = sh.FuncCmd(getFunc, addr)
fmt.Print(c.Stdout())
}
func main() {
gosh.InitMain()
- ExampleCmds()
- ExampleFns()
- ExampleShellMain()
+ ExampleCmd()
+ ExampleFuncCmd()
}
diff --git a/gosh/internal/gosh_example_lib/lib.go b/gosh/internal/gosh_example_lib/lib.go
index c2158d4..be51c33 100644
--- a/gosh/internal/gosh_example_lib/lib.go
+++ b/gosh/internal/gosh_example_lib/lib.go
@@ -15,13 +15,6 @@
"v.io/x/lib/gosh"
)
-const helloWorld = "Hello, world!"
-
-// HelloWorldMain is used to demonstrate usage of Shell.Main.
-var HelloWorldMain = gosh.Register("HelloWorldMain", func() {
- fmt.Println(helloWorld)
-})
-
func Get(addr string) {
resp, err := http.Get("http://" + addr)
if err != nil {
@@ -49,7 +42,7 @@
func Serve() {
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
- fmt.Fprintln(w, helloWorld)
+ fmt.Fprintln(w, "Hello, world!")
})
// Note: With http.ListenAndServe() there's no easy way to tell which port
// number we were assigned, so instead we use net.Listen() followed by
diff --git a/gosh/registry.go b/gosh/registry.go
index 139a4f1..64453a8 100644
--- a/gosh/registry.go
+++ b/gosh/registry.go
@@ -13,29 +13,34 @@
"errors"
"fmt"
"reflect"
+ "runtime"
+ "sync"
)
-// Fn is a registered, callable function.
-type Fn struct {
- name string
- value reflect.Value
+// Func is a registered, callable function.
+type Func struct {
+ handle string
+ value reflect.Value
}
var (
- fns = map[string]*Fn{}
errorType = reflect.TypeOf((*error)(nil)).Elem()
+ funcsMu = sync.RWMutex{} // protects funcs
+ funcs = map[string]*Func{}
)
-// Register registers the given function with the given name. 'name' must be
-// unique across the dependency graph; 'fni' must be a function that accepts
-// gob-encodable arguments and returns an error or nothing.
-func Register(name string, fni interface{}) *Fn {
- // TODO(sadovsky): Include len(fns) in registered name if it turns out that
- // initialization order is deterministic.
- if _, ok := fns[name]; ok {
- panic(fmt.Errorf("gosh: %q is already registered", name))
+// RegisterFunc registers the given function with the given name. 'fi' must be a
+// function that accepts gob-encodable arguments and returns an error or
+// nothing.
+func RegisterFunc(name string, fi interface{}) *Func {
+ funcsMu.Lock()
+ defer funcsMu.Unlock()
+ _, file, line, _ := runtime.Caller(1)
+ handle := fmt.Sprintf("%s:%d:%s", file, line, name)
+ if _, ok := funcs[handle]; ok {
+ panic(fmt.Errorf("gosh: %q is already registered", handle))
}
- v := reflect.ValueOf(fni)
+ v := reflect.ValueOf(fi)
t := v.Type()
if t.Kind() != reflect.Func {
panic(fmt.Errorf("gosh: %q is not a function: %v", name, t.Kind()))
@@ -43,7 +48,7 @@
if t.NumOut() > 1 || t.NumOut() == 1 && t.Out(0) != errorType {
panic(fmt.Errorf("gosh: %q must return an error or nothing: %v", name, t))
}
- // Register the function's args with gob. Needed because Shell.Fn takes
+ // 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
@@ -53,23 +58,34 @@
}
gob.Register(reflect.Zero(t.In(i)).Interface())
}
- fn := &Fn{name: name, value: v}
- fns[name] = fn
- return fn
+ f := &Func{handle: handle, value: v}
+ funcs[handle] = f
+ return f
}
-// Call calls the named function, which must have been registered.
-func Call(name string, args ...interface{}) error {
- fn, err := getFn(name)
+// getFunc returns the referenced function.
+func getFunc(handle string) (*Func, error) {
+ funcsMu.RLock()
+ f, ok := funcs[handle]
+ funcsMu.RUnlock()
+ if !ok {
+ return nil, fmt.Errorf("gosh: unknown function %q", handle)
+ }
+ return f, nil
+}
+
+// callFunc calls the referenced function, which must have been registered.
+func callFunc(handle string, args ...interface{}) error {
+ f, err := getFunc(handle)
if err != nil {
return err
}
- return fn.Call(args...)
+ return f.call(args...)
}
-// Call calls the function fn with the input arguments args.
-func (fn *Fn) Call(args ...interface{}) error {
- t := fn.value.Type()
+// call calls this Func with the given input arguments.
+func (f *Func) call(args ...interface{}) error {
+ t := f.value.Type()
in := []reflect.Value{}
for i, arg := range args {
var av reflect.Value
@@ -82,22 +98,13 @@
}
in = append(in, av)
}
- out := fn.value.Call(in)
+ out := f.value.Call(in)
if t.NumOut() == 1 && !out[0].IsNil() {
return out[0].Interface().(error)
}
return nil
}
-// getFn returns the named function.
-func getFn(name string) (*Fn, error) {
- fn, ok := fns[name]
- if !ok {
- return nil, fmt.Errorf("gosh: unknown function %q", name)
- }
- return fn, nil
-}
-
// argType returns the type of the nth argument to a function of type t.
func argType(t reflect.Type, n int) reflect.Type {
if !t.IsVariadic() || n < t.NumIn()-1 {
@@ -106,14 +113,14 @@
return t.In(t.NumIn() - 1).Elem()
}
-// checkCall checks that the named function exists and can be called with the
-// given arguments. Modeled after the implementation of reflect.Value.call.
-func checkCall(name string, args ...interface{}) error {
- fn, err := getFn(name)
+// checkCall checks that the referenced function exists and can be called with
+// the given arguments. Modeled after the implementation of reflect.Value.call.
+func checkCall(handle string, args ...interface{}) error {
+ f, err := getFunc(handle)
if err != nil {
return err
}
- t := fn.value.Type()
+ t := f.value.Type()
n := t.NumIn()
if t.IsVariadic() {
n--
@@ -139,16 +146,16 @@
// invocation
type invocation struct {
- Name string
- Args []interface{}
+ Handle string
+ Args []interface{}
}
-// encInvocation encodes an invocation.
-func encInvocation(name string, args ...interface{}) (string, error) {
- if err := checkCall(name, args...); err != nil {
+// encodeInvocation encodes an invocation.
+func encodeInvocation(handle string, args ...interface{}) (string, error) {
+ if err := checkCall(handle, args...); err != nil {
return "", err
}
- inv := invocation{Name: name, Args: args}
+ inv := invocation{Handle: handle, Args: args}
buf := &bytes.Buffer{}
if err := gob.NewEncoder(buf).Encode(inv); err != nil {
return "", fmt.Errorf("gosh: failed to encode invocation: %v", err)
@@ -158,8 +165,8 @@
return base64.StdEncoding.EncodeToString(buf.Bytes()), nil
}
-// decInvocation decodes an invocation.
-func decInvocation(s string) (name string, args []interface{}, err error) {
+// decodeInvocation decodes an invocation.
+func decodeInvocation(s string) (handle string, args []interface{}, err error) {
var inv invocation
b, err := base64.StdEncoding.DecodeString(s)
if err == nil {
@@ -168,5 +175,5 @@
if err != nil {
return "", nil, fmt.Errorf("gosh: failed to decode invocation: %v", err)
}
- return inv.Name, inv.Args, nil
+ return inv.Handle, inv.Args, nil
}
diff --git a/gosh/shell.go b/gosh/shell.go
index 37d3174..59a3171 100644
--- a/gosh/shell.go
+++ b/gosh/shell.go
@@ -43,14 +43,6 @@
errDidNotCallNewShell = errors.New("gosh: did not call gosh.NewShell")
)
-// TODO(sadovsky):
-// - Eliminate Shell.Main, since it's easy enough to use Shell.Fn and set the
-// returned Cmd's Args.
-// - Rename Shell.Fn to Shell.FuncCmd, Fn to Func, Register to RegisterFunc, and
-// Call to CallFunc.
-// - Eliminate gosh.Run.
-// - Revisit whether to have Cmd.Shutdown.
-
// 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
@@ -70,7 +62,7 @@
cmds []*Cmd
tempFiles []*os.File
tempDirs []string
- cleanupFns []func()
+ cleanupFuncs []func()
}
// Opts configures Shell.
@@ -109,7 +101,8 @@
}
}
-// Cmd returns a Cmd for an invocation of the named program.
+// Cmd returns a Cmd for an invocation of the named program. The given arguments
+// are passed to the child as command-line arguments.
func (sh *Shell) Cmd(name string, args ...string) *Cmd {
sh.Ok()
res, err := sh.cmd(nil, name, args...)
@@ -117,22 +110,13 @@
return res
}
-// Fn returns a Cmd for an invocation of the given registered Fn.
-func (sh *Shell) Fn(fn *Fn, args ...interface{}) *Cmd {
+// FuncCmd returns a Cmd for an invocation of the given registered Func. The
+// given arguments are gob-encoded in the parent process, then gob-decoded in
+// the child and passed to the Func as parameters. To specify command-line
+// arguments for the child invocation, append to the returned Cmd's Args.
+func (sh *Shell) FuncCmd(f *Func, args ...interface{}) *Cmd {
sh.Ok()
- res, err := sh.fn(fn, args...)
- sh.HandleError(err)
- return res
-}
-
-// Main returns a Cmd for an invocation of the given registered main() function.
-// Intended usage: Have your program's main() call RealMain, then write a parent
-// program that uses Shell.Main to run RealMain in a child process. With this
-// approach, RealMain can be compiled into the parent program's binary. Caveat:
-// potential flag collisions.
-func (sh *Shell) Main(fn *Fn, args ...string) *Cmd {
- sh.Ok()
- res, err := sh.main(fn, args...)
+ res, err := sh.funcCmd(f, args...)
sh.HandleError(err)
return res
}
@@ -192,15 +176,16 @@
}
// AddToCleanup registers the given function to be called by Shell.Cleanup().
-func (sh *Shell) AddToCleanup(fn func()) {
+func (sh *Shell) AddToCleanup(f func()) {
sh.Ok()
- sh.HandleError(sh.addToCleanup(fn))
+ sh.HandleError(sh.addToCleanup(f))
}
// Cleanup cleans up all resources (child processes, temporary files and
// directories) associated with this Shell. It is safe (and recommended) to call
// Cleanup after a Shell error. It is also safe to call Cleanup multiple times;
-// calls after the first return immediately with no effect.
+// calls after the first return immediately with no effect. Cleanup never calls
+// HandleError.
func (sh *Shell) Cleanup() {
if !sh.calledNewShell {
panic(errDidNotCallNewShell)
@@ -234,17 +219,18 @@
// onTerminationSignal starts a goroutine that listens for various termination
// signals and calls the given function when such a signal is received.
-func onTerminationSignal(fn func(os.Signal)) {
+func onTerminationSignal(f func(os.Signal)) {
ch := make(chan os.Signal, 1)
signal.Notify(ch, syscall.SIGINT, syscall.SIGQUIT, syscall.SIGTERM)
go func() {
- fn(<-ch)
+ f(<-ch)
}()
}
// 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...))
@@ -256,15 +242,20 @@
}
}
if opts.ChildOutputDir == "" {
- opts.ChildOutputDir = os.Getenv(envChildOutputDir)
+ opts.ChildOutputDir = osVars[envChildOutputDir]
+ }
+ // Filter out any gosh env vars coming from outside.
+ shVars := copyMap(osVars)
+ for _, key := range []string{envBinDir, envChildOutputDir, envExitAfter, envInvocation, envWatchParent} {
+ delete(shVars, key)
}
sh := &Shell{
Opts: opts,
- Vars: map[string]string{},
+ Vars: shVars,
calledNewShell: true,
}
if sh.Opts.BinDir == "" {
- sh.Opts.BinDir = os.Getenv(envBinDir)
+ sh.Opts.BinDir = osVars[envBinDir]
if sh.Opts.BinDir == "" {
var err error
if sh.Opts.BinDir, err = sh.makeTempDir(); err != nil {
@@ -298,9 +289,7 @@
if vars == nil {
vars = make(map[string]string)
}
- // TODO(sadovsky): Copy os.Environ() into sh.Vars in NewShell, and clear
- // envWatchParent and envExitAfter if they're set.
- c, err := newCmd(sh, mergeMaps(sliceToMap(os.Environ()), sh.Vars, vars), name, append(args, sh.Args...)...)
+ c, err := newCmd(sh, mergeMaps(sh.Vars, vars), name, append(args, sh.Args...)...)
if err != nil {
return nil, err
}
@@ -318,39 +307,20 @@
}
}
-func (sh *Shell) fn(fn *Fn, args ...interface{}) (*Cmd, error) {
+func (sh *Shell) funcCmd(f *Func, args ...interface{}) (*Cmd, error) {
// Safeguard against the developer forgetting to call InitMain, which could
// lead to infinite recursion.
if !calledInitMain {
return nil, errDidNotCallInitMain
}
- b, err := encInvocation(fn.name, args...)
+ buf, err := encodeInvocation(f.handle, args...)
if err != nil {
return nil, err
}
- vars := map[string]string{envInvocation: string(b)}
+ vars := map[string]string{envInvocation: string(buf)}
return sh.cmd(vars, executablePath)
}
-func (sh *Shell) main(fn *Fn, args ...string) (*Cmd, error) {
- // Safeguard against the developer forgetting to call InitMain, which could
- // lead to infinite recursion.
- if !calledInitMain {
- return nil, errDidNotCallInitMain
- }
- // Check that fn has the required signature.
- t := fn.value.Type()
- if t.NumIn() != 0 || t.NumOut() != 0 {
- return nil, errors.New("gosh: main function must have no input or output parameters")
- }
- b, err := encInvocation(fn.name)
- if err != nil {
- return nil, err
- }
- vars := map[string]string{envInvocation: string(b)}
- return sh.cmd(vars, executablePath, args...)
-}
-
func (sh *Shell) wait() error {
// Note: It is illegal to call newCmd() concurrently with Shell.wait(), so we
// need not hold cleanupMu when accessing sh.cmds below.
@@ -463,24 +433,24 @@
return nil
}
-func (sh *Shell) addToCleanup(fn func()) error {
+func (sh *Shell) addToCleanup(f func()) error {
sh.cleanupMu.Lock()
defer sh.cleanupMu.Unlock()
if sh.calledCleanup {
return errAlreadyCalledCleanup
}
- sh.cleanupFns = append(sh.cleanupFns, fn)
+ sh.cleanupFuncs = append(sh.cleanupFuncs, f)
return nil
}
-// forEachRunningCmd applies fn to each running child process.
-func (sh *Shell) forEachRunningCmd(fn func(*Cmd)) bool {
+// forEachRunningCmd applies f to each running child process.
+func (sh *Shell) forEachRunningCmd(f func(*Cmd)) bool {
anyRunning := false
for _, c := range sh.cmds {
if c.isRunning() {
anyRunning = true
- if fn != nil {
- fn(c)
+ if f != nil {
+ f(c)
}
}
}
@@ -489,10 +459,10 @@
// Note: It is safe to run Shell.terminateRunningCmds concurrently with the
// waiter goroutine and with Cmd.wait. In particular, Shell.terminateRunningCmds
-// only calls c.{isRunning,Pid,Signal,Kill}, all of which are thread-safe with
-// the waiter goroutine and with Cmd.wait.
+// only calls c.{isRunning,Pid,signal}, all of which are thread-safe with the
+// waiter goroutine and with Cmd.wait.
func (sh *Shell) terminateRunningCmds() {
- // Try Cmd.signal first; if that doesn't work, use Cmd.kill.
+ // 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)
@@ -505,13 +475,13 @@
sh.logf("%s (PID %d) did not die\n", c.Path, c.Pid())
})
}
- // If any child is still running, wait for another second, then call Cmd.kill
- // for all running children.
+ // If any child is still running, wait for another second, then send os.Kill
+ // to all running children.
if anyRunning {
time.Sleep(time.Second)
sh.forEachRunningCmd(func(c *Cmd) {
- if err := c.kill(); err != nil {
- sh.logf("%d.Kill() failed: %v\n", c.Pid(), err)
+ if err := c.signal(os.Kill); err != nil {
+ sh.logf("%d.Signal(os.Kill) failed: %v\n", c.Pid(), err)
}
})
sh.logf("Killed all remaining child processes\n")
@@ -546,8 +516,8 @@
}
}
// Call any registered cleanup functions in LIFO order.
- for i := len(sh.cleanupFns) - 1; i >= 0; i-- {
- sh.cleanupFns[i]()
+ for i := len(sh.cleanupFuncs) - 1; i >= 0; i-- {
+ sh.cleanupFuncs[i]()
}
}
@@ -558,7 +528,7 @@
// InitMain must be called early on in main(), before flags are parsed. In the
// parent process, it returns immediately with no effect. In a child process for
-// a Shell.Fn or Shell.Main command, it runs the specified function, then exits.
+// a Shell.FuncCmd command, it runs the specified function, then exits.
func InitMain() {
calledInitMain = true
s := os.Getenv(envInvocation)
@@ -567,23 +537,16 @@
}
os.Unsetenv(envInvocation)
InitChildMain()
- name, args, err := decInvocation(s)
+ name, args, err := decodeInvocation(s)
if err != nil {
log.Fatal(err)
}
- if err := Call(name, args...); err != nil {
+ if err := callFunc(name, args...); err != nil {
log.Fatal(err)
}
os.Exit(0)
}
-// Run calls InitMain, then returns run(). Exported so that TestMain functions
-// can simply call os.Exit(gosh.Run(m.Run)).
-func Run(run func() int) int {
- InitMain()
- return run()
-}
-
// NopWriteCloser returns a WriteCloser with a no-op Close method wrapping the
// provided Writer.
func NopWriteCloser(w io.Writer) io.WriteCloser {
diff --git a/gosh/shell_test.go b/gosh/shell_test.go
index 73e5da0..f8e1840 100644
--- a/gosh/shell_test.go
+++ b/gosh/shell_test.go
@@ -6,10 +6,10 @@
// TODO(sadovsky): Add more tests:
// - effects of Shell.Cleanup
-// - Cmd.Clone
// - Shell.{Vars,Args,Rename,MakeTempFile,MakeTempDir}
-// - Opts (including defaulting behavior)
-// - {,Maybe}WatchParent
+// - Shell.Opts.{PropagateChildOutput,ChildOutputDir,BinDir}
+// - Cmd.Clone
+// - Cmd.Opts.{IgnoreParentExit,ExitAfter,PropagateOutput}
import (
"bufio"
@@ -19,6 +19,7 @@
"io"
"io/ioutil"
"os"
+ "os/signal"
"path/filepath"
"reflect"
"runtime/debug"
@@ -78,35 +79,52 @@
}
}
+func setsErr(t *testing.T, sh *gosh.Shell, f func()) {
+ calledFatalf := false
+ sh.Opts.Fatalf = func(string, ...interface{}) { calledFatalf = true }
+ f()
+ nok(t, sh.Err)
+ eq(t, calledFatalf, true)
+ sh.Err = nil
+ sh.Opts.Fatalf = makeFatalf(t)
+}
+
////////////////////////////////////////
// Simple functions
// Simplified versions of various Unix commands.
var (
- catFn = gosh.Register("catFn", func() {
+ catFunc = gosh.RegisterFunc("catFunc", func() {
io.Copy(os.Stdout, os.Stdin)
})
- echoFn = gosh.Register("echoFn", func() {
+ echoFunc = gosh.RegisterFunc("echoFunc", func() {
fmt.Println(os.Args[1])
})
- readFn = gosh.Register("readFn", func() {
+ readFunc = gosh.RegisterFunc("readFunc", func() {
bufio.NewReader(os.Stdin).ReadString('\n')
})
)
// Functions with parameters.
var (
- exitFn = gosh.Register("exitFn", func(code int) {
+ exitFunc = gosh.RegisterFunc("exitFunc", func(code int) {
os.Exit(code)
})
- sleepFn = gosh.Register("sleepFn", func(d time.Duration, code int) {
+ sleepFunc = gosh.RegisterFunc("sleepFunc", func(d time.Duration, code int) {
+ // For TestSignal and TestTerminate.
+ ch := make(chan os.Signal, 1)
+ signal.Notify(ch, os.Interrupt)
+ go func() {
+ <-ch
+ os.Exit(0)
+ }()
time.Sleep(d)
os.Exit(code)
})
- printFn = gosh.Register("printFn", func(v ...interface{}) {
+ printFunc = gosh.RegisterFunc("printFunc", func(v ...interface{}) {
fmt.Print(v...)
})
- printfFn = gosh.Register("printfFn", func(format string, v ...interface{}) {
+ printfFunc = gosh.RegisterFunc("printfFunc", func(format string, v ...interface{}) {
fmt.Printf(format, v...)
})
)
@@ -151,9 +169,7 @@
ok(t, err)
eq(t, cwd, startDir)
// The next sh.Popd() will fail.
- sh.Opts.Fatalf = nil
- sh.Popd()
- nok(t, sh.Err)
+ setsErr(t, sh, func() { sh.Popd() })
}
func evalSymlinks(t *testing.T, dir string) string {
@@ -182,7 +198,7 @@
eq(t, getwdEvalSymlinks(t), startDir)
}
-func TestCmds(t *testing.T) {
+func TestCmd(t *testing.T) {
sh := gosh.NewShell(gosh.Opts{Fatalf: makeFatalf(t), Logf: t.Logf})
defer sh.Cleanup()
@@ -201,44 +217,36 @@
}
var (
- getFn = gosh.Register("getFn", lib.Get)
- serveFn = gosh.Register("serveFn", lib.Serve)
+ getFunc = gosh.RegisterFunc("getFunc", lib.Get)
+ serveFunc = gosh.RegisterFunc("serveFunc", lib.Serve)
)
-func TestFns(t *testing.T) {
+func TestFuncCmd(t *testing.T) {
sh := gosh.NewShell(gosh.Opts{Fatalf: makeFatalf(t), Logf: t.Logf})
defer sh.Cleanup()
// Start server.
- c := sh.Fn(serveFn)
+ c := sh.FuncCmd(serveFunc)
c.Start()
c.AwaitReady()
addr := c.AwaitVars("Addr")["Addr"]
neq(t, addr, "")
// Run client.
- c = sh.Fn(getFn, addr)
- eq(t, c.Stdout(), "Hello, world!\n")
-}
-
-func TestShellMain(t *testing.T) {
- sh := gosh.NewShell(gosh.Opts{Fatalf: makeFatalf(t), Logf: t.Logf})
- defer sh.Cleanup()
-
- c := sh.Main(lib.HelloWorldMain)
+ c = sh.FuncCmd(getFunc, addr)
eq(t, c.Stdout(), "Hello, world!\n")
}
// Functions designed for TestRegistry.
var (
- printIntsFn = gosh.Register("printIntsFn", func(v ...int) {
+ printIntsFunc = gosh.RegisterFunc("printIntsFunc", func(v ...int) {
var vi []interface{}
for _, x := range v {
vi = append(vi, x)
}
fmt.Print(vi...)
})
- printfIntsFn = gosh.Register("printfIntsFn", func(format string, v ...int) {
+ printfIntsFunc = gosh.RegisterFunc("printfIntsFunc", func(format string, v ...int) {
var vi []interface{}
for _, x := range v {
vi = append(vi, x)
@@ -252,21 +260,13 @@
sh := gosh.NewShell(gosh.Opts{Fatalf: makeFatalf(t), Logf: t.Logf})
defer sh.Cleanup()
- c := sh.Fn(exitFn, 0)
+ c := sh.FuncCmd(exitFunc, 0)
c.Start()
- sh.Opts.Fatalf = nil
- c.AwaitReady()
- nok(t, sh.Err)
- sh.Err = nil
- sh.Opts.Fatalf = makeFatalf(t)
+ setsErr(t, sh, func() { c.AwaitReady() })
- c = sh.Fn(exitFn, 0)
+ c = sh.FuncCmd(exitFunc, 0)
c.Start()
- sh.Opts.Fatalf = nil
- c.AwaitVars("foo")
- nok(t, sh.Err)
- sh.Err = nil
- sh.Opts.Fatalf = makeFatalf(t)
+ setsErr(t, sh, func() { c.AwaitVars("foo") })
}
// Tests function signature-checking and execution.
@@ -276,105 +276,81 @@
// Variadic functions. Non-variadic functions are sufficiently covered in
// other tests.
- eq(t, sh.Fn(printFn).Stdout(), "")
- eq(t, sh.Fn(printFn, 0).Stdout(), "0")
- eq(t, sh.Fn(printFn, 0, "foo").Stdout(), "0foo")
- eq(t, sh.Fn(printfFn, "").Stdout(), "")
- eq(t, sh.Fn(printfFn, "%v", 0).Stdout(), "0")
- eq(t, sh.Fn(printfFn, "%v%v", 0, "foo").Stdout(), "0foo")
- eq(t, sh.Fn(printIntsFn, 1, 2).Stdout(), "1 2")
- eq(t, sh.Fn(printfIntsFn, "%v %v", 1, 2).Stdout(), "1 2")
-
- // Error cases.
- sh.Opts.Fatalf = nil
- reset := func() {
- nok(t, sh.Err)
- sh.Err = nil
- }
+ eq(t, sh.FuncCmd(printFunc).Stdout(), "")
+ eq(t, sh.FuncCmd(printFunc, 0).Stdout(), "0")
+ eq(t, sh.FuncCmd(printFunc, 0, "foo").Stdout(), "0foo")
+ eq(t, sh.FuncCmd(printfFunc, "").Stdout(), "")
+ eq(t, sh.FuncCmd(printfFunc, "%v", 0).Stdout(), "0")
+ eq(t, sh.FuncCmd(printfFunc, "%v%v", 0, "foo").Stdout(), "0foo")
+ eq(t, sh.FuncCmd(printIntsFunc, 1, 2).Stdout(), "1 2")
+ eq(t, sh.FuncCmd(printfIntsFunc, "%v %v", 1, 2).Stdout(), "1 2")
// Too few arguments.
- sh.Fn(exitFn)
- reset()
- sh.Fn(sleepFn, time.Second)
- reset()
- sh.Fn(printfFn)
- reset()
+ setsErr(t, sh, func() { sh.FuncCmd(exitFunc) })
+ setsErr(t, sh, func() { sh.FuncCmd(sleepFunc, time.Second) })
+ setsErr(t, sh, func() { sh.FuncCmd(printfFunc) })
// Too many arguments.
- sh.Fn(exitFn, 0, 0)
- reset()
- sh.Fn(sleepFn, time.Second, 0, 0)
- reset()
+ setsErr(t, sh, func() { sh.FuncCmd(exitFunc, 0, 0) })
+ setsErr(t, sh, func() { sh.FuncCmd(sleepFunc, time.Second, 0, 0) })
// Wrong argument types.
- sh.Fn(exitFn, "foo")
- reset()
- sh.Fn(sleepFn, 0, 0)
- reset()
- sh.Fn(printfFn, 0)
- reset()
- sh.Fn(printfFn, 0, 0)
- reset()
+ setsErr(t, sh, func() { sh.FuncCmd(exitFunc, "foo") })
+ setsErr(t, sh, func() { sh.FuncCmd(sleepFunc, 0, 0) })
+ setsErr(t, sh, func() { sh.FuncCmd(printfFunc, 0) })
+ setsErr(t, sh, func() { sh.FuncCmd(printfFunc, 0, 0) })
// Wrong variadic argument types.
- sh.Fn(printIntsFn, 0.5)
- reset()
- sh.Fn(printIntsFn, 0, 0.5)
- reset()
- sh.Fn(printfIntsFn, "%v", 0.5)
- reset()
- sh.Fn(printfIntsFn, "%v", 0, 0.5)
- reset()
+ setsErr(t, sh, func() { sh.FuncCmd(printIntsFunc, 0.5) })
+ setsErr(t, sh, func() { sh.FuncCmd(printIntsFunc, 0, 0.5) })
+ setsErr(t, sh, func() { sh.FuncCmd(printfIntsFunc, "%v", 0.5) })
+ setsErr(t, sh, func() { sh.FuncCmd(printfIntsFunc, "%v", 0, 0.5) })
// Unsupported argument types.
var p *int
- sh.Fn(printFn, p)
- reset()
- sh.Fn(printfFn, "%v", p)
- reset()
+ setsErr(t, sh, func() { sh.FuncCmd(printFunc, p) })
+ setsErr(t, sh, func() { sh.FuncCmd(printfFunc, "%v", p) })
}
func TestStdin(t *testing.T) {
sh := gosh.NewShell(gosh.Opts{Fatalf: makeFatalf(t), Logf: t.Logf})
defer sh.Cleanup()
- c := sh.Main(catFn)
+ c := sh.FuncCmd(catFunc)
c.Stdin = "foo\n"
// We set c.Stdin and did not call c.StdinPipe(), so stdin should close and
// cat should exit immediately.
eq(t, c.Stdout(), "foo\n")
- c = sh.Main(catFn)
+ c = sh.FuncCmd(catFunc)
c.StdinPipe().Write([]byte("foo\n"))
// The "cat" command only exits when stdin is closed, so we must explicitly
// close the stdin pipe. Note, it's safe to call c.StdinPipe multiple times.
c.StdinPipe().Close()
eq(t, c.Stdout(), "foo\n")
- c = sh.Main(readFn)
+ c = sh.FuncCmd(readFunc)
c.StdinPipe().Write([]byte("foo\n"))
// The "read" command exits when it sees a newline, so Cmd.Wait (and thus
// Cmd.Run) should return immediately; it should not be necessary to close the
// stdin pipe.
c.Run()
- c = sh.Main(catFn)
+ c = sh.FuncCmd(catFunc)
// No stdin, so cat should exit immediately.
eq(t, c.Stdout(), "")
// It's an error (detected at command start time) to both set c.Stdin and call
// c.StdinPipe. Note, this indirectly tests that Shell.Cleanup works even if
// some Cmd.Start failed.
- c = sh.Main(catFn)
+ c = sh.FuncCmd(catFunc)
c.Stdin = "foo"
c.StdinPipe().Write([]byte("bar"))
c.StdinPipe().Close()
- sh.Opts.Fatalf = nil
- c.Start()
- nok(t, sh.Err)
+ setsErr(t, sh, func() { c.Start() })
}
-var writeFn = gosh.Register("writeFn", func(stdout, stderr bool) error {
+var writeFunc = gosh.RegisterFunc("writeFunc", func(stdout, stderr bool) error {
if stdout {
if _, err := os.Stdout.Write([]byte("A")); err != nil {
return err
@@ -403,7 +379,7 @@
defer sh.Cleanup()
// Write to stdout only.
- c := sh.Fn(writeFn, true, false)
+ c := sh.FuncCmd(writeFunc, true, false)
stdoutPipe, stderrPipe := c.StdoutPipe(), c.StderrPipe()
stdout, stderr := c.StdoutStderr()
eq(t, stdout, "AA")
@@ -412,7 +388,7 @@
eq(t, toString(t, stderrPipe), "")
// Write to stderr only.
- c = sh.Fn(writeFn, false, true)
+ c = sh.FuncCmd(writeFunc, false, true)
stdoutPipe, stderrPipe = c.StdoutPipe(), c.StderrPipe()
stdout, stderr = c.StdoutStderr()
eq(t, stdout, "")
@@ -421,7 +397,7 @@
eq(t, toString(t, stderrPipe), "BB")
// Write to both stdout and stderr.
- c = sh.Fn(writeFn, true, true)
+ c = sh.FuncCmd(writeFunc, true, true)
stdoutPipe, stderrPipe = c.StdoutPipe(), c.StderrPipe()
stdout, stderr = c.StdoutStderr()
eq(t, stdout, "AA")
@@ -430,11 +406,11 @@
eq(t, toString(t, stderrPipe), "BB")
}
-var writeMoreFn = gosh.Register("writeMoreFn", func() {
+var writeMoreFunc = gosh.RegisterFunc("writeMoreFunc", func() {
sh := gosh.NewShell(gosh.Opts{})
defer sh.Cleanup()
- c := sh.Fn(writeFn, true, true)
+ c := sh.FuncCmd(writeFunc, true, true)
c.AddStdoutWriter(gosh.NopWriteCloser(os.Stdout))
c.AddStderrWriter(gosh.NopWriteCloser(os.Stderr))
c.Run()
@@ -448,7 +424,7 @@
sh := gosh.NewShell(gosh.Opts{Fatalf: makeFatalf(t), Logf: t.Logf})
defer sh.Cleanup()
- stdout, stderr := sh.Fn(writeMoreFn).StdoutStderr()
+ stdout, stderr := sh.FuncCmd(writeMoreFunc).StdoutStderr()
eq(t, stdout, "AA stdout done")
eq(t, stderr, "BB stderr done")
}
@@ -458,27 +434,18 @@
sh := gosh.NewShell(gosh.Opts{Fatalf: makeFatalf(t), Logf: t.Logf})
defer sh.Cleanup()
- c := sh.Fn(writeMoreFn)
- sh.Opts.Fatalf = nil
- c.AddStdoutWriter(os.Stdout)
- nok(t, sh.Err)
- sh.Err = nil
- c.AddStdoutWriter(os.Stderr)
- nok(t, sh.Err)
- sh.Err = nil
- c.AddStderrWriter(os.Stdout)
- nok(t, sh.Err)
- sh.Err = nil
- c.AddStderrWriter(os.Stderr)
- nok(t, sh.Err)
- sh.Err = nil
+ c := sh.FuncCmd(writeMoreFunc)
+ setsErr(t, sh, func() { c.AddStdoutWriter(os.Stdout) })
+ setsErr(t, sh, func() { c.AddStdoutWriter(os.Stderr) })
+ setsErr(t, sh, func() { c.AddStderrWriter(os.Stdout) })
+ setsErr(t, sh, func() { c.AddStderrWriter(os.Stderr) })
}
func TestCombinedOutput(t *testing.T) {
sh := gosh.NewShell(gosh.Opts{Fatalf: makeFatalf(t), Logf: t.Logf})
defer sh.Cleanup()
- c := sh.Fn(writeFn, true, true)
+ c := sh.FuncCmd(writeFunc, true, true)
buf := &bytes.Buffer{}
c.AddStdoutWriter(gosh.NopWriteCloser(buf))
c.AddStderrWriter(gosh.NopWriteCloser(buf))
@@ -496,7 +463,7 @@
defer sh.Cleanup()
dir := sh.MakeTempDir()
- c := sh.Fn(writeFn, true, true)
+ c := sh.FuncCmd(writeFunc, true, true)
c.OutputDir = dir
c.Run()
@@ -531,7 +498,7 @@
sh := gosh.NewShell(gosh.Opts{Fatalf: makeFatalf(t), Logf: t.Logf})
defer sh.Cleanup()
- c := sh.Fn(writeFn, true, true)
+ c := sh.FuncCmd(writeFunc, true, true)
buf := &bytes.Buffer{}
wc := &countingWriteCloser{Writer: buf}
c.AddStdoutWriter(wc)
@@ -551,15 +518,16 @@
sh := gosh.NewShell(gosh.Opts{Fatalf: makeFatalf(t), Logf: t.Logf})
defer sh.Cleanup()
- echo := sh.Main(echoFn, "foo")
- cat := sh.Main(catFn)
+ echo := sh.FuncCmd(echoFunc)
+ echo.Args = append(echo.Args, "foo")
+ cat := sh.FuncCmd(catFunc)
echo.AddStdoutWriter(cat.StdinPipe())
echo.Start()
eq(t, cat.Stdout(), "foo\n")
// This time, pipe both stdout and stderr to cat's stdin.
- c := sh.Fn(writeFn, true, true)
- cat = sh.Main(catFn)
+ c := sh.FuncCmd(writeFunc, true, true)
+ cat = sh.FuncCmd(catFunc)
c.AddStdoutWriter(cat.StdinPipe())
c.AddStderrWriter(cat.StdinPipe())
c.Start()
@@ -568,21 +536,57 @@
eq(t, len(cat.Stdout()), 4)
}
-func TestShutdown(t *testing.T) {
+func TestSignal(t *testing.T) {
sh := gosh.NewShell(gosh.Opts{Fatalf: makeFatalf(t), Logf: t.Logf})
defer sh.Cleanup()
for _, d := range []time.Duration{0, time.Second} {
for _, s := range []os.Signal{os.Interrupt, os.Kill} {
fmt.Println(d, s)
- c := sh.Fn(sleepFn, d, 0)
+ c := sh.FuncCmd(sleepFunc, d, 0)
c.Start()
- // Wait for a bit to allow the zero-sleep commands to exit, to test that
- // Shutdown succeeds for an exited process.
+ // Wait for a bit to allow the zero-sleep commands to exit.
time.Sleep(100 * time.Millisecond)
- c.Shutdown(s)
+ c.Signal(s)
+ // Wait should succeed as long as the exit code was 0, regardless of
+ // whether the signal arrived or the process had already exited.
+ if s == os.Interrupt {
+ // Note: We don't call Wait in the {d: 0, s: os.Kill} case because doing
+ // so makes the test flaky on slow systems.
+ c.Wait()
+ } else if d == time.Second {
+ setsErr(t, sh, func() { c.Wait() })
+ }
}
}
+
+ // Signal should fail if Wait has been called.
+ c := sh.FuncCmd(sleepFunc, time.Duration(0), 0)
+ c.Run()
+ setsErr(t, sh, func() { c.Signal(os.Interrupt) })
+}
+
+func TestTerminate(t *testing.T) {
+ sh := gosh.NewShell(gosh.Opts{Fatalf: makeFatalf(t), Logf: t.Logf})
+ defer sh.Cleanup()
+
+ for _, d := range []time.Duration{0, time.Second} {
+ for _, s := range []os.Signal{os.Interrupt, os.Kill} {
+ fmt.Println(d, s)
+ c := sh.FuncCmd(sleepFunc, d, 0)
+ c.Start()
+ // Wait for a bit to allow the zero-sleep commands to exit.
+ time.Sleep(100 * time.Millisecond)
+ // Terminate should succeed regardless of the exit code, and regardless of
+ // whether the signal arrived or the process had already exited.
+ c.Terminate(s)
+ }
+ }
+
+ // Terminate should fail if Wait has been called.
+ c := sh.FuncCmd(sleepFunc, time.Duration(0), 0)
+ c.Run()
+ setsErr(t, sh, func() { c.Terminate(os.Interrupt) })
}
func TestShellWait(t *testing.T) {
@@ -592,14 +596,14 @@
d0 := time.Duration(0)
d200 := 200 * time.Millisecond
- c0 := sh.Fn(sleepFn, d0, 0) // not started
- c1 := sh.Fn(sleepFn, d0, 0) // failed to start
- c2 := sh.Fn(sleepFn, d200, 0) // running and will succeed
- c3 := sh.Fn(sleepFn, d200, 1) // running and will fail
- c4 := sh.Fn(sleepFn, d0, 0) // succeeded
- c5 := sh.Fn(sleepFn, d0, 0) // succeeded, called wait
- c6 := sh.Fn(sleepFn, d0, 1) // failed
- c7 := sh.Fn(sleepFn, d0, 1) // failed, called wait
+ c0 := sh.FuncCmd(sleepFunc, d0, 0) // not started
+ c1 := sh.FuncCmd(sleepFunc, d0, 0) // failed to start
+ c2 := sh.FuncCmd(sleepFunc, d200, 0) // running and will succeed
+ c3 := sh.FuncCmd(sleepFunc, d200, 1) // running and will fail
+ c4 := sh.FuncCmd(sleepFunc, d0, 0) // succeeded
+ c5 := sh.FuncCmd(sleepFunc, d0, 0) // succeeded, called wait
+ c6 := sh.FuncCmd(sleepFunc, d0, 1) // failed
+ c7 := sh.FuncCmd(sleepFunc, d0, 1) // failed, called wait
c3.ExitErrorIsOk = true
c6.ExitErrorIsOk = true
@@ -608,11 +612,7 @@
// Configure the "failed to start" command.
c1.StdinPipe()
c1.Stdin = "foo"
- sh.Opts.Fatalf = nil
- c1.Start()
- nok(t, sh.Err)
- sh.Err = nil
- sh.Opts.Fatalf = makeFatalf(t)
+ setsErr(t, sh, func() { c1.Start() })
// Start commands, then wait for them to exit.
for _, c := range []*gosh.Cmd{c2, c3, c4, c5, c6, c7} {
@@ -627,8 +627,8 @@
// It should be possible to run existing unstarted commands, and to create and
// run new commands, after calling Shell.Wait.
c0.Run()
- sh.Fn(sleepFn, d0, 0).Run()
- sh.Fn(sleepFn, d0, 0).Start()
+ sh.FuncCmd(sleepFunc, d0, 0).Run()
+ sh.FuncCmd(sleepFunc, d0, 0).Start()
// Call Shell.Wait again.
sh.Wait()
@@ -639,24 +639,22 @@
defer sh.Cleanup()
// Exit code 0 is not an error.
- c := sh.Fn(exitFn, 0)
+ c := sh.FuncCmd(exitFunc, 0)
c.Run()
ok(t, c.Err)
ok(t, sh.Err)
// Exit code 1 is an error.
- c = sh.Fn(exitFn, 1)
+ c = sh.FuncCmd(exitFunc, 1)
c.ExitErrorIsOk = true
c.Run()
nok(t, c.Err)
ok(t, sh.Err)
// If ExitErrorIsOk is false, exit code 1 triggers sh.HandleError.
- c = sh.Fn(exitFn, 1)
- sh.Opts.Fatalf = nil
- c.Run()
+ c = sh.FuncCmd(exitFunc, 1)
+ setsErr(t, sh, func() { c.Run() })
nok(t, c.Err)
- nok(t, sh.Err)
}
// Tests that sh.Ok panics under various conditions.
@@ -727,5 +725,6 @@
}
func TestMain(m *testing.M) {
- os.Exit(gosh.Run(m.Run))
+ gosh.InitMain()
+ os.Exit(m.Run())
}
diff --git a/vlog/flags_test.go b/vlog/flags_test.go
index f241165..f22ed59 100644
--- a/vlog/flags_test.go
+++ b/vlog/flags_test.go
@@ -15,7 +15,7 @@
"v.io/x/lib/vlog"
)
-var child = gosh.Register("child", func() error {
+var child = gosh.RegisterFunc("child", func() error {
tmp := filepath.Join(os.TempDir(), "foo")
flag.Set("log_dir", tmp)
flag.Set("vmodule", "foo=2")
@@ -44,9 +44,10 @@
func TestFlags(t *testing.T) {
sh := gosh.NewShell(gosh.Opts{Fatalf: t.Fatalf, Logf: t.Logf})
defer sh.Cleanup()
- sh.Fn(child).Run()
+ sh.FuncCmd(child).Run()
}
func TestMain(m *testing.M) {
- os.Exit(gosh.Run(m.Run))
+ gosh.InitMain()
+ os.Exit(m.Run())
}