ref: Change test/modules registration mechanism.

Previously modules registration and usage looked like this:

func foo(stdin io.Reader, stdout, stderr io.Writer, env map[string]string, args ...string) error {
...
}

func init() {
modules.RegisterChild("foo", "", foo)
}

func TestFoo(t *testing.T) {
sh, err := modules.NewShell(...)
h, err := sh.Start("foo", nil, ...)
...
}

The new modules registration and usage looks like this:

var foo = modules.Register(func(env *modules.Env, args ...string) error {
...
}, "foo")

func TestFoo(t *testing.T) {
sh, err := modules.NewShell(...)
h, err := sh.Start(nil, foo, ...)
...
}

The main change is that Register now returns a modules.Program,
which is typically captured in a global variable, and is used as
the argument to Shell.Start.  This makes it easy to write the
registration manually, and we also have a more obvious linkage
between program registration and the Start call through the
program variable, rather than using strings.

Since registration was annoying to write manually, we used to
have 'v23 test generate' detect the functions and automatically
add the modules.RegisterChild call.  With the new mechanism, the
registration is simple to write manually, so 'v23 test generate'
has been simplified to remove the detection logic.

In fact the Program returned by modules.Register now must be
captured, so that it can be passed to Shell.Start; this forces
the linkage between Register and Start to be obvious.

Also removed the modules Help mechanism, since it wasn't being
used, and has questionable utility.  In its place, added logic to
dump all registered programs when program lookups fail.

MultiPart: 3/5

