services/device/internal/impl: shard device unit tests (6)

This is CL 6 of 6 to shard device manager unit tests into multiple
files and packages to permit concurrent test execution. This CL moves
application lifecycle code into a separate package.

Change-Id: I2dd4bc36fda33e7e2688e3a2345f37a1a8c0b408
diff --git a/services/device/internal/impl/applife/app_life_test.go b/services/device/internal/impl/applife/app_life_test.go
new file mode 100644
index 0000000..f47c6a3
--- /dev/null
+++ b/services/device/internal/impl/applife/app_life_test.go
@@ -0,0 +1,332 @@
+// 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 applife_test
+
+import (
+	"crypto/md5"
+	"encoding/base64"
+	"fmt"
+	"io/ioutil"
+	"path/filepath"
+	"strings"
+	"syscall"
+	"testing"
+	"time"
+
+	"v.io/v23/naming"
+	"v.io/v23/services/device"
+
+	"v.io/x/ref/envvar"
+	"v.io/x/ref/lib/mgmt"
+	"v.io/x/ref/services/device/internal/impl"
+	"v.io/x/ref/services/device/internal/impl/utiltest"
+	"v.io/x/ref/services/internal/servicetest"
+	"v.io/x/ref/test"
+)
+
+func instanceDirForApp(root, appID, instanceID string) string {
+	applicationDirName := func(title string) string {
+		h := md5.New()
+		h.Write([]byte(title))
+		hash := strings.TrimRight(base64.URLEncoding.EncodeToString(h.Sum(nil)), "=")
+		return "app-" + hash
+	}
+	components := strings.Split(appID, "/")
+	appTitle, installationID := components[0], components[1]
+	return filepath.Join(root, applicationDirName(appTitle), "installation-"+installationID, "instances", "instance-"+instanceID)
+}
+
+func verifyAppWorkspace(t *testing.T, root, appID, instanceID string) {
+	// HACK ALERT: for now, we peek inside the device manager's directory
+	// structure (which ought to be opaque) to check for what the app has
+	// written to its local root.
+	//
+	// TODO(caprita): add support to device manager to browse logs/app local
+	// root.
+	rootDir := filepath.Join(instanceDirForApp(root, appID, instanceID), "root")
+	testFile := filepath.Join(rootDir, "testfile")
+	if read, err := ioutil.ReadFile(testFile); err != nil {
+		t.Fatalf("Failed to read %v: %v", testFile, err)
+	} else if want, got := "goodbye world", string(read); want != got {
+		t.Fatalf("Expected to read %v, got %v instead", want, got)
+	}
+	// END HACK
+}
+
+// TestLifeOfAnApp installs an app, instantiates, runs, kills, and deletes
+// several instances, and performs updates.
+func TestLifeOfAnApp(t *testing.T) {
+	ctx, shutdown := utiltest.InitForTest()
+	defer shutdown()
+
+	sh, deferFn := servicetest.CreateShellAndMountTable(t, ctx, nil)
+	defer deferFn()
+
+	// Set up mock application and binary repositories.
+	envelope, cleanup := utiltest.StartMockRepos(t, ctx)
+	defer cleanup()
+
+	root, cleanup := servicetest.SetupRootDir(t, "devicemanager")
+	defer cleanup()
+	if err := impl.SaveCreatorInfo(root); err != nil {
+		t.Fatal(err)
+	}
+
+	// Create a script wrapping the test target that implements suidhelper.
+	helperPath := utiltest.GenerateSuidHelperScript(t, root)
+
+	// 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")
+	servicetest.ReadPID(t, dmh)
+	utiltest.ClaimDevice(t, ctx, "claimable", "dm", "mydevice", utiltest.NoPairingToken)
+
+	// Create the local server that the app uses to let us know it's ready.
+	pingCh, cleanup := utiltest.SetupPingServer(t, ctx)
+	defer cleanup()
+
+	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", 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
+	// config-specified value for origin should override the value in the
+	// Install rpc argument.
+	mtName, ok := sh.GetVar(envvar.NamespacePrefix)
+	if !ok {
+		t.Fatalf("failed to get namespace root var from shell")
+	}
+	// This rooted name should be equivalent to the relative name "ar", but
+	// we want to test that the config override for origin works.
+	rootedAppRepoName := naming.Join(mtName, "ar")
+	appID := utiltest.InstallApp(t, ctx, device.Config{utiltest.TestFlagName: "flag-val-install", mgmt.AppOriginConfigKey: rootedAppRepoName})
+	v1 := utiltest.VerifyState(t, ctx, device.InstallationStateActive, appID)
+	installationDebug := utiltest.Debug(t, ctx, appID)
+	// We spot-check a couple pieces of information we expect in the debug
+	// output.
+	// TODO(caprita): Is there a way to verify more without adding brittle
+	// logic that assumes too much about the format?  This may be one
+	// argument in favor of making the output of Debug a struct instead of
+	// free-form string.
+	if !strings.Contains(installationDebug, fmt.Sprintf("Origin: %v", rootedAppRepoName)) {
+		t.Fatalf("debug response doesn't contain expected info: %v", installationDebug)
+	}
+	if !strings.Contains(installationDebug, "Config: map[random_test_flag:flag-val-install]") {
+		t.Fatalf("debug response doesn't contain expected info: %v", installationDebug)
+	}
+
+	// Start requires the caller to bless the app instance.
+	expectedErr := "bless failed"
+	if _, err := utiltest.LaunchAppImpl(t, ctx, appID, ""); err == nil || err.Error() != expectedErr {
+		t.Fatalf("Start(%v) expected to fail with %v, got %v instead", appID, expectedErr, err)
+	}
+
+	// Start an instance of the app.
+	instance1ID := utiltest.LaunchApp(t, ctx, appID)
+	if v := utiltest.VerifyState(t, ctx, device.InstanceStateRunning, appID, instance1ID); v != v1 {
+		t.Fatalf("Instance version expected to be %v, got %v instead", v1, v)
+	}
+
+	instanceDebug := utiltest.Debug(t, ctx, appID, instance1ID)
+	// Verify the apps default blessings.
+	if !strings.Contains(instanceDebug, fmt.Sprintf("Default Blessings                %s/forapp", test.TestBlessing)) {
+		t.Fatalf("debug response doesn't contain expected info: %v", instanceDebug)
+	}
+
+	// Wait until the app pings us that it's ready.
+	pingCh.VerifyPingArgs(t, utiltest.UserName(t), "flag-val-install", "env-val-envelope")
+
+	v1EP1 := utiltest.Resolve(t, ctx, "appV1", 1)[0]
+
+	// Stop the app instance.
+	utiltest.KillApp(t, ctx, appID, instance1ID)
+	utiltest.VerifyState(t, ctx, device.InstanceStateNotRunning, appID, instance1ID)
+	utiltest.ResolveExpectNotFound(t, ctx, "appV1")
+
+	utiltest.RunApp(t, ctx, appID, instance1ID)
+	utiltest.VerifyState(t, ctx, device.InstanceStateRunning, appID, instance1ID)
+	pingCh.VerifyPingArgs(t, utiltest.UserName(t), "flag-val-install", "env-val-envelope") // Wait until the app pings us that it's ready.
+	oldV1EP1 := v1EP1
+	if v1EP1 = utiltest.Resolve(t, ctx, "appV1", 1)[0]; v1EP1 == oldV1EP1 {
+		t.Fatalf("Expected a new endpoint for the app after kill/run")
+	}
+
+	// Start a second instance.
+	instance2ID := utiltest.LaunchApp(t, ctx, appID)
+	pingCh.VerifyPingArgs(t, utiltest.UserName(t), "flag-val-install", "env-val-envelope") // Wait until the app pings us that it's ready.
+
+	// There should be two endpoints mounted as "appV1", one for each
+	// instance of the app.
+	endpoints := utiltest.Resolve(t, ctx, "appV1", 2)
+	v1EP2 := endpoints[0]
+	if endpoints[0] == v1EP1 {
+		v1EP2 = endpoints[1]
+		if v1EP2 == v1EP1 {
+			t.Fatalf("Both endpoints are the same")
+		}
+	} else if endpoints[1] != v1EP1 {
+		t.Fatalf("Second endpoint should have been v1EP1: %v, %v", endpoints, v1EP1)
+	}
+
+	// TODO(caprita): verify various non-standard combinations (kill when
+	// canceled; run while still running).
+
+	// Kill the first instance.
+	utiltest.KillApp(t, ctx, appID, instance1ID)
+	// Only the second instance should still be running and mounted.
+	if want, got := v1EP2, utiltest.Resolve(t, ctx, "appV1", 1)[0]; want != got {
+		t.Fatalf("Resolve(%v): want: %v, got %v", "appV1", want, got)
+	}
+
+	// Updating the installation to itself is a no-op.
+	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")
+
+	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", "appV2")
+
+	utiltest.UpdateApp(t, ctx, appID)
+
+	v2 := utiltest.VerifyState(t, ctx, device.InstallationStateActive, appID)
+	if v1 == v2 {
+		t.Fatalf("Version did not change for %v: %v", appID, v1)
+	}
+
+	// Second instance should still be running.
+	if want, got := v1EP2, utiltest.Resolve(t, ctx, "appV1", 1)[0]; want != got {
+		t.Fatalf("Resolve(%v): want: %v, got %v", "appV1", want, got)
+	}
+	if v := utiltest.VerifyState(t, ctx, device.InstanceStateRunning, appID, instance2ID); v != v1 {
+		t.Fatalf("Instance version expected to be %v, got %v instead", v1, v)
+	}
+
+	// Resume first instance.
+	utiltest.RunApp(t, ctx, appID, instance1ID)
+	if v := utiltest.VerifyState(t, ctx, device.InstanceStateRunning, appID, instance1ID); v != v1 {
+		t.Fatalf("Instance version expected to be %v, got %v instead", v1, v)
+	}
+	pingCh.VerifyPingArgs(t, utiltest.UserName(t), "flag-val-install", "env-val-envelope") // Wait until the app pings us that it's ready.
+	// Both instances should still be running the first version of the app.
+	// Check that the mounttable contains two endpoints, one of which is
+	// v1EP2.
+	endpoints = utiltest.Resolve(t, ctx, "appV1", 2)
+	if endpoints[0] == v1EP2 {
+		if endpoints[1] == v1EP2 {
+			t.Fatalf("Both endpoints are the same")
+		}
+	} else if endpoints[1] != v1EP2 {
+		t.Fatalf("Second endpoint should have been v1EP2: %v, %v", endpoints, v1EP2)
+	}
+
+	// Trying to update first instance while it's running should fail.
+	utiltest.UpdateInstanceExpectError(t, ctx, appID, instance1ID, impl.ErrInvalidOperation.ID)
+	// Stop first instance and try again.
+	utiltest.KillApp(t, ctx, appID, instance1ID)
+	// Only the second instance should still be running and mounted.
+	if want, got := v1EP2, utiltest.Resolve(t, ctx, "appV1", 1)[0]; want != got {
+		t.Fatalf("Resolve(%v): want: %v, got %v", "appV1", want, got)
+	}
+	// Update succeeds now.
+	utiltest.UpdateInstance(t, ctx, appID, instance1ID)
+	if v := utiltest.VerifyState(t, ctx, device.InstanceStateNotRunning, appID, instance1ID); v != v2 {
+		t.Fatalf("Instance version expected to be %v, got %v instead", v2, v)
+	}
+	// Resume the first instance and verify it's running v2 now.
+	utiltest.RunApp(t, ctx, appID, instance1ID)
+	pingCh.VerifyPingArgs(t, utiltest.UserName(t), "flag-val-install", "env-val-envelope")
+	utiltest.Resolve(t, ctx, "appV1", 1)
+	utiltest.Resolve(t, ctx, "appV2", 1)
+
+	// Stop first instance.
+	utiltest.TerminateApp(t, ctx, appID, instance1ID)
+	verifyAppWorkspace(t, root, appID, instance1ID)
+	utiltest.ResolveExpectNotFound(t, ctx, "appV2")
+
+	// Start a third instance.
+	instance3ID := utiltest.LaunchApp(t, ctx, appID)
+	if v := utiltest.VerifyState(t, ctx, device.InstanceStateRunning, appID, instance3ID); v != v2 {
+		t.Fatalf("Instance version expected to be %v, got %v instead", v2, v)
+	}
+	// Wait until the app pings us that it's ready.
+	pingCh.VerifyPingArgs(t, utiltest.UserName(t), "flag-val-install", "env-val-envelope")
+
+	utiltest.Resolve(t, ctx, "appV2", 1)
+
+	// Stop second instance.
+	utiltest.TerminateApp(t, ctx, appID, instance2ID)
+	utiltest.ResolveExpectNotFound(t, ctx, "appV1")
+
+	// Stop third instance.
+	utiltest.TerminateApp(t, ctx, appID, instance3ID)
+	utiltest.ResolveExpectNotFound(t, ctx, "appV2")
+
+	// Revert the app.
+	utiltest.RevertApp(t, ctx, appID)
+	if v := utiltest.VerifyState(t, ctx, device.InstallationStateActive, appID); v != v1 {
+		t.Fatalf("Installation version expected to be %v, got %v instead", v1, v)
+	}
+
+	// Start a fourth instance.  It should be running from version 1.
+	instance4ID := utiltest.LaunchApp(t, ctx, appID)
+	if v := utiltest.VerifyState(t, ctx, device.InstanceStateRunning, appID, instance4ID); v != v1 {
+		t.Fatalf("Instance version expected to be %v, got %v instead", v1, v)
+	}
+	pingCh.VerifyPingArgs(t, utiltest.UserName(t), "flag-val-install", "env-val-envelope") // Wait until the app pings us that it's ready.
+	utiltest.Resolve(t, ctx, "appV1", 1)
+	utiltest.TerminateApp(t, ctx, appID, instance4ID)
+	utiltest.ResolveExpectNotFound(t, ctx, "appV1")
+
+	// We are already on the first version, no further revert possible.
+	utiltest.RevertAppExpectError(t, ctx, appID, impl.ErrUpdateNoOp.ID)
+
+	// Uninstall the app.
+	utiltest.UninstallApp(t, ctx, appID)
+	utiltest.VerifyState(t, ctx, device.InstallationStateUninstalled, appID)
+
+	// Updating the installation should no longer be allowed.
+	utiltest.UpdateAppExpectError(t, ctx, appID, impl.ErrInvalidOperation.ID)
+
+	// Reverting the installation should no longer be allowed.
+	utiltest.RevertAppExpectError(t, ctx, appID, impl.ErrInvalidOperation.ID)
+
+	// Starting new instances should no longer be allowed.
+	utiltest.LaunchAppExpectError(t, ctx, appID, impl.ErrInvalidOperation.ID)
+
+	// Make sure that Kill will actually kill an app that doesn't exit
+	// 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", "hAppV1")
+	hAppID := utiltest.InstallApp(t, ctx)
+	hInstanceID := utiltest.LaunchApp(t, ctx, hAppID)
+	hangingPid := pingCh.WaitForPingArgs(t).Pid
+	if err := syscall.Kill(hangingPid, 0); err != nil && err != syscall.EPERM {
+		t.Fatalf("Pid of hanging app (%v) is not live", hangingPid)
+	}
+	utiltest.KillApp(t, ctx, hAppID, hInstanceID)
+	pidIsAlive := true
+	for i := 0; i < 10 && pidIsAlive; i++ {
+		if err := syscall.Kill(hangingPid, 0); err == nil || err == syscall.EPERM {
+			time.Sleep(time.Second) // pid is still alive
+		} else {
+			pidIsAlive = false
+		}
+	}
+	if pidIsAlive {
+		t.Fatalf("Pid of hanging app (%d) has not exited after Stop() call", hangingPid)
+	}
+
+	// Cleanly shut down the device manager.
+	defer utiltest.VerifyNoRunningProcesses(t)
+	syscall.Kill(dmh.Pid(), syscall.SIGINT)
+	dmh.Expect("dm terminated")
+	dmh.ExpectEOF()
+}
diff --git a/services/device/internal/impl/applife/doc.go b/services/device/internal/impl/applife/doc.go
new file mode 100644
index 0000000..e1b872a
--- /dev/null
+++ b/services/device/internal/impl/applife/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 applife
+
+// Test code for application life cycle and reap reconcilliation.
diff --git a/services/device/internal/impl/applife/impl_test.go b/services/device/internal/impl/applife/impl_test.go
new file mode 100644
index 0000000..bceee06
--- /dev/null
+++ b/services/device/internal/impl/applife/impl_test.go
@@ -0,0 +1,19 @@
+// 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 applife_test
+
+import (
+	"testing"
+
+	"v.io/x/ref/services/device/internal/impl/utiltest"
+)
+
+func TestMain(m *testing.M) {
+	utiltest.TestMainImpl(m)
+}
+
+func TestSuidHelper(t *testing.T) {
+	utiltest.TestSuidHelperImpl(t)
+}
diff --git a/services/device/internal/impl/instance_reaping_test.go b/services/device/internal/impl/applife/instance_reaping_test.go
similarity index 98%
rename from services/device/internal/impl/instance_reaping_test.go
rename to services/device/internal/impl/applife/instance_reaping_test.go
index 39e74e8..ef023ca 100644
--- a/services/device/internal/impl/instance_reaping_test.go
+++ b/services/device/internal/impl/applife/instance_reaping_test.go
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style
 // license that can be found in the LICENSE file.
 
