// Copyright 2017 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package org.chromium.base.process_launcher;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.AdditionalMatchers.or;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.isNull;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.content.ComponentName;
import android.content.Intent;
import android.content.pm.ApplicationInfo;
import android.os.Binder;
import android.os.Bundle;
import android.os.IBinder;
import android.os.RemoteException;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
import org.robolectric.annotation.Config;
import org.robolectric.annotation.LooperMode;
import org.robolectric.shadows.ShadowLooper;
import org.chromium.base.BuildInfo;
import org.chromium.base.ChildBindingState;
import org.chromium.base.test.BaseRobolectricTestRunner;
import java.util.ArrayList;
/** Unit tests for ChildProcessConnection. */
@RunWith(BaseRobolectricTestRunner.class)
@Config(manifest = Config.NONE)
@LooperMode(LooperMode.Mode.LEGACY)
public class ChildProcessConnectionTest {
private static class ChildServiceConnectionMock implements ChildServiceConnection {
private final Intent mBindIntent;
private final ChildServiceConnectionDelegate mDelegate;
private boolean mBound;
private int mGroup;
private int mImportanceInGroup;
private boolean mBindResult = true;
public ChildServiceConnectionMock(
Intent bindIntent, ChildServiceConnectionDelegate delegate) {
mBindIntent = bindIntent;
mDelegate = delegate;
}
@Override
public boolean bindServiceConnection() {
mBound = mBindResult;
return mBindResult;
}
@Override
public void unbindServiceConnection() {
mBound = false;
}
@Override
public boolean isBound() {
return mBound;
}
@Override
public void updateGroupImportance(int group, int importanceInGroup) {
mGroup = group;
mImportanceInGroup = importanceInGroup;
}
@Override
public void retire() {
mBound = false;
}
public void setBindResult(boolean result) {
mBindResult = result;
}
public void notifyServiceConnected(IBinder service) {
mDelegate.onServiceConnected(service);
}
public void notifyServiceDisconnected() {
mDelegate.onServiceDisconnected();
}
public Intent getBindIntent() {
return mBindIntent;
}
public int getGroup() {
return mGroup;
}
public int getImportanceInGroup() {
return mImportanceInGroup;
}
}
;
private final ChildServiceConnectionFactory mServiceConnectionFactory =
new ChildServiceConnectionFactory() {
@Override
public ChildServiceConnection createConnection(
Intent bindIntent,
int bindFlags,
ChildServiceConnectionDelegate delegate,
String instanceName) {
ChildServiceConnectionMock connection =
spy(new ChildServiceConnectionMock(bindIntent, delegate));
if (mFirstServiceConnection == null) {
mFirstServiceConnection = connection;
}
mMockConnections.add(connection);
return connection;
}
};
@Mock private ChildProcessConnection.ServiceCallback mServiceCallback;
@Mock private ChildProcessConnection.ConnectionCallback mConnectionCallback;
@Mock private ChildProcessConnection.ZygoteInfoCallback mZygoteInfoCallback;
private IChildProcessService mIChildProcessService;
private Binder mChildProcessServiceBinder;
private ChildServiceConnectionMock mFirstServiceConnection;
private final ArrayList<ChildServiceConnectionMock> mMockConnections = new ArrayList<>();
// Parameters captured from the IChildProcessService.setupConnection() call
private Bundle mConnectionBundle;
private IParentProcess mConnectionParentProcess;
@Before
public void setUp() throws RemoteException {
MockitoAnnotations.initMocks(this);
mIChildProcessService = mock(IChildProcessService.class);
ApplicationInfo appInfo = BuildInfo.getInstance().getBrowserApplicationInfo();
when(mIChildProcessService.getAppInfo()).thenReturn(appInfo);
// Capture the parameters passed to the IChildProcessService.setupConnection() call.
doAnswer(
new Answer<Void>() {
@Override
public Void answer(InvocationOnMock invocation) {
mConnectionBundle = (Bundle) invocation.getArgument(0);
mConnectionParentProcess =
(IParentProcess) invocation.getArgument(1);
return null;
}
})
.when(mIChildProcessService)
.setupConnection(
or(isNull(), any(Bundle.class)),
or(isNull(), any()),
or(isNull(), any()),
or(isNull(), any()));
mChildProcessServiceBinder = new Binder();
mChildProcessServiceBinder.attachInterface(
mIChildProcessService, IChildProcessService.class.getName());
}
private ChildProcessConnection createDefaultTestConnection() {
return createTestConnection(
/* bindToCaller= */ false,
/* bindAsExternalService= */ false,
/* serviceBundle= */ null,
/* useFallback= */ false);
}
private ChildProcessConnection createTestConnection(
boolean bindToCaller,
boolean bindAsExternalService,
Bundle serviceBundle,
boolean useFallback) {
String packageName = "org.chromium.test";
String serviceName = "TestService";
String fallbackServiceName = "TestFallbackService";
return new ChildProcessConnection(
/* context= */ null,
new ComponentName(packageName, serviceName),
useFallback ? new ComponentName(packageName, fallbackServiceName) : null,
bindToCaller,
bindAsExternalService,
serviceBundle,
mServiceConnectionFactory,
/* instanceName= */ null);
}
private void sendPid(int pid) throws RemoteException {
mConnectionParentProcess.finishSetupConnection(
pid,
/* zygotePid= */ 0,
/* zygoteStartupTimeMillis= */ -1,
/* relroBundle= */ null);
}
@Test
public void testStrongBinding() {
ChildProcessConnection connection = createDefaultTestConnection();
connection.start(/* useStrongBinding= */ true, /* serviceCallback= */ null);
assertTrue(connection.isStrongBindingBound());
connection = createDefaultTestConnection();
connection.start(/* useStrongBinding= */ false, /* serviceCallback= */ null);
assertFalse(connection.isStrongBindingBound());
}
@Test
public void testServiceBundle() {
Bundle serviceBundle = new Bundle();
final String intKey = "org.chromium.myInt";
final int intValue = 34;
final int defaultValue = -1;
serviceBundle.putInt(intKey, intValue);
String stringKey = "org.chromium.myString";
String stringValue = "thirty four";
serviceBundle.putString(stringKey, stringValue);
ChildProcessConnection connection =
createTestConnection(
/* bindToCaller= */ false,
/* bindAsExternalService= */ false,
serviceBundle,
/* useFallback= */ false);
// Start the connection without the ChildServiceConnection connecting.
connection.start(/* useStrongBinding= */ false, /* serviceCallback= */ null);
assertNotNull(mFirstServiceConnection);
Intent bindIntent = mFirstServiceConnection.getBindIntent();
assertNotNull(bindIntent);
assertEquals(intValue, bindIntent.getIntExtra(intKey, defaultValue));
assertEquals(stringValue, bindIntent.getStringExtra(stringKey));
}
@Test
public void testServiceStartsSuccessfully() {
ChildProcessConnection connection = createDefaultTestConnection();
assertNotNull(mFirstServiceConnection);
connection.start(/* useStrongBinding= */ false, mServiceCallback);
Assert.assertTrue(connection.isVisibleBindingBound());
Assert.assertFalse(connection.didOnServiceConnectedForTesting());
verify(mServiceCallback, never()).onChildStarted();
verify(mServiceCallback, never()).onChildStartFailed(any());
verify(mServiceCallback, never()).onChildProcessDied(any());
// The service connects.
mFirstServiceConnection.notifyServiceConnected(mChildProcessServiceBinder);
Assert.assertTrue(connection.didOnServiceConnectedForTesting());
verify(mServiceCallback, times(1)).onChildStarted();
verify(mServiceCallback, never()).onChildStartFailed(any());
verify(mServiceCallback, never()).onChildProcessDied(any());
}
@Test
public void testServiceStartsAndFailsToBind() {
ChildProcessConnection connection = createDefaultTestConnection();
assertNotNull(mFirstServiceConnection);
// Note we use doReturn so the actual bindServiceConnection() method is not called (it would
// with when(mFirstServiceConnection.bindServiceConnection()).thenReturn(false).
doReturn(false).when(mFirstServiceConnection).bindServiceConnection();
connection.start(/* useStrongBinding= */ false, mServiceCallback);
Assert.assertFalse(connection.isVisibleBindingBound());
Assert.assertFalse(connection.didOnServiceConnectedForTesting());
verify(mServiceCallback, never()).onChildStarted();
verify(mServiceCallback, never()).onChildStartFailed(any());
verify(mServiceCallback, times(1)).onChildProcessDied(connection);
}
@Test
public void testServiceStops() {
ChildProcessConnection connection = createDefaultTestConnection();
assertNotNull(mFirstServiceConnection);
connection.start(/* useStrongBinding= */ false, mServiceCallback);
mFirstServiceConnection.notifyServiceConnected(mChildProcessServiceBinder);
connection.stop();
verify(mServiceCallback, times(1)).onChildStarted();
verify(mServiceCallback, never()).onChildStartFailed(any());
verify(mServiceCallback, times(1)).onChildProcessDied(connection);
}
@Test
public void testServiceDisconnects() {
ChildProcessConnection connection = createDefaultTestConnection();
assertNotNull(mFirstServiceConnection);
connection.start(/* useStrongBinding= */ false, mServiceCallback);
mFirstServiceConnection.notifyServiceConnected(mChildProcessServiceBinder);
mFirstServiceConnection.notifyServiceDisconnected();
verify(mServiceCallback, times(1)).onChildStarted();
verify(mServiceCallback, never()).onChildStartFailed(any());
verify(mServiceCallback, times(1)).onChildProcessDied(connection);
}
@Test
public void testNotBoundToCaller() throws RemoteException {
ChildProcessConnection connection =
createTestConnection(
/* bindToCaller= */ false,
/* bindAsExternalService= */ false,
/* serviceBundle= */ null,
/* useFallback= */ false);
assertNotNull(mFirstServiceConnection);
connection.start(/* useStrongBinding= */ false, mServiceCallback);
mFirstServiceConnection.notifyServiceConnected(mChildProcessServiceBinder);
// Service is started and bindToCallback is not called.
verify(mServiceCallback, times(1)).onChildStarted();
verify(mServiceCallback, never()).onChildStartFailed(any());
verify(mServiceCallback, never()).onChildProcessDied(connection);
verify(mIChildProcessService, never()).bindToCaller(any());
}
@Test
public void testBoundToCallerSuccess() throws RemoteException {
ChildProcessConnection connection =
createTestConnection(
/* bindToCaller= */ true,
/* bindAsExternalService= */ false,
/* serviceBundle= */ null,
/* useFallback= */ false);
assertNotNull(mFirstServiceConnection);
connection.start(/* useStrongBinding= */ false, mServiceCallback);
when(mIChildProcessService.bindToCaller(any())).thenReturn(true);
mFirstServiceConnection.notifyServiceConnected(mChildProcessServiceBinder);
// Service is started and bindToCallback is called.
verify(mServiceCallback, times(1)).onChildStarted();
verify(mServiceCallback, never()).onChildStartFailed(any());
verify(mServiceCallback, never()).onChildProcessDied(connection);
verify(mIChildProcessService, times(1)).bindToCaller(any());
}
@Test
public void testBoundToCallerFailure() throws RemoteException {
ChildProcessConnection connection =
createTestConnection(
/* bindToCaller= */ true,
/* bindAsExternalService= */ false,
/* serviceBundle= */ null,
/* useFallback= */ false);
assertNotNull(mFirstServiceConnection);
connection.start(/* useStrongBinding= */ false, mServiceCallback);
// Pretend bindToCaller returns false, i.e. the service is already bound to a different
// service.
when(mIChildProcessService.bindToCaller(any())).thenReturn(false);
mFirstServiceConnection.notifyServiceConnected(mChildProcessServiceBinder);
// Service fails to start.
verify(mServiceCallback, never()).onChildStarted();
verify(mServiceCallback, times(1)).onChildStartFailed(any());
verify(mServiceCallback, never()).onChildProcessDied(connection);
verify(mIChildProcessService, times(1)).bindToCaller(any());
}
@Test
public void testSetupConnectionBeforeServiceConnected() throws RemoteException {
ChildProcessConnection connection = createDefaultTestConnection();
assertNotNull(mFirstServiceConnection);
connection.start(/* useStrongBinding= */ false, /* serviceCallback= */ null);
connection.setupConnection(
/* connectionBundle= */ null,
/* callback= */ null,
/* binderBox= */ null,
mConnectionCallback,
/* zygoteInfoCallback= */ null);
verify(mConnectionCallback, never()).onConnected(any());
mFirstServiceConnection.notifyServiceConnected(mChildProcessServiceBinder);
ShadowLooper.runUiThreadTasks();
assertNotNull(mConnectionParentProcess);
sendPid(34);
verify(mConnectionCallback, times(1)).onConnected(connection);
}
@Test
public void testSendPidOnlyWorksOnce() throws RemoteException {
ChildProcessConnection connection = createDefaultTestConnection();
assertNotNull(mFirstServiceConnection);
connection.start(/* useStrongBinding= */ false, /* serviceCallback= */ null);
connection.setupConnection(
/* connectionBundle= */ null,
/* callback= */ null,
/* binderBox= */ null,
mConnectionCallback,
/* zygoteInfoCallback= */ null);
verify(mConnectionCallback, never()).onConnected(any());
mFirstServiceConnection.notifyServiceConnected(mChildProcessServiceBinder);
ShadowLooper.runUiThreadTasks();
assertNotNull(mConnectionParentProcess);
sendPid(34);
assertEquals(34, connection.getPid());
sendPid(543);
assertEquals(34, connection.getPid());
}
@Test
public void testZygotePidSaved() throws RemoteException {
ChildProcessConnection connection = createDefaultTestConnection();
assertNotNull(mFirstServiceConnection);
connection.start(/* useStrongBinding= */ false, /* serviceCallback= */ null);
connection.setupConnection(
/* connectionBundle= */ null,
/* callback= */ null,
/* binderBox= */ null,
mConnectionCallback,
/* zygoteInfoCallback= */ null);
verify(mConnectionCallback, never()).onConnected(any());
mFirstServiceConnection.notifyServiceConnected(mChildProcessServiceBinder);
ShadowLooper.runUiThreadTasks();
assertNotNull(mConnectionParentProcess);
mConnectionParentProcess.finishSetupConnection(
/* pid= */ 123,
456
/* zygotePid= */ ,
/* zygoteStartupTimeMillis= */ 789,
/* relroBundle= */ null);
assertTrue(connection.hasUsableZygoteInfo());
assertEquals(456, connection.getZygotePid());
}
@Test
public void testTwoZygoteInfosOnlyOneValid() throws RemoteException {
// Set up |connection1|.
ChildProcessConnection connection1 = createDefaultTestConnection();
assertNotNull(mFirstServiceConnection);
connection1.start(/* useStrongBinding= */ true, /* serviceCallback= */ null);
connection1.setupConnection(
/* connectionBundle= */ null,
/* callback= */ null,
/* binderBox= */ null,
mConnectionCallback,
/* zygoteInfoCallback= */ null);
mFirstServiceConnection.notifyServiceConnected(mChildProcessServiceBinder);
ShadowLooper.runUiThreadTasks();
assertNotNull(mConnectionParentProcess);
assertNotNull(mFirstServiceConnection);
mConnectionParentProcess.finishSetupConnection(
/* pid= */ 125,
/* zygotePid= */ 0,
/* zygoteStartupTimeMillis= */ -1,
/* relroBundle= */ null);
// Allow the following setupConnection() to create a new service connection for
// |connection2|.
mFirstServiceConnection = null;
// Set up |connection2|.
ChildProcessConnection connection2 = createDefaultTestConnection();
assertNotNull(mFirstServiceConnection);
connection2.start(/* useStrongBinding= */ false, /* serviceCallback= */ null);
connection2.setupConnection(
/* connectionBundle= */ null,
/* callback= */ null,
/* binderBox= */ null,
mConnectionCallback,
/* zygoteInfoCallback= */ null);
mFirstServiceConnection.notifyServiceConnected(mChildProcessServiceBinder);
ShadowLooper.runUiThreadTasks();
assertNotNull(mConnectionParentProcess);
assertNotNull(mFirstServiceConnection);
mConnectionParentProcess.finishSetupConnection(
126,
/* zygotePid= */ 300,
/* zygoteStartupTimeMillis= */ -1,
/* relroBundle= */ null);
assertTrue(connection2.hasUsableZygoteInfo());
assertEquals(300, connection2.getZygotePid());
assertFalse(connection1.hasUsableZygoteInfo());
}
@Test
public void testInvokesZygoteCallback() throws RemoteException {
ChildProcessConnection connection = createDefaultTestConnection();
assertNotNull(mFirstServiceConnection);
connection.start(/* useStrongBinding= */ false, /* serviceCallback= */ null);
connection.setupConnection(
/* connectionBundle= */ null,
/* callback= */ null,
/* binderBox= */ null,
mConnectionCallback,
mZygoteInfoCallback);
verify(mConnectionCallback, never()).onConnected(any());
mFirstServiceConnection.notifyServiceConnected(mChildProcessServiceBinder);
ShadowLooper.runUiThreadTasks();
assertNotNull(mConnectionParentProcess);
Bundle relroBundle = new Bundle();
mConnectionParentProcess.finishSetupConnection(
/* pid= */ 123,
456
/* zygotePid= */ ,
/* zygoteStartupTimeMillis= */ 789,
relroBundle);
assertTrue(connection.hasUsableZygoteInfo());
assertEquals(456, connection.getZygotePid());
verify(mZygoteInfoCallback, times(1)).onReceivedZygoteInfo(connection, relroBundle);
connection.consumeZygoteBundle(relroBundle);
verify(mIChildProcessService, times(1)).consumeRelroBundle(relroBundle);
}
@Test
public void testConsumeZygoteBundle() throws RemoteException {
ChildProcessConnection connection = createDefaultTestConnection();
assertNotNull(mFirstServiceConnection);
connection.start(/* useStrongBinding= */ false, /* serviceCallback= */ null);
connection.setupConnection(
/* connectionBundle= */ null,
/* callback= */ null,
/* binderBox= */ null,
mConnectionCallback,
mZygoteInfoCallback);
verify(mConnectionCallback, never()).onConnected(any());
mFirstServiceConnection.notifyServiceConnected(mChildProcessServiceBinder);
ShadowLooper.runUiThreadTasks();
assertNotNull(mConnectionParentProcess);
Bundle relroBundle = new Bundle();
mConnectionParentProcess.finishSetupConnection(
/* pid= */ 123,
456
/* zygotePid= */ ,
/* zygoteStartupTimeMillis= */ 789,
relroBundle);
verify(mIChildProcessService, never()).consumeRelroBundle(any());
connection.consumeZygoteBundle(relroBundle);
verify(mIChildProcessService, times(1)).consumeRelroBundle(relroBundle);
}
@Test
public void testSetupConnectionAfterServiceConnected() throws RemoteException {
ChildProcessConnection connection = createDefaultTestConnection();
assertNotNull(mFirstServiceConnection);
connection.start(/* useStrongBinding= */ false, /* serviceCallback= */ null);
mFirstServiceConnection.notifyServiceConnected(mChildProcessServiceBinder);
connection.setupConnection(
/* connectionBundle= */ null,
/* callback= */ null,
/* binderBox= */ null,
mConnectionCallback,
/* zygoteInfoCallback= */ null);
verify(mConnectionCallback, never()).onConnected(any());
ShadowLooper.runUiThreadTasks();
assertNotNull(mConnectionParentProcess);
sendPid(34);
verify(mConnectionCallback, times(1)).onConnected(connection);
}
@Test
public void testKill() throws RemoteException {
ChildProcessConnection connection = createDefaultTestConnection();
assertNotNull(mFirstServiceConnection);
connection.start(/* useStrongBinding= */ false, /* serviceCallback= */ null);
mFirstServiceConnection.notifyServiceConnected(mChildProcessServiceBinder);
connection.setupConnection(
/* connectionBundle= */ null,
/* callback= */ null,
/* binderBox= */ null,
mConnectionCallback,
/* zygoteInfoCallback= */ null);
verify(mConnectionCallback, never()).onConnected(any());
ShadowLooper.runUiThreadTasks();
assertNotNull(mConnectionParentProcess);
sendPid(34);
verify(mConnectionCallback, times(1)).onConnected(connection);
// Add strong binding so that connection is oom protected.
connection.removeVisibleBinding();
assertEquals(ChildBindingState.WAIVED, connection.bindingStateCurrentOrWhenDied());
if (ChildProcessConnection.supportNotPerceptibleBinding()) {
connection.addNotPerceptibleBinding();
assertEquals(
ChildBindingState.NOT_PERCEPTIBLE, connection.bindingStateCurrentOrWhenDied());
}
connection.addVisibleBinding();
assertEquals(ChildBindingState.VISIBLE, connection.bindingStateCurrentOrWhenDied());
connection.addStrongBinding();
assertEquals(ChildBindingState.STRONG, connection.bindingStateCurrentOrWhenDied());
// Kill and verify state.
connection.kill();
verify(mIChildProcessService).forceKill();
assertEquals(ChildBindingState.STRONG, connection.bindingStateCurrentOrWhenDied());
Assert.assertTrue(connection.isKilledByUs());
}
@Test
public void testUpdateGroupImportanceSmoke() throws RemoteException {
ChildProcessConnection connection = createDefaultTestConnection();
connection.start(/* useStrongBinding= */ false, /* serviceCallback= */ null);
when(mIChildProcessService.bindToCaller(any())).thenReturn(true);
mFirstServiceConnection.notifyServiceConnected(mChildProcessServiceBinder);
connection.updateGroupImportance(1, 2);
assertEquals(1, connection.getGroup());
assertEquals(2, connection.getImportanceInGroup());
assertEquals(3, mMockConnections.size());
// Group should be set on the wavied (last) binding.
ChildServiceConnectionMock mock = mMockConnections.get(mMockConnections.size() - 1);
assertEquals(1, mock.getGroup());
assertEquals(2, mock.getImportanceInGroup());
}
@Test
public void testExceptionDuringInit() throws RemoteException {
ChildProcessConnection connection = createDefaultTestConnection();
assertNotNull(mFirstServiceConnection);
connection.start(/* useStrongBinding= */ false, /* serviceCallback= */ null);
mFirstServiceConnection.notifyServiceConnected(mChildProcessServiceBinder);
connection.setupConnection(
/* connectionBundle= */ null,
/* callback= */ null,
/* binderBox= */ null,
mConnectionCallback,
/* zygoteInfoCallback= */ null);
verify(mConnectionCallback, never()).onConnected(any());
ShadowLooper.runUiThreadTasks();
assertNotNull(mConnectionParentProcess);
sendPid(34);
verify(mConnectionCallback, times(1)).onConnected(connection);
String exceptionString = "test exception string";
mConnectionParentProcess.reportExceptionInInit(exceptionString);
ShadowLooper.runUiThreadTasks();
Assert.assertEquals(exceptionString, connection.getExceptionDuringInit());
Assert.assertFalse(mFirstServiceConnection.isBound());
}
@Test
public void testFallback() throws RemoteException {
Bundle serviceBundle = new Bundle();
final String intKey = "org.chromium.myInt";
final int intValue = 34;
serviceBundle.putInt(intKey, intValue);
ChildProcessConnection connection =
createTestConnection(
/* bindToCaller= */ false,
/* bindAsExternalService= */ false,
serviceBundle,
/* useFallback= */ true);
assertNotNull(mFirstServiceConnection);
connection.start(/* useStrongBinding= */ false, mServiceCallback);
Assert.assertEquals(3, mMockConnections.size());
boolean anyServiceConnectionBound = false;
for (ChildServiceConnectionMock serviceConnection : mMockConnections) {
anyServiceConnectionBound = anyServiceConnectionBound || serviceConnection.isBound();
}
Assert.assertTrue(anyServiceConnectionBound);
verify(mServiceCallback, never()).onChildStarted();
verify(mServiceCallback, never()).onChildStartFailed(any());
verify(mServiceCallback, never()).onChildProcessDied(any());
{
Intent bindIntent = mFirstServiceConnection.getBindIntent();
assertNotNull(bindIntent);
assertEquals(intValue, bindIntent.getIntExtra(intKey, -1));
Assert.assertEquals("TestService", bindIntent.getComponent().getClassName());
}
connection.setupConnection(
/* connectionBundle= */ null,
/* callback= */ null,
/* binderBox= */ null,
mConnectionCallback,
/* zygoteInfoCallback= */ null);
// Do not call onServiceConnected. Simulate timeout with ShadowLooper.
ShadowLooper.runUiThreadTasksIncludingDelayedTasks();
verify(mServiceCallback, never()).onChildStarted();
verify(mServiceCallback, never()).onChildStartFailed(any());
verify(mServiceCallback, never()).onChildProcessDied(any());
Assert.assertEquals(6, mMockConnections.size());
// First 4 should be unbound now.
for (int i = 0; i < 3; ++i) {
verify(mMockConnections.get(i), times(1)).retire();
Assert.assertFalse(mMockConnections.get(i).isBound());
}
// New connection for fallback service should be bound.
ChildServiceConnectionMock boundServiceConnection = null;
for (int i = 3; i < 6; ++i) {
if (mMockConnections.get(i).isBound()) {
boundServiceConnection = mMockConnections.get(i);
}
Intent bindIntent = mMockConnections.get(i).getBindIntent();
assertNotNull(bindIntent);
assertEquals(intValue, bindIntent.getIntExtra(intKey, -1));
Assert.assertEquals("TestFallbackService", bindIntent.getComponent().getClassName());
}
Assert.assertNotNull(boundServiceConnection);
// Complete connection.
boundServiceConnection.notifyServiceConnected(mChildProcessServiceBinder);
verify(mServiceCallback, times(1)).onChildStarted();
verify(mServiceCallback, never()).onChildStartFailed(any());
verify(mServiceCallback, never()).onChildProcessDied(any());
}
@Test
public void testFallbackOnBindServiceFailure() throws RemoteException {
Bundle serviceBundle = new Bundle();
final String intKey = "org.chromium.myInt";
final int intValue = 34;
serviceBundle.putInt(intKey, intValue);
ChildProcessConnection connection =
createTestConnection(
/* bindToCaller= */ false,
/* bindAsExternalService= */ false,
serviceBundle,
/* useFallback= */ true);
assertNotNull(mFirstServiceConnection);
mFirstServiceConnection.setBindResult(false);
connection.start(/* useStrongBinding= */ false, mServiceCallback);
verify(mServiceCallback, never()).onChildStarted();
verify(mServiceCallback, never()).onChildStartFailed(any());
verify(mServiceCallback, never()).onChildProcessDied(any());
Assert.assertEquals(6, mMockConnections.size());
// First 4 should be unbound now.
for (int i = 0; i < 3; ++i) {
verify(mMockConnections.get(i), times(1)).retire();
Assert.assertFalse(mMockConnections.get(i).isBound());
}
// New connection for fallback service should be bound.
ChildServiceConnectionMock boundServiceConnection = null;
int boundConnectionCount = 0;
for (int i = 3; i < 6; ++i) {
if (mMockConnections.get(i).isBound()) {
boundServiceConnection = mMockConnections.get(i);
boundConnectionCount++;
}
Intent bindIntent = mMockConnections.get(i).getBindIntent();
assertNotNull(bindIntent);
assertEquals(intValue, bindIntent.getIntExtra(intKey, -1));
Assert.assertEquals("TestFallbackService", bindIntent.getComponent().getClassName());
}
Assert.assertTrue(boundConnectionCount >= 2);
Assert.assertTrue(connection.isVisibleBindingBound());
// Complete connection.
boundServiceConnection.notifyServiceConnected(mChildProcessServiceBinder);
verify(mServiceCallback, times(1)).onChildStarted();
verify(mServiceCallback, never()).onChildStartFailed(any());
verify(mServiceCallback, never()).onChildProcessDied(any());
}
}