Merge "profiles/internal/rpc/stream/manager: Continue after "temporary" error"
diff --git a/services/device/internal/impl/impl_test.go b/services/device/internal/impl/impl_test.go
index f89e266..326ab3e 100644
--- a/services/device/internal/impl/impl_test.go
+++ b/services/device/internal/impl/impl_test.go
@@ -400,6 +400,7 @@
 	dmh := servicetest.RunCommand(t, sh, dmPauseBeforeStopEnv, deviceManagerCmd, dmArgs...)
 	defer func() {
 		syscall.Kill(dmh.Pid(), syscall.SIGINT)
+		verifyNoRunningProcesses(t)
 	}()
 
 	servicetest.ReadPID(t, dmh)
@@ -854,6 +855,7 @@
 	startAppExpectError(t, ctx, appID, impl.ErrInvalidOperation.ID)
 
 	// Cleanly shut down the device manager.
+	defer verifyNoRunningProcesses(t)
 	syscall.Kill(dmh.Pid(), syscall.SIGINT)
 	dmh.Expect("dm terminated")
 	dmh.ExpectEOF()
@@ -1006,6 +1008,7 @@
 	dmh := servicetest.RunCommand(t, sh, nil, deviceManagerCmd, "dm", root, "unused_helper", "unused_app_repo_name", "unused_curr_link")
 	pid := servicetest.ReadPID(t, dmh)
 	defer syscall.Kill(pid, syscall.SIGINT)
+	defer verifyNoRunningProcesses(t)
 
 	// Create an envelope for an app.
 	*envelope = envelopeFromShell(sh, nil, appCmd, "google naps")
@@ -1172,6 +1175,7 @@
 
 	// Start an instance of the app.
 	instance1ID := startApp(t, ctx, appID)
+	defer stopApp(t, ctx, appID, instance1ID)
 
 	// Wait until the app pings us that it's ready.
 	select {
@@ -1287,6 +1291,7 @@
 	dmh := servicetest.RunCommand(t, sh, nil, deviceManagerCmd, "dm", root, helperPath, "unused_app_repo_name", "unused_curr_link")
 	pid := servicetest.ReadPID(t, dmh)
 	defer syscall.Kill(pid, syscall.SIGINT)
+	defer verifyNoRunningProcesses(t)
 
 	// Create the local server that the app uses to let us know it's ready.
 	pingCh, cleanup := setupPingServer(t, ctx)
@@ -1323,7 +1328,8 @@
 	appID := installApp(t, ctx, packages)
 
 	// Start an instance of the app.
-	startApp(t, ctx, appID)
+	instance1ID := startApp(t, ctx, appID)
+	defer stopApp(t, ctx, appID, instance1ID)
 
 	// Wait until the app pings us that it's ready.
 	select {
@@ -1404,6 +1410,7 @@
 	dmh := servicetest.RunCommand(t, sh, nil, deviceManagerCmd, "dm", root, "unused_helper", "unused_app_repo_name", "unused_curr_link")
 	pid := servicetest.ReadPID(t, dmh)
 	defer syscall.Kill(pid, syscall.SIGINT)
+	defer verifyNoRunningProcesses(t)
 
 	deviceStub := device.DeviceClient("dm/device")
 	// Attempt to list associations on the device manager without having
@@ -1503,6 +1510,7 @@
 	dmh := servicetest.RunCommand(t, sh, nil, deviceManagerCmd, "-mocksetuid", "dm", root, helperPath, "unused_app_repo_name", "unused_curr_link")
 	pid := servicetest.ReadPID(t, dmh)
 	defer syscall.Kill(pid, syscall.SIGINT)
+	defer verifyNoRunningProcesses(t)
 	// Claim the devicemanager with selfCtx as root/self/alice
 	claimDevice(t, selfCtx, "dm", "alice", noPairingToken)
 
diff --git a/services/device/internal/impl/instance_reaping.go b/services/device/internal/impl/instance_reaping.go
index 2c7da8c..f7e9672 100644
--- a/services/device/internal/impl/instance_reaping.go
+++ b/services/device/internal/impl/instance_reaping.go
@@ -30,8 +30,14 @@
 
 type reaper chan pidInstanceDirPair
 
+var stashedPidMap map[string]int
+
 func newReaper(ctx *context.T, root string) (reaper, error) {
 	pidMap, err := findAllTheInstances(ctx, root)
+
+	// Used only by the testing code that verifies that all processes
+	// have been shutdown.
+	stashedPidMap = pidMap
 	if err != nil {
 		return nil, err
 	}
diff --git a/services/device/internal/impl/instance_reaping_test.go b/services/device/internal/impl/instance_reaping_test.go
index bac5e15..d50d3cc 100644
--- a/services/device/internal/impl/instance_reaping_test.go
+++ b/services/device/internal/impl/instance_reaping_test.go
@@ -78,6 +78,7 @@
 	// TODO(rjkroege): Exercise the polling loop code.
 
 	// Cleanly shut down the device manager.
+	verifyNoRunningProcesses(t)
 	syscall.Kill(dmh.Pid(), syscall.SIGINT)
 	dmh.Expect("dm terminated")
 	dmh.ExpectEOF()
@@ -198,6 +199,7 @@
 
 	// TODO(rjkroege): Should be in a defer to ensure that the device
 	// manager is cleaned up even if the test fails in an exceptional way.
+	verifyNoRunningProcesses(t)
 	syscall.Kill(dmh.Pid(), syscall.SIGINT)
 	dmh.Expect("dm terminated")
 	dmh.ExpectEOF()
diff --git a/services/device/internal/impl/only_for_test.go b/services/device/internal/impl/only_for_test.go
index 23bcc37..583d545 100644
--- a/services/device/internal/impl/only_for_test.go
+++ b/services/device/internal/impl/only_for_test.go
@@ -68,3 +68,9 @@
 func WrapBaseCleanupDir(path, helper string) {
 	baseCleanupDir(path, helper)
 }
+
+// RunningChildrenProcesses uses the reaper to verify that a test has
+// successfully shut down all processes.
+func RunningChildrenProcesses() bool {
+	return len(stashedPidMap) > 0
+}
diff --git a/services/device/internal/impl/util_test.go b/services/device/internal/impl/util_test.go
index 3cc4314..3a99927 100644
--- a/services/device/internal/impl/util_test.go
+++ b/services/device/internal/impl/util_test.go
@@ -613,3 +613,9 @@
 	}
 
 }
+
+func verifyNoRunningProcesses(t *testing.T) {
+	if impl.RunningChildrenProcesses() {
+		t.Errorf("device manager incorrectly terminating with child processes still running")
+	}
+}