| Index: android_webview/javatests/src/org/chromium/android_webview/test/AwSecondBrowserProcessTest.java
|
| diff --git a/android_webview/javatests/src/org/chromium/android_webview/test/AwSecondBrowserProcessTest.java b/android_webview/javatests/src/org/chromium/android_webview/test/AwSecondBrowserProcessTest.java
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..25f951fdfb95975882953cb5b0f4177b431ab629
|
| --- /dev/null
|
| +++ b/android_webview/javatests/src/org/chromium/android_webview/test/AwSecondBrowserProcessTest.java
|
| @@ -0,0 +1,141 @@
|
| +// 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.test.suitebuilder.annotation.LargeTest;
|
| +
|
| +import org.chromium.android_webview.AwBrowserProcess;
|
| +import org.chromium.base.test.util.Feature;
|
| +
|
| +import java.util.concurrent.Callable;
|
| +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.
|
| + */
|
| +public class AwSecondBrowserProcessTest extends AwTestBase {
|
| + private CountDownLatch mSecondBrowserProcessLatch;
|
| + private int mSecondBrowserServicePid;
|
| +
|
| + @Override
|
| + protected boolean needsBrowserProcessStarted() {
|
| + return false;
|
| + }
|
| +
|
| + @Override
|
| + protected void tearDown() throws Exception {
|
| + stopSecondBrowserProcess(false);
|
| + super.tearDown();
|
| + }
|
| +
|
| + @LargeTest
|
| + @Feature({"AndroidWebView"})
|
| + public void testCreatingSecondBrowserProcessFails() throws Throwable {
|
| + startSecondBrowserProcess();
|
| + assertFalse(tryStartingBrowserProcess());
|
| + }
|
| +
|
| + @LargeTest
|
| + @Feature({"AndroidWebView"})
|
| + public void testLockCleanupOnProcessShutdown() throws Throwable {
|
| + startSecondBrowserProcess();
|
| + assertFalse(tryStartingBrowserProcess());
|
| + stopSecondBrowserProcess(true);
|
| + assertTrue(tryStartingBrowserProcess());
|
| + }
|
| +
|
| + private final ServiceConnection mConnection = new ServiceConnection() {
|
| + @Override
|
| + public void onServiceConnected(ComponentName className, IBinder service) {
|
| + Parcel result = Parcel.obtain();
|
| + try {
|
| + assertTrue(service.transact(
|
| + SecondBrowserProcess.CODE_START, Parcel.obtain(), result, 0));
|
| + } catch (RemoteException e) {
|
| + fail("RemoteException: " + e);
|
| + }
|
| + result.readException();
|
| + mSecondBrowserServicePid = result.readInt();
|
| + assertTrue(mSecondBrowserServicePid > 0);
|
| + mSecondBrowserProcessLatch.countDown();
|
| + }
|
| +
|
| + @Override
|
| + public void onServiceDisconnected(ComponentName className) {
|
| + }
|
| + };
|
| +
|
| + private void startSecondBrowserProcess() throws Exception {
|
| + Context context = getActivity();
|
| + Intent intent = new Intent(context, SecondBrowserProcess.class);
|
| + mSecondBrowserProcessLatch = new CountDownLatch(1);
|
| + assertNotNull(context.startService(intent));
|
| + assertTrue(context.bindService(intent, mConnection, 0));
|
| + assertTrue(mSecondBrowserProcessLatch.await(
|
| + AwTestBase.WAIT_TIMEOUT_MS, TimeUnit.MILLISECONDS));
|
| + mSecondBrowserProcessLatch = null;
|
| + }
|
| +
|
| + private void stopSecondBrowserProcess(boolean sync) throws Exception {
|
| + if (mSecondBrowserServicePid <= 0) return;
|
| + 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) {
|
| + poll(new Callable<Boolean>() {
|
| + @Override
|
| + public Boolean call() throws Exception {
|
| + return !isSecondBrowserServiceRunning();
|
| + }
|
| + });
|
| + }
|
| + mSecondBrowserServicePid = 0;
|
| + }
|
| +
|
| + private boolean tryStartingBrowserProcess() {
|
| + final Context context = getActivity();
|
| + final Boolean success[] = new Boolean[1];
|
| + // runOnMainSync does not catch RuntimeExceptions, they just terminate the test.
|
| + getInstrumentation().runOnMainSync(new Runnable() {
|
| + @Override
|
| + public void run() {
|
| + try {
|
| + AwBrowserProcess.start(context);
|
| + success[0] = true;
|
| + } catch (RuntimeException e) {
|
| + success[0] = false;
|
| + }
|
| + }
|
| + });
|
| + 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) getActivity().getSystemService(Context.ACTIVITY_SERVICE);
|
| + for (ActivityManager.RunningServiceInfo si : activityManager.getRunningServices(65536)) {
|
| + if (si.pid == mSecondBrowserServicePid) return true;
|
| + }
|
| + return false;
|
| + }
|
| +}
|
|
|