tools/mgmt: cleanup devmgr test to use v23tests.Run & WaitFor.

Change-Id: I8acf0a3b5c2c0ad055ea3f16b8b9e068f480ddda
diff --git a/tools/mgmt/mgmt_v23_test.go b/tools/mgmt/mgmt_v23_test.go
index 041b560..0874d3d 100644
--- a/tools/mgmt/mgmt_v23_test.go
+++ b/tools/mgmt/mgmt_v23_test.go
@@ -24,7 +24,6 @@
 //go:generate v23 test generate .
 
 import (
-	"bytes"
 	"errors"
 	"flag"
 	"fmt"
@@ -35,8 +34,6 @@
 	"strings"
 	"time"
 
-	"v.io/v23/vlog"
-
 	"v.io/core/veyron/lib/testutil/v23tests"
 	_ "v.io/core/veyron/profiles"
 )
@@ -56,60 +53,6 @@
 	hostname = name
 }
 
-// waitFor waits for the specified timeout for the supplied function parameter
-// function to return a result or an error. If error is nil, the function
-// will be called again to see if a result can be obtained until the timeout
-// expires.
-func waitFor(fn func() (string, error), timeout time.Duration) (string, error) {
-	errCh := make(chan error)
-	resultCh := make(chan string)
-	cancelCh := make(chan struct{})
-
-	eval := func() {
-		for {
-			result, err := fn()
-			switch {
-			case err != nil:
-				errCh <- err
-				close(errCh)
-				close(resultCh)
-				return
-			case len(result) > 0:
-				resultCh <- result
-				close(resultCh)
-				close(errCh)
-				return
-			}
-			time.Sleep(100 * time.Millisecond)
-			select {
-			case <-cancelCh:
-				return
-			default:
-			}
-		}
-	}
-
-	go eval()
-
-	select {
-	case <-time.After(timeout):
-		close(cancelCh)
-		return "", errTimeout
-	case result := <-resultCh:
-		return strings.TrimRight(result, "\n"), nil
-	case err := <-errCh:
-		return "", err
-	}
-}
-
-func waitForOrDie(i *v23tests.T, fn func() (string, error), timeout time.Duration) string {
-	result, err := waitFor(fn, timeout)
-	if err != nil {
-		i.Fatal(fmt.Sprintf("%s: %s", v23tests.Caller(1), err))
-	}
-	return result
-}
-
 func V23TestNodeManager(i *v23tests.T) {
 	defer fmt.Fprintf(os.Stderr, "--------------- SHUTDOWN ---------------\n")
 	userFlag := "--single_user"
@@ -177,34 +120,26 @@
 
 	mtName := "devices/" + hostname
 
-	resolve := func(name string) (string, error) {
-		inv := namespaceBin.Start("resolve", name)
-		// Avoid leaving a ton of invocations lying around to obscure
-		// logging output. Ignore the error code from Wait since it'll
-		// deadlock if we call Fatalf etc from a goroutine.
-		defer inv.Wait(nil, os.Stderr)
-		return strings.TrimRight(inv.Output(), "\n"), nil
-	}
-	resolveMtName := func() (string, error) {
-		return resolve(mtName)
-	}
-
-	mtEP := waitForOrDie(i, resolveMtName, time.Minute)
-
-	run := func(bin *v23tests.Binary, args ...string) string {
-		var stdout, stderr bytes.Buffer
-		inv := bin.Start(args...)
-		if err := inv.Wait(&stdout, &stderr); err != nil {
-			loc := v23tests.Caller(1)
-			i.Logf("%s: %v failed: %s", loc, filepath.Base(bin.Path()), err)
-			i.Fatalf("%s: %v", loc, stderr.String())
+	resolve := func(name string) string {
+		resolver := func() (interface{}, error) {
+			// Use Start, rather than Run, sinde it's ok for 'namespace resolve'
+			// to fail with 'name doesn't exist'
+			inv := namespaceBin.Start("resolve", name)
+			// Cleanup after ourselves to avoid leaving a ton of invocations
+			// lying around which obscure logging output.
+			defer inv.Wait(nil, os.Stderr)
+			if r := strings.TrimRight(inv.Output(), "\n"); len(r) > 0 {
+				return r, nil
+			}
+			return nil, nil
 		}
-		return strings.TrimRight(stdout.String(), "\n")
+		return i.WaitFor(resolver, 100*time.Millisecond, time.Minute).(string)
 	}
+	mtEP := resolve(mtName)
 
 	// Verify that device manager's mounttable is published under the expected
 	// name (hostname).
-	if got := run(namespaceBin, "glob", mtName); len(got) == 0 {
+	if got := namespaceBin.Run("glob", mtName); len(got) == 0 {
 		i.Fatalf("glob failed for %q", mtName)
 	}
 
@@ -218,37 +153,31 @@
 	// run with a different which gets a principal forked from the
 	// process principal.
 	blessingFilename := filepath.Join(workDir, "alice.bless")
-	blessing := run(principalBin, "blessself", "alice")
+	blessing := principalBin.Run("blessself", "alice")
 	if err := ioutil.WriteFile(blessingFilename, []byte(blessing), 0755); err != nil {
 		i.Fatal(err)
 	}
-	run(principalBin, "store", "setdefault", blessingFilename)
-	run(principalBin, "store", "set", blessingFilename, "...")
+	principalBin.Run("store", "setdefault", blessingFilename)
+	principalBin.Run("store", "set", blessingFilename, "...")
 	defer os.Remove(blessingFilename)
 
 	// Claim the device as "alice/myworkstation".
 	deviceBin.Start("claim", mtName+"/devmgr/device", "myworkstation")
 
-	resolveChanged := func(name, prev string) (string, error) {
-		ep, err := resolve(name)
-		if err == nil && ep != prev {
-			return ep, nil
+	resolveChange := func(name, old string) string {
+		resolver := func() (interface{}, error) {
+			inv := namespaceBin.Start("resolve", name)
+			defer inv.Wait(nil, os.Stderr)
+			if r := strings.TrimRight(inv.Output(), "\n"); r != old {
+				return r, nil
+			}
+			return nil, nil
 		}
-		return "", nil
-	}
-
-	waitForChange := func(name, prevEP string) string {
-		newEP := waitForOrDie(i, func() (string, error) { return resolveChanged(name, prevEP) }, time.Minute)
-		loc := v23tests.Caller(1)
-		if newEP == prevEP {
-			i.Fatalf("%s: failed to obtain new endpoint for %s: same as before %v", loc, name, newEP)
-		}
-		vlog.Infof("%s: new endpoint: %s -> %s", loc, name, newEP)
-		return newEP
+		return i.WaitFor(resolver, 100*time.Millisecond, time.Minute).(string)
 	}
 
 	// Wait for the device manager to update its mount table entry.
-	mtEP = waitForChange(mtName, mtEP)
+	mtEP = resolveChange(mtName, mtEP)
 
 	if withSuid {
 		/*
@@ -289,12 +218,12 @@
 		"--http=127.0.0.1:0")
 
 	sampleAppBinName := binarydName + "/testapp"
-	run(binaryBin, "upload", sampleAppBinName, binarydBin.Path())
+	binaryBin.Run("upload", sampleAppBinName, binarydBin.Path())
 
 	// Verify that the binary we uploaded is shown by glob, we need to run
 	// with the same blessed credentials as binaryd in order to be able to
 	// glob its names pace.
-	if got := run(namespaceBin.WithEnv(credentials), "glob", sampleAppBinName); len(got) == 0 {
+	if got := namespaceBin.WithEnv(credentials).Run("glob", sampleAppBinName); len(got) == 0 {
 		i.Fatalf("glob failed for %q", sampleAppBinName)
 	}
 
@@ -313,7 +242,7 @@
 	ioutil.WriteFile(appEnvelopeFilename, []byte(appEnvelope), 0666)
 	defer os.Remove(appEnvelopeFilename)
 
-	output := run(applicationBin, "put", sampleAppName, deviceProfile, appEnvelopeFilename)
+	output := applicationBin.Run("put", sampleAppName, deviceProfile, appEnvelopeFilename)
 	if got, want := output, "Application envelope added successfully."; got != want {
 		i.Fatalf("got %q, want %q", got, want)
 	}
@@ -336,7 +265,7 @@
 	installationName := expectOneMatch(parts)
 
 	// Verify that the installation shows up when globbing the device manager.
-	output = run(namespaceBin, "glob", mtName+"/devmgr/apps/BINARYD/*")
+	output = namespaceBin.Run("glob", mtName+"/devmgr/apps/BINARYD/*")
 	if got, want := output, installationName; got != want {
 		i.Fatalf("got %q, want %q", got, want)
 	}
@@ -346,13 +275,10 @@
 	parts = inv.ExpectRE(`Successfully started: "(.*)"`, 1)
 	instanceName := expectOneMatch(parts)
 
-	resolveInstanceName := func() (string, error) {
-		return resolve(mtName + "/" + appPubName)
-	}
-	waitForOrDie(i, resolveInstanceName, time.Minute)
+	resolve(mtName + "/" + appPubName)
 
 	// Verify that the instance shows up when globbing the device manager.
-	output = run(namespaceBin, "glob", mtName+"/devmgr/apps/BINARYD/*/*")
+	output = namespaceBin.Run("glob", mtName+"/devmgr/apps/BINARYD/*/*")
 	if got, want := output, instanceName; got != want {
 		i.Fatalf("got %q, want %q", got, want)
 	}
@@ -366,22 +292,20 @@
 	inv.ExpectRE(".*Default blessings: alice/myworkstation/myapp/BINARYD$", -1)
 
 	// Stop the instance
-	run(deviceBin, "stop", instanceName)
+	deviceBin.Run("stop", instanceName)
 
 	// Verify that logs, but not stats, show up when globbing the
 	// stopped instance.
-	output = run(namespaceBin, "glob", instanceName+"/stats/...")
-	if len(output) > 0 {
+	if output = namespaceBin.Run("glob", instanceName+"/stats/..."); len(output) > 0 {
 		i.Fatalf("no output expected for glob %s/stats/..., got %q", output, instanceName)
 	}
-	output = run(namespaceBin, "glob", instanceName+"/logs/...")
-	if len(output) == 0 {
+	if output = namespaceBin.Run("glob", instanceName+"/logs/..."); len(output) == 0 {
 		i.Fatalf("output expected for glob %s/logs/..., but got none", instanceName)
 	}
 
 	// Upload a deviced binary
 	devicedAppBinName := binarydName + "/deviced"
-	run(binaryBin, "upload", devicedAppBinName, devicedBin.Path())
+	binaryBin.Run("upload", devicedAppBinName, devicedBin.Path())
 
 	// Upload a device manager envelope, make sure that we set
 	// VEYRON_CREDENTIALS in the enevelope, otherwise the updated device
@@ -390,36 +314,36 @@
 	devicedEnvelope := fmt.Sprintf("{\"Title\":\"device manager\", \"Binary\":{\"File\":%q}, \"Env\":[%q]}", devicedAppBinName, credentials)
 	ioutil.WriteFile(devicedEnvelopeFilename, []byte(devicedEnvelope), 0666)
 	defer os.Remove(devicedEnvelopeFilename)
-	run(applicationBin, "put", devicedAppName, deviceProfile, devicedEnvelopeFilename)
+	applicationBin.Run("put", devicedAppName, deviceProfile, devicedEnvelopeFilename)
 
 	// Update the device manager.
-	run(deviceBin, "update", mtName+"/devmgr/device")
-	mtEP = waitForChange(mtName, mtEP)
+	deviceBin.Run("update", mtName+"/devmgr/device")
+	mtEP = resolveChange(mtName, mtEP)
 
 	// Verify that device manager's mounttable is still published under the
 	// expected name (hostname).
-	if run(namespaceBin, "glob", mtName) == "" {
+	if namespaceBin.Run("glob", mtName) == "" {
 		i.Fatalf("failed to glob %s", mtName)
 	}
 
 	// Revert the device manager
-	run(deviceBin, "revert", mtName+"/devmgr/device")
-	mtEP = waitForChange(mtName, mtEP)
+	deviceBin.Run("revert", mtName+"/devmgr/device")
+	mtEP = resolveChange(mtName, mtEP)
 
 	// Verify that device manager's mounttable is still published under the
 	// expected name (hostname).
-	if run(namespaceBin, "glob", mtName) == "" {
+	if namespaceBin.Run("glob", mtName) == "" {
 		i.Fatalf("failed to glob %s", mtName)
 	}
 
 	// Verify that the local mounttable exists, and that the device manager,
 	// the global namespace, and the neighborhood are mounted on it.
 	n := mtEP + "/devmgr"
-	if run(namespaceBin, "resolve", n) == "" {
+	if namespaceBin.Run("resolve", n) == "" {
 		i.Fatalf("failed to resolve %s", n)
 	}
 	n = mtEP + "/nh"
-	if run(namespaceBin, "resolve", n) == "" {
+	if namespaceBin.Run("resolve", n) == "" {
 		i.Fatalf("failed to resolve %s", n)
 	}
 	namespaceRoot, _ := i.GetVar("NAMESPACE_ROOT")
@@ -428,33 +352,37 @@
 	// also be from some VAR or something.  For now, hardcoded, but this
 	// should be fixed along with
 	// https://github.com/veyron/release-issues/issues/98
-	if got, want := run(namespaceBin, "resolve", n), fmt.Sprintf("[alice/myworkstation]%v", namespaceRoot); got != want {
+	if got, want := namespaceBin.Run("resolve", n), fmt.Sprintf("[alice/myworkstation]%v", namespaceRoot); got != want {
 		i.Fatalf("got %q, want %q", got, want)
 	}
 
-	// Suspend the device manager.
-	run(deviceBin, "suspend", mtName+"/devmgr/device")
-	mtEP = waitForChange(mtName, mtEP)
+	// Suspend the device manager, wait for the endpoint to change
+	deviceBin.Run("suspend", mtName+"/devmgr/device")
+	mtEP = resolveChange(mtName, mtEP)
 
 	// Stop the device manager.
-	run(deviceScript, "stop")
+	deviceScript.Run("stop")
 
 	// Wait for the mounttable entry to go away.
-	noEntry := func() (string, error) {
-		ep, err := resolve(mtName)
-		if err == nil && len(ep) == 0 {
-			return "done", nil
+	resolveGone := func(name string) string {
+		resolver := func() (interface{}, error) {
+			inv := namespaceBin.Start("resolve", name)
+			defer inv.Wait(nil, os.Stderr)
+			if r := strings.TrimRight(inv.Output(), "\n"); len(r) == 0 {
+				return r, nil
+			}
+			return nil, nil
 		}
-		return "", nil
+		return i.WaitFor(resolver, 100*time.Millisecond, time.Minute).(string)
 	}
-	waitForOrDie(i, noEntry, time.Minute)
+	resolveGone(mtName)
 
 	fi, err := ioutil.ReadDir(dmInstallDir)
 	if err != nil {
 		i.Fatalf("failed to readdir for %q: %v", dmInstallDir, err)
 	}
 
-	run(deviceScript, "uninstall")
+	deviceScript.Run("uninstall")
 
 	fi, err = ioutil.ReadDir(dmInstallDir)
 	if err == nil || len(fi) > 0 {