-package impl_test
+package applife_test
 
 import (
 	"syscall"
diff --git a/services/device/internal/impl/globsuid/impl_test.go b/services/device/internal/impl/globsuid/impl_test.go
index a6be62e..820a15e 100644
--- a/services/device/internal/impl/globsuid/impl_test.go
+++ b/services/device/internal/impl/globsuid/impl_test.go
@@ -14,8 +14,6 @@
 	utiltest.TestMainImpl(m)
 }
 
-// TestSuidHelper is testing boilerplate for suidhelper that does not
-// create a runtime because the suidhelper is not a Vanadium application.
 func TestSuidHelper(t *testing.T) {
 	utiltest.TestSuidHelperImpl(t)
 }
diff --git a/services/device/internal/impl/impl_test.go b/services/device/internal/impl/impl_test.go
index c51f1ac..87b4cd9 100644
--- a/services/device/internal/impl/impl_test.go
+++ b/services/device/internal/impl/impl_test.go
@@ -2,15 +2,11 @@
 // Use of this source code is governed by a BSD-style
 // license that can be found in the LICENSE file.
 
-// TODO(caprita): This file is becoming unmanageable; split into several test
-// files.
 // TODO(rjkroege): Add a more extensive unit test case to exercise AccessList logic.
 
 package impl_test
 
 import (
-	"crypto/md5"
-	"encoding/base64"
 	"fmt"
 	"io/ioutil"
 	"os"
@@ -18,7 +14,6 @@
 	"strings"
 	"syscall"
 	"testing"
-	"time"
 
 	"v.io/x/lib/vlog"
 
@@ -29,13 +24,11 @@
 	"v.io/v23/services/device"
 
 	"v.io/x/ref/envvar"
-	"v.io/x/ref/lib/mgmt"
 	"v.io/x/ref/services/device/internal/config"
 	"v.io/x/ref/services/device/internal/impl"
 	"v.io/x/ref/services/device/internal/impl/utiltest"
 	"v.io/x/ref/services/internal/binarylib"
 	"v.io/x/ref/services/internal/servicetest"
-	"v.io/x/ref/test"
 	"v.io/x/ref/test/expect"
 	"v.io/x/ref/test/testutil"
 )
