Bug 1607537: Fix timeouts in ParentCrashTest.crashParent and re-enable the test; r=geckoview-reviewers,esawin

The fundamental issue here is that `ParentCrashTest` starts a second
`GeckoRuntime` via `RemoteGeckoService` within the same logical Android
Application.

This patch adds `RuntimeCreator.shutdownRuntime()` so that the test can
shut down the existing `GeckoRuntime` before starting up its own.

Differential Revision: https://phabricator.services.mozilla.com/D61249

--HG--
extra : moz-landing-system : lando
This commit is contained in:
Aaron Klotz 2020-02-05 06:52:11 +00:00
Родитель 8801b3cc45
Коммит cf9539a846
2 изменённых файлов: 93 добавлений и 2 удалений

Просмотреть файл

@ -23,6 +23,7 @@ import org.mozilla.geckoview.BuildConfig
import org.mozilla.geckoview.GeckoRuntime
import org.mozilla.geckoview.test.TestCrashHandler
import org.mozilla.geckoview.test.util.Environment
import org.mozilla.geckoview.test.util.RuntimeCreator
import java.io.File
import java.util.concurrent.TimeUnit
@ -36,13 +37,17 @@ class ParentCrashTest {
@Before
fun setup() {
// Since this test starts up its own GeckoRuntime via
// RemoteGeckoService, we need to shutdown any runtime already running
// in the RuntimeCreator.
RuntimeCreator.shutdownRuntime()
val context = InstrumentationRegistry.getInstrumentation().targetContext
val binder = rule.bindService(Intent(context, RemoteGeckoService::class.java))
messenger = Messenger(binder)
assertThat("messenger should not be null", binder, notNullValue())
}
@Ignore // Bug 1607537 disabled due to frequent timeouts
@Test
@UiThreadTest
fun crashParent() {

Просмотреть файл

@ -6,6 +6,7 @@ import org.mozilla.geckoview.RuntimeTelemetry;
import org.mozilla.geckoview.WebExtension;
import org.mozilla.geckoview.test.TestCrashHandler;
import android.os.Looper;
import android.os.Process;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
@ -14,6 +15,8 @@ import androidx.test.platform.app.InstrumentationRegistry;
import android.util.Log;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.FutureTask;
import java.util.concurrent.TimeUnit;
public class RuntimeCreator {
public static final int TEST_SUPPORT_INITIAL = 0;
@ -123,6 +126,20 @@ public class RuntimeCreator {
sPortDelegate = portDelegate;
}
private static GeckoRuntime.Delegate sShutdownDelegate;
private static GeckoRuntime.Delegate sWrapperShutdownDelegate = new GeckoRuntime.Delegate() {
@Override
public void onShutdown() {
if (sShutdownDelegate != null) {
sShutdownDelegate.onShutdown();
return;
}
Process.killProcess(Process.myPid());
}
};
@UiThread
public static GeckoRuntime getRuntime() {
if (sRuntime != null) {
@ -144,8 +161,77 @@ public class RuntimeCreator {
registerTestSupport();
sRuntime.setDelegate(() -> Process.killProcess(Process.myPid()));
sRuntime.setDelegate(sWrapperShutdownDelegate);
return sRuntime;
}
private static final class ShutdownCompleteIndicator implements GeckoRuntime.Delegate {
private boolean mDone = false;
@Override
public void onShutdown() {
mDone = true;
}
public boolean isDone() {
return mDone;
}
}
@UiThread
private static void shutdownRuntimeInternal(final long timeoutMillis) {
if (sRuntime == null) {
return;
}
final ShutdownCompleteIndicator indicator = new ShutdownCompleteIndicator();
sShutdownDelegate = indicator;
sRuntime.shutdown();
UiThreadUtils.waitForCondition(() -> indicator.isDone(), timeoutMillis);
if (!indicator.isDone()) {
throw new RuntimeException("Timed out waiting for GeckoRuntime shutdown to complete");
}
sRuntime = null;
sShutdownDelegate = null;
}
/**
* ParentCrashTest needs to start a GeckoRuntime inside a separate service in a separate
* process from this one. Unfortunately that does not play well with the GeckoRuntime in this
* process, since as far as Android is concerned, they are both running inside the same
* Application.
*
* Any test that starts its own GeckoRuntime should call this method during its setup to shut
* down any extant GeckoRuntime, thus ensuring only one GeckoRuntime is active at once.
*/
public static void shutdownRuntime() {
final Environment env = new Environment();
// It takes a while to shutdown an existing runtime in debug builds, so
// we double the timeout for this method.
final long timeoutMillis = 2 * env.getDefaultTimeoutMillis();
if (Looper.myLooper() == Looper.getMainLooper()) {
shutdownRuntimeInternal(timeoutMillis);
return;
}
final Runnable runnable = new Runnable() {
@Override
public void run() {
RuntimeCreator.shutdownRuntimeInternal(timeoutMillis);
}
};
FutureTask<Void> task = new FutureTask<>(runnable, null);
InstrumentationRegistry.getInstrumentation().runOnMainSync(task);
try {
task.get(timeoutMillis, TimeUnit.MILLISECONDS);
} catch (Throwable e) {
throw new RuntimeException(e.toString());
}
}
}