Merge "services/device/internal/impl: additional app liveness mechanism"
diff --git a/cmd/mounttable/impl_test.go b/cmd/mounttable/impl_test.go
index 4e50553..cc46ed1 100644
--- a/cmd/mounttable/impl_test.go
+++ b/cmd/mounttable/impl_test.go
@@ -131,7 +131,7 @@
 	env := &cmdline.Env{Stdout: &stdout, Stderr: &stderr}
 
 	// Test the 'glob' command.
-	if err := v23cmd.ParseAndRun(cmdRoot, ctx, env, []string{"glob", naming.JoinAddressName(endpoint.String(), ""), "*"}); err != nil {
+	if err := v23cmd.ParseAndRunForTest(cmdRoot, ctx, env, []string{"glob", naming.JoinAddressName(endpoint.String(), ""), "*"}); err != nil {
 		t.Fatalf("%v", err)
 	}
 	const deadRE = `\(Deadline ([^)]+)\)`
@@ -141,7 +141,7 @@
 	stdout.Reset()
 
 	// Test the 'mount' command.
-	if err := v23cmd.ParseAndRun(cmdRoot, ctx, env, []string{"mount", "server", endpoint.Name(), "123s"}); err != nil {
+	if err := v23cmd.ParseAndRunForTest(cmdRoot, ctx, env, []string{"mount", "server", endpoint.Name(), "123s"}); err != nil {
 		t.Fatalf("%v", err)
 	}
 	if got, want := strings.TrimSpace(stdout.String()), "Name mounted successfully."; got != want {
@@ -150,7 +150,7 @@
 	stdout.Reset()
 
 	// Test the 'unmount' command.
-	if err := v23cmd.ParseAndRun(cmdRoot, ctx, env, []string{"unmount", "server", endpoint.Name()}); err != nil {
+	if err := v23cmd.ParseAndRunForTest(cmdRoot, ctx, env, []string{"unmount", "server", endpoint.Name()}); err != nil {
 		t.Fatalf("%v", err)
 	}
 	if got, want := strings.TrimSpace(stdout.String()), "Unmount successful or name not mounted."; got != want {
@@ -160,7 +160,7 @@
 
 	// Test the 'resolvestep' command.
 	vlog.Infof("resovestep %s", naming.JoinAddressName(endpoint.String(), "name"))
-	if err := v23cmd.ParseAndRun(cmdRoot, ctx, env, []string{"resolvestep", naming.JoinAddressName(endpoint.String(), "name")}); err != nil {
+	if err := v23cmd.ParseAndRunForTest(cmdRoot, ctx, env, []string{"resolvestep", naming.JoinAddressName(endpoint.String(), "name")}); err != nil {
 		t.Fatalf("%v", err)
 	}
 	if got, wantRE := strings.TrimSpace(stdout.String()), regexp.MustCompile(`Servers: \[\{server1 [^}]+\}\] Suffix: "name" MT: false`); !wantRE.MatchString(got) {
diff --git a/cmd/vrpc/vrpc_test.go b/cmd/vrpc/vrpc_test.go
index a6643fc..4ff920f 100644
--- a/cmd/vrpc/vrpc_test.go
+++ b/cmd/vrpc/vrpc_test.go
@@ -144,7 +144,7 @@
 	var stdout, stderr bytes.Buffer
 	env := &cmdline.Env{Stdout: &stdout, Stderr: &stderr}
 	args := []string{"signature", fmt.Sprintf("-show-reserved=%v", showReserved), name}
-	if err := v23cmd.ParseAndRun(cmdVRPC, ctx, env, args); err != nil {
+	if err := v23cmd.ParseAndRunForTest(cmdVRPC, ctx, env, args); err != nil {
 		t.Fatalf("%s: %v", args, err)
 	}
 
@@ -292,7 +292,7 @@
 	for _, test := range tests {
 		var stdout, stderr bytes.Buffer
 		env := &cmdline.Env{Stdout: &stdout, Stderr: &stderr}
-		if err := v23cmd.ParseAndRun(cmdVRPC, ctx, env, []string{"signature", name, test.Method}); err != nil {
+		if err := v23cmd.ParseAndRunForTest(cmdVRPC, ctx, env, []string{"signature", name, test.Method}); err != nil {
 			t.Errorf("%q failed: %v", test.Method, err)
 			continue
 		}
@@ -335,7 +335,7 @@
 	for _, test := range tests {
 		var stdout, stderr bytes.Buffer
 		env := &cmdline.Env{Stdout: &stdout, Stderr: &stderr}
-		if err := v23cmd.ParseAndRun(cmdVRPC, ctx, env, []string{"call", name, test.Method, test.InArgs}); err != nil {
+		if err := v23cmd.ParseAndRunForTest(cmdVRPC, ctx, env, []string{"call", name, test.Method, test.InArgs}); err != nil {
 			t.Errorf("%q(%s) failed: %v", test.Method, test.InArgs, err)
 			continue
 		}
diff --git a/lib/v23cmd/v23cmd.go b/lib/v23cmd/v23cmd.go
index 86154a3..3174701 100644
--- a/lib/v23cmd/v23cmd.go
+++ b/lib/v23cmd/v23cmd.go
@@ -11,80 +11,83 @@
 //
 // The RunnerFunc package-level function allows us to write run functions of the
 // form Run(ctx, env, args), retaining static type-safety, and also getting the
-// flag.Parse ordering right.  In addition the Run and ParseAndRun functions may
-// be called in tests, to pass your own ctx into your command runners.
+// flag.Parse ordering right.
 package v23cmd
 
 import (
-	"errors"
-
 	"v.io/v23"
 	"v.io/v23/context"
 	"v.io/x/lib/cmdline"
 )
 
-var (
-	ErrUnknownRunner = errors.New("v23cmd: unknown runner")
-
-	// The strategy behind this package is this slice, which only grows and never
-	// shrinks.  Here we maintain a mapping between an index number and the
-	// originally registered function, so that we can look the function up in a
-	// typesafe manner.
-	funcs  []func(*context.T, *cmdline.Env, []string) error
-	initFn = v23.Init
-)
-
-// indexRunner implements cmdline.Runner by looking up the index in the funcs
-// slice to retrieve the originally registered function.
-type indexRunner uint
-
-func (ix indexRunner) Run(env *cmdline.Env, args []string) error {
-	if int(ix) < len(funcs) {
-		ctx, shutdown := initFn()
-		err := funcs[ix](ctx, env, args)
-		shutdown()
-		return err
-	}
-	return ErrUnknownRunner
+type runner struct {
+	run  func(*context.T, *cmdline.Env, []string) error
+	init func() (*context.T, v23.Shutdown)
 }
 
-// RunnerFunc behaves similarly to cmdline.RunnerFunc, but takes a run function
-// fn that includes a context as the first arg.  The context is created via
-// v23.Init when Run is called on the returned Runner.
-func RunnerFunc(fn func(*context.T, *cmdline.Env, []string) error) cmdline.Runner {
-	ix := indexRunner(len(funcs))
-	funcs = append(funcs, fn)
-	return ix
+func (r runner) Run(env *cmdline.Env, args []string) error {
+	ctx, shutdown := r.init()
+	defer shutdown()
+	return r.run(ctx, env, args)
 }
 
-// Lookup returns the function registered via RunnerFunc corresponding to
-// runner, or nil if it doesn't exist.
-func Lookup(runner cmdline.Runner) func(*context.T, *cmdline.Env, []string) error {
-	if ix, ok := runner.(indexRunner); ok && int(ix) < len(funcs) {
-		return funcs[ix]
-	}
-	return nil
+// RunnerFunc is like cmdline.RunnerFunc, but takes a run function that includes
+// a context as the first arg.  The context is created via v23.Init when Run is
+// called on the returned Runner.
+func RunnerFunc(run func(*context.T, *cmdline.Env, []string) error) cmdline.Runner {
+	return runner{run, v23.Init}
 }
 
-// Run performs Lookup, and then runs the function with the given ctx, env and
-// args.
+// RunnerFuncWithInit is like RunnerFunc, but allows specifying the init
+// function used to create the context.
+//
+// This is typically used to set properties on the context before it is passed
+// to the run function.  E.g. you may use this to set a deadline on the context:
+//
+//   var cmdRoot = &cmdline.Command{
+//     Runner: v23cmd.RunnerFuncWithInit(runRoot, initWithDeadline)
+//     ...
+//   }
+//
+//   func runRoot(ctx *context.T, env *cmdline.Env, args []string) error {
+//     ...
+//   }
+//
+//   func initWithDeadline() (*context.T, v23.Shutdown) {
+//     ctx, shutdown := v23.Init()
+//     ctx, cancel := context.WithTimeout(ctx, time.Minute)
+//     return ctx, func(){ cancel(); shutdown() }
+//   }
+//
+//   func main() {
+//     cmdline.Main(cmdRoot)
+//   }
+//
+// An alternative to the above example is to call context.WithTimeout within
+// runRoot.  The advantage of using RunnerFuncWithInit is that your regular code
+// can use a context with a 1 minute timeout, while your testing code can use
+// v23cmd.ParseAndRunForTest to pass a context with a 10 second timeout.
+func RunnerFuncWithInit(run func(*context.T, *cmdline.Env, []string) error, init func() (*context.T, v23.Shutdown)) cmdline.Runner {
+	return runner{run, init}
+}
+
+// ParseAndRunForTest parses the cmd with the given env and args, and calls Run
+// on the returned runner.  If the runner was created by the v23cmd package,
+// calls the run function directly with the given ctx, env and args.
 //
 // Doesn't call v23.Init; the context initialization is up to you.
-func Run(runner cmdline.Runner, ctx *context.T, env *cmdline.Env, args []string) error {
-	if fn := Lookup(runner); fn != nil {
-		return fn(ctx, env, args)
-	}
-	return ErrUnknownRunner
-}
-
-// ParseAndRun parses the cmd with the given env and args, and calls Run to run
-// the returned runner with the ctx, env and args.
 //
-// Doesn't call v23.Init; the context initialization is up to you.
-func ParseAndRun(cmd *cmdline.Command, ctx *context.T, env *cmdline.Env, args []string) error {
-	runner, args, err := cmdline.Parse(cmd, env, args)
+// Only meant to be called within tests - if used in non-test code the ordering
+// of flag.Parse calls will be wrong.  The correct ordering is for cmdline.Parse
+// to be called before v23.Init, but this function takes a ctx argument and then
+// calls cmdline.Parse.
+func ParseAndRunForTest(cmd *cmdline.Command, ctx *context.T, env *cmdline.Env, args []string) error {
+	r, args, err := cmdline.Parse(cmd, env, args)
 	if err != nil {
 		return err
 	}
-	return Run(runner, ctx, env, args)
+	if x, ok := r.(runner); ok {
+		return x.run(ctx, env, args)
+	}
+	return r.Run(env, args)
 }
diff --git a/lib/v23cmd/v23cmd_test.go b/lib/v23cmd/v23cmd_test.go
new file mode 100644
index 0000000..c053962
--- /dev/null
+++ b/lib/v23cmd/v23cmd_test.go
@@ -0,0 +1,117 @@
+// Copyright 2015 The Vanadium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package v23cmd
+
+import (
+	"bytes"
+	"fmt"
+	"testing"
+
+	"v.io/v23"
+	"v.io/v23/context"
+	"v.io/x/lib/cmdline"
+	_ "v.io/x/ref/runtime/factories/generic"
+)
+
+var cmdRoot = &cmdline.Command{
+	Name:     "root",
+	Short:    "root",
+	Long:     "root",
+	Children: []*cmdline.Command{cmdNoV23, cmdNoInit, cmdWithInit},
+}
+
+var cmdNoV23 = &cmdline.Command{
+	Name:   "no_v23",
+	Short:  "no_v23",
+	Long:   "no_v23",
+	Runner: cmdline.RunnerFunc(runNoV23),
+}
+
+var cmdNoInit = &cmdline.Command{
+	Name:   "no_init",
+	Short:  "no_init",
+	Long:   "no_init",
+	Runner: RunnerFunc(runNoInit),
+}
+
+var cmdWithInit = &cmdline.Command{
+	Name:   "with_init",
+	Short:  "with_init",
+	Long:   "with_init",
+	Runner: RunnerFuncWithInit(runWithInit, initFunc),
+}
+
+func runNoV23(env *cmdline.Env, args []string) error {
+	fmt.Fprintf(env.Stdout, "NoV23")
+	return nil
+}
+
+func runNoInit(ctx *context.T, env *cmdline.Env, args []string) error {
+	fmt.Fprintf(env.Stdout, "NoInit %v %v", ctx.Value(initKey), ctx.Value(forTestKey))
+	return nil
+}
+
+func runWithInit(ctx *context.T, env *cmdline.Env, args []string) error {
+	fmt.Fprintf(env.Stdout, "WithInit %v %v", ctx.Value(initKey), ctx.Value(forTestKey))
+	return nil
+}
+
+const (
+	initKey      = "init key"
+	initValue    = "<init value>"
+	forTestKey   = "for test key"
+	forTestValue = "<for test value>"
+)
+
+func initFunc() (*context.T, v23.Shutdown) {
+	ctx, shutdown := v23.Init()
+	return context.WithValue(ctx, initKey, initValue), shutdown
+}
+
+func TestParseAndRun(t *testing.T) {
+	tests := []struct {
+		cmd    string
+		stdout string
+	}{
+		{"no_v23", "NoV23"},
+		{"no_init", "NoInit <nil> <nil>"},
+		{"with_init", "WithInit <init value> <nil>"},
+	}
+	for _, test := range tests {
+		var stdout bytes.Buffer
+		env := &cmdline.Env{Stdout: &stdout}
+		if err := cmdline.ParseAndRun(cmdRoot, env, []string{test.cmd}); err != nil {
+			t.Errorf("ParseAndRun failed: %v", err)
+		}
+		if got, want := stdout.String(), test.stdout; got != want {
+			t.Errorf("got stdout %q, want %q", got, want)
+		}
+	}
+}
+
+func TestParseAndRunForTest(t *testing.T) {
+	tests := []struct {
+		cmd    string
+		stdout string
+	}{
+		{"no_v23", "NoV23"},
+		{"no_init", "NoInit <nil> <for test value>"},
+		{"with_init", "WithInit <nil> <for test value>"},
+	}
+	for _, test := range tests {
+		ctx, shutdown := v23.Init()
+		ctx = context.WithValue(ctx, forTestKey, forTestValue)
+
+		var stdout bytes.Buffer
+		env := &cmdline.Env{Stdout: &stdout}
+		if err := ParseAndRunForTest(cmdRoot, ctx, env, []string{test.cmd}); err != nil {
+			t.Errorf("ParseAndRunForTest failed: %v", err)
+		}
+		if got, want := stdout.String(), test.stdout; got != want {
+			t.Errorf("got stdout %q, want %q", got, want)
+		}
+		shutdown()
+	}
+}
diff --git a/services/application/application/impl_test.go b/services/application/application/impl_test.go
index d9b872f..7b55427 100644
--- a/services/application/application/impl_test.go
+++ b/services/application/application/impl_test.go
@@ -157,7 +157,7 @@
 	profile := "myprofile"
 
 	// Test the 'Match' command.
-	if err := v23cmd.ParseAndRun(cmdRoot, ctx, env, []string{"match", appName, profile}); err != nil {
+	if err := v23cmd.ParseAndRunForTest(cmdRoot, ctx, env, []string{"match", appName, profile}); err != nil {
 		t.Fatalf("%v", err)
 	}
 	if expected, got := jsonEnv, strings.TrimSpace(stdout.String()); got != expected {
@@ -178,7 +178,7 @@
 	if err = f.Close(); err != nil {
 		t.Fatalf("%v", err)
 	}
-	if err := v23cmd.ParseAndRun(cmdRoot, ctx, env, []string{"put", appName, profile, fileName}); err != nil {
+	if err := v23cmd.ParseAndRunForTest(cmdRoot, ctx, env, []string{"put", appName, profile, fileName}); err != nil {
 		t.Fatalf("%v", err)
 	}
 	if expected, got := "Application envelope added successfully.", strings.TrimSpace(stdout.String()); got != expected {
@@ -187,7 +187,7 @@
 	stdout.Reset()
 
 	// Test the 'remove' command.
-	if err := v23cmd.ParseAndRun(cmdRoot, ctx, env, []string{"remove", appName, profile}); err != nil {
+	if err := v23cmd.ParseAndRunForTest(cmdRoot, ctx, env, []string{"remove", appName, profile}); err != nil {
 		t.Fatalf("%v", err)
 	}
 	if expected, got := "Application envelope removed successfully.", strings.TrimSpace(stdout.String()); got != expected {
@@ -197,7 +197,7 @@
 
 	// Test the 'edit' command. (nothing changed)
 	os.Setenv("EDITOR", "true")
-	if err := v23cmd.ParseAndRun(cmdRoot, ctx, env, []string{"edit", appName, profile}); err != nil {
+	if err := v23cmd.ParseAndRunForTest(cmdRoot, ctx, env, []string{"edit", appName, profile}); err != nil {
 		t.Fatalf("%v", err)
 	}
 	if expected, got := "Nothing changed", strings.TrimSpace(stdout.String()); got != expected {
@@ -207,7 +207,7 @@
 
 	// Test the 'edit' command.
 	os.Setenv("EDITOR", "perl -pi -e 's/arg1/arg111/'")
-	if err := v23cmd.ParseAndRun(cmdRoot, ctx, env, []string{"edit", appName, profile}); err != nil {
+	if err := v23cmd.ParseAndRunForTest(cmdRoot, ctx, env, []string{"edit", appName, profile}); err != nil {
 		t.Fatalf("%v", err)
 	}
 	if expected, got := "Application envelope updated successfully.", strings.TrimSpace(stdout.String()); got != expected {
diff --git a/services/binary/binary/impl_test.go b/services/binary/binary/impl_test.go
index 85414b3..36a5c01 100644
--- a/services/binary/binary/impl_test.go
+++ b/services/binary/binary/impl_test.go
@@ -141,7 +141,7 @@
 	env := &cmdline.Env{Stdout: &out, Stderr: &out}
 
 	// Test the 'delete' command.
-	if err := v23cmd.ParseAndRun(cmdRoot, ctx, env, []string{"delete", naming.JoinAddressName(endpoint.String(), "exists")}); err != nil {
+	if err := v23cmd.ParseAndRunForTest(cmdRoot, ctx, env, []string{"delete", naming.JoinAddressName(endpoint.String(), "exists")}); err != nil {
 		t.Fatalf("%v failed: %v\n%v", "delete", err, out.String())
 	}
 	if expected, got := "Binary deleted successfully", strings.TrimSpace(out.String()); got != expected {
@@ -157,7 +157,7 @@
 	defer os.RemoveAll(dir)
 	file := path.Join(dir, "testfile")
 	defer os.Remove(file)
-	if err := v23cmd.ParseAndRun(cmdRoot, ctx, env, []string{"download", naming.JoinAddressName(endpoint.String(), "exists"), file}); err != nil {
+	if err := v23cmd.ParseAndRunForTest(cmdRoot, ctx, env, []string{"download", naming.JoinAddressName(endpoint.String(), "exists"), file}); err != nil {
 		t.Fatalf("%v failed: %v\n%v", "download", err, out.String())
 	}
 	if expected, got := "Binary downloaded to file "+file, strings.TrimSpace(out.String()); got != expected {
@@ -173,13 +173,13 @@
 	out.Reset()
 
 	// Test the 'upload' command.
-	if err := v23cmd.ParseAndRun(cmdRoot, ctx, env, []string{"upload", naming.JoinAddressName(endpoint.String(), "exists"), file}); err != nil {
+	if err := v23cmd.ParseAndRunForTest(cmdRoot, ctx, env, []string{"upload", naming.JoinAddressName(endpoint.String(), "exists"), file}); err != nil {
 		t.Fatalf("%v failed: %v\n%v", "upload", err, out.String())
 	}
 	out.Reset()
 
 	// Test the 'url' command.
-	if err := v23cmd.ParseAndRun(cmdRoot, ctx, env, []string{"url", naming.JoinAddressName(endpoint.String(), "")}); err != nil {
+	if err := v23cmd.ParseAndRunForTest(cmdRoot, ctx, env, []string{"url", naming.JoinAddressName(endpoint.String(), "")}); err != nil {
 		t.Fatalf("%v failed: %v\n%v", "url", err, out.String())
 	}
 	if expected, got := "test-download-url", strings.TrimSpace(out.String()); got != expected {
diff --git a/services/build/build/impl_test.go b/services/build/build/impl_test.go
index 4df3e5a..c37c367 100644
--- a/services/build/build/impl_test.go
+++ b/services/build/build/impl_test.go
@@ -79,7 +79,7 @@
 	var stdout, stderr bytes.Buffer
 	env := &cmdline.Env{Stdout: &stdout, Stderr: &stderr}
 	args := []string{"build", naming.JoinAddressName(endpoint.String(), ""), "v.io/x/ref/services/build/build"}
-	if err := v23cmd.ParseAndRun(cmdRoot, ctx, env, args); err != nil {
+	if err := v23cmd.ParseAndRunForTest(cmdRoot, ctx, env, args); err != nil {
 		t.Fatalf("Run failed: %v", err)
 	}
 	if got, want := strings.TrimSpace(stdout.String()), ""; got != want {
diff --git a/services/device/device/acl_test.go b/services/device/device/acl_test.go
index 3e76185..c33e717 100644
--- a/services/device/device/acl_test.go
+++ b/services/device/device/acl_test.go
@@ -60,7 +60,7 @@
 		err:     nil,
 	}})
 
-	if err := v23cmd.ParseAndRun(cmd, ctx, env, []string{"acl", "get", deviceName}); err != nil {
+	if err := v23cmd.ParseAndRunForTest(cmd, ctx, env, []string{"acl", "get", deviceName}); err != nil {
 		t.Fatalf("error: %v", err)
 	}
 	if expected, got := strings.TrimSpace(`
@@ -93,7 +93,7 @@
 	deviceName := endpoint.Name()
 
 	// Some tests to validate parse.
-	if err := v23cmd.ParseAndRun(cmd, ctx, env, []string{"acl", "set", deviceName}); err == nil {
+	if err := v23cmd.ParseAndRunForTest(cmd, ctx, env, []string{"acl", "set", deviceName}); err == nil {
 		t.Fatalf("failed to correctly detect insufficient parameters")
 	}
 	if expected, got := "ERROR: set: incorrect number of arguments 1, must be 1 + 2n", strings.TrimSpace(stderr.String()); !strings.HasPrefix(got, expected) {
@@ -102,7 +102,7 @@
 
 	stderr.Reset()
 	stdout.Reset()
-	if err := v23cmd.ParseAndRun(cmd, ctx, env, []string{"acl", "set", deviceName, "foo"}); err == nil {
+	if err := v23cmd.ParseAndRunForTest(cmd, ctx, env, []string{"acl", "set", deviceName, "foo"}); err == nil {
 		t.Fatalf("failed to correctly detect insufficient parameters")
 	}
 	if expected, got := "ERROR: set: incorrect number of arguments 2, must be 1 + 2n", strings.TrimSpace(stderr.String()); !strings.HasPrefix(got, expected) {
@@ -111,7 +111,7 @@
 
 	stderr.Reset()
 	stdout.Reset()
-	if err := v23cmd.ParseAndRun(cmd, ctx, env, []string{"acl", "set", deviceName, "foo", "bar", "ohno"}); err == nil {
+	if err := v23cmd.ParseAndRunForTest(cmd, ctx, env, []string{"acl", "set", deviceName, "foo", "bar", "ohno"}); err == nil {
 		t.Fatalf("failed to correctly detect insufficient parameters")
 	}
 	if expected, got := "ERROR: set: incorrect number of arguments 4, must be 1 + 2n", strings.TrimSpace(stderr.String()); !strings.HasPrefix(got, expected) {
@@ -120,7 +120,7 @@
 
 	stderr.Reset()
 	stdout.Reset()
-	if err := v23cmd.ParseAndRun(cmd, ctx, env, []string{"acl", "set", deviceName, "foo", "!"}); err == nil {
+	if err := v23cmd.ParseAndRunForTest(cmd, ctx, env, []string{"acl", "set", deviceName, "foo", "!"}); err == nil {
 		t.Fatalf("failed to detect invalid parameter")
 	}
 	if expected, got := "ERROR: failed to parse access tags for \"foo\": empty access tag", strings.TrimSpace(stderr.String()); !strings.HasPrefix(got, expected) {
@@ -166,7 +166,7 @@
 	// - Adds a blacklist entry for "friend/alice"  for "Admin"
 	// - Edits existing entry for "self" (adding "Write" access)
 	// - Removes entry for "other/bob/baddevice"
-	if err := v23cmd.ParseAndRun(cmd, ctx, env, []string{
+	if err := v23cmd.ParseAndRunForTest(cmd, ctx, env, []string{
 		"acl",
 		"set",
 		deviceName,
@@ -240,7 +240,7 @@
 	},
 	})
 
-	if err := v23cmd.ParseAndRun(cmd, ctx, env, []string{"acl", "set", deviceName, "vana/bad", "Read"}); err == nil {
+	if err := v23cmd.ParseAndRunForTest(cmd, ctx, env, []string{"acl", "set", deviceName, "vana/bad", "Read"}); err == nil {
 		t.Fatalf("GetPermissions RPC inside perms set command failed but error wrongly not detected")
 	} else if expected, got := `^GetPermissions\(`+deviceName+`\) failed:.*oops!`, err.Error(); !regexp.MustCompile(expected).MatchString(got) {
 		t.Fatalf("Unexpected output from list. Got %q, regexp %q", got, expected)
@@ -272,7 +272,7 @@
 		verror.New(errOops, nil),
 	})
 
-	if err := v23cmd.ParseAndRun(cmd, ctx, env, []string{"acl", "set", deviceName, "friend", "Read"}); err == nil {
+	if err := v23cmd.ParseAndRunForTest(cmd, ctx, env, []string{"acl", "set", deviceName, "friend", "Read"}); err == nil {
 		t.Fatalf("SetPermissions should have failed: %v", err)
 	} else if expected, got := `^SetPermissions\(`+deviceName+`\) failed:.*oops!`, err.Error(); !regexp.MustCompile(expected).MatchString(got) {
 		t.Fatalf("Unexpected output from list. Got %q, regexp %q", got, expected)
diff --git a/services/device/device/impl_test.go b/services/device/device/impl_test.go
index fdef9b9..2f43945 100644
--- a/services/device/device/impl_test.go
+++ b/services/device/device/impl_test.go
@@ -61,7 +61,7 @@
 		err: nil,
 	}})
 
-	if err := v23cmd.ParseAndRun(cmd, ctx, env, []string{"associate", "list", deviceName}); err != nil {
+	if err := v23cmd.ParseAndRunForTest(cmd, ctx, env, []string{"associate", "list", deviceName}); err != nil {
 		t.Fatalf("%v", err)
 	}
 	if expected, got := "root/self alice_self_account\nroot/other alice_other_account", strings.TrimSpace(stdout.String()); got != expected {
@@ -74,7 +74,7 @@
 	stdout.Reset()
 
 	// Test list with bad parameters.
-	if err := v23cmd.ParseAndRun(cmd, ctx, env, []string{"associate", "list", deviceName, "hello"}); err == nil {
+	if err := v23cmd.ParseAndRunForTest(cmd, ctx, env, []string{"associate", "list", deviceName, "hello"}); err == nil {
 		t.Fatalf("wrongly failed to receive a non-nil error.")
 	}
 	if got, expected := len(rootTape.Play()), 0; got != expected {
@@ -99,7 +99,7 @@
 	env := &cmdline.Env{Stdout: &stdout, Stderr: &stderr}
 	deviceName := naming.JoinAddressName(endpoint.String(), "")
 
-	if err := v23cmd.ParseAndRun(cmd, ctx, env, []string{"add", "one"}); err == nil {
+	if err := v23cmd.ParseAndRunForTest(cmd, ctx, env, []string{"add", "one"}); err == nil {
 		t.Fatalf("wrongly failed to receive a non-nil error.")
 	}
 	rootTape := tapes.forSuffix("")
@@ -110,7 +110,7 @@
 	stdout.Reset()
 
 	rootTape.SetResponses([]interface{}{nil})
-	if err := v23cmd.ParseAndRun(cmd, ctx, env, []string{"associate", "add", deviceName, "alice", "root/self"}); err != nil {
+	if err := v23cmd.ParseAndRunForTest(cmd, ctx, env, []string{"associate", "add", deviceName, "alice", "root/self"}); err != nil {
 		t.Fatalf("%v", err)
 	}
 	expected := []interface{}{
@@ -123,7 +123,7 @@
 	stdout.Reset()
 
 	rootTape.SetResponses([]interface{}{nil})
-	if err := v23cmd.ParseAndRun(cmd, ctx, env, []string{"associate", "add", deviceName, "alice", "root/other", "root/self"}); err != nil {
+	if err := v23cmd.ParseAndRunForTest(cmd, ctx, env, []string{"associate", "add", deviceName, "alice", "root/other", "root/self"}); err != nil {
 		t.Fatalf("%v", err)
 	}
 	expected = []interface{}{
@@ -151,7 +151,7 @@
 	env := &cmdline.Env{Stdout: &stdout, Stderr: &stderr}
 	deviceName := naming.JoinAddressName(endpoint.String(), "")
 
-	if err := v23cmd.ParseAndRun(cmd, ctx, env, []string{"remove", "one"}); err == nil {
+	if err := v23cmd.ParseAndRunForTest(cmd, ctx, env, []string{"remove", "one"}); err == nil {
 		t.Fatalf("wrongly failed to receive a non-nil error.")
 	}
 	rootTape := tapes.forSuffix("")
@@ -162,7 +162,7 @@
 	stdout.Reset()
 
 	rootTape.SetResponses([]interface{}{nil})
-	if err := v23cmd.ParseAndRun(cmd, ctx, env, []string{"associate", "remove", deviceName, "root/self"}); err != nil {
+	if err := v23cmd.ParseAndRunForTest(cmd, ctx, env, []string{"associate", "remove", deviceName, "root/self"}); err != nil {
 		t.Fatalf("%v", err)
 	}
 	expected := []interface{}{
@@ -258,7 +258,7 @@
 			c.args = append([]string{fmt.Sprintf("--packages=%s", string(jsonPackages))}, c.args...)
 		}
 		c.args = append([]string{"install"}, c.args...)
-		err := v23cmd.ParseAndRun(cmd, ctx, env, c.args)
+		err := v23cmd.ParseAndRunForTest(cmd, ctx, env, c.args)
 		if c.shouldErr {
 			if err == nil {
 				t.Fatalf("test case %d: wrongly failed to receive a non-nil error.", i)
@@ -304,7 +304,7 @@
 	}
 
 	// Confirm that we correctly enforce the number of arguments.
-	if err := v23cmd.ParseAndRun(cmd, ctx, env, []string{"claim", "nope"}); err == nil {
+	if err := v23cmd.ParseAndRunForTest(cmd, ctx, env, []string{"claim", "nope"}); err == nil {
 		t.Fatalf("wrongly failed to receive a non-nil error.")
 	}
 	if expected, got := "ERROR: claim: incorrect number of arguments, expected atleast 2 (max: 4), got 1", strings.TrimSpace(stderr.String()); !strings.HasPrefix(got, expected) {
@@ -315,7 +315,7 @@
 	rootTape := tapes.forSuffix("")
 	rootTape.Rewind()
 
-	if err := v23cmd.ParseAndRun(cmd, ctx, env, []string{"claim", "nope", "nope", "nope", "nope", "nope"}); err == nil {
+	if err := v23cmd.ParseAndRunForTest(cmd, ctx, env, []string{"claim", "nope", "nope", "nope", "nope", "nope"}); err == nil {
 		t.Fatalf("wrongly failed to receive a non-nil error.")
 	}
 	if expected, got := "ERROR: claim: incorrect number of arguments, expected atleast 2 (max: 4), got 5", strings.TrimSpace(stderr.String()); !strings.HasPrefix(got, expected) {
@@ -335,7 +335,7 @@
 			t.Fatalf("Failed to marshal principal public key: %v", err)
 		}
 	}
-	if err := v23cmd.ParseAndRun(cmd, ctx, env, []string{"claim", deviceName, "grant", pairingToken, base64.URLEncoding.EncodeToString(deviceKeyWrong)}); verror.ErrorID(err) != verror.ErrNotTrusted.ID {
+	if err := v23cmd.ParseAndRunForTest(cmd, ctx, env, []string{"claim", deviceName, "grant", pairingToken, base64.URLEncoding.EncodeToString(deviceKeyWrong)}); verror.ErrorID(err) != verror.ErrNotTrusted.ID {
 		t.Fatalf("wrongly failed to receive correct error on claim with incorrect device key:%v id:%v", err, verror.ErrorID(err))
 	}
 	stdout.Reset()
@@ -346,7 +346,7 @@
 	rootTape.SetResponses([]interface{}{
 		nil,
 	})
-	if err := v23cmd.ParseAndRun(cmd, ctx, env, []string{"claim", deviceName, "grant", pairingToken, base64.URLEncoding.EncodeToString(deviceKey)}); err != nil {
+	if err := v23cmd.ParseAndRunForTest(cmd, ctx, env, []string{"claim", deviceName, "grant", pairingToken, base64.URLEncoding.EncodeToString(deviceKey)}); err != nil {
 		t.Fatalf("Claim(%s, %s, %s) failed: %v", deviceName, "grant", pairingToken, err)
 	}
 	if got, expected := len(rootTape.Play()), 1; got != expected {
@@ -369,7 +369,7 @@
 	rootTape.SetResponses([]interface{}{
 		verror.New(errOops, nil),
 	})
-	if err := v23cmd.ParseAndRun(cmd, ctx, env, []string{"claim", deviceName, "grant", pairingToken}); err == nil {
+	if err := v23cmd.ParseAndRunForTest(cmd, ctx, env, []string{"claim", deviceName, "grant", pairingToken}); err == nil {
 		t.Fatal("claim() failed to detect error:", err)
 	}
 	expected = []interface{}{
@@ -398,7 +398,7 @@
 	appName := naming.JoinAddressName(endpoint.String(), "")
 
 	// Confirm that we correctly enforce the number of arguments.
-	if err := v23cmd.ParseAndRun(cmd, ctx, env, []string{"instantiate", "nope"}); err == nil {
+	if err := v23cmd.ParseAndRunForTest(cmd, ctx, env, []string{"instantiate", "nope"}); err == nil {
 		t.Fatalf("wrongly failed to receive a non-nil error.")
 	}
 	if expected, got := "ERROR: instantiate: incorrect number of arguments, expected 2, got 1", strings.TrimSpace(stderr.String()); !strings.HasPrefix(got, expected) {
@@ -409,7 +409,7 @@
 	rootTape := tapes.forSuffix("")
 	rootTape.Rewind()
 
-	if err := v23cmd.ParseAndRun(cmd, ctx, env, []string{"instantiate", "nope", "nope", "nope"}); err == nil {
+	if err := v23cmd.ParseAndRunForTest(cmd, ctx, env, []string{"instantiate", "nope", "nope", "nope"}); err == nil {
 		t.Fatalf("wrongly failed to receive a non-nil error.")
 	}
 	if expected, got := "ERROR: instantiate: incorrect number of arguments, expected 2, got 3", strings.TrimSpace(stderr.String()); !strings.HasPrefix(got, expected) {
@@ -425,7 +425,7 @@
 		instanceID: "app1",
 	},
 	})
-	if err := v23cmd.ParseAndRun(cmd, ctx, env, []string{"instantiate", appName, "grant"}); err != nil {
+	if err := v23cmd.ParseAndRunForTest(cmd, ctx, env, []string{"instantiate", appName, "grant"}); err != nil {
 		t.Fatalf("instantiate %s %s failed: %v", appName, "grant", err)
 	}
 
@@ -450,7 +450,7 @@
 		"",
 	},
 	})
-	if err := v23cmd.ParseAndRun(cmd, ctx, env, []string{"instantiate", appName, "grant"}); err == nil {
+	if err := v23cmd.ParseAndRunForTest(cmd, ctx, env, []string{"instantiate", appName, "grant"}); err == nil {
 		t.Fatalf("instantiate failed to detect error")
 	}
 	expected = []interface{}{
@@ -479,7 +479,7 @@
 	debugMessage := "the secrets of the universe, revealed"
 	rootTape := tapes.forSuffix("")
 	rootTape.SetResponses([]interface{}{debugMessage})
-	if err := v23cmd.ParseAndRun(cmd, ctx, env, []string{"debug", appName}); err != nil {
+	if err := v23cmd.ParseAndRunForTest(cmd, ctx, env, []string{"debug", appName}); err != nil {
 		t.Fatalf("%v", err)
 	}
 	if expected, got := debugMessage, strings.TrimSpace(stdout.String()); got != expected {
@@ -528,7 +528,7 @@
 		rootTape.Rewind()
 		stdout.Reset()
 		rootTape.SetResponses([]interface{}{c.tapeResponse})
-		if err := v23cmd.ParseAndRun(cmd, ctx, env, []string{"status", appName}); err != nil {
+		if err := v23cmd.ParseAndRunForTest(cmd, ctx, env, []string{"status", appName}); err != nil {
 			t.Errorf("%v", err)
 		}
 		if expected, got := c.expected, strings.TrimSpace(stdout.String()); got != expected {
diff --git a/services/device/device/instance_impl_test.go b/services/device/device/instance_impl_test.go
index 6f56cc2..4ee1777 100644
--- a/services/device/device/instance_impl_test.go
+++ b/services/device/device/instance_impl_test.go
@@ -38,7 +38,7 @@
 	appName := naming.JoinAddressName(endpoint.String(), "appname")
 
 	// Confirm that we correctly enforce the number of arguments.
-	if err := v23cmd.ParseAndRun(cmd, ctx, env, []string{"kill"}); err == nil {
+	if err := v23cmd.ParseAndRunForTest(cmd, ctx, env, []string{"kill"}); err == nil {
 		t.Fatalf("wrongly failed to receive a non-nil error.")
 	}
 	if expected, got := "ERROR: kill: incorrect number of arguments, expected 1, got 0", strings.TrimSpace(stderr.String()); !strings.HasPrefix(got, expected) {
@@ -49,7 +49,7 @@
 	appTape := tapes.forSuffix("appname")
 	appTape.Rewind()
 
-	if err := v23cmd.ParseAndRun(cmd, ctx, env, []string{"kill", "nope", "nope"}); err == nil {
+	if err := v23cmd.ParseAndRunForTest(cmd, ctx, env, []string{"kill", "nope", "nope"}); err == nil {
 		t.Fatalf("wrongly failed to receive a non-nil error.")
 	}
 	if expected, got := "ERROR: kill: incorrect number of arguments, expected 1, got 2", strings.TrimSpace(stderr.String()); !strings.HasPrefix(got, expected) {
@@ -64,7 +64,7 @@
 		nil,
 	})
 
-	if err := v23cmd.ParseAndRun(cmd, ctx, env, []string{"kill", appName}); err != nil {
+	if err := v23cmd.ParseAndRunForTest(cmd, ctx, env, []string{"kill", appName}); err != nil {
 		t.Fatalf("kill failed when it shouldn't: %v", err)
 	}
 	if expected, got := "Kill succeeded", strings.TrimSpace(stdout.String()); got != expected {
@@ -84,7 +84,7 @@
 	appTape.SetResponses([]interface{}{
 		verror.New(errOops, nil),
 	})
-	if err := v23cmd.ParseAndRun(cmd, ctx, env, []string{"kill", appName}); err == nil {
+	if err := v23cmd.ParseAndRunForTest(cmd, ctx, env, []string{"kill", appName}); err == nil {
 		t.Fatalf("wrongly didn't receive a non-nil error.")
 	}
 	// expected the same.
@@ -111,7 +111,7 @@
 	appName := naming.JoinAddressName(endpoint.String(), "appname")
 
 	// Confirm that we correctly enforce the number of arguments.
-	if err := v23cmd.ParseAndRun(cmd, ctx, env, []string{lower}); err == nil {
+	if err := v23cmd.ParseAndRunForTest(cmd, ctx, env, []string{lower}); err == nil {
 		t.Fatalf("wrongly failed to receive a non-nil error.")
 	}
 	if expected, got := "ERROR: "+lower+": incorrect number of arguments, expected 1, got 0", strings.TrimSpace(stderr.String()); !strings.HasPrefix(got, expected) {
@@ -122,7 +122,7 @@
 	appTape := tapes.forSuffix("appname")
 	appTape.Rewind()
 
-	if err := v23cmd.ParseAndRun(cmd, ctx, env, []string{lower, "nope", "nope"}); err == nil {
+	if err := v23cmd.ParseAndRunForTest(cmd, ctx, env, []string{lower, "nope", "nope"}); err == nil {
 		t.Fatalf("wrongly failed to receive a non-nil error.")
 	}
 	if expected, got := "ERROR: "+lower+": incorrect number of arguments, expected 1, got 2", strings.TrimSpace(stderr.String()); !strings.HasPrefix(got, expected) {
@@ -136,7 +136,7 @@
 	appTape.SetResponses([]interface{}{
 		nil,
 	})
-	if err := v23cmd.ParseAndRun(cmd, ctx, env, []string{lower, appName}); err != nil {
+	if err := v23cmd.ParseAndRunForTest(cmd, ctx, env, []string{lower, appName}); err != nil {
 		t.Fatalf("%s failed when it shouldn't: %v", lower, err)
 	}
 	if expected, got := upper+" succeeded", strings.TrimSpace(stdout.String()); got != expected {
@@ -153,7 +153,7 @@
 	appTape.SetResponses([]interface{}{
 		verror.New(errOops, nil),
 	})
-	if err := v23cmd.ParseAndRun(cmd, ctx, env, []string{lower, appName}); err == nil {
+	if err := v23cmd.ParseAndRunForTest(cmd, ctx, env, []string{lower, appName}); err == nil {
 		t.Fatalf("wrongly didn't receive a non-nil error.")
 	}
 	// expected the same.
diff --git a/services/device/device/local_install_test.go b/services/device/device/local_install_test.go
index 5899c04..16ff40c 100644
--- a/services/device/device/local_install_test.go
+++ b/services/device/device/local_install_test.go
@@ -76,7 +76,7 @@
 		},
 	} {
 		c.args = append([]string{"install-local"}, c.args...)
-		if err := v23cmd.ParseAndRun(cmd, ctx, env, c.args); err == nil {
+		if err := v23cmd.ParseAndRunForTest(cmd, ctx, env, c.args); err == nil {
 			t.Fatalf("test case %d: wrongly failed to receive a non-nil error.", i)
 		} else {
 			fmt.Fprintln(&stderr, "ERROR:", err)
@@ -240,7 +240,7 @@
 			c.args = append([]string{fmt.Sprintf("--packages=%s", string(jsonPackages))}, c.args...)
 		}
 		c.args = append([]string{"install-local"}, c.args...)
-		if err := v23cmd.ParseAndRun(cmd, ctx, env, c.args); err != nil {
+		if err := v23cmd.ParseAndRunForTest(cmd, ctx, env, c.args); err != nil {
 			t.Fatalf("test case %d: %v", i, err)
 		}
 		if expected, got := naming.Join(deviceName, appId), strings.TrimSpace(stdout.String()); got != expected {
diff --git a/services/profile/profile/impl_test.go b/services/profile/profile/impl_test.go
index 2c4e135..cf2b402 100644
--- a/services/profile/profile/impl_test.go
+++ b/services/profile/profile/impl_test.go
@@ -131,7 +131,7 @@
 	exists := naming.JoinAddressName(endpoint.String(), "exists")
 
 	// Test the 'label' command.
-	if err := v23cmd.ParseAndRun(cmdRoot, ctx, env, []string{"label", exists}); err != nil {
+	if err := v23cmd.ParseAndRunForTest(cmdRoot, ctx, env, []string{"label", exists}); err != nil {
 		t.Fatalf("%v", err)
 	}
 	if expected, got := spec.Label, strings.TrimSpace(stdout.String()); got != expected {
@@ -140,7 +140,7 @@
 	stdout.Reset()
 
 	// Test the 'description' command.
-	if err := v23cmd.ParseAndRun(cmdRoot, ctx, env, []string{"description", exists}); err != nil {
+	if err := v23cmd.ParseAndRunForTest(cmdRoot, ctx, env, []string{"description", exists}); err != nil {
 		t.Fatalf("%v", err)
 	}
 	if expected, got := spec.Description, strings.TrimSpace(stdout.String()); got != expected {
@@ -149,7 +149,7 @@
 	stdout.Reset()
 
 	// Test the 'spec' command.
-	if err := v23cmd.ParseAndRun(cmdRoot, ctx, env, []string{"specification", exists}); err != nil {
+	if err := v23cmd.ParseAndRunForTest(cmdRoot, ctx, env, []string{"specification", exists}); err != nil {
 		t.Fatalf("%v", err)
 	}
 	if expected, got := fmt.Sprintf("%#v", spec), strings.TrimSpace(stdout.String()); got != expected {
@@ -158,7 +158,7 @@
 	stdout.Reset()
 
 	// Test the 'put' command.
-	if err := v23cmd.ParseAndRun(cmdRoot, ctx, env, []string{"put", exists}); err != nil {
+	if err := v23cmd.ParseAndRunForTest(cmdRoot, ctx, env, []string{"put", exists}); err != nil {
 		t.Fatalf("%v", err)
 	}
 	if expected, got := "Profile added successfully.", strings.TrimSpace(stdout.String()); got != expected {
@@ -167,7 +167,7 @@
 	stdout.Reset()
 
 	// Test the 'remove' command.
-	if err := v23cmd.ParseAndRun(cmdRoot, ctx, env, []string{"remove", exists}); err != nil {
+	if err := v23cmd.ParseAndRunForTest(cmdRoot, ctx, env, []string{"remove", exists}); err != nil {
 		t.Fatalf("%v", err)
 	}
 	if expected, got := "Profile removed successfully.", strings.TrimSpace(stdout.String()); got != expected {