@@ -274,311 +267,6 @@
 	dmh.ExpectEOF()
 }
 
-func instanceDirForApp(root, appID, instanceID string) string {
-	applicationDirName := func(title string) string {
-		h := md5.New()
-		h.Write([]byte(title))
-		hash := strings.TrimRight(base64.URLEncoding.EncodeToString(h.Sum(nil)), "=")
-		return "app-" + hash
-	}
-	components := strings.Split(appID, "/")
-	appTitle, installationID := components[0], components[1]
-	return filepath.Join(root, applicationDirName(appTitle), "installation-"+installationID, "instances", "instance-"+instanceID)
-}
-
-func verifyAppWorkspace(t *testing.T, root, appID, instanceID string) {
-	// HACK ALERT: for now, we peek inside the device manager's directory
-	// structure (which ought to be opaque) to check for what the app has
-	// written to its local root.
-	//
-	// TODO(caprita): add support to device manager to browse logs/app local
-	// root.
-	rootDir := filepath.Join(instanceDirForApp(root, appID, instanceID), "root")
-	testFile := filepath.Join(rootDir, "testfile")
-	if read, err := ioutil.ReadFile(testFile); err != nil {
-		t.Fatalf("Failed to read %v: %v", testFile, err)
-	} else if want, got := "goodbye world", string(read); want != got {
-		t.Fatalf("Expected to read %v, got %v instead", want, got)
-	}
-	// END HACK
-}
-
-// TestLifeOfAnApp installs an app, instantiates, runs, kills, and deletes
-// several instances, and performs updates.
-func TestLifeOfAnApp(t *testing.T) {
-	ctx, shutdown := utiltest.InitForTest()
-	defer shutdown()
-
-	sh, deferFn := servicetest.CreateShellAndMountTable(t, ctx, nil)
-	defer deferFn()
-
-	// Set up mock application and binary repositories.
-	envelope, cleanup := utiltest.StartMockRepos(t, ctx)
-	defer cleanup()
-
-	root, cleanup := servicetest.SetupRootDir(t, "devicemanager")
-	defer cleanup()
-	if err := impl.SaveCreatorInfo(root); err != nil {
-		t.Fatal(err)
-	}
-
-	// Create a script wrapping the test target that implements suidhelper.
-	helperPath := utiltest.GenerateSuidHelperScript(t, root)
-
-	// 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")
-	servicetest.ReadPID(t, dmh)
-	utiltest.ClaimDevice(t, ctx, "claimable", "dm", "mydevice", utiltest.NoPairingToken)
-
-	// Create the local server that the app uses to let us know it's ready.
-	pingCh, cleanup := utiltest.SetupPingServer(t, ctx)
-	defer cleanup()
-
-	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", 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
-	// config-specified value for origin should override the value in the
-	// Install rpc argument.
-	mtName, ok := sh.GetVar(envvar.NamespacePrefix)
-	if !ok {
-		t.Fatalf("failed to get namespace root var from shell")
-	}
-	// This rooted name should be equivalent to the relative name "ar", but
-	// we want to test that the config override for origin works.
-	rootedAppRepoName := naming.Join(mtName, "ar")
-	appID := utiltest.InstallApp(t, ctx, device.Config{utiltest.TestFlagName: "flag-val-install", mgmt.AppOriginConfigKey: rootedAppRepoName})
-	v1 := utiltest.VerifyState(t, ctx, device.InstallationStateActive, appID)
-	installationDebug := utiltest.Debug(t, ctx, appID)
-	// We spot-check a couple pieces of information we expect in the debug
-	// output.
-	// TODO(caprita): Is there a way to verify more without adding brittle
-	// logic that assumes too much about the format?  This may be one
-	// argument in favor of making the output of Debug a struct instead of
-	// free-form string.
-	if !strings.Contains(installationDebug, fmt.Sprintf("Origin: %v", rootedAppRepoName)) {
-		t.Fatalf("debug response doesn't contain expected info: %v", installationDebug)
-	}
-	if !strings.Contains(installationDebug, "Config: map[random_test_flag:flag-val-install]") {
-		t.Fatalf("debug response doesn't contain expected info: %v", installationDebug)
-	}
-
-	// Start requires the caller to bless the app instance.
-	expectedErr := "bless failed"
-	if _, err := utiltest.LaunchAppImpl(t, ctx, appID, ""); err == nil || err.Error() != expectedErr {
-		t.Fatalf("Start(%v) expected to fail with %v, got %v instead", appID, expectedErr, err)
-	}
-
-	// Start an instance of the app.
-	instance1ID := utiltest.LaunchApp(t, ctx, appID)
-	if v := utiltest.VerifyState(t, ctx, device.InstanceStateRunning, appID, instance1ID); v != v1 {
-		t.Fatalf("Instance version expected to be %v, got %v instead", v1, v)
-	}
-
-	instanceDebug := utiltest.Debug(t, ctx, appID, instance1ID)
-	// Verify the apps default blessings.
-	if !strings.Contains(instanceDebug, fmt.Sprintf("Default Blessings                %s/forapp", test.TestBlessing)) {
-		t.Fatalf("debug response doesn't contain expected info: %v", instanceDebug)
-	}
-
-	// Wait until the app pings us that it's ready.
-	pingCh.VerifyPingArgs(t, utiltest.UserName(t), "flag-val-install", "env-val-envelope")
-
-	v1EP1 := utiltest.Resolve(t, ctx, "appV1", 1)[0]
-
-	// Stop the app instance.
-	utiltest.KillApp(t, ctx, appID, instance1ID)
-	utiltest.VerifyState(t, ctx, device.InstanceStateNotRunning, appID, instance1ID)
-	utiltest.ResolveExpectNotFound(t, ctx, "appV1")
-
-	utiltest.RunApp(t, ctx, appID, instance1ID)
-	utiltest.VerifyState(t, ctx, device.InstanceStateRunning, appID, instance1ID)
-	pingCh.VerifyPingArgs(t, utiltest.UserName(t), "flag-val-install", "env-val-envelope") // Wait until the app pings us that it's ready.
-	oldV1EP1 := v1EP1
-	if v1EP1 = utiltest.Resolve(t, ctx, "appV1", 1)[0]; v1EP1 == oldV1EP1 {
-		t.Fatalf("Expected a new endpoint for the app after kill/run")
-	}
-
-	// Start a second instance.
-	instance2ID := utiltest.LaunchApp(t, ctx, appID)
-	pingCh.VerifyPingArgs(t, utiltest.UserName(t), "flag-val-install", "env-val-envelope") // Wait until the app pings us that it's ready.
-
-	// There should be two endpoints mounted as "appV1", one for each
-	// instance of the app.
-	endpoints := utiltest.Resolve(t, ctx, "appV1", 2)
-	v1EP2 := endpoints[0]
-	if endpoints[0] == v1EP1 {
-		v1EP2 = endpoints[1]
-		if v1EP2 == v1EP1 {
-			t.Fatalf("Both endpoints are the same")
-		}
-	} else if endpoints[1] != v1EP1 {
-		t.Fatalf("Second endpoint should have been v1EP1: %v, %v", endpoints, v1EP1)
-	}
-
-	// TODO(caprita): verify various non-standard combinations (kill when
-	// canceled; run while still running).
-
-	// Kill the first instance.
-	utiltest.KillApp(t, ctx, appID, instance1ID)
-	// Only the second instance should still be running and mounted.
-	if want, got := v1EP2, utiltest.Resolve(t, ctx, "appV1", 1)[0]; want != got {
-		t.Fatalf("Resolve(%v): want: %v, got %v", "appV1", want, got)
-	}
-
-	// Updating the installation to itself is a no-op.
-	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")
-
-	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", "appV2")
-
-	utiltest.UpdateApp(t, ctx, appID)
-
-	v2 := utiltest.VerifyState(t, ctx, device.InstallationStateActive, appID)
-	if v1 == v2 {
-		t.Fatalf("Version did not change for %v: %v", appID, v1)
-	}
-
-	// Second instance should still be running.
-	if want, got := v1EP2, utiltest.Resolve(t, ctx, "appV1", 1)[0]; want != got {
-		t.Fatalf("Resolve(%v): want: %v, got %v", "appV1", want, got)
-	}
-	if v := utiltest.VerifyState(t, ctx, device.InstanceStateRunning, appID, instance2ID); v != v1 {
-		t.Fatalf("Instance version expected to be %v, got %v instead", v1, v)
-	}
-
-	// Resume first instance.
-	utiltest.RunApp(t, ctx, appID, instance1ID)
-	if v := utiltest.VerifyState(t, ctx, device.InstanceStateRunning, appID, instance1ID); v != v1 {
-		t.Fatalf("Instance version expected to be %v, got %v instead", v1, v)
-	}
-	pingCh.VerifyPingArgs(t, utiltest.UserName(t), "flag-val-install", "env-val-envelope") // Wait until the app pings us that it's ready.
-	// Both instances should still be running the first version of the app.
-	// Check that the mounttable contains two endpoints, one of which is
-	// v1EP2.
-	endpoints = utiltest.Resolve(t, ctx, "appV1", 2)
-	if endpoints[0] == v1EP2 {
-		if endpoints[1] == v1EP2 {
-			t.Fatalf("Both endpoints are the same")
-		}
-	} else if endpoints[1] != v1EP2 {
-		t.Fatalf("Second endpoint should have been v1EP2: %v, %v", endpoints, v1EP2)
-	}
-
-	// Trying to update first instance while it's running should fail.
-	utiltest.UpdateInstanceExpectError(t, ctx, appID, instance1ID, impl.ErrInvalidOperation.ID)
-	// Stop first instance and try again.
-	utiltest.KillApp(t, ctx, appID, instance1ID)
-	// Only the second instance should still be running and mounted.
-	if want, got := v1EP2, utiltest.Resolve(t, ctx, "appV1", 1)[0]; want != got {
-		t.Fatalf("Resolve(%v): want: %v, got %v", "appV1", want, got)
-	}
-	// Update succeeds now.
-	utiltest.UpdateInstance(t, ctx, appID, instance1ID)
-	if v := utiltest.VerifyState(t, ctx, device.InstanceStateNotRunning, appID, instance1ID); v != v2 {
-		t.Fatalf("Instance version expected to be %v, got %v instead", v2, v)
-	}
-	// Resume the first instance and verify it's running v2 now.
-	utiltest.RunApp(t, ctx, appID, instance1ID)
-	pingCh.VerifyPingArgs(t, utiltest.UserName(t), "flag-val-install", "env-val-envelope")
-	utiltest.Resolve(t, ctx, "appV1", 1)
-	utiltest.Resolve(t, ctx, "appV2", 1)
-
-	// Stop first instance.
-	utiltest.TerminateApp(t, ctx, appID, instance1ID)
-	verifyAppWorkspace(t, root, appID, instance1ID)
-	utiltest.ResolveExpectNotFound(t, ctx, "appV2")
-
-	// Start a third instance.
-	instance3ID := utiltest.LaunchApp(t, ctx, appID)
-	if v := utiltest.VerifyState(t, ctx, device.InstanceStateRunning, appID, instance3ID); v != v2 {
-		t.Fatalf("Instance version expected to be %v, got %v instead", v2, v)
-	}
-	// Wait until the app pings us that it's ready.
-	pingCh.VerifyPingArgs(t, utiltest.UserName(t), "flag-val-install", "env-val-envelope")
-
-	utiltest.Resolve(t, ctx, "appV2", 1)
-
-	// Stop second instance.
-	utiltest.TerminateApp(t, ctx, appID, instance2ID)
-	utiltest.ResolveExpectNotFound(t, ctx, "appV1")
-
-	// Stop third instance.
-	utiltest.TerminateApp(t, ctx, appID, instance3ID)
-	utiltest.ResolveExpectNotFound(t, ctx, "appV2")
-
-	// Revert the app.
-	utiltest.RevertApp(t, ctx, appID)
-	if v := utiltest.VerifyState(t, ctx, device.InstallationStateActive, appID); v != v1 {
-		t.Fatalf("Installation version expected to be %v, got %v instead", v1, v)
-	}
-
-	// Start a fourth instance.  It should be running from version 1.
-	instance4ID := utiltest.LaunchApp(t, ctx, appID)
-	if v := utiltest.VerifyState(t, ctx, device.InstanceStateRunning, appID, instance4ID); v != v1 {
-		t.Fatalf("Instance version expected to be %v, got %v instead", v1, v)
-	}
-	pingCh.VerifyPingArgs(t, utiltest.UserName(t), "flag-val-install", "env-val-envelope") // Wait until the app pings us that it's ready.
-	utiltest.Resolve(t, ctx, "appV1", 1)
-	utiltest.TerminateApp(t, ctx, appID, instance4ID)
-	utiltest.ResolveExpectNotFound(t, ctx, "appV1")
-
-	// We are already on the first version, no further revert possible.
-	utiltest.RevertAppExpectError(t, ctx, appID, impl.ErrUpdateNoOp.ID)
-
-	// Uninstall the app.
-	utiltest.UninstallApp(t, ctx, appID)
-	utiltest.VerifyState(t, ctx, device.InstallationStateUninstalled, appID)
-
-	// Updating the installation should no longer be allowed.
-	utiltest.UpdateAppExpectError(t, ctx, appID, impl.ErrInvalidOperation.ID)
-
-	// Reverting the installation should no longer be allowed.
-	utiltest.RevertAppExpectError(t, ctx, appID, impl.ErrInvalidOperation.ID)
-
-	// Starting new instances should no longer be allowed.
-	utiltest.LaunchAppExpectError(t, ctx, appID, impl.ErrInvalidOperation.ID)
-
-	// Make sure that Kill will actually kill an app that doesn't exit
-	// 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", "hAppV1")
-	hAppID := utiltest.InstallApp(t, ctx)
-	hInstanceID := utiltest.LaunchApp(t, ctx, hAppID)
-	hangingPid := pingCh.WaitForPingArgs(t).Pid
-	if err := syscall.Kill(hangingPid, 0); err != nil && err != syscall.EPERM {
-		t.Fatalf("Pid of hanging app (%v) is not live", hangingPid)
-	}
-	utiltest.KillApp(t, ctx, hAppID, hInstanceID)
-	pidIsAlive := true
-	for i := 0; i < 10 && pidIsAlive; i++ {
-		if err := syscall.Kill(hangingPid, 0); err == nil || err == syscall.EPERM {
-			time.Sleep(time.Second) // pid is still alive
-		} else {
-			pidIsAlive = false
-		}
-	}
-	if pidIsAlive {
-		t.Fatalf("Pid of hanging app (%d) has not exited after Stop() call", hangingPid)
-	}
-
-	// Cleanly shut down the device manager.
-	defer utiltest.VerifyNoRunningProcesses(t)
-	syscall.Kill(dmh.Pid(), syscall.SIGINT)
-	dmh.Expect("dm terminated")
-	dmh.ExpectEOF()
-}
-
 type simpleRW chan []byte
 
 func (s simpleRW) Write(p []byte) (n int, err error) {