Bug 1465480 - Add @IgnoreCrash to GeckoSessionTestRule r=jchen
This allows a test to declare that crashes should not be considered
fatal. It does not, however, assert that a crash will occur. That
responsibility is left with the test itself.
MozReview-Commit-ID: 4DB6xs5jlEZ
--- a/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/BaseSessionTest.kt
+++ b/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/BaseSessionTest.kt
@@ -21,16 +21,17 @@ import kotlin.reflect.KClass
/**
* Common base class for tests using GeckoSessionTestRule,
* providing the test rule and other utilities.
*/
open class BaseSessionTest(noErrorCollector: Boolean = false) {
companion object {
const val CLICK_TO_RELOAD_HTML_PATH = "/assets/www/clickToReload.html"
+ const val CONTENT_CRASH_URL = "about:crashcontent"
const val DOWNLOAD_HTML_PATH = "/assets/www/download.html"
const val HELLO_HTML_PATH = "/assets/www/hello.html"
const val HELLO2_HTML_PATH = "/assets/www/hello2.html"
const val INVALID_URI = "http://www.test.invalid/"
const val NEW_SESSION_HTML_PATH = "/assets/www/newSession.html"
const val NEW_SESSION_CHILD_HTML_PATH = "/assets/www/newSession_child.html"
const val SELECTION_ACTION_PATH = "/assets/www/selectionAction.html"
const val TITLE_CHANGE_HTML_PATH = "/assets/www/titleChange.html"
--- a/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/GeckoSessionTestRuleTest.kt
+++ b/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/GeckoSessionTestRuleTest.kt
@@ -3,18 +3,20 @@
http://creativecommons.org/publicdomain/zero/1.0/ */
package org.mozilla.geckoview.test
import org.mozilla.geckoview.GeckoSession
import org.mozilla.geckoview.GeckoSessionSettings
import org.mozilla.geckoview.test.rule.GeckoSessionTestRule.AssertCalled
import org.mozilla.geckoview.test.rule.GeckoSessionTestRule.ClosedSessionAtStart
+import org.mozilla.geckoview.test.rule.GeckoSessionTestRule.IgnoreCrash
import org.mozilla.geckoview.test.rule.GeckoSessionTestRule.NullDelegate
import org.mozilla.geckoview.test.rule.GeckoSessionTestRule.RejectedPromiseException
+import org.mozilla.geckoview.test.rule.GeckoSessionTestRule.ReuseSession
import org.mozilla.geckoview.test.rule.GeckoSessionTestRule.Setting
import org.mozilla.geckoview.test.rule.GeckoSessionTestRule.TimeoutException
import org.mozilla.geckoview.test.rule.GeckoSessionTestRule.TimeoutMillis
import org.mozilla.geckoview.test.rule.GeckoSessionTestRule.WithDevToolsAPI
import org.mozilla.geckoview.test.rule.GeckoSessionTestRule.WithDisplay
import org.mozilla.geckoview.test.util.Callbacks
import android.support.test.filters.MediumTest
@@ -1599,36 +1601,57 @@ class GeckoSessionTestRuleTest : BaseSes
}
@Test fun addExternalDelegateDuringNextWait_hasPrecedence() {
var delegate: TestDelegate? = null
val register = { newDelegate: TestDelegate -> delegate = newDelegate }
val unregister = { _: TestDelegate -> delegate = null }
sessionRule.addExternalDelegateDuringNextWait(TestDelegate::class, register, unregister,
- object : TestDelegate {
- @AssertCalled(count = 1)
- override fun onDelegate(foo: String, bar: String): Int {
- return 24
- }
- })
+ object : TestDelegate {
+ @AssertCalled(count = 1)
+ override fun onDelegate(foo: String, bar: String): Int {
+ return 24
+ }
+ })
sessionRule.addExternalDelegateUntilTestEnd(TestDelegate::class, register, unregister,
- object : TestDelegate {
- @AssertCalled(count = 1)
- override fun onDelegate(foo: String, bar: String): Int {
- return 42
- }
- })
+ object : TestDelegate {
+ @AssertCalled(count = 1)
+ override fun onDelegate(foo: String, bar: String): Int {
+ return 42
+ }
+ })
assertThat("Wait delegate should be registered", delegate, notNullValue())
assertThat("Wait delegate return value should be correct",
- delegate?.onDelegate("", ""), equalTo(24))
+ delegate?.onDelegate("", ""), equalTo(24))
mainSession.reload()
mainSession.waitForPageStop()
assertThat("Test delegate should still be registered", delegate, notNullValue())
assertThat("Test delegate return value should be correct",
- delegate?.onDelegate("", ""), equalTo(42))
+ delegate?.onDelegate("", ""), equalTo(42))
sessionRule.performTestEndCheck()
}
+
+ @IgnoreCrash
+ @ReuseSession(false)
+ @Test fun contentCrashIgnored() {
+ assumeThat(sessionRule.env.isMultiprocess, equalTo(true))
+
+ sessionRule.session.loadUri(CONTENT_CRASH_URL)
+ sessionRule.waitUntilCalled(object : Callbacks.ContentDelegate {
+ @AssertCalled(count = 1)
+ override fun onCrash(session: GeckoSession) = Unit
+ })
+ }
+
+ @Test(expected = RuntimeException::class)
+ @ReuseSession(false)
+ fun contentCrashFails() {
+ assumeThat(sessionRule.env.isMultiprocess, equalTo(true))
+
+ sessionRule.session.loadUri(CONTENT_CRASH_URL)
+ sessionRule.waitForPageStop()
+ }
}
--- a/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/rule/GeckoSessionTestRule.java
+++ b/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/rule/GeckoSessionTestRule.java
@@ -92,25 +92,28 @@ public class GeckoSessionTestRule extend
private static final long DEFAULT_X86_EMULATOR_TIMEOUT_MILLIS = 5000;
private static final long DEFAULT_IDE_DEBUG_TIMEOUT_MILLIS = 86400000;
public static final String APK_URI_PREFIX = "resource://android/";
private static final Method sGetNextMessage;
private static final Method sOnPageStop;
private static final Method sOnNewSession;
+ private static final Method sOnCrash;
static {
try {
sGetNextMessage = MessageQueue.class.getDeclaredMethod("next");
sGetNextMessage.setAccessible(true);
sOnPageStop = GeckoSession.ProgressDelegate.class.getMethod(
"onPageStop", GeckoSession.class, boolean.class);
sOnNewSession = GeckoSession.NavigationDelegate.class.getMethod(
"onNewSession", GeckoSession.class, String.class, GeckoResponse.class);
+ sOnCrash = GeckoSession.ContentDelegate.class.getMethod(
+ "onCrash", GeckoSession.class);
} catch (final NoSuchMethodException e) {
throw new RuntimeException(e);
}
}
/**
* Specify the timeout for any of the wait methods, in milliseconds, relative to
* {@link #DEFAULT_TIMEOUT_MILLIS}. When the default timeout scales to account
@@ -310,16 +313,30 @@ public class GeckoSessionTestRule extend
/**
* Interface that represents a function that registers or unregisters a delegate.
*/
public interface DelegateRegistrar<T> {
void invoke(T delegate) throws Throwable;
}
+ /*
+ * If the value here is true, content crashes will be ignored. If false, the test will
+ * be failed immediately if a content crash occurs. This is also the case when
+ * {@link IgnoreCrash} is not present.
+ */
+ @Target(ElementType.METHOD)
+ @Retention(RetentionPolicy.RUNTIME)
+ public @interface IgnoreCrash {
+ /**
+ * @return True if content crashes should be ignored, false otherwise. Default is true.
+ */
+ boolean value() default true;
+ }
+
public static class TimeoutException extends RuntimeException {
public TimeoutException(final String detailMessage) {
super(detailMessage);
}
}
public static class RejectedPromiseException extends RuntimeException {
private final Object mReason;
@@ -899,16 +916,17 @@ public class GeckoSessionTestRule extend
protected SurfaceTexture mDisplayTexture;
protected Surface mDisplaySurface;
protected GeckoDisplay mDisplay;
protected boolean mClosedSession;
protected boolean mWithDevTools;
protected Map<GeckoSession, Tab> mRDPTabs;
protected Tab mRDPChromeProcess;
protected boolean mReuseSession;
+ protected boolean mIgnoreCrash;
public GeckoSessionTestRule() {
mDefaultSettings = new GeckoSessionSettings();
mDefaultSettings.setBoolean(GeckoSessionSettings.USE_MULTIPROCESS, env.isMultiprocess());
}
/**
* Set an ErrorCollector for assertion errors, or null to not use one.
@@ -1071,16 +1089,18 @@ public class GeckoSessionTestRule extend
final WithDisplay displaySize = (WithDisplay)annotation;
mDisplaySize = new Point(displaySize.width(), displaySize.height());
} else if (ClosedSessionAtStart.class.equals(annotation.annotationType())) {
mClosedSession = ((ClosedSessionAtStart) annotation).value();
} else if (WithDevToolsAPI.class.equals(annotation.annotationType())) {
mWithDevTools = ((WithDevToolsAPI) annotation).value();
} else if (ReuseSession.class.equals(annotation.annotationType())) {
mReuseSession = ((ReuseSession) annotation).value();
+ } else if (IgnoreCrash.class.equals(annotation.annotationType())) {
+ mIgnoreCrash = ((IgnoreCrash) annotation).value();
}
}
}
private static RuntimeException unwrapRuntimeException(final Throwable e) {
final Throwable cause = e.getCause();
if (cause != null && cause instanceof RuntimeException) {
return (RuntimeException) cause;
@@ -1139,18 +1159,23 @@ public class GeckoSessionTestRule extend
case "toString":
return "Call Recorder";
}
ignore = true;
} else if (mCallRecordHandler != null) {
ignore = mCallRecordHandler.handleCall(method, args);
}
+ if (!mIgnoreCrash && sOnCrash.equals(method)) {
+ throw new RuntimeException("Content process crashed");
+ }
+
final boolean isExternalDelegate =
!DEFAULT_DELEGATES.contains(method.getDeclaringClass());
+
if (!ignore) {
assertThat("Callbacks must be on UI thread",
Looper.myLooper(), equalTo(Looper.getMainLooper()));
final GeckoSession session;
if (isExternalDelegate) {
session = null;
} else {