Bug 1464184 - Reuse test session by default; r?snorp draft
authorJim Chen <nchen@mozilla.com>
Thu, 24 May 2018 15:13:20 -0400
changeset 799473 b520e9f070fced5dc8ed6ccbd7201614ca9a858e
parent 798691 d36cd8bdbc5c0df1d1d7a167f5fedb95c3a3648e
push id111072
push userbmo:nchen@mozilla.com
push dateThu, 24 May 2018 19:13:46 +0000
reviewerssnorp
bugs1464184
milestone62.0a1
Bug 1464184 - Reuse test session by default; r?snorp Instead of creating a new session for every test case, we can get away with reusing the same session for the most part. This results in a large decrease in testing time due to lower overhead. MozReview-Commit-ID: 3MDAEtBVfxN
mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/NavigationDelegateTest.kt
mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/SessionLifecycleTest.kt
mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/rdp/Promises.java
mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/rdp/Tab.java
mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/rule/GeckoSessionTestRule.java
--- a/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/NavigationDelegateTest.kt
+++ b/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/NavigationDelegateTest.kt
@@ -3,27 +3,29 @@
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 package org.mozilla.geckoview.test
 
 import org.mozilla.geckoview.GeckoResponse
 import org.mozilla.geckoview.GeckoSession
 import org.mozilla.geckoview.test.rule.GeckoSessionTestRule.AssertCalled
 import org.mozilla.geckoview.test.rule.GeckoSessionTestRule.NullDelegate
+import org.mozilla.geckoview.test.rule.GeckoSessionTestRule.ReuseSession
 import org.mozilla.geckoview.test.rule.GeckoSessionTestRule.WithDevToolsAPI
 import org.mozilla.geckoview.test.util.Callbacks
 
 import android.support.test.filters.MediumTest
 import android.support.test.runner.AndroidJUnit4
 import org.hamcrest.Matchers.*
 import org.junit.Test
 import org.junit.runner.RunWith
 
 @RunWith(AndroidJUnit4::class)
 @MediumTest