Change-Id: I6442c6959a4cb27fc1515f6ea14a4018ceb9f6b8
diff --git a/services/agent/agentlib/agent_test.go b/services/agent/agentlib/agent_test.go
index 93621a9..65b6f3d 100644
--- a/services/agent/agentlib/agent_test.go
+++ b/services/agent/agentlib/agent_test.go
@@ -6,7 +6,6 @@
 
 import (
 	"fmt"
-	"io"
 	"io/ioutil"
 	"os"
 	"reflect"
@@ -43,15 +42,15 @@
 
 //go:generate v23 test generate
 
-func getPrincipalAndHang(stdin io.Reader, stdout, stderr io.Writer, env map[string]string, args ...string) error {
+var getPrincipalAndHang = modules.Register(func(env *modules.Env, args ...string) error {
 	ctx, shutdown := test.InitForTest()
 	defer shutdown()
 
 	p := v23.GetPrincipal(ctx)
-	fmt.Fprintf(stdout, "DEFAULT_BLESSING=%s\n", p.BlessingStore().Default())
-	ioutil.ReadAll(stdin)
+	fmt.Fprintf(env.Stdout, "DEFAULT_BLESSING=%s\n", p.BlessingStore().Default())
+	ioutil.ReadAll(env.Stdin)
 	return nil
-}
+}, "getPrincipalAndHang")
 
 func newAgent(ctx *context.T, endpoint string, cached bool) (security.Principal, error) {
 	ep, err := v23.NewEndpoint(endpoint)
@@ -165,7 +164,7 @@
 		t.Fatal(err)
 	}
 	// The child process will connect to the agent
-	h, err := sh.Start("getPrincipalAndHang", nil)
+	h, err := sh.Start(nil, getPrincipalAndHang)
 	if err != nil {
 		t.Fatal(err)
 	}
diff --git a/services/agent/agentlib/v23_test.go b/services/agent/agentlib/v23_test.go
index 3e4061b..334393a 100644
--- a/services/agent/agentlib/v23_test.go
+++ b/services/agent/agentlib/v23_test.go
@@ -4,29 +4,21 @@
 
 // This file was auto-generated via go generate.
 // DO NOT UPDATE MANUALLY
+
 package agentlib_test
 
-import "fmt"
-import "testing"
-import "os"
+import (
+	"os"
+	"testing"
 
-import "v.io/x/ref/test"
-import "v.io/x/ref/test/modules"
-import "v.io/x/ref/test/v23tests"
-
-func init() {
-	modules.RegisterChild("getPrincipalAndHang", ``, getPrincipalAndHang)
-}
+	"v.io/x/ref/test"
+	"v.io/x/ref/test/modules"
+	"v.io/x/ref/test/v23tests"
+)
 
 func TestMain(m *testing.M) {
 	test.Init()
-	if modules.IsModulesChildProcess() {
-		if err := modules.Dispatch(); err != nil {
-			fmt.Fprintf(os.Stderr, "modules.Dispatch failed: %v\n", err)
-			os.Exit(1)
-		}
-		return
-	}
+	modules.DispatchAndExitIfChild()
 	cleanup := v23tests.UseSharedBinDir()
 	r := m.Run()
 	cleanup()
diff --git a/services/agent/vbecome/v23_test.go b/services/agent/vbecome/v23_test.go
index 35ff567..69f2726 100644
--- a/services/agent/vbecome/v23_test.go
+++ b/services/agent/vbecome/v23_test.go
@@ -4,16 +4,21 @@
 
 // This file was auto-generated via go generate.
 // DO NOT UPDATE MANUALLY
+
 package main_test
 
-import "testing"
-import "os"
+import (
+	"os"
+	"testing"
 
-import "v.io/x/ref/test"
-import "v.io/x/ref/test/v23tests"
+	"v.io/x/ref/test"
+	"v.io/x/ref/test/modules"
+	"v.io/x/ref/test/v23tests"
+)
 
 func TestMain(m *testing.M) {
 	test.Init()
+	modules.DispatchAndExitIfChild()
 	cleanup := v23tests.UseSharedBinDir()
 	r := m.Run()
 	cleanup()
diff --git a/services/application/application/v23_internal_test.go b/services/application/application/v23_internal_test.go
index dcd0029..ae59080 100644
--- a/services/application/application/v23_internal_test.go
+++ b/services/application/application/v23_internal_test.go
@@ -4,12 +4,15 @@
 
 // This file was auto-generated via go generate.
 // DO NOT UPDATE MANUALLY
+
 package main
 
-import "testing"
-import "os"
+import (
+	"os"
+	"testing"
 
-import "v.io/x/ref/test"
+	"v.io/x/ref/test"
+)
 
 func TestMain(m *testing.M) {
 	test.Init()
diff --git a/services/application/applicationd/perms_test.go b/services/application/applicationd/perms_test.go
index e787080..720b64b 100644
--- a/services/application/applicationd/perms_test.go
+++ b/services/application/applicationd/perms_test.go
@@ -6,7 +6,6 @@
 
 import (
 	"fmt"
-	"io"
 	"os"
 	"reflect"
 	"syscall"
@@ -19,22 +18,18 @@
 	"v.io/v23/services/application"
 	"v.io/v23/verror"
 	"v.io/x/lib/vlog"
-
 	"v.io/x/ref/lib/signals"
 	appd "v.io/x/ref/services/application/applicationd"
 	"v.io/x/ref/services/internal/servicetest"
 	"v.io/x/ref/services/repository"
 	"v.io/x/ref/test"
+	"v.io/x/ref/test/modules"
 	"v.io/x/ref/test/testutil"
 )
 
 //go:generate v23 test generate
 
-const (
-	repoCmd = "appRepository"
-)
-
-func appRepository(stdin io.Reader, stdout, stderr io.Writer, env map[string]string, args ...string) error {
+var appRepository = modules.Register(func(env *modules.Env, args ...string) error {
 	if len(args) < 2 {
 		vlog.Fatalf("repository expected at least name and store arguments and optionally Permissions flags per PermissionsFromFlag")
 	}
@@ -46,7 +41,7 @@
 
 	v23.GetNamespace(ctx).CacheCtl(naming.DisableCache(true))
 
-	defer fmt.Fprintf(stdout, "%v terminating\n", publishName)
+	defer fmt.Fprintf(env.Stdout, "%v terminating\n", publishName)
 	defer vlog.VI(1).Infof("%v terminating", publishName)
 	server, endpoint := servicetest.NewServer(ctx)
 	defer server.Stop()
@@ -62,11 +57,11 @@
 		vlog.Fatalf("Serve(%v) failed: %v", publishName, err)
 	}
 
-	fmt.Fprintf(stdout, "ready:%d\n", os.Getpid())
+	fmt.Fprintf(env.Stdout, "ready:%d\n", os.Getpid())
 	<-signals.ShutdownOnSignals(ctx)
 
 	return nil
-}
+}, "appRepository")
 
 func TestApplicationUpdatePermissions(t *testing.T) {
 	ctx, shutdown := test.InitForTest()
@@ -88,7 +83,7 @@
 	storedir, cleanup := servicetest.SetupRootDir(t, "application")
 	defer cleanup()
 
-	nmh := servicetest.RunCommand(t, sh, nil, repoCmd, "repo", storedir)
+	nmh := servicetest.RunCommand(t, sh, nil, appRepository, "repo", storedir)
 	pid := servicetest.ReadPID(t, nmh)
 	defer syscall.Kill(pid, syscall.SIGINT)
 
@@ -241,7 +236,7 @@
 		t.Fatal(err)
 	}
 
-	nmh := servicetest.RunCommand(t, sh, nil, repoCmd, "repo", storedir)
+	nmh := servicetest.RunCommand(t, sh, nil, appRepository, "repo", storedir)
 	pid := servicetest.ReadPID(t, nmh)
 	defer syscall.Kill(pid, syscall.SIGINT)
 
diff --git a/services/application/applicationd/v23_test.go b/services/application/applicationd/v23_test.go
index f032617..fff5ec8 100644
--- a/services/application/applicationd/v23_test.go
+++ b/services/application/applicationd/v23_test.go
@@ -4,29 +4,21 @@
 
 // This file was auto-generated via go generate.
 // DO NOT UPDATE MANUALLY
+
 package main_test
 
-import "fmt"
-import "testing"
-import "os"
+import (
+	"os"
+	"testing"
 
-import "v.io/x/ref/test"
-import "v.io/x/ref/test/modules"
-import "v.io/x/ref/test/v23tests"
-
-func init() {
-	modules.RegisterChild("appRepository", ``, appRepository)
-}
+	"v.io/x/ref/test"
+	"v.io/x/ref/test/modules"
+	"v.io/x/ref/test/v23tests"
+)
 
 func TestMain(m *testing.M) {
 	test.Init()
-	if modules.IsModulesChildProcess() {
-		if err := modules.Dispatch(); err != nil {
-			fmt.Fprintf(os.Stderr, "modules.Dispatch failed: %v\n", err)
-			os.Exit(1)
-		}
-		return
-	}
+	modules.DispatchAndExitIfChild()
 	cleanup := v23tests.UseSharedBinDir()
 	r := m.Run()
 	cleanup()
diff --git a/services/binary/binary/v23_internal_test.go b/services/binary/binary/v23_internal_test.go
index dcd0029..ae59080 100644
--- a/services/binary/binary/v23_internal_test.go
+++ b/services/binary/binary/v23_internal_test.go
@@ -4,12 +4,15 @@
 
 // This file was auto-generated via go generate.
 // DO NOT UPDATE MANUALLY
+
 package main
 
-import "testing"
-import "os"
+import (
+	"os"
+	"testing"
 
-import "v.io/x/ref/test"
+	"v.io/x/ref/test"
+)
 
 func TestMain(m *testing.M) {
 	test.Init()
diff --git a/services/binary/binaryd/v23_test.go b/services/binary/binaryd/v23_test.go
index b7aae18..5c12adf 100644
--- a/services/binary/binaryd/v23_test.go
+++ b/services/binary/binaryd/v23_test.go
@@ -4,16 +4,21 @@
 
 // This file was auto-generated via go generate.
 // DO NOT UPDATE MANUALLY
+
 package main_test
 
-import "testing"
-import "os"
+import (
+	"os"
+	"testing"
 
-import "v.io/x/ref/test"
-import "v.io/x/ref/test/v23tests"
+	"v.io/x/ref/test"
+	"v.io/x/ref/test/modules"
+	"v.io/x/ref/test/v23tests"
+)
 
 func TestMain(m *testing.M) {
 	test.Init()
+	modules.DispatchAndExitIfChild()
 	cleanup := v23tests.UseSharedBinDir()
 	r := m.Run()
 	cleanup()
diff --git a/services/build/build/v23_internal_test.go b/services/build/build/v23_internal_test.go
index dcd0029..ae59080 100644
--- a/services/build/build/v23_internal_test.go
+++ b/services/build/build/v23_internal_test.go
@@ -4,12 +4,15 @@
 
 // This file was auto-generated via go generate.
 // DO NOT UPDATE MANUALLY
+
 package main
 
-import "testing"
-import "os"
+import (
+	"os"
+	"testing"
 
-import "v.io/x/ref/test"
+	"v.io/x/ref/test"
+)
 
 func TestMain(m *testing.M) {
 	test.Init()
diff --git a/services/build/buildd/v23_test.go b/services/build/buildd/v23_test.go
index 6ec6ef0..dbfb5ac 100644
--- a/services/build/buildd/v23_test.go
+++ b/services/build/buildd/v23_test.go
@@ -4,16 +4,21 @@
 
 // This file was auto-generated via go generate.
 // DO NOT UPDATE MANUALLY
+
 package main_test
 
-import "testing"
-import "os"
+import (
+	"os"
+	"testing"
 
-import "v.io/x/ref/test"
-import "v.io/x/ref/test/v23tests"
+	"v.io/x/ref/test"
+	"v.io/x/ref/test/modules"
+	"v.io/x/ref/test/v23tests"
+)
 
 func TestMain(m *testing.M) {
 	test.Init()
+	modules.DispatchAndExitIfChild()
 	cleanup := v23tests.UseSharedBinDir()
 	r := m.Run()
 	cleanup()
diff --git a/services/debug/debug/v23_test.go b/services/debug/debug/v23_test.go
index b9cd380..007a5fd 100644
--- a/services/debug/debug/v23_test.go
+++ b/services/debug/debug/v23_test.go
@@ -4,16 +4,21 @@
 
 // This file was auto-generated via go generate.
 // DO NOT UPDATE MANUALLY
+
 package main_test
 
-import "testing"
-import "os"
+import (
+	"os"
+	"testing"
 
-import "v.io/x/ref/test"
-import "v.io/x/ref/test/v23tests"
+	"v.io/x/ref/test"
+	"v.io/x/ref/test/modules"
+	"v.io/x/ref/test/v23tests"
+)
 
 func TestMain(m *testing.M) {
 	test.Init()
+	modules.DispatchAndExitIfChild()
 	cleanup := v23tests.UseSharedBinDir()
 	r := m.Run()
 	cleanup()
diff --git a/services/debug/debuglib/v23_internal_test.go b/services/debug/debuglib/v23_internal_test.go
index cdbaeda..77006f9 100644
--- a/services/debug/debuglib/v23_internal_test.go
+++ b/services/debug/debuglib/v23_internal_test.go
@@ -4,12 +4,15 @@
 
 // This file was auto-generated via go generate.
 // DO NOT UPDATE MANUALLY
+
 package debuglib
 
-import "testing"
-import "os"
+import (
+	"os"
+	"testing"
 
-import "v.io/x/ref/test"
+	"v.io/x/ref/test"
+)
 
 func TestMain(m *testing.M) {
 	test.Init()
diff --git a/services/device/device/v23_internal_test.go b/services/device/device/v23_internal_test.go
index dcd0029..ae59080 100644
--- a/services/device/device/v23_internal_test.go
+++ b/services/device/device/v23_internal_test.go
@@ -4,12 +4,15 @@
 
 // This file was auto-generated via go generate.
 // DO NOT UPDATE MANUALLY
+
 package main
 
-import "testing"
-import "os"
+import (
+	"os"
+	"testing"
 
-import "v.io/x/ref/test"
+	"v.io/x/ref/test"
+)
 
 func TestMain(m *testing.M) {
 	test.Init()
diff --git a/services/device/internal/impl/applife/app_life_test.go b/services/device/internal/impl/applife/app_life_test.go
index 8484495..efdef51 100644
--- a/services/device/internal/impl/applife/app_life_test.go
+++ b/services/device/internal/impl/applife/app_life_test.go
@@ -78,7 +78,7 @@
 
 	// Set up the device manager.  Since we won't do device manager updates,
 	// don't worry about its application envelope and current link.
-	dmh := servicetest.RunCommand(t, sh, nil, utiltest.DeviceManagerCmd, "dm", root, helperPath, "unused_app_repo_name", "unused_curr_link")
+	dmh := servicetest.RunCommand(t, sh, nil, utiltest.DeviceManager, "dm", root, helperPath, "unused_app_repo_name", "unused_curr_link")
 	servicetest.ReadPID(t, dmh)
 	utiltest.ClaimDevice(t, ctx, "claimable", "dm", "mydevice", utiltest.NoPairingToken)
 
@@ -89,7 +89,7 @@
 	utiltest.Resolve(t, ctx, "pingserver", 1)
 
 	// Create an envelope for a first version of the app.
-	*envelope = utiltest.EnvelopeFromShell(sh, []string{utiltest.TestEnvVarName + "=env-val-envelope"}, utiltest.AppCmd, "google naps", 0, 0, fmt.Sprintf("--%s=flag-val-envelope", utiltest.TestFlagName), "appV1")
+	*envelope = utiltest.EnvelopeFromShell(sh, []string{utiltest.TestEnvVarName + "=env-val-envelope"}, utiltest.App, "google naps", 0, 0, fmt.Sprintf("--%s=flag-val-envelope", utiltest.TestFlagName), "appV1")
 
 	// Install the app.  The config-specified flag value for testFlagName
 	// should override the value specified in the envelope above, and the
@@ -185,12 +185,12 @@
 	utiltest.UpdateAppExpectError(t, ctx, appID, impl.ErrUpdateNoOp.ID)
 
 	// Updating the installation should not work with a mismatched title.
-	*envelope = utiltest.EnvelopeFromShell(sh, nil, utiltest.AppCmd, "bogus", 0, 0)
+	*envelope = utiltest.EnvelopeFromShell(sh, nil, utiltest.App, "bogus", 0, 0)
 
 	utiltest.UpdateAppExpectError(t, ctx, appID, impl.ErrAppTitleMismatch.ID)
 
 	// Create a second version of the app and update the app to it.
-	*envelope = utiltest.EnvelopeFromShell(sh, []string{utiltest.TestEnvVarName + "=env-val-envelope"}, utiltest.AppCmd, "google naps", 0, 0, "appV2")
+	*envelope = utiltest.EnvelopeFromShell(sh, []string{utiltest.TestEnvVarName + "=env-val-envelope"}, utiltest.App, "google naps", 0, 0, "appV2")
 
 	utiltest.UpdateApp(t, ctx, appID)
 
@@ -303,7 +303,7 @@
 	// cleanly Do this by installing, instantiating, running, and killing
 	// hangingApp, which sleeps (rather than exits) after being asked to
 	// Stop()
-	*envelope = utiltest.EnvelopeFromShell(sh, nil, utiltest.HangingAppCmd, "hanging ap", 0, 0, "hAppV1")
+	*envelope = utiltest.EnvelopeFromShell(sh, nil, utiltest.HangingApp, "hanging ap", 0, 0, "hAppV1")
 	hAppID := utiltest.InstallApp(t, ctx)
 	hInstanceID := utiltest.LaunchApp(t, ctx, hAppID)
 	hangingPid := pingCh.WaitForPingArgs(t).Pid
diff --git a/services/device/internal/impl/applife/instance_reaping_test.go b/services/device/internal/impl/applife/instance_reaping_test.go
index dfe3592..47ca75c 100644
--- a/services/device/internal/impl/applife/instance_reaping_test.go
+++ b/services/device/internal/impl/applife/instance_reaping_test.go
@@ -23,7 +23,7 @@
 
 	// Set up the device manager.  Since we won't do device manager updates,
 	// don't worry about its application envelope and current link.
-	dmh := servicetest.RunCommand(t, sh, nil, utiltest.DeviceManagerCmd, "dm", root, helperPath, "unused_app_repo_name", "unused_curr_link")
+	dmh := servicetest.RunCommand(t, sh, nil, utiltest.DeviceManager, "dm", root, helperPath, "unused_app_repo_name", "unused_curr_link")
 	servicetest.ReadPID(t, dmh)
 	utiltest.ClaimDevice(t, ctx, "claimable", "dm", "mydevice", utiltest.NoPairingToken)
 
@@ -34,7 +34,7 @@
 	utiltest.Resolve(t, ctx, "pingserver", 1)
 
 	// Create an envelope for a first version of the app.
-	*envelope = utiltest.EnvelopeFromShell(sh, nil, utiltest.AppCmd, "google naps", 0, 0, "appV1")
+	*envelope = utiltest.EnvelopeFromShell(sh, nil, utiltest.App, "google naps", 0, 0, "appV1")
 
 	// Install the app.  The config-specified flag value for testFlagName
 	// should override the value specified in the envelope above.
diff --git a/services/device/internal/impl/daemonreap/daemon_reaping_test.go b/services/device/internal/impl/daemonreap/daemon_reaping_test.go
index 84d0602..03dd6d7 100644
--- a/services/device/internal/impl/daemonreap/daemon_reaping_test.go
+++ b/services/device/internal/impl/daemonreap/daemon_reaping_test.go
@@ -24,7 +24,7 @@
 
 	// Set up the device manager.  Since we won't do device manager updates,
 	// don't worry about its application envelope and current link.
-	dmh := servicetest.RunCommand(t, sh, nil, utiltest.DeviceManagerCmd, "dm", root, helperPath, "unused_app_repo_name", "unused_curr_link")
+	dmh := servicetest.RunCommand(t, sh, nil, utiltest.DeviceManager, "dm", root, helperPath, "unused_app_repo_name", "unused_curr_link")
 	servicetest.ReadPID(t, dmh)
 	utiltest.ClaimDevice(t, ctx, "claimable", "dm", "mydevice", utiltest.NoPairingToken)
 
@@ -35,7 +35,7 @@
 	utiltest.Resolve(t, ctx, "pingserver", 1)
 
 	// Create an envelope for a first version of the app that will be restarted once.
-	*envelope = utiltest.EnvelopeFromShell(sh, nil, utiltest.AppCmd, "google naps", 1, 10*time.Second, "appV1")
+	*envelope = utiltest.EnvelopeFromShell(sh, nil, utiltest.App, "google naps", 1, 10*time.Second, "appV1")
 	appID := utiltest.InstallApp(t, ctx)
 
 	// Start an instance of the app.
diff --git a/services/device/internal/impl/daemonreap/instance_reaping_kill_test.go b/services/device/internal/impl/daemonreap/instance_reaping_kill_test.go
index c04f8b0..9d234dc 100644
--- a/services/device/internal/impl/daemonreap/instance_reaping_kill_test.go
+++ b/services/device/internal/impl/daemonreap/instance_reaping_kill_test.go
@@ -32,7 +32,7 @@
 	defer os.RemoveAll(dmCreds)
 	dmEnv := []string{fmt.Sprintf("%v=%v", ref.EnvCredentials, dmCreds)}
 
-	dmh := servicetest.RunCommand(t, sh, dmEnv, utiltest.DeviceManagerCmd, "dm", root, helperPath, "unused_app_repo_name", "unused_curr_link")
+	dmh := servicetest.RunCommand(t, sh, dmEnv, utiltest.DeviceManager, "dm", root, helperPath, "unused_app_repo_name", "unused_curr_link")
 	servicetest.ReadPID(t, dmh)
 	utiltest.ClaimDevice(t, ctx, "claimable", "dm", "mydevice", utiltest.NoPairingToken)
 
@@ -42,7 +42,7 @@
 	utiltest.Resolve(t, ctx, "pingserver", 1)
 
 	// Create an envelope for the app.
-	*envelope = utiltest.EnvelopeFromShell(sh, nil, utiltest.AppCmd, "google naps", 0, 0, "appV1")
+	*envelope = utiltest.EnvelopeFromShell(sh, nil, utiltest.App, "google naps", 0, 0, "appV1")
 
 	// Install the app.
 	appID := utiltest.InstallApp(t, ctx)
@@ -77,7 +77,7 @@
 	}
 
 	// Run another device manager to replace the dead one.
-	dmh = servicetest.RunCommand(t, sh, dmEnv, utiltest.DeviceManagerCmd, "dm", root, helperPath, "unused_app_repo_name", "unused_curr_link")
+	dmh = servicetest.RunCommand(t, sh, dmEnv, utiltest.DeviceManager, "dm", root, helperPath, "unused_app_repo_name", "unused_curr_link")
 	servicetest.ReadPID(t, dmh)
 	utiltest.Resolve(t, ctx, "dm", 1) // Verify the device manager has published itself.
 
diff --git a/services/device/internal/impl/globsuid/glob_test.go b/services/device/internal/impl/globsuid/glob_test.go
index 30fdc23..1d6a04d 100644
--- a/services/device/internal/impl/globsuid/glob_test.go
+++ b/services/device/internal/impl/globsuid/glob_test.go
@@ -36,7 +36,7 @@
 
 	// Set up the device manager.  Since we won't do device manager updates,
 	// don't worry about its application envelope and current link.
-	dmh := servicetest.RunCommand(t, sh, nil, utiltest.DeviceManagerCmd, "dm", root, helperPath, "unused_app_repo_name", "unused_curr_link")
+	dmh := servicetest.RunCommand(t, sh, nil, utiltest.DeviceManager, "dm", root, helperPath, "unused_app_repo_name", "unused_curr_link")
 	pid := servicetest.ReadPID(t, dmh)
 	defer syscall.Kill(pid, syscall.SIGINT)
 
@@ -45,7 +45,7 @@
 	defer cleanup()
 
 	// Create the envelope for the first version of the app.
-	*envelope = utiltest.EnvelopeFromShell(sh, nil, utiltest.AppCmd, "google naps", 0, 0, "appV1")
+	*envelope = utiltest.EnvelopeFromShell(sh, nil, utiltest.App, "google naps", 0, 0, "appV1")
 
 	// Device must be claimed before applications can be installed.
 	utiltest.ClaimDevice(t, ctx, "claimable", "dm", "mydevice", utiltest.NoPairingToken)
diff --git a/services/device/internal/impl/globsuid/signature_match_test.go b/services/device/internal/impl/globsuid/signature_match_test.go
index ded825f..aedb942 100644
--- a/services/device/internal/impl/globsuid/signature_match_test.go
+++ b/services/device/internal/impl/globsuid/signature_match_test.go
@@ -73,7 +73,7 @@
 
 	// Set up the device manager.  Since we won't do device manager updates,
 	// don't worry about its application envelope and current link.
-	dmh := servicetest.RunCommand(t, sh, nil, utiltest.DeviceManagerCmd, "dm", root, helperPath, "unused_app_repo_name", "unused_curr_link")
+	dmh := servicetest.RunCommand(t, sh, nil, utiltest.DeviceManager, "dm", root, helperPath, "unused_app_repo_name", "unused_curr_link")
 	pid := servicetest.ReadPID(t, dmh)
 	defer syscall.Kill(pid, syscall.SIGINT)
 	utiltest.ClaimDevice(t, ctx, "claimable", "dm", "mydevice", utiltest.NoPairingToken)
diff --git a/services/device/internal/impl/globsuid/suid_test.go b/services/device/internal/impl/globsuid/suid_test.go
index 12afe73..313b720 100644
--- a/services/device/internal/impl/globsuid/suid_test.go
+++ b/services/device/internal/impl/globsuid/suid_test.go
@@ -67,7 +67,7 @@
 	// Create a script wrapping the test target that implements suidhelper.
 	helperPath := utiltest.GenerateSuidHelperScript(t, root)
 
-	dmh := servicetest.RunCommand(t, sh, nil, utiltest.DeviceManagerCmd, "-mocksetuid", "dm", root, helperPath, "unused_app_repo_name", "unused_curr_link")
+	dmh := servicetest.RunCommand(t, sh, nil, utiltest.DeviceManager, "-mocksetuid", "dm", root, helperPath, "unused_app_repo_name", "unused_curr_link")
 	pid := servicetest.ReadPID(t, dmh)
 	defer syscall.Kill(pid, syscall.SIGINT)
 	defer utiltest.VerifyNoRunningProcesses(t)
@@ -82,7 +82,7 @@
 	defer cleanup()
 
 	// Create an envelope for a first version of the app.
-	*envelope = utiltest.EnvelopeFromShell(sh, []string{utiltest.TestEnvVarName + "=env-var"}, utiltest.AppCmd, "google naps", 0, 0, fmt.Sprintf("--%s=flag-val-envelope", utiltest.TestFlagName), "appV1")
+	*envelope = utiltest.EnvelopeFromShell(sh, []string{utiltest.TestEnvVarName + "=env-var"}, utiltest.App, "google naps", 0, 0, fmt.Sprintf("--%s=flag-val-envelope", utiltest.TestFlagName), "appV1")
 
 	// Install and start the app as root/self.
 	appID := utiltest.InstallApp(t, selfCtx)
diff --git a/services/device/internal/impl/impl_test.go b/services/device/internal/impl/impl_test.go
index 8e21f14..ef302f3 100644
--- a/services/device/internal/impl/impl_test.go
+++ b/services/device/internal/impl/impl_test.go
@@ -108,7 +108,7 @@
 	defer os.RemoveAll(dmCreds)
 	dmEnv := []string{fmt.Sprintf("%v=%v", ref.EnvCredentials, dmCreds)}
 	dmArgs := []string{"factoryDM", root, "unused_helper", utiltest.MockApplicationRepoName, currLink}
-	args, env := sh.CommandEnvelope(utiltest.DeviceManagerCmd, dmEnv, dmArgs...)
+	args, env := sh.ProgramEnvelope(dmEnv, utiltest.DeviceManager, dmArgs...)
 	scriptPathFactory := generateDeviceManagerScript(t, root, args, env)
 
 	if err := os.Symlink(scriptPathFactory, currLink); err != nil {
@@ -126,7 +126,7 @@
 	// demonstrates that the initial device manager could be running by hand
 	// as long as the right initial configuration is passed into the device
 	// manager implementation.
-	dmh := servicetest.RunCommand(t, sh, dmPauseBeforeStopEnv, utiltest.DeviceManagerCmd, dmArgs...)
+	dmh := servicetest.RunCommand(t, sh, dmPauseBeforeStopEnv, utiltest.DeviceManager, dmArgs...)
 	defer func() {
 		syscall.Kill(dmh.Pid(), syscall.SIGINT)
 		utiltest.VerifyNoRunningProcesses(t)
@@ -142,7 +142,7 @@
 	}
 
 	// Simulate an invalid envelope in the application repository.
-	*envelope = utiltest.EnvelopeFromShell(sh, dmPauseBeforeStopEnv, utiltest.DeviceManagerCmd, "bogus", 0, 0, dmArgs...)
+	*envelope = utiltest.EnvelopeFromShell(sh, dmPauseBeforeStopEnv, utiltest.DeviceManager, "bogus", 0, 0, dmArgs...)
 
 	utiltest.UpdateDeviceExpectError(t, ctx, "factoryDM", impl.ErrAppTitleMismatch.ID)
 	utiltest.RevertDeviceExpectError(t, ctx, "factoryDM", impl.ErrUpdateNoOp.ID)
@@ -150,7 +150,7 @@
 	// Set up a second version of the device manager. The information in the
 	// envelope will be used by the device manager to stage the next
 	// version.
-	*envelope = utiltest.EnvelopeFromShell(sh, dmEnv, utiltest.DeviceManagerCmd, application.DeviceManagerTitle, 0, 0, "v2DM")
+	*envelope = utiltest.EnvelopeFromShell(sh, dmEnv, utiltest.DeviceManager, application.DeviceManagerTitle, 0, 0, "v2DM")
 	utiltest.UpdateDevice(t, ctx, "factoryDM")
 
 	// Current link should have been updated to point to v2.
@@ -179,7 +179,7 @@
 	// relaunch it from the current link.
 	utiltest.ResolveExpectNotFound(t, ctx, "v2DM") // Ensure a clean slate.
 
-	dmh = servicetest.RunCommand(t, sh, dmEnv, utiltest.ExecScriptCmd, currLink)
+	dmh = servicetest.RunCommand(t, sh, dmEnv, utiltest.ExecScript, currLink)
 
 	servicetest.ReadPID(t, dmh)
 	utiltest.Resolve(t, ctx, "v2DM", 1) // Current link should have been launching v2.
@@ -195,7 +195,7 @@
 	// Try issuing an update with a binary that has a different major version
 	// number. It should fail.
 	utiltest.ResolveExpectNotFound(t, ctx, "v2.5DM") // Ensure a clean slate.
-	*envelope = utiltest.EnvelopeFromShell(sh, dmEnv, utiltest.DeviceManagerV10Cmd, application.DeviceManagerTitle, 0, 0, "v2.5DM")
+	*envelope = utiltest.EnvelopeFromShell(sh, dmEnv, utiltest.DeviceManagerV10, application.DeviceManagerTitle, 0, 0, "v2.5DM")
 	utiltest.UpdateDeviceExpectError(t, ctx, "v2DM", impl.ErrOperationFailed.ID)
 
 	if evalLink() != scriptPathV2 {
@@ -203,7 +203,7 @@
 	}
 
 	// Create a third version of the device manager and issue an update.
-	*envelope = utiltest.EnvelopeFromShell(sh, dmEnv, utiltest.DeviceManagerCmd, application.DeviceManagerTitle, 0, 0, "v3DM")
+	*envelope = utiltest.EnvelopeFromShell(sh, dmEnv, utiltest.DeviceManager, application.DeviceManagerTitle, 0, 0, "v3DM")
 	utiltest.UpdateDevice(t, ctx, "v2DM")
 
 	scriptPathV3 := evalLink()
@@ -221,7 +221,7 @@
 	// Re-lanuch the device manager from current link.  We instruct the
 	// device manager to pause before stopping its server, so that we can
 	// verify that a second revert fails while a revert is in progress.
-	dmh = servicetest.RunCommand(t, sh, dmPauseBeforeStopEnv, utiltest.ExecScriptCmd, currLink)
+	dmh = servicetest.RunCommand(t, sh, dmPauseBeforeStopEnv, utiltest.ExecScript, currLink)
 
 	servicetest.ReadPID(t, dmh)
 	utiltest.Resolve(t, ctx, "v3DM", 1) // Current link should have been launching v3.
@@ -243,7 +243,7 @@
 
 	utiltest.ResolveExpectNotFound(t, ctx, "v2DM") // Ensure a clean slate.
 
-	dmh = servicetest.RunCommand(t, sh, dmEnv, utiltest.ExecScriptCmd, currLink)
+	dmh = servicetest.RunCommand(t, sh, dmEnv, utiltest.ExecScript, currLink)
 	servicetest.ReadPID(t, dmh)
 	utiltest.Resolve(t, ctx, "v2DM", 1) // Current link should have been launching v2.
 
@@ -258,7 +258,7 @@
 
 	utiltest.ResolveExpectNotFound(t, ctx, "factoryDM") // Ensure a clean slate.
 
-	dmh = servicetest.RunCommand(t, sh, dmEnv, utiltest.ExecScriptCmd, currLink)
+	dmh = servicetest.RunCommand(t, sh, dmEnv, utiltest.ExecScript, currLink)
 	servicetest.ReadPID(t, dmh)
 	utiltest.Resolve(t, ctx, "factoryDM", 1) // Current link should have been launching factory version.
 	utiltest.ShutdownDevice(t, ctx, "factoryDM")
@@ -267,7 +267,7 @@
 
 	// Re-launch the device manager, to exercise the behavior of Stop.
 	utiltest.ResolveExpectNotFound(t, ctx, "factoryDM") // Ensure a clean slate.
-	dmh = servicetest.RunCommand(t, sh, dmEnv, utiltest.ExecScriptCmd, currLink)
+	dmh = servicetest.RunCommand(t, sh, dmEnv, utiltest.ExecScript, currLink)
 	servicetest.ReadPID(t, dmh)
 	utiltest.Resolve(t, ctx, "factoryDM", 1)
 	utiltest.KillDevice(t, ctx, "factoryDM")
@@ -310,7 +310,7 @@
 	// Create an 'envelope' for the device manager that we can pass to the
 	// installer, to ensure that the device manager that the installer
 	// configures can run.
-	dmargs, dmenv := sh.CommandEnvelope(utiltest.DeviceManagerCmd, nil, "dm")
+	dmargs, dmenv := sh.ProgramEnvelope(nil, utiltest.DeviceManager, "dm")
 	dmDir := filepath.Join(testDir, "dm")
 	// TODO(caprita): Add test logic when initMode = true.
 	singleUser, sessionMode, initMode := true, true, false
@@ -404,7 +404,7 @@
 
 	// Set up the device manager.  Since we won't do device manager updates,
 	// don't worry about its application envelope and current link.
-	dmh := servicetest.RunCommand(t, sh, nil, utiltest.DeviceManagerCmd, "dm", root, helperPath, "unused_app_repo_name", "unused_curr_link")
+	dmh := servicetest.RunCommand(t, sh, nil, utiltest.DeviceManager, "dm", root, helperPath, "unused_app_repo_name", "unused_curr_link")
 	pid := servicetest.ReadPID(t, dmh)
 	defer syscall.Kill(pid, syscall.SIGINT)
 	defer utiltest.VerifyNoRunningProcesses(t)
@@ -414,7 +414,7 @@
 	defer cleanup()
 
 	// Create the envelope for the first version of the app.
-	*envelope = utiltest.EnvelopeFromShell(sh, nil, utiltest.AppCmd, "google naps", 0, 0, "appV1")
+	*envelope = utiltest.EnvelopeFromShell(sh, nil, utiltest.App, "google naps", 0, 0, "appV1")
 	envelope.Packages = map[string]application.SignedFile{
 		"test": application.SignedFile{
 			File: "realbin/testpkg",
@@ -519,7 +519,7 @@
 		v23.GetPrincipal(c).AddToRoots(v23.GetPrincipal(ctx).BlessingStore().Default())
 	}
 
-	dmh := servicetest.RunCommand(t, sh, nil, utiltest.DeviceManagerCmd, "dm", root, "unused_helper", "unused_app_repo_name", "unused_curr_link")
+	dmh := servicetest.RunCommand(t, sh, nil, utiltest.DeviceManager, "dm", root, "unused_helper", "unused_app_repo_name", "unused_curr_link")
 	pid := servicetest.ReadPID(t, dmh)
 	defer syscall.Kill(pid, syscall.SIGINT)
 	defer utiltest.VerifyNoRunningProcesses(t)
diff --git a/services/device/internal/impl/perms/debug_perms_test.go b/services/device/internal/impl/perms/debug_perms_test.go
index 296c3b8..77576cf 100644
--- a/services/device/internal/impl/perms/debug_perms_test.go
+++ b/services/device/internal/impl/perms/debug_perms_test.go
@@ -44,7 +44,7 @@
 	defer cleanup()
 
 	// Set up the device manager.
-	dmh := servicetest.RunCommand(t, sh, nil, utiltest.DeviceManagerCmd, "dm", root, helperPath, "unused_app_repo_name", "unused_curr_link")
+	dmh := servicetest.RunCommand(t, sh, nil, utiltest.DeviceManager, "dm", root, helperPath, "unused_app_repo_name", "unused_curr_link")
 	servicetest.ReadPID(t, dmh)
 	utiltest.ClaimDevice(t, ctx, "claimable", "dm", "mydevice", utiltest.NoPairingToken)
 
@@ -62,7 +62,7 @@
 	// TODO(rjkroege): Set AccessLists here that conflict with the one provided by the device
 	// manager and show that the one set here is overridden.
 	// Create the envelope for the first version of the app.
-	*envelope = utiltest.EnvelopeFromShell(sh, nil, utiltest.AppCmd, "google naps", 0, 0, "appV1")
+	*envelope = utiltest.EnvelopeFromShell(sh, nil, utiltest.App, "google naps", 0, 0, "appV1")
 
 	// Install the app.
 	appID := utiltest.InstallApp(t, ctx)
@@ -200,7 +200,7 @@
 	}
 
 	// Set up the device manager.
-	dmh := servicetest.RunCommand(t, sh, nil, utiltest.DeviceManagerCmd, "--log_dir="+extraLogDir, "dm", root, helperPath, "unused", "unused_curr_link")
+	dmh := servicetest.RunCommand(t, sh, nil, utiltest.DeviceManager, "--log_dir="+extraLogDir, "dm", root, helperPath, "unused", "unused_curr_link")
 	servicetest.ReadPID(t, dmh)
 
 	// Make some users.
diff --git a/services/device/internal/impl/perms/perms_test.go b/services/device/internal/impl/perms/perms_test.go
index 69fe3c1..166de49 100644
--- a/services/device/internal/impl/perms/perms_test.go
+++ b/services/device/internal/impl/perms/perms_test.go
@@ -55,11 +55,11 @@
 	// Set up the device manager.  Since we won't do device manager updates,
 	// don't worry about its application envelope and current link.
 	pairingToken := "abcxyz"
-	dmh := servicetest.RunCommand(t, sh, nil, utiltest.DeviceManagerCmd, "dm", root, helperPath, "unused_app_repo_name", "unused_curr_link", pairingToken)
+	dmh := servicetest.RunCommand(t, sh, nil, utiltest.DeviceManager, "dm", root, helperPath, "unused_app_repo_name", "unused_curr_link", pairingToken)
 	pid := servicetest.ReadPID(t, dmh)
 	defer syscall.Kill(pid, syscall.SIGINT)
 
-	*envelope = utiltest.EnvelopeFromShell(sh, nil, utiltest.AppCmd, "google naps", 0, 0, "trapp")
+	*envelope = utiltest.EnvelopeFromShell(sh, nil, utiltest.App, "google naps", 0, 0, "trapp")
 
 	claimantCtx := utiltest.CtxWithNewPrincipal(t, ctx, idp, "claimant")
 	octx, err := v23.WithPrincipal(ctx, testutil.NewPrincipal("other"))
@@ -136,13 +136,13 @@
 
 	// Set up the device manager.  Since we won't do device manager updates,
 	// don't worry about its application envelope and current link.
-	dmh := servicetest.RunCommand(t, sh, nil, utiltest.DeviceManagerCmd, "dm", root, "unused_helper", "unused_app_repo_name", "unused_curr_link")
+	dmh := servicetest.RunCommand(t, sh, nil, utiltest.DeviceManager, "dm", root, "unused_helper", "unused_app_repo_name", "unused_curr_link")
 	pid := servicetest.ReadPID(t, dmh)
 	defer syscall.Kill(pid, syscall.SIGINT)
 	defer utiltest.VerifyNoRunningProcesses(t)
 
 	// Create an envelope for an app.
-	*envelope = utiltest.EnvelopeFromShell(sh, nil, utiltest.AppCmd, "google naps", 0, 0)
+	*envelope = utiltest.EnvelopeFromShell(sh, nil, utiltest.App, "google naps", 0, 0)
 
 	// On an unclaimed device manager, there will be no AccessLists.
 	if _, _, err := device.DeviceClient("claimable").GetPermissions(selfCtx); err == nil {
diff --git a/services/device/internal/impl/reaping/instance_reaping_test.go b/services/device/internal/impl/reaping/instance_reaping_test.go
index 81dd2ac..d7b360a 100644
--- a/services/device/internal/impl/reaping/instance_reaping_test.go
+++ b/services/device/internal/impl/reaping/instance_reaping_test.go
@@ -33,7 +33,7 @@
 	defer os.RemoveAll(dmCreds)
 	dmEnv := []string{fmt.Sprintf("%v=%v", ref.EnvCredentials, dmCreds), fmt.Sprintf("%v=%v", impl.AppcycleReconciliation, "1")}
 
-	dmh := servicetest.RunCommand(t, sh, dmEnv, utiltest.DeviceManagerCmd, "dm", root, helperPath, "unused_app_repo_name", "unused_curr_link")
+	dmh := servicetest.RunCommand(t, sh, dmEnv, utiltest.DeviceManager, "dm", root, helperPath, "unused_app_repo_name", "unused_curr_link")
 	servicetest.ReadPID(t, dmh)
 	utiltest.ClaimDevice(t, ctx, "claimable", "dm", "mydevice", utiltest.NoPairingToken)
 
@@ -43,7 +43,7 @@
 	utiltest.Resolve(t, ctx, "pingserver", 1)
 
 	// Create an envelope for the app.
-	*envelope = utiltest.EnvelopeFromShell(sh, nil, utiltest.AppCmd, "google naps", 0, 0, "appV1")
+	*envelope = utiltest.EnvelopeFromShell(sh, nil, utiltest.App, "google naps", 0, 0, "appV1")
 
 	// Install the app.
 	appID := utiltest.InstallApp(t, ctx)
@@ -78,7 +78,7 @@
 	}
 
 	// Run another device manager to replace the dead one.
-	dmh = servicetest.RunCommand(t, sh, dmEnv, utiltest.DeviceManagerCmd, "dm", root, helperPath, "unused_app_repo_name", "unused_curr_link")
+	dmh = servicetest.RunCommand(t, sh, dmEnv, utiltest.DeviceManager, "dm", root, helperPath, "unused_app_repo_name", "unused_curr_link")
 	servicetest.ReadPID(t, dmh)
 	utiltest.Resolve(t, ctx, "dm", 1) // Verify the device manager has published itself.
 
diff --git a/services/device/internal/impl/utiltest/app.go b/services/device/internal/impl/utiltest/app.go
index 47d88a4..bc194c9 100644
--- a/services/device/internal/impl/utiltest/app.go
+++ b/services/device/internal/impl/utiltest/app.go
@@ -8,7 +8,6 @@
 	"encoding/json"
 	"flag"
 	"fmt"
-	"io"
 	"io/ioutil"
 	"os"
 	"path/filepath"
@@ -22,13 +21,12 @@
 	"v.io/v23/naming"
 	"v.io/v23/rpc"
 	"v.io/v23/security"
-
 	"v.io/x/lib/vlog"
-
 	"v.io/x/ref/lib/signals"
 	"v.io/x/ref/services/device/internal/suid"
 	"v.io/x/ref/services/internal/servicetest"
 	"v.io/x/ref/test"
+	"v.io/x/ref/test/modules"
 	"v.io/x/ref/test/testutil"
 )
 
@@ -113,8 +111,10 @@
 	return content, nil
 }
 
-// app is a test application. It pings the invoking device manager with state information.
-func app(stdin io.Reader, stdout, stderr io.Writer, env map[string]string, args ...string) error {
+// App is a test application. It pings the invoking device manager with state information.
+var App = modules.Register(appFunc, "App")
+
+func appFunc(env *modules.Env, args ...string) error {
 	ctx, shutdown := test.InitForTest()
 	defer shutdown()
 
@@ -193,9 +193,10 @@
 	}
 }
 
-// Same as app, except that it does not exit properly after being stopped
-func hangingApp(stdin io.Reader, stdout, stderr io.Writer, env map[string]string, args ...string) error {
-	err := app(stdin, stdout, stderr, env, args...)
+// HangingApp is the same as App, except that it does not exit properly after
+// being stopped.
+var HangingApp = modules.Register(func(env *modules.Env, args ...string) error {
+	err := appFunc(env, args...)
 	time.Sleep(24 * time.Hour)
 	return err
-}
+}, "HangingApp")
diff --git a/services/device/internal/impl/utiltest/helpers.go b/services/device/internal/impl/utiltest/helpers.go
index d461eb2..498c05b 100644
--- a/services/device/internal/impl/utiltest/helpers.go
+++ b/services/device/internal/impl/utiltest/helpers.go
@@ -69,8 +69,8 @@
 	}
 }
 
-func EnvelopeFromShell(sh *modules.Shell, env []string, cmd, title string, retries int, window time.Duration, args ...string) application.Envelope {
-	args, nenv := sh.CommandEnvelope(cmd, env, args...)
+func EnvelopeFromShell(sh *modules.Shell, env []string, prog modules.Program, title string, retries int, window time.Duration, args ...string) application.Envelope {
+	args, nenv := sh.ProgramEnvelope(env, prog, args...)
 	return application.Envelope{
 		Title: title,
 		Args:  args[1:],
diff --git a/services/device/internal/impl/utiltest/modules.go b/services/device/internal/impl/utiltest/modules.go
index 96d6f6b..13656e3 100644
--- a/services/device/internal/impl/utiltest/modules.go
+++ b/services/device/internal/impl/utiltest/modules.go
@@ -6,7 +6,6 @@
 
 import (
 	"fmt"
-	"io"
 	"os"
 	goexec "os/exec"
 	"strings"
@@ -31,48 +30,42 @@
 	RedirectEnv    = "DEVICE_MANAGER_DONT_REDIRECT_STDOUT_STDERR"
 	TestEnvVarName = "V23_RANDOM_ENV_VALUE"
 	NoPairingToken = ""
-
-	// Modules names.
-	ExecScriptCmd       = "execScript"
-	DeviceManagerCmd    = "deviceManager"
-	DeviceManagerV10Cmd = "deviceManagerV10" // deviceManager with a different major version number
-	AppCmd              = "app"
-	HangingAppCmd       = "hangingApp"
 )
 
-// execScript launches the script passed as argument.
-func execScript(stdin io.Reader, stdout, stderr io.Writer, env map[string]string, args ...string) error {
+// ExecScript launches the script passed as argument.
+var ExecScript = modules.Register(func(env *modules.Env, args ...string) error {
 	if want, got := 1, len(args); want != got {
-		vlog.Fatalf("execScript expected %d arguments, got %d instead", want, got)
+		vlog.Fatalf("ExecScript expected %d arguments, got %d instead", want, got)
 	}
 	script := args[0]
 	osenv := []string{RedirectEnv + "=1"}
-	if env["PAUSE_BEFORE_STOP"] == "1" {
+	if env.Vars["PAUSE_BEFORE_STOP"] == "1" {
 		osenv = append(osenv, "PAUSE_BEFORE_STOP=1")
 	}
 
 	cmd := goexec.Cmd{
 		Path:   script,
 		Env:    osenv,
-		Stdin:  stdin,
-		Stderr: stderr,
-		Stdout: stdout,
+		Stdin:  env.Stdin,
+		Stderr: env.Stderr,
+		Stdout: env.Stdout,
 	}
-
 	return cmd.Run()
-}
+}, "ExecScript")
 
-// deviceManager sets up a device manager server.  It accepts the name to
+// DeviceManager sets up a device manager server.  It accepts the name to
 // publish the server under as an argument.  Additional arguments can optionally
 // specify device manager config settings.
-func deviceManager(stdin io.Reader, stdout, stderr io.Writer, env map[string]string, args ...string) error {
+var DeviceManager = modules.Register(deviceManagerFunc, "DeviceManager")
+
+func deviceManagerFunc(env *modules.Env, args ...string) error {
 	ctx, shutdown := test.InitForTest()
 	if len(args) == 0 {
 		vlog.Fatalf("deviceManager expected at least an argument")
 	}
 	publishName := args[0]
 	args = args[1:]
-	defer fmt.Fprintf(stdout, "%v terminated\n", publishName)
+	defer fmt.Fprintf(env.Stdout, "%v terminated\n", publishName)
 	defer vlog.VI(1).Infof("%v terminated", publishName)
 	defer shutdown()
 	v23.GetNamespace(ctx).CacheCtl(naming.DisableCache(true))
@@ -134,11 +127,11 @@
 	}
 	// Manually mount the claimable service in the 'global' mounttable.
 	v23.GetNamespace(ctx).Mount(ctx, "claimable", claimableName, 0)
-	fmt.Fprintf(stdout, "ready:%d\n", os.Getpid())
+	fmt.Fprintf(env.Stdout, "ready:%d\n", os.Getpid())
 
 	<-shutdownChan
-	if val, present := env["PAUSE_BEFORE_STOP"]; present && val == "1" {
-		modules.WaitForEOF(stdin)
+	if val, present := env.Vars["PAUSE_BEFORE_STOP"]; present && val == "1" {
+		modules.WaitForEOF(env.Stdin)
 	}
 	// TODO(ashankar): Figure out a way to incorporate this check in the test.
 	// if impl.DispatcherLeaking(dispatcher) {
@@ -148,15 +141,15 @@
 }
 
 // This is the same as DeviceManager above, except that it has a different major version number
-func deviceManagerV10(stdin io.Reader, stdout, stderr io.Writer, env map[string]string, args ...string) error {
+var DeviceManagerV10 = modules.Register(func(env *modules.Env, args ...string) error {
 	impl.CurrentVersion = impl.Version{10, 0} // Set the version number to 10.0
-	return deviceManager(stdin, stdout, stderr, env, args...)
-}
+	return deviceManagerFunc(env, args...)
+}, "DeviceManagerV10")
 
 func TestMainImpl(m *testing.M) {
 	test.Init()
 	isSuidHelper := len(os.Getenv("V23_SUIDHELPER_TEST")) > 0
-	if modules.IsModulesChildProcess() && !isSuidHelper {
+	if modules.IsChildProcess() && !isSuidHelper {
 		if err := modules.Dispatch(); err != nil {
 			fmt.Fprintf(os.Stderr, "modules.Dispatch failed: %v\n", err)
 			os.Exit(1)
@@ -177,13 +170,3 @@
 		vlog.Fatalf("Failed to Run() setuidhelper: %v", err)
 	}
 }
-
-func init() {
-	modules.RegisterChild("execScript", `execScript launches the script passed as argument.`, execScript)
-	modules.RegisterChild("deviceManager", `deviceManager sets up a device manager server.  It accepts the name to
-publish the server under as an argument.  Additional arguments can optionally
- specify device manager config settings.`, deviceManager)
-	modules.RegisterChild("deviceManagerV10", `This is the same as deviceManager above, except that it has a different major version number`, deviceManagerV10)
-	modules.RegisterChild("app", ``, app)
-	modules.RegisterChild("hangingApp", `Same as app, except that it does not exit properly after being stopped`, hangingApp)
-}
diff --git a/services/device/mgmt_v23_test.go b/services/device/mgmt_v23_test.go
index c07c9da..61d2ada 100644
--- a/services/device/mgmt_v23_test.go
+++ b/services/device/mgmt_v23_test.go
@@ -126,7 +126,7 @@
 		// Create those credentials and options to use to setup the
 		// binaries with them.
 		aliceCreds, _ = i.Shell().NewChildCredentials("alice")
-		aliceOpts     = i.Shell().DefaultStartOpts().ExternalCommand().WithCustomCredentials(aliceCreds)
+		aliceOpts     = i.Shell().DefaultStartOpts().ExternalProgram().WithCustomCredentials(aliceCreds)
 
 		// Build all the command-line tools and set them up to run as alice.
 		// applicationd/binaryd servers will be run by alice too.
diff --git a/services/device/v23_test.go b/services/device/v23_test.go
index 3eeab2f..0bb141e 100644
--- a/services/device/v23_test.go
+++ b/services/device/v23_test.go
@@ -4,16 +4,21 @@
 
 // This file was auto-generated via go generate.
 // DO NOT UPDATE MANUALLY
+
 package device_test
 
-import "testing"
-import "os"
+import (
+	"os"
+	"testing"
 
-import "v.io/x/ref/test"
-import "v.io/x/ref/test/v23tests"
+	"v.io/x/ref/test"
+	"v.io/x/ref/test/modules"
+	"v.io/x/ref/test/v23tests"
+)
 
 func TestMain(m *testing.M) {
 	test.Init()
+	modules.DispatchAndExitIfChild()
 	cleanup := v23tests.UseSharedBinDir()
 	r := m.Run()
 	cleanup()
diff --git a/services/identity/identityd/v23_test.go b/services/identity/identityd/v23_test.go
index 13c2816..17cc37d 100644
--- a/services/identity/identityd/v23_test.go
+++ b/services/identity/identityd/v23_test.go
@@ -4,16 +4,21 @@
 
 // This file was auto-generated via go generate.
 // DO NOT UPDATE MANUALLY
+
 package main_test
 
-import "testing"
-import "os"
+import (
+	"os"
+	"testing"
 
-import "v.io/x/ref/test"
-import "v.io/x/ref/test/v23tests"
+	"v.io/x/ref/test"
+	"v.io/x/ref/test/modules"
+	"v.io/x/ref/test/v23tests"
+)
 
 func TestMain(m *testing.M) {
 	test.Init()
+	modules.DispatchAndExitIfChild()
 	cleanup := v23tests.UseSharedBinDir()
 	r := m.Run()
 	cleanup()
diff --git a/services/identity/identitylib/test_identityd.go b/services/identity/identitylib/test_identityd.go
index 03cdf0a..a633bbd 100644
--- a/services/identity/identitylib/test_identityd.go
+++ b/services/identity/identitylib/test_identityd.go
@@ -9,7 +9,6 @@
 import (
 	"flag"
 	"fmt"
-	"io"
 	"net"
 	"strconv"
 	"time"
@@ -32,15 +31,7 @@
 	tlsConfig        = flag.CommandLine.String("tls-config", "", "Comma-separated list of TLS certificate and private key files. This must be provided.")
 )
 
-const (
-	TestIdentitydCommand = "test_identityd"
-)
-
-func init() {
-	modules.RegisterChild(TestIdentitydCommand, modules.Usage(flag.CommandLine), startTestIdentityd)
-}
-
-func startTestIdentityd(stdin io.Reader, stdout, stderr io.Writer, env map[string]string, args ...string) error {
+var TestIdentityd = modules.Register(func(env *modules.Env, args ...string) error {
 	// Duration to use for tls cert and blessing duration.
 	duration := 365 * 24 * time.Hour
 
@@ -101,12 +92,12 @@
 
 	_, eps, externalHttpAddress := s.Listen(ctx, &l, *externalHttpAddr, *httpAddr, *tlsConfig)
 
-	fmt.Fprintf(stdout, "TEST_IDENTITYD_NAME=%s\n", eps[0])
-	fmt.Fprintf(stdout, "TEST_IDENTITYD_HTTP_ADDR=%s\n", externalHttpAddress)
+	fmt.Fprintf(env.Stdout, "TEST_IDENTITYD_NAME=%s\n", eps[0])
+	fmt.Fprintf(env.Stdout, "TEST_IDENTITYD_HTTP_ADDR=%s\n", externalHttpAddress)
 
-	modules.WaitForEOF(stdin)
+	modules.WaitForEOF(env.Stdin)
 	return nil
-}
+}, "TestIdentityd")
 
 func freePort() string {
 	l, _ := net.Listen("tcp", ":0")
diff --git a/services/identity/internal/revocation/v23_internal_test.go b/services/identity/internal/revocation/v23_internal_test.go
index ed4c9f9..d53fea3 100644
--- a/services/identity/internal/revocation/v23_internal_test.go
+++ b/services/identity/internal/revocation/v23_internal_test.go
@@ -4,12 +4,15 @@
 
 // This file was auto-generated via go generate.
 // DO NOT UPDATE MANUALLY
+
 package revocation
 
-import "testing"
-import "os"
+import (
+	"os"
+	"testing"
 
-import "v.io/x/ref/test"
+	"v.io/x/ref/test"
+)
 
 func TestMain(m *testing.M) {
 	test.Init()
diff --git a/services/internal/binarylib/perms_test.go b/services/internal/binarylib/perms_test.go
index 59de734..0b2e978 100644
--- a/services/internal/binarylib/perms_test.go
+++ b/services/internal/binarylib/perms_test.go
@@ -6,7 +6,6 @@
 
 import (
 	"fmt"
-	"io"
 	"os"
 	"reflect"
 	"syscall"
@@ -25,16 +24,13 @@
 	"v.io/x/ref/services/internal/binarylib"
 	"v.io/x/ref/services/internal/servicetest"
 	"v.io/x/ref/test"
+	"v.io/x/ref/test/modules"
 	"v.io/x/ref/test/testutil"
 )
 
 //go:generate v23 test generate
 
-const (
-	binaryCmd = "binaryd"
-)
-
-func binaryd(stdin io.Reader, stdout, stderr io.Writer, env map[string]string, args ...string) error {
+var binaryd = modules.Register(func(env *modules.Env, args ...string) error {
 	if len(args) < 2 {
 		vlog.Fatalf("binaryd expected at least name and store arguments and optionally AccessList flags per PermissionsFromFlag")
 	}
@@ -43,7 +39,7 @@
 
 	ctx, shutdown := test.InitForTest()
 
-	defer fmt.Fprintf(stdout, "%v terminating\n", publishName)
+	defer fmt.Fprintf(env.Stdout, "%v terminating\n", publishName)
 	defer vlog.VI(1).Infof("%v terminating", publishName)
 	defer shutdown()
 
@@ -64,11 +60,11 @@
 		vlog.Fatalf("Serve(%v) failed: %v", publishName, err)
 	}
 
-	fmt.Fprintf(stdout, "ready:%d\n", os.Getpid())
+	fmt.Fprintf(env.Stdout, "ready:%d\n", os.Getpid())
 	<-signals.ShutdownOnSignals(ctx)
 
 	return nil
-}
+}, "binaryd")
 
 func b(name string) repository.BinaryClientStub {
 	return repository.BinaryClient(name)
@@ -112,7 +108,7 @@
 	defer cleanup()
 	prepDirectory(t, storedir)
 
-	nmh := servicetest.RunCommand(t, sh, nil, binaryCmd, "bini", storedir)
+	nmh := servicetest.RunCommand(t, sh, nil, binaryd, "bini", storedir)
 	pid := servicetest.ReadPID(t, nmh)
 	defer syscall.Kill(pid, syscall.SIGINT)
 
@@ -169,7 +165,7 @@
 		t.Fatalf("WithPrincipal() failed: %v", err)
 	}
 
-	nmh := servicetest.RunCommand(t, sh, nil, binaryCmd, "bini", storedir)
+	nmh := servicetest.RunCommand(t, sh, nil, binaryd, "bini", storedir)
 	pid := servicetest.ReadPID(t, nmh)
 	defer syscall.Kill(pid, syscall.SIGINT)
 
@@ -449,7 +445,7 @@
 		t.Fatalf("otherPrincipal.AddToRoots() failed: %v", err)
 	}
 
-	nmh := servicetest.RunCommand(t, sh, nil, binaryCmd, "bini", storedir)
+	nmh := servicetest.RunCommand(t, sh, nil, binaryd, "bini", storedir)
 	pid := servicetest.ReadPID(t, nmh)
 	defer syscall.Kill(pid, syscall.SIGINT)
 
diff --git a/services/internal/binarylib/v23_test.go b/services/internal/binarylib/v23_test.go
index 06b7552..9e2e792 100644
--- a/services/internal/binarylib/v23_test.go
+++ b/services/internal/binarylib/v23_test.go
@@ -4,27 +4,19 @@
 
 // This file was auto-generated via go generate.
 // DO NOT UPDATE MANUALLY
+
 package binarylib_test
 
-import "fmt"
-import "testing"
-import "os"
+import (
+	"os"
+	"testing"
 
-import "v.io/x/ref/test"
-import "v.io/x/ref/test/modules"
-
-func init() {
-	modules.RegisterChild("binaryd", ``, binaryd)
-}
+	"v.io/x/ref/test"
+	"v.io/x/ref/test/modules"
+)
 
 func TestMain(m *testing.M) {
 	test.Init()
-	if modules.IsModulesChildProcess() {
-		if err := modules.Dispatch(); err != nil {
-			fmt.Fprintf(os.Stderr, "modules.Dispatch failed: %v\n", err)
-			os.Exit(1)
-		}
-		return
-	}
+	modules.DispatchAndExitIfChild()
 	os.Exit(m.Run())
 }
diff --git a/services/internal/logreaderlib/v23_internal_test.go b/services/internal/logreaderlib/v23_internal_test.go
index bcdf9fb..c0b54bf 100644
--- a/services/internal/logreaderlib/v23_internal_test.go
+++ b/services/internal/logreaderlib/v23_internal_test.go
@@ -4,12 +4,15 @@
 
 // This file was auto-generated via go generate.
 // DO NOT UPDATE MANUALLY
+
 package logreaderlib
 
-import "testing"
-import "os"
+import (
+	"os"
+	"testing"
 
-import "v.io/x/ref/test"
+	"v.io/x/ref/test"
+)
 
 func TestMain(m *testing.M) {
 	test.Init()
diff --git a/services/internal/pproflib/v23_internal_test.go b/services/internal/pproflib/v23_internal_test.go
index 266140e..2b18e2d 100644
--- a/services/internal/pproflib/v23_internal_test.go
+++ b/services/internal/pproflib/v23_internal_test.go
@@ -4,12 +4,15 @@
 
 // This file was auto-generated via go generate.
 // DO NOT UPDATE MANUALLY
+
 package pproflib
 
-import "testing"
-import "os"
+import (
+	"os"
+	"testing"
 
-import "v.io/x/ref/test"
+	"v.io/x/ref/test"
+)
 
 func TestMain(m *testing.M) {
 	test.Init()
diff --git a/services/internal/servicetest/modules.go b/services/internal/servicetest/modules.go
index 7954346..e69c624 100644
--- a/services/internal/servicetest/modules.go
+++ b/services/internal/servicetest/modules.go
@@ -6,7 +6,6 @@
 
 import (
 	"fmt"
-	"io"
 	"io/ioutil"
 	"os"
 	"path/filepath"
@@ -33,11 +32,7 @@
 	preserveWorkspaceEnv = "V23_TEST_PRESERVE_WORKSPACE"
 )
 
-func init() {
-	modules.RegisterChild("rootMT", ``, rootMT)
-}
-
-func rootMT(stdin io.Reader, stdout, stderr io.Writer, env map[string]string, args ...string) error {
+var rootMT = modules.Register(func(env *modules.Env, args ...string) error {
 	ctx, shutdown := v23.Init()
 	defer shutdown()
 
@@ -57,17 +52,17 @@
 	if err := server.ServeDispatcher("", mt); err != nil {
 		return fmt.Errorf("root failed: %s", err)
 	}
-	fmt.Fprintf(stdout, "PID=%d\n", os.Getpid())
+	fmt.Fprintf(env.Stdout, "PID=%d\n", os.Getpid())
 	for _, ep := range eps {
-		fmt.Fprintf(stdout, "MT_NAME=%s\n", ep.Name())
+		fmt.Fprintf(env.Stdout, "MT_NAME=%s\n", ep.Name())
 	}
-	modules.WaitForEOF(stdin)
+	modules.WaitForEOF(env.Stdin)
 	return nil
-}
+}, "rootMT")
 
 // startRootMT sets up a root mount table for tests.
 func startRootMT(t *testing.T, sh *modules.Shell) (string, modules.Handle) {
-	h, err := sh.Start("rootMT", nil, "--v23.tcp.address=127.0.0.1:0")
+	h, err := sh.Start(nil, rootMT, "--v23.tcp.address=127.0.0.1:0")
 	if err != nil {
 		t.Fatalf("failed to start root mount table: %s", err)
 	}
@@ -123,10 +118,10 @@
 }
 
 // RunCommand runs a modules command.
-func RunCommand(t *testing.T, sh *modules.Shell, env []string, cmd string, args ...string) modules.Handle {
-	h, err := sh.StartWithOpts(sh.DefaultStartOpts(), env, cmd, args...)
+func RunCommand(t *testing.T, sh *modules.Shell, env []string, prog modules.Program, args ...string) modules.Handle {
+	h, err := sh.StartWithOpts(sh.DefaultStartOpts(), env, prog, args...)
 	if err != nil {
-		t.Fatalf(testutil.FormatLogLine(2, "failed to start %q: %s", cmd, err))
+		t.Fatalf(testutil.FormatLogLine(2, "failed to start %q: %s", prog, err))
 		return nil
 	}
 	h.SetVerbosity(testing.Verbose())
diff --git a/services/internal/statslib/v23_internal_test.go b/services/internal/statslib/v23_internal_test.go
index 8f6c7c9..b34a92f 100644
--- a/services/internal/statslib/v23_internal_test.go
+++ b/services/internal/statslib/v23_internal_test.go
@@ -4,12 +4,15 @@
 
 // This file was auto-generated via go generate.
 // DO NOT UPDATE MANUALLY
+
 package statslib
 
-import "testing"
-import "os"
+import (
+	"os"
+	"testing"
 
-import "v.io/x/ref/test"
+	"v.io/x/ref/test"
+)
 
 func TestMain(m *testing.M) {
 	test.Init()
diff --git a/services/internal/vtracelib/v23_internal_test.go b/services/internal/vtracelib/v23_internal_test.go
index 386c81f..2229a8d 100644
--- a/services/internal/vtracelib/v23_internal_test.go
+++ b/services/internal/vtracelib/v23_internal_test.go
@@ -4,12 +4,15 @@
 
 // This file was auto-generated via go generate.
 // DO NOT UPDATE MANUALLY
+
 package vtracelib
 
-import "testing"
-import "os"
+import (
+	"os"
+	"testing"
 
-import "v.io/x/ref/test"
+	"v.io/x/ref/test"
+)
 
 func TestMain(m *testing.M) {
 	test.Init()
diff --git a/services/mounttable/mounttabled/v23_test.go b/services/mounttable/mounttabled/v23_test.go
index 0d570e3..b88590f 100644
--- a/services/mounttable/mounttabled/v23_test.go
+++ b/services/mounttable/mounttabled/v23_test.go
@@ -4,16 +4,21 @@
 
 // This file was auto-generated via go generate.
 // DO NOT UPDATE MANUALLY
+
 package main_test
 
-import "testing"
-import "os"
+import (
+	"os"
+	"testing"
 
-import "v.io/x/ref/test"
-import "v.io/x/ref/test/v23tests"
+	"v.io/x/ref/test"
+	"v.io/x/ref/test/modules"
+	"v.io/x/ref/test/v23tests"
+)
 
 func TestMain(m *testing.M) {
 	test.Init()
+	modules.DispatchAndExitIfChild()
 	cleanup := v23tests.UseSharedBinDir()
 	r := m.Run()
 	cleanup()
diff --git a/services/mounttable/mounttablelib/v23_internal_test.go b/services/mounttable/mounttablelib/v23_internal_test.go
index 1d210b4..82b7beb 100644
--- a/services/mounttable/mounttablelib/v23_internal_test.go
+++ b/services/mounttable/mounttablelib/v23_internal_test.go
@@ -4,12 +4,15 @@
 
 // This file was auto-generated via go generate.
 // DO NOT UPDATE MANUALLY
+
 package mounttablelib
 
-import "testing"
-import "os"
+import (
+	"os"
+	"testing"
 
-import "v.io/x/ref/test"
+	"v.io/x/ref/test"
+)
 
 func TestMain(m *testing.M) {
 	test.Init()
diff --git a/services/profile/profile/v23_internal_test.go b/services/profile/profile/v23_internal_test.go
index dcd0029..ae59080 100644
--- a/services/profile/profile/v23_internal_test.go
+++ b/services/profile/profile/v23_internal_test.go
@@ -4,12 +4,15 @@
 
 // This file was auto-generated via go generate.
 // DO NOT UPDATE MANUALLY
+
 package main
 
-import "testing"
-import "os"
+import (
+	"os"
+	"testing"
 
-import "v.io/x/ref/test"
+	"v.io/x/ref/test"
+)
 
 func TestMain(m *testing.M) {
 	test.Init()
diff --git a/services/profile/profiled/v23_test.go b/services/profile/profiled/v23_test.go
index cb53f83..9df2772 100644
--- a/services/profile/profiled/v23_test.go
+++ b/services/profile/profiled/v23_test.go
@@ -4,16 +4,21 @@
 
 // This file was auto-generated via go generate.
 // DO NOT UPDATE MANUALLY
+
 package main_test
 
-import "testing"
-import "os"
+import (
+	"os"
+	"testing"
 
-import "v.io/x/ref/test"
-import "v.io/x/ref/test/v23tests"
+	"v.io/x/ref/test"
+	"v.io/x/ref/test/modules"
+	"v.io/x/ref/test/v23tests"
+)
 
 func TestMain(m *testing.M) {
 	test.Init()
+	modules.DispatchAndExitIfChild()
 	cleanup := v23tests.UseSharedBinDir()
 	r := m.Run()
 	cleanup()
diff --git a/services/proxy/proxyd/proxyd_v23_test.go b/services/proxy/proxyd/proxyd_v23_test.go
index e58c90d..167ea42 100644
--- a/services/proxy/proxyd/proxyd_v23_test.go
+++ b/services/proxy/proxyd/proxyd_v23_test.go
@@ -6,13 +6,11 @@
 
 import (
 	"fmt"
-	"io"
 
 	"v.io/v23"
 	"v.io/v23/context"
 	"v.io/v23/rpc"
 	"v.io/v23/security"
-
 	"v.io/x/ref/test/modules"
 	"v.io/x/ref/test/v23tests"
 )
@@ -20,18 +18,11 @@
 //go:generate v23 test generate
 
 const (
-	serverCmd   = "server"
-	clientCmd   = "client"
 	proxyName   = "proxy"    // Name which the proxy mounts itself at
 	serverName  = "server"   // Name which the server mounts itself at
 	responseVar = "RESPONSE" // Name of the variable used by client program to output the response
 )
 
-func init() {
-	modules.RegisterChild(serverCmd, "server", runServer)
-	modules.RegisterChild(clientCmd, "client", runClient)
-}
-
 func V23TestProxyd(t *v23tests.T) {
 	v23tests.RunRootMT(t, "--v23.tcp.address=127.0.0.1:0")
 	var (
@@ -47,14 +38,14 @@
 	if _, err := t.Shell().StartWithOpts(
 		t.Shell().DefaultStartOpts().WithCustomCredentials(serverCreds),
 		nil,
-		serverCmd); err != nil {
+		runServer); err != nil {
 		t.Fatal(err)
 	}
 	// Run the client.
 	client, err := t.Shell().StartWithOpts(
 		t.Shell().DefaultStartOpts().WithCustomCredentials(clientCreds),
 		nil,
-		clientCmd)
+		runClient)
 	if err != nil {
 		t.Fatal(err)
 	}
@@ -63,7 +54,7 @@
 	}
 }
 
-func runServer(stdin io.Reader, stdout, stderr io.Writer, env map[string]string, args ...string) error {
+var runServer = modules.Register(func(env *modules.Env, args ...string) error {
 	ctx, shutdown := v23.Init()
 	defer shutdown()
 
@@ -79,11 +70,11 @@
 		return err
 	}
 
-	modules.WaitForEOF(stdin)
+	modules.WaitForEOF(env.Stdin)
 	return nil
-}
+}, "runServer")
 
-func runClient(stdin io.Reader, stdout, stderr io.Writer, env map[string]string, args ...string) error {
+var runClient = modules.Register(func(env *modules.Env, args ...string) error {
 	ctx, shutdown := v23.Init()
 	defer shutdown()
 
@@ -95,9 +86,9 @@
 	if err := call.Finish(&response); err != nil {
 		return err
 	}
-	fmt.Fprintf(stdout, "%v=%v\n", responseVar, response)
+	fmt.Fprintf(env.Stdout, "%v=%v\n", responseVar, response)
 	return nil
-}
+}, "runClient")
 
 type service struct{}
 
diff --git a/services/proxy/proxyd/v23_test.go b/services/proxy/proxyd/v23_test.go
index 436b072..98cd476 100644
--- a/services/proxy/proxyd/v23_test.go
+++ b/services/proxy/proxyd/v23_test.go
@@ -4,30 +4,21 @@
 
 // This file was auto-generated via go generate.
 // DO NOT UPDATE MANUALLY
+
 package main_test
 
-import "fmt"
-import "testing"
-import "os"
+import (
+	"os"
+	"testing"
 
-import "v.io/x/ref/test"
-import "v.io/x/ref/test/modules"
-import "v.io/x/ref/test/v23tests"
-
-func init() {
-	modules.RegisterChild("runServer", ``, runServer)
-	modules.RegisterChild("runClient", ``, runClient)
-}
+	"v.io/x/ref/test"
+	"v.io/x/ref/test/modules"
+	"v.io/x/ref/test/v23tests"
+)
 
 func TestMain(m *testing.M) {
 	test.Init()
-	if modules.IsModulesChildProcess() {
-		if err := modules.Dispatch(); err != nil {
-			fmt.Fprintf(os.Stderr, "modules.Dispatch failed: %v\n", err)
-			os.Exit(1)
-		}
-		return
-	}
+	modules.DispatchAndExitIfChild()
 	cleanup := v23tests.UseSharedBinDir()
 	r := m.Run()
 	cleanup()
diff --git a/services/wspr/internal/app/v23_internal_test.go b/services/wspr/internal/app/v23_internal_test.go
index 9ae5683..70d6982 100644
--- a/services/wspr/internal/app/v23_internal_test.go
+++ b/services/wspr/internal/app/v23_internal_test.go
@@ -4,12 +4,15 @@
 
 // This file was auto-generated via go generate.
 // DO NOT UPDATE MANUALLY
+
 package app
 
-import "testing"
-import "os"
+import (
+	"os"
+	"testing"
 
-import "v.io/x/ref/test"
+	"v.io/x/ref/test"
+)
 
 func TestMain(m *testing.M) {
 	test.Init()
diff --git a/services/wspr/internal/browspr/v23_internal_test.go b/services/wspr/internal/browspr/v23_internal_test.go
index 9b21db0..6c94e49 100644
--- a/services/wspr/internal/browspr/v23_internal_test.go
+++ b/services/wspr/internal/browspr/v23_internal_test.go
@@ -4,12 +4,15 @@
 
 // This file was auto-generated via go generate.
 // DO NOT UPDATE MANUALLY
+
 package browspr
 
-import "testing"
-import "os"
+import (
+	"os"
+	"testing"
 
-import "v.io/x/ref/test"
+	"v.io/x/ref/test"
+)
 
 func TestMain(m *testing.M) {
 	test.Init()