blob: 83802341e0671b43f9e66134564052dafad668a7 [file] [log] [blame]
// Copyright 2015 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.android_webview.test;
import android.app.ActivityManager;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.IBinder;
import android.os.Parcel;
import android.os.Process;
import android.os.RemoteException;
import android.support.test.InstrumentationRegistry;
import org.junit.After;
import org.junit.Assert;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.chromium.android_webview.AwBrowserProcess;
import org.chromium.base.test.util.DisabledTest;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
/**
* Tests to ensure that it is impossible to launch two browser processes within
* the same application. Chromium is not designed for that, and attempting to do that
* can cause data files corruption.
*/
@RunWith(AwJUnit4ClassRunner.class)
public class AwSecondBrowserProcessTest {
@Rule
public AwActivityTestRule mActivityTestRule = new AwActivityTestRule() {
@Override
public boolean needsBrowserProcessStarted() {
return false;
}
};
private CountDownLatch mSecondBrowserProcessLatch;
private int mSecondBrowserServicePid;
@After
public void tearDown() throws Exception {
stopSecondBrowserProcess(false);
}
/*
* @LargeTest
* @Feature({"AndroidWebView"})
* We can't test that creating second browser
* process succeeds either, because in debug it will crash due to an assert
* in the SQL DB code.
*/
@Test
@DisabledTest(message = "crbug.com/582146")
public void testCreatingSecondBrowserProcessFails() throws Throwable {
startSecondBrowserProcess();
Assert.assertFalse(tryStartingBrowserProcess());
}
/*
* @LargeTest
* @Feature({"AndroidWebView"})
*/
@Test
@DisabledTest(message = "crbug.com/582146")
public void testLockCleanupOnProcessShutdown() throws Throwable {
startSecondBrowserProcess();
Assert.assertFalse(tryStartingBrowserProcess());
stopSecondBrowserProcess(true);
Assert.assertTrue(tryStartingBrowserProcess());
}
private final ServiceConnection mConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName className, IBinder service) {
Parcel result = Parcel.obtain();
try {
Assert.assertTrue(service.transact(
SecondBrowserProcess.CODE_START, Parcel.obtain(), result, 0));
} catch (RemoteException e) {
Assert.fail("RemoteException: " + e);
}
result.readException();
mSecondBrowserServicePid = result.readInt();
Assert.assertTrue(mSecondBrowserServicePid > 0);
mSecondBrowserProcessLatch.countDown();
}
@Override
public void onServiceDisconnected(ComponentName className) {
}
};
private void startSecondBrowserProcess() throws Exception {
Context context = mActivityTestRule.getActivity();
Intent intent = new Intent(context, SecondBrowserProcess.class);
mSecondBrowserProcessLatch = new CountDownLatch(1);
Assert.assertNotNull(context.startService(intent));
Assert.assertTrue(context.bindService(intent, mConnection, 0));
Assert.assertTrue(mSecondBrowserProcessLatch.await(
AwActivityTestRule.WAIT_TIMEOUT_MS, TimeUnit.MILLISECONDS));
mSecondBrowserProcessLatch = null;
}
private void stopSecondBrowserProcess(boolean sync) throws Exception {
if (mSecondBrowserServicePid <= 0) return;
Assert.assertTrue(isSecondBrowserServiceRunning());
// Note that using killProcess ensures that the service record gets removed
// from ActivityManager after the process has actually died. While using
// Context.stopService would result in the opposite outcome.
Process.killProcess(mSecondBrowserServicePid);
if (sync) {
AwActivityTestRule.pollInstrumentationThread(() -> !isSecondBrowserServiceRunning());
}
mSecondBrowserServicePid = 0;
}
private boolean tryStartingBrowserProcess() {
final Boolean success[] = new Boolean[1];
// The activity must be launched in order for proper webview statics to be setup.
mActivityTestRule.getActivity();
// runOnMainSync does not catch RuntimeExceptions, they just terminate the test.
InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
try {
AwBrowserProcess.start();
success[0] = true;
} catch (RuntimeException e) {
success[0] = false;
}
});
Assert.assertNotNull(success[0]);
return success[0];
}
// Note that both onServiceDisconnected and Binder.DeathRecipient fire prematurely for our
// purpose. We need to ensure that the service process has actually terminated, releasing all
// the locks. The only reliable way to do that is to scan the process list.
private boolean isSecondBrowserServiceRunning() {
ActivityManager activityManager =
(ActivityManager) mActivityTestRule.getActivity().getSystemService(
Context.ACTIVITY_SERVICE);
for (ActivityManager.RunningServiceInfo si : activityManager.getRunningServices(65536)) {
if (si.pid == mSecondBrowserServicePid) return true;
}
return false;
}
}