+@ReuseSession(false)
 class NavigationDelegateTest : BaseSessionTest() {
 
     @Test fun load() {
         sessionRule.session.loadTestPath(HELLO_HTML_PATH)
         sessionRule.waitForPageStop()
 
         sessionRule.forCallbacksDuringWait(object : Callbacks.NavigationDelegate {
             @AssertCalled(count = 1, order = [1])
--- a/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/SessionLifecycleTest.kt
+++ b/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/SessionLifecycleTest.kt
@@ -3,27 +3,29 @@
    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.ClosedSessionAtStart
 import org.mozilla.geckoview.test.rule.GeckoSessionTestRule.NullDelegate
+import org.mozilla.geckoview.test.rule.GeckoSessionTestRule.ReuseSession
 import org.mozilla.geckoview.test.util.Callbacks
 
 import android.support.test.filters.MediumTest
 import android.support.test.runner.AndroidJUnit4
 
 import org.hamcrest.Matchers.*
 import org.junit.Test
 import org.junit.runner.RunWith
 
 @RunWith(AndroidJUnit4::class)
 @MediumTest
+@ReuseSession(false)
 class SessionLifecycleTest : BaseSessionTest() {
 
     @Test fun open_interleaved() {
         val session1 = sessionRule.createOpenSession()
         val session2 = sessionRule.createOpenSession()
         session1.close()
         val session3 = sessionRule.createOpenSession()
         session2.close()
--- a/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/rdp/Promises.java
+++ b/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/rdp/Promises.java
@@ -48,16 +48,17 @@ public final class Promises extends Acto
     /**
      * Detach from the promises API.
      */
     public void detach() {
         for (final Promise promise : mPromises) {
             promise.release();
         }
         sendPacket("{\"type\":\"detach\"}", JSON_PARSER).get();
+        release();
     }
 
     /* package */ Promise[] getPromisesFromArray(final @NonNull JSONArray array,
                                                  final boolean canCreate) {
         final Promise[] promises = new Promise[array.length()];
         for (int i = 0; i < promises.length; i++) {
             final JSONObject grip = array.optJSONObject(i);
             final Promise promise = (Promise) connection.getActor(grip);
--- a/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/rdp/Tab.java
+++ b/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/rdp/Tab.java
@@ -47,16 +47,17 @@ public final class Tab extends Actor {
         sendPacket("{\"type\":\"attach\"}", TAB_STATE_PARSER).get();
     }
 
     /**
      * Detach from the server tab.
      */
     public void detach() {
         sendPacket("{\"type\":\"detach\"}", TAB_STATE_PARSER).get();
+        release();
     }
 
     /**
      * Get the console object for access to the webconsole API.
      *
      * @return Console object.
      */
     public Console getConsole() {
--- 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
@@ -182,17 +182,17 @@ public class GeckoSessionTestRule extend
      * Use multiple settings:
      * <pre>
      * &#64;Setting.List({&#64;Setting(key = Setting.Key.USE_MULTIPROCESS,
      *                         value = "false"),
      *                &#64;Setting(key = Setting.Key.USE_TRACKING_PROTECTION,
      *                         value = "true")})
      * </pre>
      */
-    @Target({ElementType.ANNOTATION_TYPE, ElementType.METHOD, ElementType.TYPE})
+    @Target({ElementType.METHOD, ElementType.TYPE})
     @Retention(RetentionPolicy.RUNTIME)
     public @interface Setting {
         enum Key {
             CHROME_URI,
             DISPLAY_MODE,
             SCREEN_ID,
             USE_MULTIPROCESS,
             USE_PRIVATE_MODE,
@@ -244,16 +244,27 @@ public class GeckoSessionTestRule extend
             Setting[] value();
         }
 
         Key key();
         String value();
     }
 
     /**
+     * If a test requests a default open session, reuse a cached session instead of creating an
+     * open session every time. A new session is still created if the test requests a non-default
+     * session such as a closed session or a session with custom settings.
+     */
+    @Target({ElementType.METHOD, ElementType.TYPE})
+    @Retention(RetentionPolicy.RUNTIME)
+    public @interface ReuseSession {
+        boolean value() default true;
+    }
+
+    /**
      * Assert that a method is called or not called, and if called, the order and number
      * of times it is called. The order number is a monotonically increasing integer; if
      * an called method's order number is less than the current order number, an exception
      * is raised for out-of-order call.
      * <p>
      * {@code @AssertCalled} asserts the method must be called at least once.
      * <p>
      * {@code @AssertCalled(false)} asserts the method must not be called.
@@ -759,16 +770,18 @@ public class GeckoSessionTestRule extend
             HANDLER.sendMessageAtFrontOfQueue(msg);
             return false; // Remove this idle handler.
         }
     };
 
     private static GeckoRuntime sRuntime;
     private static RDPConnection sRDPConnection;
     private static long sLongestWait;
+    protected static GeckoSession sCachedSession;
+    protected static Tab sCachedRDPTab;
 
     public final Environment env = new Environment();
 
     protected final Instrumentation mInstrumentation =
             InstrumentationRegistry.getInstrumentation();
     protected final GeckoSessionSettings mDefaultSettings;
     protected final Set<GeckoSession> mSubSessions = new HashSet<>();
 
@@ -787,16 +800,17 @@ public class GeckoSessionTestRule extend
     protected Point mDisplaySize;
     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;
 
     public GeckoSessionTestRule() {
         mDefaultSettings = new GeckoSessionSettings();
         mDefaultSettings.setBoolean(GeckoSessionSettings.USE_MULTIPROCESS, env.isMultiprocess());
     }
 
     /**
      * Set an ErrorCollector for assertion errors, or null to not use one.
@@ -926,16 +940,18 @@ public class GeckoSessionTestRule extend
                 }
             } else if (WithDisplay.class.equals(annotation.annotationType())) {
                 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();
             }
         }
     }
 
     private static RuntimeException unwrapRuntimeException(final Throwable e) {
         final Throwable cause = e.getCause();
         if (cause != null && cause instanceof RuntimeException) {
             return (RuntimeException) cause;
@@ -961,31 +977,29 @@ public class GeckoSessionTestRule extend
     }
 
     protected void prepareStatement(final Description description) throws Throwable {
         final GeckoSessionSettings settings = new GeckoSessionSettings(mDefaultSettings);
         mTimeoutMillis = getDefaultTimeoutMillis();
         mNullDelegates = new HashSet<>();
         mClosedSession = false;
         mWithDevTools = false;
+        mReuseSession = true;
 
         applyAnnotations(Arrays.asList(description.getTestClass().getAnnotations()), settings);
         applyAnnotations(description.getAnnotations(), settings);
 
         final List<CallRecord> records = new ArrayList<>();
         final CallbackDelegates waitDelegates = new CallbackDelegates();
         final CallbackDelegates testDelegates = new CallbackDelegates();
         mCallRecords = records;
         mWaitScopeDelegates = waitDelegates;
         mTestScopeDelegates = testDelegates;
         mLastWaitStart = 0;
         mLastWaitEnd = 0;
-        if (mWithDevTools) {
-            mRDPTabs = new HashMap<>();
-        }
 
         final InvocationHandler recorder = new InvocationHandler() {
             @Override
             public Object invoke(final Object proxy, final Method method,
                                  final Object[] args) {
                 boolean ignore = false;
                 MethodCall call = null;
 
@@ -1048,45 +1062,62 @@ public class GeckoSessionTestRule extend
                                                 classes, recorder);
 
         if (sRuntime == null) {
             final GeckoRuntimeSettings.Builder runtimeSettingsBuilder =
                 new GeckoRuntimeSettings.Builder();
             runtimeSettingsBuilder.arguments(new String[] { "-purgecaches" })
                     .extras(InstrumentationRegistry.getArguments())
                     .nativeCrashReportingEnabled(true)
-                    .javaCrashReportingEnabled(true);
+                    .javaCrashReportingEnabled(true)
+                    .remoteDebuggingEnabled(true);
 
             sRuntime = GeckoRuntime.create(
                 InstrumentationRegistry.getTargetContext(),
                 runtimeSettingsBuilder.build());
         }
 
-        sRuntime.getSettings().setRemoteDebuggingEnabled(mWithDevTools);
-
-        mMainSession = new GeckoSession(settings);
+        final boolean useDefaultSession = !mClosedSession && mDefaultSettings.equals(settings);
+        if (useDefaultSession && mReuseSession && sCachedSession != null) {
+            mMainSession = sCachedSession;
+        } else {
+            mMainSession = new GeckoSession(settings);
+        }
         prepareSession(mMainSession);
 
         if (mDisplaySize != null) {
             mDisplayTexture = new SurfaceTexture(0);
             mDisplaySurface = new Surface(mDisplayTexture);
             mDisplay = mMainSession.acquireDisplay();
             mDisplay.surfaceChanged(mDisplaySurface, mDisplaySize.x, mDisplaySize.y);
         }
 
-        if (!mClosedSession) {
+        if (useDefaultSession && mReuseSession) {
+            if (sCachedSession == null) {
+                // We are creating a cached session.
+                final boolean withDevTools = mWithDevTools;
+                mWithDevTools = true; // Always get an RDP tab for cached session.
+                openSession(mMainSession);
+                sCachedSession = mMainSession;
+                sCachedRDPTab = mRDPTabs.get(mMainSession);
+                mWithDevTools = withDevTools;
+            } else {
+                // We are reusing a cached session.
+                mMainSession.loadUri("about:blank");
+                waitForOpenSession(mMainSession);
+            }
+        } else if (!mClosedSession) {
             openSession(mMainSession);
         }
     }
 
     protected void prepareSession(final GeckoSession session) throws Throwable {
         for (final Class<?> cls : CALLBACK_CLASSES) {
-            if (!mNullDelegates.contains(cls)) {
-                getCallbackSetter(cls).invoke(session, mCallbackProxy);
-            }
+            getCallbackSetter(cls).invoke(
+                    session, mNullDelegates.contains(cls) ? null : mCallbackProxy);
         }
     }
 
     /**
      * Call open() on a session, and ensure it's ready for use by the test. In particular,
      * remove any extra calls recorded as part of opening the session.
      *
      * @param session Session to open.
@@ -1104,17 +1135,21 @@ public class GeckoSessionTestRule extend
                 final String dataDir = InstrumentationRegistry.getTargetContext()
                                                               .getApplicationInfo().dataDir;
                 final LocalSocketAddress address = new LocalSocketAddress(
                         dataDir + "/firefox-debugger-socket",
                         LocalSocketAddress.Namespace.FILESYSTEM);
                 sRDPConnection = new RDPConnection(address);
                 sRDPConnection.setTimeout(mTimeoutMillis);
             }
-            final Tab tab = sRDPConnection.getMostRecentTab();
+            if (mRDPTabs == null) {
+                mRDPTabs = new HashMap<>();
+            }
+            final Tab tab = session.equals(sCachedSession) ? sCachedRDPTab
+                                                           : sRDPConnection.getMostRecentTab();
             mRDPTabs.put(session, tab);
         }
     }
 
     private void waitForInitialLoad(final GeckoSession session) {
         // We receive an initial about:blank load; don't expose that to the test. The initial
         // load ends with the first onPageStop call, so ignore everything from the session
         // until the first onPageStop call.
@@ -1166,17 +1201,22 @@ public class GeckoSessionTestRule extend
 
     protected void cleanupStatement() throws Throwable {
         mWaitScopeDelegates.clear();
         mTestScopeDelegates.clear();
 
         for (final GeckoSession session : mSubSessions) {
             cleanupSession(session);
         }
-        cleanupSession(mMainSession);
+        if (mMainSession.equals(sCachedSession)) {
+            // We have to detach the Promises object, but keep the Tab itself.
+            sCachedRDPTab.getPromises().detach();
+        } else {
+            cleanupSession(mMainSession);
+        }
 
         if (mDisplay != null) {
             mDisplay.surfaceDestroyed();
             mMainSession.releaseDisplay(mDisplay);
             mDisplay = null;
             mDisplaySurface.release();
             mDisplaySurface = null;
             mDisplayTexture.release();