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/runtime/internal/rt/mgmt_test.go b/runtime/internal/rt/mgmt_test.go
index 73860e5..428b733 100644
--- a/runtime/internal/rt/mgmt_test.go
+++ b/runtime/internal/rt/mgmt_test.go
@@ -6,7 +6,6 @@
import (
"fmt"
- "io"
"os"
"reflect"
"strings"
@@ -30,12 +29,6 @@
//go:generate v23 test generate
-const (
- noWaitersCmd = "noWaiters"
- forceStopCmd = "forceStop"
- appCmd = "app"
-)
-
// TestBasic verifies that the basic plumbing works: LocalStop calls result in
// stop messages being sent on the channel passed to WaitForStop.
func TestBasic(t *testing.T) {
@@ -103,17 +96,17 @@
}
}
-func noWaiters(stdin io.Reader, stdout, stderr io.Writer, env map[string]string, args ...string) error {
+var noWaiters = modules.Register(func(env *modules.Env, args ...string) error {
ctx, shutdown := test.InitForTest()
defer shutdown()
m := v23.GetAppCycle(ctx)
- fmt.Fprintf(stdout, "ready\n")
- modules.WaitForEOF(stdin)
+ fmt.Fprintf(env.Stdout, "ready\n")
+ modules.WaitForEOF(env.Stdin)
m.Stop()
os.Exit(42) // This should not be reached.
return nil
-}
+}, "noWaiters")
// TestNoWaiters verifies that the child process exits in the absence of any
// wait channel being registered with its runtime.
@@ -123,7 +116,7 @@
t.Fatalf("unexpected error: %s", err)
}
defer sh.Cleanup(os.Stderr, os.Stderr)
- h, err := sh.Start(noWaitersCmd, nil)
+ h, err := sh.Start(nil, noWaiters)
if err != nil {
t.Fatalf("unexpected error: %s", err)
}
@@ -134,18 +127,18 @@
}
}
-func forceStop(stdin io.Reader, stdout, stderr io.Writer, env map[string]string, args ...string) error {
+var forceStop = modules.Register(func(env *modules.Env, args ...string) error {
ctx, shutdown := test.InitForTest()
defer shutdown()
m := v23.GetAppCycle(ctx)
- fmt.Fprintf(stdout, "ready\n")
- modules.WaitForEOF(stdin)
+ fmt.Fprintf(env.Stdout, "ready\n")
+ modules.WaitForEOF(env.Stdin)
m.WaitForStop(make(chan string, 1))
m.ForceStop()
os.Exit(42) // This should not be reached.
return nil
-}
+}, "forceStop")
// TestForceStop verifies that ForceStop causes the child process to exit
// immediately.
@@ -155,7 +148,7 @@
t.Fatalf("unexpected error: %s", err)
}
defer sh.Cleanup(os.Stderr, os.Stderr)
- h, err := sh.Start(forceStopCmd, nil)
+ h, err := sh.Start(nil, forceStop)
if err != nil {
t.Fatalf("unexpected error: %s", err)
}
@@ -250,21 +243,21 @@
}
}
-func app(stdin io.Reader, stdout, stderr io.Writer, env map[string]string, args ...string) error {
+var app = modules.Register(func(env *modules.Env, args ...string) error {
ctx, shutdown := test.InitForTest()
defer shutdown()
m := v23.GetAppCycle(ctx)
ch := make(chan string, 1)
m.WaitForStop(ch)
- fmt.Fprintf(stdout, "Got %s\n", <-ch)
+ fmt.Fprintf(env.Stdout, "Got %s\n", <-ch)
m.AdvanceGoal(10)
- fmt.Fprintf(stdout, "Doing some work\n")
+ fmt.Fprintf(env.Stdout, "Doing some work\n")
m.AdvanceProgress(2)
- fmt.Fprintf(stdout, "Doing some more work\n")
+ fmt.Fprintf(env.Stdout, "Doing some more work\n")
m.AdvanceProgress(5)
return nil
-}
+}, "app")
type configServer struct {
ch chan<- string
@@ -306,7 +299,7 @@
sh.SetConfigKey(mgmt.ParentNameConfigKey, configServiceName)
sh.SetConfigKey(mgmt.ProtocolConfigKey, "tcp")
sh.SetConfigKey(mgmt.AddressConfigKey, "127.0.0.1:0")
- h, err := sh.Start("app", nil)
+ h, err := sh.Start(nil, app)
if err != nil {
t.Fatalf("unexpected error: %s", err)
}
diff --git a/runtime/internal/rt/rt_test.go b/runtime/internal/rt/rt_test.go
index 3e53548..2b49ef7 100644
--- a/runtime/internal/rt/rt_test.go
+++ b/runtime/internal/rt/rt_test.go
@@ -6,7 +6,6 @@
import (
"fmt"
- "io"
"io/ioutil"
"os"
"regexp"
@@ -53,17 +52,17 @@
}
}
-func child(stdin io.Reader, stdout, stderr io.Writer, env map[string]string, args ...string) error {
+var child = modules.Register(func(env *modules.Env, args ...string) error {
_, shutdown := test.InitForTest()
defer shutdown()
logger := vlog.Log
vlog.Infof("%s\n", logger)
- fmt.Fprintf(stdout, "%s\n", logger)
- modules.WaitForEOF(stdin)
- fmt.Fprintf(stdout, "done\n")
+ fmt.Fprintf(env.Stdout, "%s\n", logger)
+ modules.WaitForEOF(env.Stdin)
+ fmt.Fprintf(env.Stdout, "done\n")
return nil
-}
+}, "child")
func TestInitArgs(t *testing.T) {
sh, err := modules.NewShell(nil, nil, testing.Verbose(), t)
@@ -71,7 +70,7 @@
t.Fatalf("unexpected error: %s", err)
}
defer sh.Cleanup(os.Stderr, os.Stderr)
- h, err := sh.Start("child", nil, "--logtostderr=true", "--vmodule=*=3", "--", "foobar")
+ h, err := sh.Start(nil, child, "--logtostderr=true", "--vmodule=*=3", "--", "foobar")
if err != nil {
t.Fatalf("unexpected error: %s", err)
}
@@ -121,7 +120,7 @@
return dir
}
-func principal(stdin io.Reader, stdout, stderr io.Writer, env map[string]string, args ...string) error {
+var principal = modules.Register(func(env *modules.Env, args ...string) error {
ctx, shutdown := test.InitForTest()
defer shutdown()
@@ -129,13 +128,13 @@
if err := validatePrincipal(p); err != nil {
return err
}
- fmt.Fprintf(stdout, "DEFAULT_BLESSING=%s\n", defaultBlessing(p))
+ fmt.Fprintf(env.Stdout, "DEFAULT_BLESSING=%s\n", defaultBlessing(p))
return nil
-}
+}, "principal")
// Runner runs a principal as a subprocess and reports back with its
// own security info and it's childs.
-func runner(stdin io.Reader, stdout, stderr io.Writer, env map[string]string, args ...string) error {
+var runner = modules.Register(func(env *modules.Env, args ...string) error {
ctx, shutdown := test.InitForTest()
defer shutdown()
@@ -143,18 +142,18 @@
if err := validatePrincipal(p); err != nil {
return err
}
- fmt.Fprintf(stdout, "RUNNER_DEFAULT_BLESSING=%v\n", defaultBlessing(p))
+ fmt.Fprintf(env.Stdout, "RUNNER_DEFAULT_BLESSING=%v\n", defaultBlessing(p))
sh, err := modules.NewShell(ctx, p, false, nil)
if err != nil {
return err
}
- if _, err := sh.Start("principal", nil, args...); err != nil {
+ if _, err := sh.Start(nil, principal, args...); err != nil {
return err
}
// Cleanup copies the output of sh to these Writers.
- sh.Cleanup(stdout, stderr)
+ sh.Cleanup(env.Stdout, env.Stderr)
return nil
-}
+}, "runner")
func createCredentialsInDir(t *testing.T, dir string, blessing string) {
principal, err := vsecurity.CreatePersistentPrincipal(dir, nil)
@@ -186,7 +185,7 @@
// directory supplied by the environment.
credEnv := []string{ref.EnvCredentials + "=" + cdir}
- h, err := sh.Start("runner", credEnv)
+ h, err := sh.Start(credEnv, runner)
if err != nil {
t.Fatalf("unexpected error: %s", err)
}
@@ -209,7 +208,7 @@
func TestPrincipalInit(t *testing.T) {
// Collect the process' public key and error status
collect := func(sh *modules.Shell, env []string, args ...string) string {
- h, err := sh.Start("principal", env, args...)
+ h, err := sh.Start(env, principal, args...)
if err != nil {
t.Fatalf("unexpected error: %s", err)
}
diff --git a/runtime/internal/rt/shutdown_servers_test.go b/runtime/internal/rt/shutdown_servers_test.go
index 1a05535..c0713e5 100644
--- a/runtime/internal/rt/shutdown_servers_test.go
+++ b/runtime/internal/rt/shutdown_servers_test.go
@@ -23,11 +23,6 @@
"v.io/x/ref/test/modules"
)
-func init() {
- modules.RegisterChild("simpleServerProgram", "", simpleServerProgram)
- modules.RegisterChild("complexServerProgram", "", complexServerProgram)
-}
-
type dummy struct{}
func (*dummy) Echo(*context.T, rpc.ServerCall) error { return nil }
@@ -73,7 +68,7 @@
// complex server application (with several servers, a mix of interruptible
// and blocking cleanup, and parallel and sequential cleanup execution).
// For a more typical server, see simpleServerProgram.
-func complexServerProgram(stdin io.Reader, stdout, stderr io.Writer, env map[string]string, args ...string) error {
+var complexServerProgram = modules.Register(func(env *modules.Env, args ...string) error {
// Initialize the runtime. This is boilerplate.
ctx, shutdown := test.InitForTest()
// shutdown is optional, but it's a good idea to clean up, especially
@@ -84,7 +79,7 @@
// commands from the parent process to simulate Stop and
// RemoteStop commands that would normally be issued from
// application code.
- defer remoteCmdLoop(ctx, stdin)()
+ defer remoteCmdLoop(ctx, env.Stdin)()
// Create a couple servers, and start serving.
server1 := makeServer(ctx)
@@ -118,9 +113,9 @@
case sig := <-sigChan:
// If the developer wants to take different actions
// depending on the type of signal, they can do it here.
- fmt.Fprintln(stdout, "Received signal", sig)
+ fmt.Fprintln(env.Stdout, "Received signal", sig)
case stop := <-stopChan:
- fmt.Fprintln(stdout, "Stop", stop)
+ fmt.Fprintln(env.Stdout, "Stop", stop)
}
// This commences the cleanup stage.
done.Done()
@@ -138,7 +133,7 @@
// This communicates to the parent test driver process in our unit test
// that this server is ready and waiting on signals or stop commands.
// It's purely an artifact of our test setup.
- fmt.Fprintln(stdout, "Ready")
+ fmt.Fprintln(env.Stdout, "Ready")
// Wait for shutdown.
done.Wait()
@@ -174,7 +169,7 @@
parallelCleanup.Add(1)
blocking.Add(1)
go func() {
- fmt.Fprintln(stdout, "Parallel blocking cleanup1")
+ fmt.Fprintln(env.Stdout, "Parallel blocking cleanup1")
blocking.Done()
parallelCleanup.Done()
}()
@@ -182,40 +177,40 @@
parallelCleanup.Add(1)
blocking.Add(1)
go func() {
- fmt.Fprintln(stdout, "Parallel blocking cleanup2")
+ fmt.Fprintln(env.Stdout, "Parallel blocking cleanup2")
blocking.Done()
parallelCleanup.Done()
}()
parallelCleanup.Add(1)
go func() {
- fmt.Fprintln(stdout, "Parallel interruptible cleanup1")
+ fmt.Fprintln(env.Stdout, "Parallel interruptible cleanup1")
parallelCleanup.Done()
}()
parallelCleanup.Add(1)
go func() {
- fmt.Fprintln(stdout, "Parallel interruptible cleanup2")
+ fmt.Fprintln(env.Stdout, "Parallel interruptible cleanup2")
parallelCleanup.Done()
}()
// Simulate two sequential cleanup steps, one blocking and one
// interruptible.
- fmt.Fprintln(stdout, "Sequential blocking cleanup")
+ fmt.Fprintln(env.Stdout, "Sequential blocking cleanup")
blocking.Wait()
close(blockingCh)
- fmt.Fprintln(stdout, "Sequential interruptible cleanup")
+ fmt.Fprintln(env.Stdout, "Sequential interruptible cleanup")
parallelCleanup.Wait()
return nil
-}
+}, "complexServerProgram")
// simpleServerProgram demonstrates the recommended way to write a typical
// simple server application (with one server and a clean shutdown triggered by
// a signal or a stop command). For an example of something more involved, see
// complexServerProgram.
-func simpleServerProgram(stdin io.Reader, stdout, stderr io.Writer, env map[string]string, args ...string) error {
+var simpleServerProgram = modules.Register(func(env *modules.Env, args ...string) error {
// Initialize the runtime. This is boilerplate.
ctx, shutdown := test.InitForTest()
// Calling shutdown is optional, but it's a good idea to clean up, especially
@@ -230,7 +225,7 @@
// commands from the parent process to simulate Stop and
// RemoteStop commands that would normally be issued from
// application code.
- defer remoteCmdLoop(ctx, stdin)()
+ defer remoteCmdLoop(ctx, env.Stdin)()
// Create a server, and start serving.
server := makeServer(ctx)
@@ -246,17 +241,17 @@
// This communicates to the parent test driver process in our unit test
// that this server is ready and waiting on signals or stop commands.
// It's purely an artifact of our test setup.
- fmt.Fprintln(stdout, "Ready")
+ fmt.Fprintln(env.Stdout, "Ready")
// Use defer for anything that should still execute even if a panic
// occurs.
- defer fmt.Fprintln(stdout, "Deferred cleanup")
+ defer fmt.Fprintln(env.Stdout, "Deferred cleanup")
// Wait for shutdown.
sig := <-waiter
// The developer could take different actions depending on the type of
// signal.
- fmt.Fprintln(stdout, "Received signal", sig)
+ fmt.Fprintln(env.Stdout, "Received signal", sig)
// Cleanup code starts here. Alternatively, these steps could be
// invoked through defer, but we list them here to make the order of
@@ -268,7 +263,7 @@
// Note, this will not execute in cases of forced shutdown
// (e.g. SIGSTOP), when the process calls os.Exit (e.g. via log.Fatal),
// or when a panic occurs.
- fmt.Fprintln(stdout, "Interruptible cleanup")
+ fmt.Fprintln(env.Stdout, "Interruptible cleanup")
return nil
-}
+}, "simpleServerProgram")
diff --git a/runtime/internal/rt/shutdown_test.go b/runtime/internal/rt/shutdown_test.go
index 85798b3..efb7aff 100644
--- a/runtime/internal/rt/shutdown_test.go
+++ b/runtime/internal/rt/shutdown_test.go
@@ -39,7 +39,7 @@
func TestSimpleServerSignal(t *testing.T) {
sh := newShell(t)
defer sh.Cleanup(os.Stdout, cstderr)
- h, _ := sh.Start("simpleServerProgram", nil)
+ h, _ := sh.Start(nil, simpleServerProgram)
h.Expect("Ready")
syscall.Kill(h.Pid(), syscall.SIGINT)
h.Expect("Received signal interrupt")
@@ -54,7 +54,7 @@
func TestSimpleServerLocalStop(t *testing.T) {
sh := newShell(t)
defer sh.Cleanup(os.Stdout, cstderr)
- h, _ := sh.Start("simpleServerProgram", nil)
+ h, _ := sh.Start(nil, simpleServerProgram)
h.Expect("Ready")
fmt.Fprintln(h.Stdin(), "stop")
h.Expect(fmt.Sprintf("Received signal %s", v23.LocalStop))
@@ -70,7 +70,7 @@
func TestSimpleServerDoubleSignal(t *testing.T) {
sh := newShell(t)
defer sh.Cleanup(os.Stdout, cstderr)
- h, _ := sh.Start("simpleServerProgram", nil)
+ h, _ := sh.Start(nil, simpleServerProgram)
h.Expect("Ready")
syscall.Kill(h.Pid(), syscall.SIGINT)
h.Expect("Received signal interrupt")
@@ -89,7 +89,7 @@
func TestSimpleServerLocalForceStop(t *testing.T) {
sh := newShell(t)
defer sh.Cleanup(os.Stdout, cstderr)
- h, _ := sh.Start("simpleServerProgram", nil)
+ h, _ := sh.Start(nil, simpleServerProgram)
h.Expect("Ready")
fmt.Fprintln(h.Stdin(), "forcestop")
h.Expect("straight exit")
@@ -107,7 +107,7 @@
func TestSimpleServerKill(t *testing.T) {
sh := newShell(t)
defer sh.Cleanup(os.Stdout, cstderr)
- h, _ := sh.Start("simpleServerProgram", nil)
+ h, _ := sh.Start(nil, simpleServerProgram)
h.Expect("Ready")
syscall.Kill(h.Pid(), syscall.SIGKILL)
err := h.Shutdown(os.Stdout, cstderr)
@@ -126,7 +126,7 @@
func TestComplexServerSignal(t *testing.T) {
sh := newShell(t)
defer sh.Cleanup(os.Stdout, cstderr)
- h, _ := sh.Start("complexServerProgram", nil)
+ h, _ := sh.Start(nil, complexServerProgram)
h.Expect("Ready")
syscall.Kill(h.Pid(), syscall.SIGINT)
h.Expect("Received signal interrupt")
@@ -147,7 +147,7 @@
func TestComplexServerLocalStop(t *testing.T) {
sh := newShell(t)
defer sh.Cleanup(os.Stdout, cstderr)
- h, _ := sh.Start("complexServerProgram", nil)
+ h, _ := sh.Start(nil, complexServerProgram)
h.Expect("Ready")
fmt.Fprintln(h.Stdin(), "stop")
@@ -173,7 +173,7 @@
func TestComplexServerDoubleSignal(t *testing.T) {
sh := newShell(t)
defer sh.Cleanup(os.Stdout, cstderr)
- h, _ := sh.Start("complexServerProgram", nil)
+ h, _ := sh.Start(nil, complexServerProgram)
h.Expect("Ready")
syscall.Kill(h.Pid(), syscall.SIGINT)
h.Expect("Received signal interrupt")
@@ -196,7 +196,7 @@
func TestComplexServerLocalForceStop(t *testing.T) {
sh := newShell(t)
defer sh.Cleanup(os.Stdout, cstderr)
- h, _ := sh.Start("complexServerProgram", nil)
+ h, _ := sh.Start(nil, complexServerProgram)
h.Expect("Ready")
fmt.Fprintln(h.Stdin(), "forcestop")
h.Expect("straight exit")
@@ -214,7 +214,7 @@
func TestComplexServerKill(t *testing.T) {
sh := newShell(t)
defer sh.Cleanup(os.Stdout, cstderr)
- h, _ := sh.Start("complexServerProgram", nil)
+ h, _ := sh.Start(nil, complexServerProgram)
h.Expect("Ready")
syscall.Kill(h.Pid(), syscall.SIGKILL)
err := h.Shutdown(os.Stdout, cstderr)
diff --git a/runtime/internal/rt/signal_test.go b/runtime/internal/rt/signal_test.go
index d84455b..7027071 100644
--- a/runtime/internal/rt/signal_test.go
+++ b/runtime/internal/rt/signal_test.go
@@ -18,11 +18,6 @@
"v.io/x/ref/test/modules"
)
-func init() {
- modules.RegisterChild("withRuntime", "", withRuntime)
- modules.RegisterChild("withoutRuntime", "", withoutRuntime)
-}
-
func simpleEchoProgram(stdin io.Reader, stdout io.Writer) {
fmt.Fprintf(stdout, "ready\n")
scanner := bufio.NewScanner(stdin)
@@ -32,18 +27,18 @@
modules.WaitForEOF(stdin)
}
-func withRuntime(stdin io.Reader, stdout, stderr io.Writer, env map[string]string, args ...string) error {
+var withRuntime = modules.Register(func(env *modules.Env, args ...string) error {
_, shutdown := test.InitForTest()
defer shutdown()
- simpleEchoProgram(stdin, stdout)
+ simpleEchoProgram(env.Stdin, env.Stdout)
return nil
-}
+}, "withRuntime")
-func withoutRuntime(stdin io.Reader, stdout, stderr io.Writer, env map[string]string, args ...string) error {
- simpleEchoProgram(stdin, stdout)
+var withoutRuntime = modules.Register(func(env *modules.Env, args ...string) error {
+ simpleEchoProgram(env.Stdin, env.Stdout)
return nil
-}
+}, "withoutRuntime")
func TestWithRuntime(t *testing.T) {
sh, err := modules.NewShell(nil, nil, testing.Verbose(), t)
@@ -51,7 +46,7 @@
t.Fatalf("unexpected error: %s", err)
}
defer sh.Cleanup(os.Stderr, os.Stderr)
- h, err := sh.Start("withRuntime", nil)
+ h, err := sh.Start(nil, withRuntime)
if err != nil {
t.Fatalf("unexpected error: %s", err)
}
@@ -72,7 +67,7 @@
defer sh.Cleanup(os.Stderr, os.Stderr)
opts := sh.DefaultStartOpts()
opts.ShutdownTimeout = 5 * time.Second
- h, err := sh.StartWithOpts(opts, nil, "withoutRuntime")
+ h, err := sh.StartWithOpts(opts, nil, withoutRuntime)
if err != nil {
t.Fatalf("unexpected error: %s", err)
}
diff --git a/runtime/internal/rt/v23_test.go b/runtime/internal/rt/v23_test.go
index 774f3c3..2145611 100644
--- a/runtime/internal/rt/v23_test.go
+++ b/runtime/internal/rt/v23_test.go
@@ -4,43 +4,19 @@
// This file was auto-generated via go generate.
// DO NOT UPDATE MANUALLY
+
package rt_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("noWaiters", ``, noWaiters)
- modules.RegisterChild("forceStop", ``, forceStop)
- modules.RegisterChild("app", ``, app)
- modules.RegisterChild("child", ``, child)
- modules.RegisterChild("principal", ``, principal)
- modules.RegisterChild("runner", `Runner runs a principal as a subprocess and reports back with its
-own security info and it's childs.`, runner)
- modules.RegisterChild("complexServerProgram", `complexServerProgram demonstrates the recommended way to write a more
-complex server application (with several servers, a mix of interruptible
-and blocking cleanup, and parallel and sequential cleanup execution).
-For a more typical server, see simpleServerProgram.`, complexServerProgram)
- modules.RegisterChild("simpleServerProgram", `simpleServerProgram demonstrates the recommended way to write a typical
-simple server application (with one server and a clean shutdown triggered by
-a signal or a stop command). For an example of something more involved, see
-complexServerProgram.`, simpleServerProgram)
- modules.RegisterChild("withRuntime", ``, withRuntime)
- modules.RegisterChild("withoutRuntime", ``, withoutRuntime)
-}
+ "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())
}