| // Copyright 2017 The Chromium 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 org.chromium.content.browser; |
| |
| import android.content.ComponentName; |
| import android.content.Context; |
| import android.os.Bundle; |
| import android.os.IBinder; |
| import android.os.RemoteException; |
| import android.support.test.InstrumentationRegistry; |
| import android.support.test.filters.LargeTest; |
| import android.support.test.filters.MediumTest; |
| |
| import org.junit.Assert; |
| import org.junit.Before; |
| import org.junit.Test; |
| import org.junit.runner.RunWith; |
| |
| import org.chromium.base.process_launcher.FileDescriptorInfo; |
| import org.chromium.base.test.util.CallbackHelper; |
| import org.chromium.base.test.util.Feature; |
| import org.chromium.content.browser.test.ContentJUnit4ClassRunner; |
| import org.chromium.content.browser.test.util.Criteria; |
| import org.chromium.content.browser.test.util.CriteriaHelper; |
| import org.chromium.content_shell_apk.ChildProcessLauncherTestUtils; |
| import org.chromium.content_shell_apk.IChildProcessTest; |
| |
| import java.util.concurrent.Callable; |
| import java.util.concurrent.TimeoutException; |
| import java.util.concurrent.atomic.AtomicReference; |
| |
| /** |
| * Instrumentation tests for ChildProcessLauncher. |
| */ |
| @RunWith(ContentJUnit4ClassRunner.class) |
| public class ChildProcessLauncherTest { |
| private static final long CONDITION_WAIT_TIMEOUT_MS = 5000; |
| |
| private static final String SERVICE_PACKAGE_NAME = "org.chromium.content_shell_apk"; |
| private static final String SERVICE_NAME_META_DATA_KEY = |
| "org.chromium.content.browser.TEST_SERVICES_NAME"; |
| private static final String SERVICE_COUNT_META_DATA_KEY = |
| "org.chromium.content.browser.NUM_TEST_SERVICES"; |
| private static final String SERVICE0_FULL_NAME = |
| "org.chromium.content_shell_apk.TestChildProcessService0"; |
| |
| private static final String EXTRA_SERVICE_PARAM = "org.chromium.content.browser.SERVICE_EXTRA"; |
| private static final String EXTRA_SERVICE_PARAM_VALUE = "SERVICE_EXTRA"; |
| |
| private static final String EXTRA_CONNECTION_PARAM = |
| "org.chromium.content.browser.CONNECTION_EXTRA"; |
| private static final String EXTRA_CONNECTION_PARAM_VALUE = "CONNECTION_EXTRA"; |
| |
| private static final int CONNECTION_BLOCK_UNTIL_CONNECTED = 1; |
| private static final int CONNECTION_BLOCK_UNTIL_SETUP = 2; |
| |
| /** |
| * A factory used to create a ChildProcessLauncher with a bound connection or with a connection |
| * connection allocator so that the test code can be reused for both scenarios. |
| */ |
| private abstract static class ChildProcessLauncherFactory { |
| private final boolean mConnectionProvided; |
| |
| public ChildProcessLauncherFactory(boolean connectionProvided) { |
| mConnectionProvided = connectionProvided; |
| } |
| |
| public abstract ChildProcessLauncher createChildProcessLauncher( |
| ChildProcessLauncher.Delegate delegate, String[] commandLine, |
| FileDescriptorInfo[] filesToBeMapped, IBinder binderCallback); |
| |
| public boolean isConnectionProvided() { |
| return mConnectionProvided; |
| } |
| } |
| |
| private static final ChildProcessLauncher.Delegate EMPTY_LAUNCHER_DELEGATE = |
| new ChildProcessLauncher.Delegate() { |
| @Override |
| public void onBeforeConnectionAllocated(Bundle serviceBundle) {} |
| |
| @Override |
| public void onBeforeConnectionSetup(Bundle connectionBundle) {} |
| |
| @Override |
| public void onConnectionEstablished(ChildProcessConnection connection) {} |
| |
| @Override |
| public void onConnectionLost(ChildProcessConnection connection) {} |
| }; |
| |
| private ChildConnectionAllocator mConnectionAllocator; |
| |
| @Before |
| public void setUp() throws Exception { |
| mConnectionAllocator = ChildProcessLauncherTestUtils.runOnLauncherAndGetResult( |
| new Callable<ChildConnectionAllocator>() { |
| @Override |
| public ChildConnectionAllocator call() { |
| Context context = |
| InstrumentationRegistry.getInstrumentation().getTargetContext(); |
| return ChildConnectionAllocator.create(context, null /* creationParams */, |
| SERVICE_PACKAGE_NAME, SERVICE_NAME_META_DATA_KEY, |
| SERVICE_COUNT_META_DATA_KEY, false /* bindAsExternalService */, |
| false /* useStrongBinding */); |
| } |
| }); |
| } |
| |
| private static class IChildProcessBinder extends IChildProcessTest.Stub { |
| private final CallbackHelper mOnConnectionSetupHelper = new CallbackHelper(); |
| private final CallbackHelper mOnLoadNativeHelper = new CallbackHelper(); |
| private final CallbackHelper mOnBeforeMainHelper = new CallbackHelper(); |
| private final CallbackHelper mOnRunMainHelper = new CallbackHelper(); |
| private final CallbackHelper mOnDestroyHelper = new CallbackHelper(); |
| |
| // Can be accessed after mOnConnectionSetupCalled is signaled. |
| private boolean mServiceCreated; |
| private Bundle mServiceBundle; |
| private Bundle mConnectionBundle; |
| |
| // Can be accessed after mOnLoadNativeCalled is signaled. |
| private boolean mNativeLibraryLoaded; |
| |
| // Can be accessed after mOnBeforeMainCalled is signaled. |
| private String[] mCommandLine; |
| |
| @Override |
| public void onConnectionSetup( |
| boolean serviceCreatedCalled, Bundle serviceBundle, Bundle connectionBundle) { |
| mServiceCreated = serviceCreatedCalled; |
| mServiceBundle = serviceBundle; |
| mConnectionBundle = connectionBundle; |
| Assert.assertEquals(0, mOnConnectionSetupHelper.getCallCount()); |
| mOnConnectionSetupHelper.notifyCalled(); |
| } |
| |
| @Override |
| public void onLoadNativeLibrary(boolean loadedSuccessfully) { |
| mNativeLibraryLoaded = loadedSuccessfully; |
| Assert.assertEquals(0, mOnLoadNativeHelper.getCallCount()); |
| mOnLoadNativeHelper.notifyCalled(); |
| } |
| |
| @Override |
| public void onBeforeMain(String[] commandLine) { |
| mCommandLine = commandLine; |
| Assert.assertEquals(0, mOnBeforeMainHelper.getCallCount()); |
| mOnBeforeMainHelper.notifyCalled(); |
| } |
| |
| @Override |
| public void onRunMain() { |
| Assert.assertEquals(0, mOnRunMainHelper.getCallCount()); |
| mOnRunMainHelper.notifyCalled(); |
| } |
| |
| @Override |
| public void onDestroy() { |
| Assert.assertEquals(0, mOnDestroyHelper.getCallCount()); |
| mOnDestroyHelper.notifyCalled(); |
| } |
| |
| public void waitForOnConnectionSetupCalled() throws InterruptedException, TimeoutException { |
| mOnConnectionSetupHelper.waitForCallback(0 /* currentCallCount */); |
| } |
| |
| public void waitForOnNativeLibraryCalled() throws InterruptedException, TimeoutException { |
| mOnLoadNativeHelper.waitForCallback(0 /* currentCallCount */); |
| } |
| |
| public void waitOnBeforeMainCalled() throws InterruptedException, TimeoutException { |
| mOnBeforeMainHelper.waitForCallback(0 /* currentCallCount */); |
| } |
| |
| public void waitOnRunMainCalled() throws InterruptedException, TimeoutException { |
| mOnRunMainHelper.waitForCallback(0 /* currentCallCount */); |
| } |
| |
| public void waitOnDestroyCalled() throws InterruptedException, TimeoutException { |
| mOnDestroyHelper.waitForCallback(0 /* currentCallCount */); |
| } |
| }; |
| |
| /** |
| * Creates a child process with the ChildProcessLauncher created with {@param launcherFactory} |
| * and tests that the all callbacks on the client and in the service are called appropriately. |
| * The service echos back the delegate calls through the IBinder callback so that the test can |
| * validate them. |
| */ |
| private void testProcessLauncher(final ChildProcessLauncherFactory launcherFactory) |
| throws InterruptedException, TimeoutException { |
| // ConditionVariables used to check the ChildProcessLauncher.Delegate methods get called. |
| final CallbackHelper onBeforeConnectionAllocatedHelper = new CallbackHelper(); |
| final CallbackHelper onBeforeConnectionSetupHelper = new CallbackHelper(); |
| final CallbackHelper onConnectionEstablishedHelper = new CallbackHelper(); |
| final CallbackHelper onConnectionLostHelper = new CallbackHelper(); |
| |
| final ChildProcessLauncher.Delegate delegate = new ChildProcessLauncher.Delegate() { |
| @Override |
| public void onBeforeConnectionAllocated(Bundle serviceBundle) { |
| // Should only be called when the ChildProcessLauncher creates the connection. |
| Assert.assertFalse(launcherFactory.isConnectionProvided()); |
| Assert.assertEquals(0, onBeforeConnectionAllocatedHelper.getCallCount()); |
| serviceBundle.putString(EXTRA_SERVICE_PARAM, EXTRA_SERVICE_PARAM_VALUE); |
| onBeforeConnectionAllocatedHelper.notifyCalled(); |
| } |
| |
| @Override |
| public void onBeforeConnectionSetup(Bundle connectionBundle) { |
| connectionBundle.putString(EXTRA_CONNECTION_PARAM, EXTRA_CONNECTION_PARAM_VALUE); |
| Assert.assertEquals(0, onBeforeConnectionSetupHelper.getCallCount()); |
| onBeforeConnectionSetupHelper.notifyCalled(); |
| } |
| |
| @Override |
| public void onConnectionEstablished(ChildProcessConnection connection) { |
| Assert.assertEquals(0, onConnectionEstablishedHelper.getCallCount()); |
| onConnectionEstablishedHelper.notifyCalled(); |
| } |
| |
| @Override |
| public void onConnectionLost(ChildProcessConnection connection) { |
| Assert.assertEquals(0, onConnectionLostHelper.getCallCount()); |
| onConnectionLostHelper.notifyCalled(); |
| } |
| }; |
| |
| final String[] commandLine = new String[] {"--test-param1", "--test-param2"}; |
| final FileDescriptorInfo[] filesToBeMapped = new FileDescriptorInfo[0]; |
| |
| final IChildProcessBinder childProcessBinder = new IChildProcessBinder(); |
| |
| final ChildProcessLauncher processLauncher = |
| ChildProcessLauncherTestUtils.runOnLauncherAndGetResult( |
| new Callable<ChildProcessLauncher>() { |
| @Override |
| public ChildProcessLauncher call() { |
| ChildProcessLauncher processLauncher = |
| launcherFactory.createChildProcessLauncher(delegate, |
| commandLine, filesToBeMapped, childProcessBinder); |
| processLauncher.start(true /* setupConnection */, |
| false /*queueIfNoFreeConnection */); |
| return processLauncher; |
| } |
| }); |
| |
| Assert.assertNotNull(processLauncher); |
| |
| if (!launcherFactory.isConnectionProvided()) { |
| onBeforeConnectionAllocatedHelper.waitForCallback(0 /* currentCallback */); |
| } |
| |
| onBeforeConnectionSetupHelper.waitForCallback(0 /* currentCallback */); |
| |
| // Wait for the service to notify its onConnectionSetup was called. |
| childProcessBinder.waitForOnConnectionSetupCalled(); |
| Assert.assertTrue(childProcessBinder.mServiceCreated); |
| Assert.assertNotNull(childProcessBinder.mServiceBundle); |
| Assert.assertNotNull(childProcessBinder.mConnectionBundle); |
| if (!launcherFactory.isConnectionProvided()) { |
| Assert.assertEquals(EXTRA_SERVICE_PARAM_VALUE, |
| childProcessBinder.mServiceBundle.getString(EXTRA_SERVICE_PARAM)); |
| } |
| Assert.assertEquals(EXTRA_CONNECTION_PARAM_VALUE, |
| childProcessBinder.mConnectionBundle.getString(EXTRA_CONNECTION_PARAM)); |
| |
| // Wait for the client onConnectionEstablished call. |
| onConnectionEstablishedHelper.waitForCallback(0 /* currentCallback */); |
| |
| // Wait for the service to notify its library got loaded. |
| childProcessBinder.waitForOnNativeLibraryCalled(); |
| Assert.assertTrue(childProcessBinder.mNativeLibraryLoaded); |
| |
| // Wait for the service to notify its onBeforeMain was called. |
| childProcessBinder.waitOnBeforeMainCalled(); |
| Assert.assertArrayEquals(commandLine, childProcessBinder.mCommandLine); |
| |
| // Wait for the service to notify its onRunMain was called. |
| childProcessBinder.waitOnRunMainCalled(); |
| |
| // Stop the launcher. |
| ChildProcessLauncherTestUtils.runOnLauncherThreadBlocking(new Runnable() { |
| @Override |
| public void run() { |
| processLauncher.stop(); |
| } |
| }); |
| // Wait for service to notify its onDestroy was called. |
| childProcessBinder.waitOnDestroyCalled(); |
| // The client should also get a notification that the connection was lost. |
| onConnectionLostHelper.waitForCallback(0 /* currentCallback */); |
| } |
| |
| @Test |
| @LargeTest |
| @Feature({"ProcessManagement"}) |
| public void testLaunchServiceCreatedWithConnectionAllocator() throws Exception { |
| final ChildProcessLauncherFactory childProcessLauncherFactory = |
| new ChildProcessLauncherFactory(false /* providesConnection */) { |
| @Override |
| public ChildProcessLauncher createChildProcessLauncher( |
| ChildProcessLauncher.Delegate delegate, String[] commandLine, |
| FileDescriptorInfo[] filesToBeMapped, IBinder binderCallback) { |
| return ChildProcessLauncher.createWithConnectionAllocator(delegate, |
| commandLine, filesToBeMapped, mConnectionAllocator, binderCallback); |
| } |
| }; |
| |
| testProcessLauncher(childProcessLauncherFactory); |
| } |
| |
| @Test |
| @LargeTest |
| @Feature({"ProcessManagement"}) |
| public void testLaunchServiceCreatedWithBoundConnection() throws Exception { |
| // Wraps the serviceCallback provided by the ChildProcessLauncher so that the |
| // ChildProcessConnection can forward to them appropriately. |
| final AtomicReference<ChildProcessConnection.ServiceCallback> serviceCallbackWrapper = |
| new AtomicReference<>(); |
| |
| final ChildProcessConnection boundConnection = |
| ChildProcessLauncherTestUtils.runOnLauncherAndGetResult(new Callable< |
| ChildProcessConnection>() { |
| @Override |
| public ChildProcessConnection call() { |
| Context context = |
| InstrumentationRegistry.getInstrumentation().getTargetContext(); |
| ComponentName serviceName = |
| new ComponentName(SERVICE_PACKAGE_NAME, SERVICE0_FULL_NAME); |
| ChildProcessConnection connection = new ChildProcessConnection(context, |
| serviceName, false /* bindAsExternalService */, |
| new Bundle() /* serviceBundle */, null /* creationParams */); |
| connection.start(false /* useStrongBinding */, |
| new ChildProcessConnection.ServiceCallback() { |
| @Override |
| public void onChildStarted() { |
| if (serviceCallbackWrapper.get() != null) { |
| serviceCallbackWrapper.get().onChildStarted(); |
| } |
| } |
| |
| @Override |
| public void onChildStartFailed() { |
| if (serviceCallbackWrapper.get() != null) { |
| serviceCallbackWrapper.get().onChildStartFailed(); |
| } |
| } |
| |
| @Override |
| public void onChildProcessDied( |
| ChildProcessConnection connection) { |
| if (serviceCallbackWrapper.get() != null) { |
| serviceCallbackWrapper.get().onChildProcessDied( |
| connection); |
| } |
| } |
| }); |
| return connection; |
| } |
| }); |
| |
| Assert.assertNotNull(boundConnection); |
| |
| CriteriaHelper.pollInstrumentationThread(new Criteria("Connection failed to connect") { |
| @Override |
| public boolean isSatisfied() { |
| return boundConnection.isConnected(); |
| } |
| }); |
| |
| final ChildProcessLauncher.BoundConnectionProvider connectionProvider = |
| new ChildProcessLauncher.BoundConnectionProvider() { |
| @Override |
| public ChildProcessConnection getConnection( |
| ChildProcessConnection.ServiceCallback serviceCallback) { |
| serviceCallbackWrapper.set(serviceCallback); |
| return boundConnection; |
| } |
| }; |
| |
| final ChildProcessLauncherFactory childProcessLauncherFactory = |
| new ChildProcessLauncherFactory(true /* providesConnection */) { |
| @Override |
| public ChildProcessLauncher createChildProcessLauncher( |
| ChildProcessLauncher.Delegate delegate, String[] commandLine, |
| FileDescriptorInfo[] filesToBeMapped, IBinder binderCallback) { |
| return ChildProcessLauncher.createWithBoundConnectionProvider(delegate, |
| commandLine, filesToBeMapped, connectionProvider, binderCallback); |
| } |
| }; |
| |
| testProcessLauncher(childProcessLauncherFactory); |
| } |
| |
| /** |
| * Tests cleanup for a connection that fails to connect in the first place. |
| */ |
| @Test |
| @MediumTest |
| @Feature({"ProcessManagement"}) |
| public void testServiceFailedToBind() { |
| final ChildConnectionAllocator badConnectionAllocator = |
| ChildProcessLauncherTestUtils.runOnLauncherAndGetResult( |
| new Callable<ChildConnectionAllocator>() { |
| @Override |
| public ChildConnectionAllocator call() { |
| return ChildConnectionAllocator.createForTest( |
| null /* creationParams */, "org.chromium.wrong_package", |
| "WrongService", 2 /* serviceCount */, |
| false /* bindAsExternalService */, |
| false /* useStrongBinding */); |
| } |
| }); |
| Assert.assertFalse(badConnectionAllocator.anyConnectionAllocated()); |
| |
| // Try to allocate a connection to service class in incorrect package. We can do that by |
| // using the instrumentation context (getContext()) instead of the app context |
| // (getTargetContext()). |
| ChildProcessLauncher processLauncher = createChildProcessLauncher(badConnectionAllocator, |
| true /* setupConnection */, false /* queueIfNoFreeConnection */); |
| |
| Assert.assertNotNull(processLauncher); |
| |
| // Verify that the connection is not considered as allocated (or only briefly, as the |
| // freeing is delayed). |
| waitForConnectionAllocatorState(badConnectionAllocator, true /* isEmpty */); |
| } |
| |
| /** |
| * Tests cleanup for a connection that terminates before setup. |
| */ |
| @Test |
| @MediumTest |
| @Feature({"ProcessManagement"}) |
| public void testServiceCrashedBeforeSetup() throws RemoteException { |
| Assert.assertFalse(mConnectionAllocator.anyConnectionAllocated()); |
| |
| // Start and connect to a new service. |
| ChildProcessLauncher processLauncher = createChildProcessLauncher(mConnectionAllocator, |
| false /* setupConnection */, false /* queueIfNoFreeConnection */); |
| |
| // Verify that the service is bound but not yet set up. |
| Assert.assertTrue(mConnectionAllocator.anyConnectionAllocated()); |
| ChildProcessConnection connection = processLauncher.getConnection(); |
| Assert.assertNotNull(connection); |
| waitForConnectionState(connection, CONNECTION_BLOCK_UNTIL_CONNECTED); |
| Assert.assertEquals(0, getConnectionPid(connection)); |
| |
| // Crash the service. |
| connection.crashServiceForTesting(); |
| |
| // Verify that the connection gets cleaned-up. |
| waitForConnectionAllocatorState(mConnectionAllocator, true /* isEmpty */); |
| } |
| |
| @Test |
| @MediumTest |
| @Feature({"ProcessManagement"}) |
| public void testServiceCrashedAfterSetup() throws RemoteException { |
| Assert.assertFalse(mConnectionAllocator.anyConnectionAllocated()); |
| |
| // Start and connect to a new service. |
| ChildProcessLauncher processLauncher = createChildProcessLauncher(mConnectionAllocator, |
| true /* setupConnection */, false /* queueIfNoFreeConnection */); |
| |
| Assert.assertTrue(mConnectionAllocator.anyConnectionAllocated()); |
| ChildProcessConnection connection = processLauncher.getConnection(); |
| Assert.assertNotNull(connection); |
| waitForConnectionState(connection, CONNECTION_BLOCK_UNTIL_SETUP); |
| // We are passed set-up, the connection should have received its PID. |
| Assert.assertNotEquals(0, getConnectionPid(connection)); |
| |
| // Crash the service. |
| connection.crashServiceForTesting(); |
| |
| // Verify that the connection gets cleaned-up. |
| waitForConnectionAllocatorState(mConnectionAllocator, true /* isEmpty */); |
| |
| // Verify that the connection pid remains set after termination. |
| Assert.assertNotEquals(0, getConnectionPid(connection)); |
| } |
| |
| @Test |
| @MediumTest |
| @Feature({"ProcessManagement"}) |
| public void testPendingSpawnQueue() throws RemoteException { |
| Assert.assertFalse(mConnectionAllocator.anyConnectionAllocated()); |
| |
| // Launch 4 processes. Since we have only 2 services, the 3rd and 4th should get queued. |
| ChildProcessLauncher[] launchers = new ChildProcessLauncher[4]; |
| ChildProcessConnection[] connections = new ChildProcessConnection[4]; |
| for (int i = 0; i < 4; i++) { |
| launchers[i] = createChildProcessLauncher(mConnectionAllocator, |
| true /* setupConnection */, true /* queueIfNoFreeConnection */); |
| Assert.assertNotNull(launchers[i]); |
| connections[i] = launchers[i].getConnection(); |
| } |
| Assert.assertNotNull(connections[0]); |
| Assert.assertNotNull(connections[1]); |
| Assert.assertNull(connections[2]); |
| Assert.assertNull(connections[3]); |
| |
| // Test creating a launcher with queueIfNoFreeConnection false with no connection available. |
| Assert.assertNull(createChildProcessLauncher(mConnectionAllocator, |
| true /* setupConnection */, false /* queueIfNoFreeConnection */)); |
| |
| // Stop one connection, that should free-up a connection and the first queued launcher |
| // should use it. |
| stopLauncher(launchers[0]); |
| waitUntilLauncherSetup(launchers[2]); |
| |
| // Last launcher is still queued. |
| Assert.assertNull(launchers[3].getConnection()); |
| |
| // Crash another launcher. It should free-up another connection that the queued-up launcher |
| // should use. |
| connections[1].crashServiceForTesting(); |
| waitUntilLauncherSetup(launchers[3]); |
| } |
| |
| private static ChildProcessLauncher createChildProcessLauncher( |
| final ChildConnectionAllocator connectionAllocator, final boolean setupConnection, |
| final boolean queueIfNoFreeConnection) { |
| return ChildProcessLauncherTestUtils.runOnLauncherAndGetResult( |
| new Callable<ChildProcessLauncher>() { |
| @Override |
| public ChildProcessLauncher call() { |
| ChildProcessLauncher processLauncher = |
| ChildProcessLauncher.createWithConnectionAllocator( |
| EMPTY_LAUNCHER_DELEGATE, new String[0], |
| new FileDescriptorInfo[0], connectionAllocator, |
| null /* binderCallback */); |
| if (!processLauncher.start(setupConnection, queueIfNoFreeConnection)) { |
| return null; |
| } |
| return processLauncher; |
| } |
| }); |
| } |
| |
| private static void waitForConnectionAllocatorState( |
| final ChildConnectionAllocator connectionAllocator, final boolean emptyState) { |
| CriteriaHelper.pollInstrumentationThread( |
| new Criteria("Failed to wait for connection allocator.") { |
| @Override |
| public boolean isSatisfied() { |
| return emptyState ? !connectionAllocator.anyConnectionAllocated() |
| : connectionAllocator.anyConnectionAllocated(); |
| } |
| }); |
| } |
| |
| private static void waitForConnectionState( |
| final ChildProcessConnection connection, final int connectionState) { |
| assert connectionState == CONNECTION_BLOCK_UNTIL_CONNECTED |
| || connectionState == CONNECTION_BLOCK_UNTIL_SETUP; |
| CriteriaHelper.pollInstrumentationThread( |
| new Criteria("Failed wait for connection to connect.") { |
| @Override |
| public boolean isSatisfied() { |
| if (connectionState == CONNECTION_BLOCK_UNTIL_CONNECTED) { |
| return connection.isConnected(); |
| } |
| assert connectionState == CONNECTION_BLOCK_UNTIL_SETUP; |
| return getConnectionPid(connection) != 0; |
| } |
| }); |
| } |
| |
| private static void waitUntilLauncherSetup(final ChildProcessLauncher launcher) { |
| CriteriaHelper.pollInstrumentationThread( |
| new Criteria("Failed wait for launcher to connect.") { |
| @Override |
| public boolean isSatisfied() { |
| return launcher.getConnection() != null; |
| } |
| }); |
| waitForConnectionState(launcher.getConnection(), CONNECTION_BLOCK_UNTIL_SETUP); |
| } |
| |
| private static int getConnectionPid(final ChildProcessConnection connection) { |
| return ChildProcessLauncherTestUtils.runOnLauncherAndGetResult(new Callable<Integer>() { |
| @Override |
| public Integer call() { |
| return connection.getPid(); |
| } |
| }); |
| } |
| |
| private static void stopLauncher(final ChildProcessLauncher launcher) { |
| ChildProcessLauncherTestUtils.runOnLauncherThreadBlocking(new Runnable() { |
| @Override |
| public void run() { |
| launcher.stop(); |
| } |
| }); |
| } |
| } |