Bug 1465480 - Add @IgnoreCrash to GeckoSessionTestRule r=jchen
authorJames Willcox <snorp@snorp.net>
Thu, 31 May 2018 10:28:34 -0500
changeset 422037 8814700645f0c33ad34d99bb9a56c8320ea28043
parent 422036 d9d0a8abad4028f967d63fa8667a7c925c6d1de9
child 422038 d0b57ae0972a0acd4d7bb46e61f78a5d05e462b9
push id34114
push userbtara@mozilla.com
push dateSat, 09 Jun 2018 15:31:58 +0000
treeherdermozilla-central@e02a5155d815 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjchen
bugs1465480
milestone62.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
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
mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/BaseSessionTest.kt
mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/GeckoSessionTestRuleTest.kt
mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/rule/GeckoSessionTestRule.java
--- 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,61 @@ 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))
+
+        // This test has some kind of strange race on ARM emulators
+        assumeThat(!(sessionRule.env.isEmulator && sessionRule.env.cpuArch.contains("arm")),
+                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 {