Merge "veyron/runtimes/google/rt: use modules instead of blackbox."
diff --git a/lib/modules/exec.go b/lib/modules/exec.go
index 5ca6f09..341b60f 100644
--- a/lib/modules/exec.go
+++ b/lib/modules/exec.go
@@ -133,7 +133,10 @@
 	eh.mu.Lock()
 	defer eh.mu.Unlock()
 	eh.sh = sh
-	newargs := append(testFlags(), args...)
+	// Take care to not pass the command line as an arg to the child
+	// process since that'll prevent parsing any subsequent args by
+	// the flag package.
+	newargs := append(testFlags(), args[1:]...)
 	cmd := exec.Command(os.Args[0], newargs...)
 	cmd.Env = append(sh.mergeOSEnvSlice(), eh.entryPoint)
 	fname := strings.TrimPrefix(eh.entryPoint, ShellEntryPoint+"=")
@@ -280,7 +283,8 @@
 		}
 	}(os.Getppid())
 
-	return m.fn(os.Stdin, os.Stdout, os.Stderr, osEnvironMap(), flag.Args()...)
+	args := append([]string{command}, flag.Args()...)
+	return m.fn(os.Stdin, os.Stdout, os.Stderr, osEnvironMap(), args...)
 }
 
 func (child *childRegistrar) addSubprocesses(sh *Shell, pattern string) error {
diff --git a/lib/testutil/init.go b/lib/testutil/init.go
index 9cc119f..07fa6d4 100644
--- a/lib/testutil/init.go
+++ b/lib/testutil/init.go
@@ -20,6 +20,7 @@
 	// flag.Parse in init() is the right solution?
 	_ "testing"
 	"time"
+
 	_ "veyron.io/veyron/veyron/services/mgmt/suidhelper/impl/flag"
 
 	// Import blackbox to ensure that it gets to define its flags.
diff --git a/runtimes/google/rt/mgmt_test.go b/runtimes/google/rt/mgmt_test.go
index e15eaa2..bd0c97b 100644
--- a/runtimes/google/rt/mgmt_test.go
+++ b/runtimes/google/rt/mgmt_test.go
@@ -2,10 +2,12 @@
 
 import (
 	"fmt"
+	"io"
 	"os"
 	"reflect"
 	"strings"
 	"testing"
+	"time"
 
 	"veyron.io/veyron/veyron2"
 	"veyron.io/veyron/veyron2/ipc"
@@ -13,8 +15,9 @@
 	"veyron.io/veyron/veyron2/naming"
 	"veyron.io/veyron/veyron2/services/mgmt/appcycle"
 
+	"veyron.io/veyron/veyron/lib/expect"
+	"veyron.io/veyron/veyron/lib/modules"
 	_ "veyron.io/veyron/veyron/lib/testutil"
-	"veyron.io/veyron/veyron/lib/testutil/blackbox"
 	"veyron.io/veyron/veyron/lib/testutil/security"
 	"veyron.io/veyron/veyron/profiles"
 	"veyron.io/veyron/veyron/runtimes/google/rt"
@@ -22,6 +25,18 @@
 	"veyron.io/veyron/veyron/services/mgmt/node"
 )
 
+const (
+	noWaitersCmd = "noWaiters"
+	forceStopCmd = "forceStop"
+	appCmd       = "app"
+)
+
+func init() {
+	modules.RegisterChild(noWaitersCmd, "", noWaiters)
+	modules.RegisterChild(forceStopCmd, "", forceStop)
+	modules.RegisterChild(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) {
@@ -80,51 +95,57 @@
 	}
 }
 
-func init() {
-	blackbox.CommandTable["noWaiters"] = noWaiters
-}
-
-func noWaiters([]string) {
+func noWaiters(stdin io.Reader, stdout, stderr io.Writer, env map[string]string, args ...string) error {
 	m, _ := rt.New()
-	fmt.Println("ready")
-	blackbox.WaitForEOFOnStdin()
+	fmt.Fprintf(stdout, "ready\n")
+	modules.WaitForEOF(stdin)
 	m.Stop()
 	os.Exit(42) // This should not be reached.
+	return nil
 }
 
 // TestNoWaiters verifies that the child process exits in the absence of any
 // wait channel being registered with its runtime.
 func TestNoWaiters(t *testing.T) {
-	c := blackbox.HelperCommand(t, "noWaiters")
-	defer c.Cleanup()
-	c.Cmd.Start()
-	c.Expect("ready")
-	c.CloseStdin()
-	c.ExpectEOFAndWaitForExitCode(fmt.Errorf("exit status %d", veyron2.UnhandledStopExitCode))
+	sh := modules.NewShell(noWaitersCmd)
+	defer sh.Cleanup(os.Stderr, os.Stderr)
+	h, err := sh.Start(noWaitersCmd)
+	if err != nil {
+		t.Fatalf("unexpected error: %s", err)
+	}
+	expect.NewSession(t, h.Stdout(), time.Minute).Expect("ready")
+	want := fmt.Sprintf("exit status %d", veyron2.UnhandledStopExitCode)
+	if err = h.Shutdown(os.Stderr, os.Stderr); err == nil || err.Error() != want {
+		t.Errorf("got %v, want %s", err, want)
+	}
 }
 
-func init() {
-	blackbox.CommandTable["forceStop"] = forceStop
-}
-
-func forceStop([]string) {
+func forceStop(stdin io.Reader, stdout, stderr io.Writer, env map[string]string, args ...string) error {
 	m, _ := rt.New()
-	fmt.Println("ready")
-	blackbox.WaitForEOFOnStdin()
+	fmt.Fprintf(stdout, "ready\n")
+	modules.WaitForEOF(stdin)
 	m.WaitForStop(make(chan string, 1))
 	m.ForceStop()
 	os.Exit(42) // This should not be reached.
+	return nil
 }
 
 // TestForceStop verifies that ForceStop causes the child process to exit
 // immediately.
 func TestForceStop(t *testing.T) {
-	c := blackbox.HelperCommand(t, "forceStop")
-	defer c.Cleanup()
-	c.Cmd.Start()
-	c.Expect("ready")
-	c.CloseStdin()
-	c.ExpectEOFAndWaitForExitCode(fmt.Errorf("exit status %d", veyron2.ForceStopExitCode))
+	sh := modules.NewShell(forceStopCmd)
+	defer sh.Cleanup(os.Stderr, os.Stderr)
+	h, err := sh.Start(forceStopCmd)
+	if err != nil {
+		t.Fatalf("unexpected error: %s", err)
+	}
+	s := expect.NewSession(t, h.Stdout(), time.Minute)
+	s.Expect("ready")
+	err = h.Shutdown(os.Stderr, os.Stderr)
+	want := fmt.Sprintf("exit status %d", veyron2.UnhandledStopExitCode)
+	if err == nil || err.Error() != want {
+		t.Errorf("got %v, want %s", err, want)
+	}
 }
 
 func checkProgress(t *testing.T, ch <-chan veyron2.Task, progress, goal int) {
@@ -206,25 +227,21 @@
 	}
 }
 
-func init() {
-	blackbox.CommandTable["app"] = app
-}
-
-func app([]string) {
+func app(stdin io.Reader, stdout, stderr io.Writer, env map[string]string, args ...string) error {
 	r, err := rt.New()
 	if err != nil {
-		fmt.Printf("Error creating runtime: %v\n", err)
-		return
+		return err
 	}
 	defer r.Cleanup()
 	ch := make(chan string, 1)
 	r.WaitForStop(ch)
-	fmt.Printf("Got %s\n", <-ch)
+	fmt.Fprintf(stdout, "Got %s\n", <-ch)
 	r.AdvanceGoal(10)
-	fmt.Println("Doing some work")
+	fmt.Fprintf(stdout, "Doing some work\n")
 	r.AdvanceProgress(2)
-	fmt.Println("Doing some more work")
+	fmt.Fprintf(stdout, "Doing some more work\n")
 	r.AdvanceProgress(5)
+	return nil
 }
 
 type configServer struct {
@@ -258,27 +275,30 @@
 
 }
 
-func setupRemoteAppCycleMgr(t *testing.T) (veyron2.Runtime, *blackbox.Child, appcycle.AppCycle, func()) {
+func setupRemoteAppCycleMgr(t *testing.T) (veyron2.Runtime, modules.Handle, appcycle.AppCycle, func()) {
 	// We need to use the public API since stubs are used below (and they
 	// refer to the global rt.R() function), but we take care to make sure
 	// that the "google" runtime we are trying to test in this package is
 	// the one being used.
 	r, _ := rt.New(veyron2.RuntimeOpt{veyron2.GoogleRuntimeName}, veyron2.ForceNewSecurityModel{})
-	c := blackbox.HelperCommand(t, "app")
-	childcreds := security.NewVeyronCredentials(r.Principal(), "app")
+
 	configServer, configServiceName, ch := createConfigServer(t, r)
-	c.Cmd.Env = append(c.Cmd.Env, fmt.Sprintf("VEYRON_CREDENTIALS=%v", childcreds),
-		fmt.Sprintf("%v=%v", mgmt.ParentNodeManagerConfigKey, configServiceName))
-	c.Cmd.Start()
+	sh := modules.NewShell(appCmd)
+	sh.SetVar("VEYRON_CREDENTIALS", security.NewVeyronCredentials(r.Principal(), appCmd))
+	sh.SetVar(mgmt.ParentNodeManagerConfigKey, configServiceName)
+	h, err := sh.Start("app")
+	if err != nil {
+		t.Fatalf("unexpected error: %s", err)
+	}
+
 	appCycleName := <-ch
 	appCycle, err := appcycle.BindAppCycle(appCycleName)
 	if err != nil {
 		t.Fatalf("Got error: %v", err)
 	}
-	return r, c, appCycle, func() {
+	return r, h, appCycle, func() {
 		configServer.Stop()
-		c.Cleanup()
-		os.RemoveAll(childcreds)
+		sh.Cleanup(os.Stderr, os.Stderr)
 		// Don't do r.Cleanup() since the runtime needs to be used by
 		// more than one test case.
 	}
@@ -287,18 +307,25 @@
 // TestRemoteForceStop verifies that the child process exits when sending it
 // a remote ForceStop rpc.
 func TestRemoteForceStop(t *testing.T) {
-	r, c, appCycle, cleanup := setupRemoteAppCycleMgr(t)
+	r, h, appCycle, cleanup := setupRemoteAppCycleMgr(t)
 	defer cleanup()
 	if err := appCycle.ForceStop(r.NewContext()); err == nil || !strings.Contains(err.Error(), "EOF") {
 		t.Fatalf("Expected EOF error, got %v instead", err)
 	}
-	c.ExpectEOFAndWaitForExitCode(fmt.Errorf("exit status %d", veyron2.ForceStopExitCode))
+	s := expect.NewSession(t, h.Stdout(), time.Minute)
+	s.ExpectEOF()
+	err := h.Shutdown(os.Stderr, os.Stderr)
+	want := fmt.Sprintf("exit status %d", veyron2.ForceStopExitCode)
+	if err == nil || err.Error() != want {
+		t.Errorf("got %v, want %s", err, want)
+	}
+
 }
 
 // TestRemoteStop verifies that the child shuts down cleanly when sending it
 // a remote Stop rpc.
 func TestRemoteStop(t *testing.T) {
-	r, c, appCycle, cleanup := setupRemoteAppCycleMgr(t)
+	r, h, appCycle, cleanup := setupRemoteAppCycleMgr(t)
 	defer cleanup()
 	stream, err := appCycle.Stop(r.NewContext())
 	if err != nil {
@@ -323,8 +350,12 @@
 	if err := stream.Finish(); err != nil {
 		t.Errorf("Got error %v", err)
 	}
-	c.Expect(fmt.Sprintf("Got %s", veyron2.RemoteStop))
-	c.Expect("Doing some work")
-	c.Expect("Doing some more work")
-	c.ExpectEOFAndWait()
+	s := expect.NewSession(t, h.Stdout(), time.Minute)
+	s.Expect(fmt.Sprintf("Got %s", veyron2.RemoteStop))
+	s.Expect("Doing some work")
+	s.Expect("Doing some more work")
+	s.ExpectEOF()
+	if err := h.Shutdown(os.Stderr, os.Stderr); err != nil {
+		t.Fatalf("unexpected error: %s", err)
+	}
 }
diff --git a/runtimes/google/rt/rt_test.go b/runtimes/google/rt/rt_test.go
index 4faaf1c..51f2031 100644
--- a/runtimes/google/rt/rt_test.go
+++ b/runtimes/google/rt/rt_test.go
@@ -2,21 +2,24 @@
 
 import (
 	"fmt"
+	"io"
 	"io/ioutil"
 	"os"
 	"reflect"
 	"regexp"
 	"testing"
-
-	_ "veyron.io/veyron/veyron/lib/testutil"
-	"veyron.io/veyron/veyron/lib/testutil/blackbox"
-	irt "veyron.io/veyron/veyron/runtimes/google/rt"
+	"time"
 
 	"veyron.io/veyron/veyron2"
 	"veyron.io/veyron/veyron2/naming"
 	"veyron.io/veyron/veyron2/rt"
 	"veyron.io/veyron/veyron2/security"
 	"veyron.io/veyron/veyron2/vlog"
+
+	"veyron.io/veyron/veyron/lib/expect"
+	"veyron.io/veyron/veyron/lib/modules"
+	_ "veyron.io/veyron/veyron/lib/testutil"
+	irt "veyron.io/veyron/veyron/runtimes/google/rt"
 )
 
 type context struct {
@@ -37,11 +40,11 @@
 func (*context) RemoteEndpoint() naming.Endpoint           { return nil }
 
 func init() {
-	blackbox.CommandTable["child"] = child
+	modules.RegisterChild("child", "", child)
 }
 
 func TestHelperProcess(t *testing.T) {
-	blackbox.HelperProcess(t)
+	modules.DispatchInTest()
 }
 
 func TestInit(t *testing.T) {
@@ -60,24 +63,24 @@
 	}
 }
 
-func child(argv []string) {
+func child(stdin io.Reader, stdout, stderr io.Writer, env map[string]string, args ...string) error {
 	r := rt.Init()
 	vlog.Infof("%s\n", r.Logger())
-	fmt.Printf("%s\n", r.Logger())
-	_ = r
-	blackbox.WaitForEOFOnStdin()
+	fmt.Fprintf(stdout, "%s\n", r.Logger())
+	modules.WaitForEOF(stdin)
 	fmt.Printf("done\n")
+	return nil
 }
 
 func TestInitArgs(t *testing.T) {
-	c := blackbox.HelperCommand(t, "child", "--logtostderr=true", "--vv=3", "--", "foobar")
-	defer c.Cleanup()
-	c.Cmd.Start()
-	str, err := c.ReadLineFromChild()
+	sh := modules.NewShell("child")
+	defer sh.Cleanup(os.Stderr, os.Stderr)
+	h, err := sh.Start("child", "--logtostderr=true", "--vv=3", "--", "foobar")
 	if err != nil {
-		t.Fatalf("failed to read from child: %v", err)
+		t.Fatalf("unexpected error: %s", err)
 	}
-	expected := fmt.Sprintf("name=veyron "+
+	s := expect.NewSession(t, h.Stdout(), time.Minute)
+	s.Expect(fmt.Sprintf("name=veyron "+
 		"logdirs=[%s] "+
 		"logtostderr=true "+
 		"alsologtostderr=true "+
@@ -86,14 +89,11 @@
 		"stderrthreshold=2 "+
 		"vmodule= "+
 		"log_backtrace_at=:0",
-		os.TempDir())
-
-	if str != expected {
-		t.Fatalf("incorrect child output: got %s, expected %s", str, expected)
-	}
-	c.CloseStdin()
-	c.Expect("done")
-	c.ExpectEOFAndWait()
+		os.TempDir()))
+	h.CloseStdin()
+	s.Expect("done")
+	s.ExpectEOF()
+	h.Shutdown(os.Stderr, os.Stderr)
 }
 
 func TestInitPrincipal(t *testing.T) {
diff --git a/runtimes/google/rt/signal_test.go b/runtimes/google/rt/signal_test.go
index c0f6235..5c400a9 100644
--- a/runtimes/google/rt/signal_test.go
+++ b/runtimes/google/rt/signal_test.go
@@ -1,56 +1,82 @@
 package rt_test
 
 import (
+	"bufio"
 	"fmt"
+	"io"
+	"os"
 	"syscall"
 	"testing"
+	"time"
 
 	"veyron.io/veyron/veyron2"
 	"veyron.io/veyron/veyron2/rt"
 
-	"veyron.io/veyron/veyron/lib/testutil/blackbox"
+	"veyron.io/veyron/veyron/lib/expect"
+	"veyron.io/veyron/veyron/lib/modules"
 )
 
 func init() {
-	blackbox.CommandTable["withRuntime"] = withRuntime
-	blackbox.CommandTable["withoutRuntime"] = withoutRuntime
+	modules.RegisterChild("withRuntime", "", withRuntime)
+	modules.RegisterChild("withoutRuntime", "", withoutRuntime)
 }
 
-func simpleEchoProgram() {
-	fmt.Println("ready")
-	fmt.Println(blackbox.ReadLineFromStdin())
-	blackbox.WaitForEOFOnStdin()
+func simpleEchoProgram(stdin io.Reader, stdout io.Writer) {
+	fmt.Fprintf(stdout, "ready\n")
+	scanner := bufio.NewScanner(stdin)
+	if scanner.Scan() {
+		fmt.Fprintf(stdout, "%s\n", scanner.Text())
+	}
+	modules.WaitForEOF(stdin)
 }
 
-func withRuntime([]string) {
+func withRuntime(stdin io.Reader, stdout, stderr io.Writer, env map[string]string, args ...string) error {
 	// Make sure that we use "google" runtime implementation in this
 	// package even though we have to use the public API which supports
 	// arbitrary runtime implementations.
 	rt.Init(veyron2.RuntimeOpt{veyron2.GoogleRuntimeName})
-	simpleEchoProgram()
+	simpleEchoProgram(stdin, stdout)
+	return nil
 }
 
-func withoutRuntime([]string) {
-	simpleEchoProgram()
+func withoutRuntime(stdin io.Reader, stdout, stderr io.Writer, env map[string]string, args ...string) error {
+	simpleEchoProgram(stdin, stdout)
+	return nil
 }
 
 func TestWithRuntime(t *testing.T) {
-	c := blackbox.HelperCommand(t, "withRuntime")
-	defer c.Cleanup()
-	c.Cmd.Start()
-	c.Expect("ready")
-	syscall.Kill(c.Cmd.Process.Pid, syscall.SIGHUP)
-	c.WriteLine("foo")
-	c.Expect("foo")
-	c.CloseStdin()
-	c.ExpectEOFAndWait()
+	sh := modules.NewShell("withRuntime")
+	defer sh.Cleanup(os.Stderr, os.Stderr)
+	h, err := sh.Start("withRuntime")
+	if err != nil {
+		t.Fatalf("unexpected error: %s", err)
+	}
+	defer h.Shutdown(os.Stderr, os.Stderr)
+	s := expect.NewSession(t, h.Stdout(), time.Minute)
+	s.Expect("ready")
+	syscall.Kill(h.Pid(), syscall.SIGHUP)
+	h.Stdin().Write([]byte("foo\n"))
+	s.Expect("foo")
+	h.CloseStdin()
+	s.ExpectEOF()
 }
 
 func TestWithoutRuntime(t *testing.T) {
-	c := blackbox.HelperCommand(t, "withoutRuntime")
-	defer c.Cleanup()
-	c.Cmd.Start()
-	c.Expect("ready")
-	syscall.Kill(c.Cmd.Process.Pid, syscall.SIGHUP)
-	c.ExpectEOFAndWaitForExitCode(fmt.Errorf("exit status 2"))
+	sh := modules.NewShell("withoutRuntime")
+	defer sh.Cleanup(os.Stderr, os.Stderr)
+	h, err := sh.Start("withoutRuntime")
+	if err != nil {
+		t.Fatalf("unexpected error: %s", err)
+	}
+	defer h.Shutdown(os.Stderr, os.Stderr)
+	s := expect.NewSession(t, h.Stdout(), time.Minute)
+	s.Expect("ready")
+	syscall.Kill(h.Pid(), syscall.SIGHUP)
+	s.ExpectEOF()
+	err = h.Shutdown(os.Stderr, os.Stderr)
+	want := "exit status 2"
+	if err == nil || err.Error() != want {
+		t.Errorf("got %s, want %s", err, want)
+
+	}
 }