| // 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() { |
| 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) { |
| 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; |
| } |
| } |