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
--- 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>
* @Setting.List({@Setting(key = Setting.Key.USE_MULTIPROCESS,
* value = "false"),
* @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();