Merge "x/ref: make use of ctx arg to Lookup."
diff --git a/services/device/device/acl_test.go b/services/device/device/acl_test.go
index 5a401c9..0deb899 100644
--- a/services/device/device/acl_test.go
+++ b/services/device/device/acl_test.go
@@ -20,6 +20,7 @@
 	"v.io/x/ref/test"
 
 	cmd_device "v.io/x/ref/services/device/device"
+	"v.io/x/ref/services/internal/servicetest"
 )
 
 const pkgPath = "v.io/x/ref/services/device/main"
@@ -32,7 +33,7 @@
 	ctx, shutdown := test.V23Init()
 	defer shutdown()
 
-	tapes := newTapeMap()
+	tapes := servicetest.NewTapeMap()
 	server, err := xrpc.NewDispatchingServer(ctx, "", newDispatcher(t, tapes))
 	if err != nil {
 		t.Fatalf("NewServer failed: %v", err)
@@ -45,7 +46,7 @@
 	deviceName := server.Status().Endpoints[0].Name()
 
 	// Test the 'get' command.
-	rootTape := tapes.forSuffix("")
+	rootTape := tapes.ForSuffix("")
 	rootTape.SetResponses(GetPermissionsResponse{
 		perms: access.Permissions{
 			"Admin": access.AccessList{
@@ -79,7 +80,7 @@
 	ctx, shutdown := test.V23Init()
 	defer shutdown()
 
-	tapes := newTapeMap()
+	tapes := servicetest.NewTapeMap()
 	server, err := xrpc.NewDispatchingServer(ctx, "", newDispatcher(t, tapes))
 	if err != nil {
 		t.Fatalf("NewServer failed: %v", err)
@@ -129,7 +130,7 @@
 	// Correct operation in the absence of errors.
 	stderr.Reset()
 	stdout.Reset()
-	rootTape := tapes.forSuffix("")
+	rootTape := tapes.ForSuffix("")
 	rootTape.SetResponses(
 		GetPermissionsResponse{
 			perms: access.Permissions{
diff --git a/services/device/device/associate_test.go b/services/device/device/associate_test.go
index 78a5140..099d424 100644
--- a/services/device/device/associate_test.go
+++ b/services/device/device/associate_test.go
@@ -17,13 +17,14 @@
 	"v.io/x/ref/test"
 
 	cmd_device "v.io/x/ref/services/device/device"
+	"v.io/x/ref/services/internal/servicetest"
 )
 
 func TestListCommand(t *testing.T) {
 	ctx, shutdown := test.V23Init()
 	defer shutdown()
 
-	tapes := newTapeMap()
+	tapes := servicetest.NewTapeMap()
 	server, err := xrpc.NewDispatchingServer(ctx, "", newDispatcher(t, tapes))
 	if err != nil {
 		t.Fatalf("NewServer failed: %v", err)
@@ -35,7 +36,7 @@
 	env := &cmdline.Env{Stdout: &stdout, Stderr: &stderr}
 	deviceName := server.Status().Endpoints[0].Name()
 
-	rootTape := tapes.forSuffix("")
+	rootTape := tapes.ForSuffix("")
 	// Test the 'list' command.
 	rootTape.SetResponses(ListAssociationResponse{
 		na: []device.Association{
@@ -76,7 +77,7 @@
 	ctx, shutdown := test.V23Init()
 	defer shutdown()
 
-	tapes := newTapeMap()
+	tapes := servicetest.NewTapeMap()
 	server, err := xrpc.NewDispatchingServer(ctx, "", newDispatcher(t, tapes))
 	if err != nil {
 		t.Fatalf("NewServer failed: %v", err)
@@ -91,7 +92,7 @@
 	if err := v23cmd.ParseAndRunForTest(cmd, ctx, env, []string{"add", "one"}); err == nil {
 		t.Fatalf("wrongly failed to receive a non-nil error.")
 	}
-	rootTape := tapes.forSuffix("")
+	rootTape := tapes.ForSuffix("")
 	if got, expected := len(rootTape.Play()), 0; got != expected {
 		t.Errorf("invalid call sequence. Got %v, want %v", got, expected)
 	}
@@ -127,7 +128,7 @@
 	ctx, shutdown := test.V23Init()
 	defer shutdown()
 
-	tapes := newTapeMap()
+	tapes := servicetest.NewTapeMap()
 	server, err := xrpc.NewDispatchingServer(ctx, "", newDispatcher(t, tapes))
 	if err != nil {
 		t.Fatalf("NewServer failed: %v", err)
@@ -142,7 +143,7 @@
 	if err := v23cmd.ParseAndRunForTest(cmd, ctx, env, []string{"remove", "one"}); err == nil {
 		t.Fatalf("wrongly failed to receive a non-nil error.")
 	}
-	rootTape := tapes.forSuffix("")
+	rootTape := tapes.ForSuffix("")
 	if got, expected := len(rootTape.Play()), 0; got != expected {
 		t.Errorf("invalid call sequence. Got %v, want %v", got, expected)
 	}
diff --git a/services/device/device/claim_test.go b/services/device/device/claim_test.go
index e5df2cb..0ac45aa 100644
--- a/services/device/device/claim_test.go
+++ b/services/device/device/claim_test.go
@@ -20,13 +20,14 @@
 	"v.io/x/ref/test"
 
 	cmd_device "v.io/x/ref/services/device/device"
+	"v.io/x/ref/services/internal/servicetest"
 )
 
 func TestClaimCommand(t *testing.T) {
 	ctx, shutdown := test.V23Init()
 	defer shutdown()
 
-	tapes := newTapeMap()
+	tapes := servicetest.NewTapeMap()
 	server, err := xrpc.NewDispatchingServer(ctx, "", newDispatcher(t, tapes))
 	if err != nil {
 		t.Fatalf("NewServer failed: %v", err)
@@ -51,7 +52,7 @@
 	}
 	stdout.Reset()
 	stderr.Reset()
-	rootTape := tapes.forSuffix("")
+	rootTape := tapes.ForSuffix("")
 	rootTape.Rewind()
 
 	if err := v23cmd.ParseAndRunForTest(cmd, ctx, env, []string{"claim", "nope", "nope", "nope", "nope", "nope"}); err == nil {
diff --git a/services/device/device/debug_test.go b/services/device/device/debug_test.go
index 11295a9..cc8f528 100644
--- a/services/device/device/debug_test.go
+++ b/services/device/device/debug_test.go
@@ -18,12 +18,13 @@
 	"v.io/x/ref/test"
 
 	cmd_device "v.io/x/ref/services/device/device"
+	"v.io/x/ref/services/internal/servicetest"
 )
 
 func TestDebugCommand(t *testing.T) {
 	ctx, shutdown := test.V23Init()
 	defer shutdown()
-	tapes := newTapeMap()
+	tapes := servicetest.NewTapeMap()
 	server, err := xrpc.NewDispatchingServer(ctx, "", newDispatcher(t, tapes))
 	if err != nil {
 		t.Fatalf("NewServer failed: %v", err)
@@ -33,7 +34,7 @@
 	addr := server.Status().Endpoints[0].String()
 	globName := naming.JoinAddressName(addr, "glob")
 	appName := naming.JoinAddressName(addr, "app")
-	rootTape, appTape := tapes.forSuffix(""), tapes.forSuffix("app")
+	rootTape, appTape := tapes.ForSuffix(""), tapes.ForSuffix("app")
 	rootTape.SetResponses(GlobResponse{results: []string{"app"}})
 
 	var stdout, stderr bytes.Buffer
diff --git a/services/device/device/devicemanager_mock_test.go b/services/device/device/devicemanager_mock_test.go
index 527e951..d683d0b 100644
--- a/services/device/device/devicemanager_mock_test.go
+++ b/services/device/device/devicemanager_mock_test.go
@@ -25,10 +25,11 @@
 
 	"v.io/x/ref/services/internal/binarylib"
 	"v.io/x/ref/services/internal/packages"
+	"v.io/x/ref/services/internal/servicetest"
 )
 
 type mockDeviceInvoker struct {
-	tape *tape
+	tape *servicetest.Tape
 	t    *testing.T
 }
 
@@ -302,14 +303,14 @@
 }
 
 type dispatcher struct {
-	tapes *tapeMap
+	tapes *servicetest.TapeMap
 	t     *testing.T
 }
 
-func newDispatcher(t *testing.T, tapes *tapeMap) rpc.Dispatcher {
+func newDispatcher(t *testing.T, tapes *servicetest.TapeMap) rpc.Dispatcher {
 	return &dispatcher{tapes: tapes, t: t}
 }
 
 func (d *dispatcher) Lookup(_ *context.T, suffix string) (interface{}, security.Authorizer, error) {
-	return &mockDeviceInvoker{tape: d.tapes.forSuffix(suffix), t: d.t}, nil, nil
+	return &mockDeviceInvoker{tape: d.tapes.ForSuffix(suffix), t: d.t}, nil, nil
 }
diff --git a/services/device/device/glob_test.go b/services/device/device/glob_test.go
index ad8db50..1569872 100644
--- a/services/device/device/glob_test.go
+++ b/services/device/device/glob_test.go
@@ -24,6 +24,7 @@
 	"v.io/x/ref/test"
 
 	cmd_device "v.io/x/ref/services/device/device"
+	"v.io/x/ref/services/internal/servicetest"
 )
 
 func simplePrintHandler(entry cmd_device.GlobResult, _ *context.T, stdout, _ io.Writer) error {
@@ -147,8 +148,8 @@
 func TestGlob(t *testing.T) {
 	ctx, shutdown := test.V23Init()
 	defer shutdown()
-	tapes := newTapeMap()
-	rootTape := tapes.forSuffix("")
+	tapes := servicetest.NewTapeMap()
+	rootTape := tapes.ForSuffix("")
 	server, err := xrpc.NewDispatchingServer(ctx, "", newDispatcher(t, tapes))
 	if err != nil {
 		t.Fatalf("NewServer failed: %v", err)
@@ -421,14 +422,14 @@
 			"encountered a total of 2 error(s)",
 		},
 	} {
-		tapes.rewind()
+		tapes.Rewind()
 		var rootTapeResponses []interface{}
 		for _, r := range c.globResponses {
 			rootTapeResponses = append(rootTapeResponses, r)
 		}
 		rootTape.SetResponses(rootTapeResponses...)
 		for n, r := range c.statusResponses {
-			tapes.forSuffix(n).SetResponses(r...)
+			tapes.ForSuffix(n).SetResponses(r...)
 		}
 		var stdout, stderr bytes.Buffer
 		env := &cmdline.Env{Stdout: &stdout, Stderr: &stderr}
diff --git a/services/device/device/install_test.go b/services/device/device/install_test.go
index dd95099..61e766d 100644
--- a/services/device/device/install_test.go
+++ b/services/device/device/install_test.go
@@ -21,13 +21,14 @@
 	"v.io/x/ref/test"
 
 	cmd_device "v.io/x/ref/services/device/device"
+	"v.io/x/ref/services/internal/servicetest"
 )
 
 func TestInstallCommand(t *testing.T) {
 	ctx, shutdown := test.V23Init()
 	defer shutdown()
 
-	tapes := newTapeMap()
+	tapes := servicetest.NewTapeMap()
 	server, err := xrpc.NewDispatchingServer(ctx, "", newDispatcher(t, tapes))
 	if err != nil {
 		t.Fatalf("NewServer failed: %v", err)
@@ -41,7 +42,7 @@
 	appId := "myBestAppID"
 	cfg := device.Config{"someflag": "somevalue"}
 	pkg := application.Packages{"pkg": application.SignedFile{File: "somename"}}
-	rootTape := tapes.forSuffix("")
+	rootTape := tapes.ForSuffix("")
 	for i, c := range []struct {
 		args         []string
 		config       device.Config
diff --git a/services/device/device/instantiate_test.go b/services/device/device/instantiate_test.go
index db6d97d..53ff9ac 100644
--- a/services/device/device/instantiate_test.go
+++ b/services/device/device/instantiate_test.go
@@ -18,13 +18,14 @@
 	"v.io/x/ref/test"
 
 	cmd_device "v.io/x/ref/services/device/device"
+	"v.io/x/ref/services/internal/servicetest"
 )
 
 func TestInstantiateCommand(t *testing.T) {
 	ctx, shutdown := test.V23Init()
 	defer shutdown()
 
-	tapes := newTapeMap()
+	tapes := servicetest.NewTapeMap()
 	server, err := xrpc.NewDispatchingServer(ctx, "", newDispatcher(t, tapes))
 	if err != nil {
 		t.Fatalf("NewServer failed: %v", err)
@@ -45,7 +46,7 @@
 	}
 	stdout.Reset()
 	stderr.Reset()
-	rootTape := tapes.forSuffix("")
+	rootTape := tapes.ForSuffix("")
 	rootTape.Rewind()
 
 	if err := v23cmd.ParseAndRunForTest(cmd, ctx, env, []string{"instantiate", "nope", "nope", "nope"}); err == nil {
diff --git a/services/device/device/kill_test.go b/services/device/device/kill_test.go
index f5fd65d..3b37881 100644
--- a/services/device/device/kill_test.go
+++ b/services/device/device/kill_test.go
@@ -19,13 +19,14 @@
 	"v.io/x/ref/test"
 
 	cmd_device "v.io/x/ref/services/device/device"
+	"v.io/x/ref/services/internal/servicetest"
 )
 
 func TestKillCommand(t *testing.T) {
 	ctx, shutdown := test.V23Init()
 	defer shutdown()
 
-	tapes := newTapeMap()
+	tapes := servicetest.NewTapeMap()
 	server, err := xrpc.NewDispatchingServer(ctx, "", newDispatcher(t, tapes))
 	if err != nil {
 		t.Fatalf("NewServer failed: %v", err)
@@ -46,7 +47,7 @@
 	}
 	stdout.Reset()
 	stderr.Reset()
-	appTape := tapes.forSuffix("appname")
+	appTape := tapes.ForSuffix("appname")
 	appTape.Rewind()
 
 	if err := v23cmd.ParseAndRunForTest(cmd, ctx, env, []string{"kill", "nope", "nope"}); err == nil {
diff --git a/services/device/device/local_install_test.go b/services/device/device/local_install_test.go
index b9996ca..aa0d615 100644
--- a/services/device/device/local_install_test.go
+++ b/services/device/device/local_install_test.go
@@ -25,6 +25,7 @@
 	"v.io/x/ref/test"
 
 	cmd_device "v.io/x/ref/services/device/device"
+	"v.io/x/ref/services/internal/servicetest"
 )
 
 func createFile(t *testing.T, path string, contents string) {
@@ -37,7 +38,7 @@
 	ctx, shutdown := test.V23Init()
 	defer shutdown()
 
-	tapes := newTapeMap()
+	tapes := servicetest.NewTapeMap()
 	server, err := xrpc.NewDispatchingServer(ctx, "", newDispatcher(t, tapes))
 	if err != nil {
 		t.Fatalf("NewServer failed: %v", err)
@@ -54,7 +55,7 @@
 		t.Fatalf("Failed to stat %v: %v", binary, err)
 	}
 	binarySize := fi.Size()
-	rootTape := tapes.forSuffix("")
+	rootTape := tapes.ForSuffix("")
 	for i, c := range []struct {
 		args         []string
 		stderrSubstr string
diff --git a/services/device/device/ls_test.go b/services/device/device/ls_test.go
index d85b6d3..79974b1 100644
--- a/services/device/device/ls_test.go
+++ b/services/device/device/ls_test.go
@@ -16,6 +16,7 @@
 	"v.io/x/ref/test"
 
 	cmd_device "v.io/x/ref/services/device/device"
+	"v.io/x/ref/services/internal/servicetest"
 )
 
 // TestLsCommand verifies the device ls command.  It also acts as a test for the
@@ -24,7 +25,7 @@
 func TestLsCommand(t *testing.T) {
 	ctx, shutdown := test.V23Init()
 	defer shutdown()
-	tapes := newTapeMap()
+	tapes := servicetest.NewTapeMap()
 	server, err := xrpc.NewDispatchingServer(ctx, "", newDispatcher(t, tapes))
 	if err != nil {
 		t.Fatalf("NewServer failed: %v", err)
@@ -32,7 +33,7 @@
 	cmd := cmd_device.CmdRoot
 	endpoint := server.Status().Endpoints[0]
 	appName := naming.JoinAddressName(endpoint.String(), "app")
-	rootTape := tapes.forSuffix("")
+	rootTape := tapes.ForSuffix("")
 	cannedGlobResponses := [][]string{
 		[]string{"app/3", "app/4", "app/6", "app/5"},
 		[]string{"app/2", "app/1"},
@@ -134,14 +135,14 @@
 	} {
 		var stdout, stderr bytes.Buffer
 		env := &cmdline.Env{Stdout: &stdout, Stderr: &stderr}
-		tapes.rewind()
+		tapes.Rewind()
 		var rootTapeResponses []interface{}
 		for _, r := range c.globResponses {
 			rootTapeResponses = append(rootTapeResponses, GlobResponse{results: r})
 		}
 		rootTape.SetResponses(rootTapeResponses...)
 		for n, r := range c.statusResponses {
-			tapes.forSuffix(n).SetResponses(r...)
+			tapes.ForSuffix(n).SetResponses(r...)
 		}
 		args := append([]string{"ls"}, c.lsFlags...)
 		for _, p := range c.globPatterns {
diff --git a/services/device/device/mock_test.go b/services/device/device/mock_test.go
deleted file mode 100644
index 7925dc4..0000000
--- a/services/device/device/mock_test.go
+++ /dev/null
@@ -1,173 +0,0 @@
-// Copyright 2015 The Vanadium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-package main_test
-
-import (
-	"fmt"
-	"reflect"
-	"sort"
-	"sync"
-	"testing"
-)
-
-type tape struct {
-	sync.Mutex
-	stimuli   []interface{}
-	responses []interface{}
-}
-
-func (t *tape) Record(call interface{}) interface{} {
-	t.Lock()
-	defer t.Unlock()
-	t.stimuli = append(t.stimuli, call)
-
-	if len(t.responses) < 1 {
-		// Returning an error at this point will likely cause the mock
-		// device manager to panic trying to cast the response to what
-		// it expects.  Panic'ing here at least makes the issue more
-		// apparent.
-		// TODO(caprita): Don't panic.
-		panic(fmt.Errorf("Record(%#v) had no response", call))
-	}
-	resp := t.responses[0]
-	t.responses = t.responses[1:]
-	return resp
-}
-
-func (t *tape) SetResponses(responses ...interface{}) {
-	t.Lock()
-	defer t.Unlock()
-	t.responses = make([]interface{}, len(responses))
-	copy(t.responses, responses)
-}
-
-func (t *tape) Rewind() {
-	t.Lock()
-	defer t.Unlock()
-	t.stimuli = make([]interface{}, 0)
-	t.responses = make([]interface{}, 0)
-}
-
-func (t *tape) Play() []interface{} {
-	t.Lock()
-	defer t.Unlock()
-	resp := make([]interface{}, len(t.stimuli))
-	copy(resp, t.stimuli)
-	return resp
-}
-
-func newTape() *tape {
-	t := new(tape)
-	t.Rewind()
-	return t
-}
-
-type tapeMap struct {
-	sync.Mutex
-	tapes map[string]*tape
-}
-
-func newTapeMap() *tapeMap {
-	tm := &tapeMap{
-		tapes: make(map[string]*tape),
-	}
-	return tm
-}
-
-func (tm *tapeMap) forSuffix(s string) *tape {
-	tm.Lock()
-	defer tm.Unlock()
-	t, ok := tm.tapes[s]
-	if !ok {
-		t = new(tape)
-		tm.tapes[s] = t
-	}
-	return t
-}
-
-func (tm *tapeMap) rewind() {
-	tm.Lock()
-	defer tm.Unlock()
-	for _, t := range tm.tapes {
-		t.Rewind()
-	}
-}
-
-func TestOneTape(t *testing.T) {
-	tm := newTapeMap()
-	tm.forSuffix("mytape").SetResponses("b", "c")
-	if want, got := "b", tm.forSuffix("mytape").Record("bar"); want != got {
-		t.Errorf("Expected %v, got %v", want, got)
-	}
-	if want, got := "c", tm.forSuffix("mytape").Record("baz"); want != got {
-		t.Errorf("Expected %v, got %v", want, got)
-	}
-	if want, got := []interface{}{"bar", "baz"}, tm.forSuffix("mytape").Play(); !reflect.DeepEqual(want, got) {
-		t.Errorf("Expected %v, got %v", want, got)
-	}
-	tm.forSuffix("mytape").Rewind()
-	if want, got := []interface{}{}, tm.forSuffix("mytape").Play(); !reflect.DeepEqual(want, got) {
-		t.Errorf("Expected %v, got %v", want, got)
-	}
-}
-
-func TestManyTapes(t *testing.T) {
-	tm := newTapeMap()
-	tapes := []string{"duct tape", "cassette tape", "watergate tape", "tape worm"}
-	for _, tp := range tapes {
-		tm.forSuffix(tp).SetResponses(tp + "resp")
-	}
-	for _, tp := range tapes {
-		if want, got := tp+"resp", tm.forSuffix(tp).Record(tp+"stimulus"); want != got {
-			t.Errorf("Expected %v, got %v", want, got)
-		}
-	}
-	for _, tp := range tapes {
-		if want, got := []interface{}{tp + "stimulus"}, tm.forSuffix(tp).Play(); !reflect.DeepEqual(want, got) {
-			t.Errorf("Expected %v, got %v", want, got)
-		}
-	}
-}
-
-func TestTapeParallelism(t *testing.T) {
-	tm := newTapeMap()
-	var resp []interface{}
-	const N = 100
-	for i := 0; i < N; i++ {
-		resp = append(resp, fmt.Sprintf("resp%010d", i))
-	}
-	tm.forSuffix("mytape").SetResponses(resp...)
-	results := make(chan string, N)
-	for i := 0; i < N; i++ {
-		go func(index int) {
-			results <- tm.forSuffix("mytape").Record(fmt.Sprintf("stimulus%010d", index)).(string)
-		}(i)
-	}
-	var res []string
-	for i := 0; i < N; i++ {
-		r := <-results
-		res = append(res, r)
-	}
-	sort.Strings(res)
-	var expectedRes []string
-	for i := 0; i < N; i++ {
-		expectedRes = append(expectedRes, fmt.Sprintf("resp%010d", i))
-	}
-	if want, got := expectedRes, res; !reflect.DeepEqual(want, got) {
-		t.Errorf("Expected %v, got %v", want, got)
-	}
-	var expectedStimuli []string
-	for i := 0; i < N; i++ {
-		expectedStimuli = append(expectedStimuli, fmt.Sprintf("stimulus%010d", i))
-	}
-	var gotStimuli []string
-	for _, s := range tm.forSuffix("mytape").Play() {
-		gotStimuli = append(gotStimuli, s.(string))
-	}
-	sort.Strings(gotStimuli)
-	if want, got := expectedStimuli, gotStimuli; !reflect.DeepEqual(want, got) {
-		t.Errorf("Expected %v, got %v", want, got)
-	}
-}
diff --git a/services/device/device/status_test.go b/services/device/device/status_test.go
index 6ea4bf5..81a1410 100644
--- a/services/device/device/status_test.go
+++ b/services/device/device/status_test.go
@@ -19,12 +19,13 @@
 	"v.io/x/ref/test"
 
 	cmd_device "v.io/x/ref/services/device/device"
+	"v.io/x/ref/services/internal/servicetest"
 )
 
 func TestStatusCommand(t *testing.T) {
 	ctx, shutdown := test.V23Init()
 	defer shutdown()
-	tapes := newTapeMap()
+	tapes := servicetest.NewTapeMap()
 	server, err := xrpc.NewDispatchingServer(ctx, "", newDispatcher(t, tapes))
 	if err != nil {
 		t.Fatalf("NewServer failed: %v", err)
@@ -35,7 +36,7 @@
 	globName := naming.JoinAddressName(addr, "glob")
 	appName := naming.JoinAddressName(addr, "app")
 
-	rootTape, appTape := tapes.forSuffix(""), tapes.forSuffix("app")
+	rootTape, appTape := tapes.ForSuffix(""), tapes.ForSuffix("app")
 	for _, c := range []struct {
 		tapeResponse device.Status
 		expected     string
@@ -55,7 +56,7 @@
 	} {
 		var stdout, stderr bytes.Buffer
 		env := &cmdline.Env{Stdout: &stdout, Stderr: &stderr}
-		tapes.rewind()
+		tapes.Rewind()
 		rootTape.SetResponses(GlobResponse{results: []string{"app"}})
 		appTape.SetResponses(c.tapeResponse)
 		if err := v23cmd.ParseAndRunForTest(cmd, ctx, env, []string{"status", globName}); err != nil {
diff --git a/services/device/device/update_test.go b/services/device/device/update_test.go
index f0f2720..d39981b 100644
--- a/services/device/device/update_test.go
+++ b/services/device/device/update_test.go
@@ -23,6 +23,7 @@
 	"v.io/x/ref/test"
 
 	cmd_device "v.io/x/ref/services/device/device"
+	"v.io/x/ref/services/internal/servicetest"
 )
 
 func capitalize(s string) string {
@@ -37,7 +38,7 @@
 func TestUpdateAndRevertCommands(t *testing.T) {
 	ctx, shutdown := test.V23Init()
 	defer shutdown()
-	tapes := newTapeMap()
+	tapes := servicetest.NewTapeMap()
 	server, err := xrpc.NewDispatchingServer(ctx, "", newDispatcher(t, tapes))
 	if err != nil {
 		t.Fatalf("NewServer failed: %v", err)
@@ -45,7 +46,7 @@
 	addr := server.Status().Endpoints[0].String()
 	root := cmd_device.CmdRoot
 	appName := naming.JoinAddressName(addr, "app")
-	rootTape := tapes.forSuffix("")
+	rootTape := tapes.ForSuffix("")
 	globName := naming.JoinAddressName(addr, "glob")
 	// TODO(caprita): Move joinLines to a common place.
 	joinLines := func(args ...string) string {
@@ -118,10 +119,10 @@
 		} {
 			var stdout, stderr bytes.Buffer
 			env := &cmdline.Env{Stdout: &stdout, Stderr: &stderr}
-			tapes.rewind()
+			tapes.Rewind()
 			rootTape.SetResponses(GlobResponse{results: c.globResponses})
 			for n, r := range c.statusResponses {
-				tapes.forSuffix(n).SetResponses(r...)
+				tapes.ForSuffix(n).SetResponses(r...)
 			}
 			args := []string{cmd, globName}
 			if err := v23cmd.ParseAndRunForTest(root, ctx, env, args); err != nil {
@@ -141,7 +142,7 @@
 				t.Errorf("Unexpected stderr output from %s.\nGot:\n%v\nExpected:\n%v", cmd, got, expected)
 			}
 			for n, m := range c.expectedStimuli {
-				if want, got := m, tapes.forSuffix(n).Play(); !reflect.DeepEqual(want, got) {
+				if want, got := m, tapes.ForSuffix(n).Play(); !reflect.DeepEqual(want, got) {
 					t.Errorf("Unexpected stimuli for %v. Want: %v, got %v.", n, want, got)
 				}
 			}
diff --git a/services/device/device/util_test.go b/services/device/device/util_test.go
index 7ee71bb..9b4bc6c 100644
--- a/services/device/device/util_test.go
+++ b/services/device/device/util_test.go
@@ -19,6 +19,7 @@
 	"v.io/x/ref/test"
 
 	cmd_device "v.io/x/ref/services/device/device"
+	"v.io/x/ref/services/internal/servicetest"
 )
 
 //go:generate v23 test generate
@@ -58,7 +59,7 @@
 	ctx, shutdown := test.V23Init()
 	defer shutdown()
 
-	tapes := newTapeMap()
+	tapes := servicetest.NewTapeMap()
 	server, err := xrpc.NewDispatchingServer(ctx, "", newDispatcher(t, tapes))
 	if err != nil {
 		t.Fatalf("NewServer failed: %v", err)
@@ -80,7 +81,7 @@
 	}
 	stdout.Reset()
 	stderr.Reset()
-	appTape := tapes.forSuffix("appname")
+	appTape := tapes.ForSuffix("appname")
 	appTape.Rewind()
 
 	if err := v23cmd.ParseAndRunForTest(cmd, ctx, env, []string{lower, "nope", "nope"}); err == nil {
diff --git a/services/device/device/v23_internal_test.go b/services/device/device/v23_test.go
similarity index 81%
rename from services/device/device/v23_internal_test.go
rename to services/device/device/v23_test.go
index ae59080..b307e06 100644
--- a/services/device/device/v23_internal_test.go
+++ b/services/device/device/v23_test.go
@@ -5,16 +5,18 @@
 // This file was auto-generated via go generate.
 // DO NOT UPDATE MANUALLY
 
-package main
+package main_test
 
 import (
 	"os"
 	"testing"
 
 	"v.io/x/ref/test"
+	"v.io/x/ref/test/modules"
 )
 
 func TestMain(m *testing.M) {
 	test.Init()
+	modules.DispatchAndExitIfChild()
 	os.Exit(m.Run())
 }
diff --git a/services/internal/servicetest/doc.go b/services/internal/servicetest/doc.go
new file mode 100644
index 0000000..28a6564
--- /dev/null
+++ b/services/internal/servicetest/doc.go
@@ -0,0 +1,7 @@
+// Copyright 2015 The Vanadium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Package servicetest provides functionality to help write tests
+// for the Vanadium services.
+package servicetest
diff --git a/services/internal/servicetest/mock.go b/services/internal/servicetest/mock.go
new file mode 100644
index 0000000..5419382
--- /dev/null
+++ b/services/internal/servicetest/mock.go
@@ -0,0 +1,110 @@
+// Copyright 2015 The Vanadium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package servicetest
+
+import (
+	"fmt"
+	"sync"
+)
+
+// Tape holds a record of function call stimuli and each function call's
+// response. Use Tape to help build a mock framework by first creating a
+// new Tape, then SetResponses to define the mock responses and then
+// Record each function invocation. Play returns the function invocations
+// for verification in a test.
+type Tape struct {
+	sync.Mutex
+	stimuli   []interface{}
+	responses []interface{}
+}
+
+// Record stores a new function invocation in a Tape and returns the
+// response for that function interface.
+func (t *Tape) Record(call interface{}) interface{} {
+	t.Lock()
+	defer t.Unlock()
+	t.stimuli = append(t.stimuli, call)
+
+	if len(t.responses) < 1 {
+		// Returning an error at this point will likely cause the mock
+		// device manager to panic trying to cast the response to what
+		// it expects.  Panic'ing here at least makes the issue more
+		// apparent.
+		// TODO(caprita): Don't panic.
+		panic(fmt.Errorf("Record(%#v) had no response", call))
+	}
+	resp := t.responses[0]
+	t.responses = t.responses[1:]
+	return resp
+}
+
+// SetResponses updates the Tape's associated responses.
+func (t *Tape) SetResponses(responses ...interface{}) {
+	t.Lock()
+	defer t.Unlock()
+	t.responses = make([]interface{}, len(responses))
+	copy(t.responses, responses)
+}
+
+// Rewind resets the tape to the beginning so that it could be used again
+// for further tests.
+func (t *Tape) Rewind() {
+	t.Lock()
+	defer t.Unlock()
+	t.stimuli = make([]interface{}, 0)
+	t.responses = make([]interface{}, 0)
+}
+
+// Play returns the function call stimuli recorded to this Tape.
+func (t *Tape) Play() []interface{} {
+	t.Lock()
+	defer t.Unlock()
+	resp := make([]interface{}, len(t.stimuli))
+	copy(resp, t.stimuli)
+	return resp
+}
+
+// NewTape creates a new Tape.
+func NewTape() *Tape {
+	t := new(Tape)
+	t.Rewind()
+	return t
+}
+
+// TapeMap provides multiple tapes for different strings. Use TapeMap to
+// record separate Tapes for each suffix in a service.
+type TapeMap struct {
+	sync.Mutex
+	tapes map[string]*Tape
+}
+
+// NewTapeMap creates a new empty TapeMap.
+func NewTapeMap() *TapeMap {
+	tm := &TapeMap{
+		tapes: make(map[string]*Tape),
+	}
+	return tm
+}
+
+// ForSuffix returns the Tape for suffix s.
+func (tm *TapeMap) ForSuffix(s string) *Tape {
+	tm.Lock()
+	defer tm.Unlock()
+	t, ok := tm.tapes[s]
+	if !ok {
+		t = new(Tape)
+		tm.tapes[s] = t
+	}
+	return t
+}
+
+// Rewind rewinds all of the Tapes in the TapeMap.
+func (tm *TapeMap) Rewind() {
+	tm.Lock()
+	defer tm.Unlock()
+	for _, t := range tm.tapes {
+		t.Rewind()
+	}
+}
diff --git a/services/internal/servicetest/mock_test.go b/services/internal/servicetest/mock_test.go
new file mode 100644
index 0000000..dc9d4c2
--- /dev/null
+++ b/services/internal/servicetest/mock_test.go
@@ -0,0 +1,89 @@
+// Copyright 2015 The Vanadium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package servicetest
+
+import (
+	"fmt"
+	"reflect"
+	"sort"
+	"testing"
+)
+
+func TestOneTape(t *testing.T) {
+	tm := NewTapeMap()
+	tm.ForSuffix("mytape").SetResponses("b", "c")
+	if want, got := "b", tm.ForSuffix("mytape").Record("bar"); want != got {
+		t.Errorf("Expected %v, got %v", want, got)
+	}
+	if want, got := "c", tm.ForSuffix("mytape").Record("baz"); want != got {
+		t.Errorf("Expected %v, got %v", want, got)
+	}
+	if want, got := []interface{}{"bar", "baz"}, tm.ForSuffix("mytape").Play(); !reflect.DeepEqual(want, got) {
+		t.Errorf("Expected %v, got %v", want, got)
+	}
+	tm.ForSuffix("mytape").Rewind()
+	if want, got := []interface{}{}, tm.ForSuffix("mytape").Play(); !reflect.DeepEqual(want, got) {
+		t.Errorf("Expected %v, got %v", want, got)
+	}
+}
+
+func TestManyTapes(t *testing.T) {
+	tm := NewTapeMap()
+	tapes := []string{"duct tape", "cassette tape", "watergate tape", "tape worm"}
+	for _, tp := range tapes {
+		tm.ForSuffix(tp).SetResponses(tp + "resp")
+	}
+	for _, tp := range tapes {
+		if want, got := tp+"resp", tm.ForSuffix(tp).Record(tp+"stimulus"); want != got {
+			t.Errorf("Expected %v, got %v", want, got)
+		}
+	}
+	for _, tp := range tapes {
+		if want, got := []interface{}{tp + "stimulus"}, tm.ForSuffix(tp).Play(); !reflect.DeepEqual(want, got) {
+			t.Errorf("Expected %v, got %v", want, got)
+		}
+	}
+}
+
+func TestTapeParallelism(t *testing.T) {
+	tm := NewTapeMap()
+	var resp []interface{}
+	const N = 100
+	for i := 0; i < N; i++ {
+		resp = append(resp, fmt.Sprintf("resp%010d", i))
+	}
+	tm.ForSuffix("mytape").SetResponses(resp...)
+	results := make(chan string, N)
+	for i := 0; i < N; i++ {
+		go func(index int) {
+			results <- tm.ForSuffix("mytape").Record(fmt.Sprintf("stimulus%010d", index)).(string)
+		}(i)
+	}
+	var res []string
+	for i := 0; i < N; i++ {
+		r := <-results
+		res = append(res, r)
+	}
+	sort.Strings(res)
+	var expectedRes []string
+	for i := 0; i < N; i++ {
+		expectedRes = append(expectedRes, fmt.Sprintf("resp%010d", i))
+	}
+	if want, got := expectedRes, res; !reflect.DeepEqual(want, got) {
+		t.Errorf("Expected %v, got %v", want, got)
+	}
+	var expectedStimuli []string
+	for i := 0; i < N; i++ {
+		expectedStimuli = append(expectedStimuli, fmt.Sprintf("stimulus%010d", i))
+	}
+	var gotStimuli []string
+	for _, s := range tm.ForSuffix("mytape").Play() {
+		gotStimuli = append(gotStimuli, s.(string))
+	}
+	sort.Strings(gotStimuli)
+	if want, got := expectedStimuli, gotStimuli; !reflect.DeepEqual(want, got) {
+		t.Errorf("Expected %v, got %v", want, got)
+	}
+}