Bug 1459299 - 2. Add pref setting methods for tests; r?snorp draft
authorJim Chen <nchen@mozilla.com>
Fri, 11 May 2018 10:25:04 -0400
changeset 794194 059c596960b890623d63414aa8459aceeb895bf5
parent 794193 71d9d15d1baea29016b602b0a3506f318c294bf5
child 794195 defd9f57d8e17c0afa19b5966cacc2cd64bd04f7
push id109606
push userbmo:nchen@mozilla.com
push dateFri, 11 May 2018 14:25:53 +0000
reviewerssnorp
bugs1459299
milestone62.0a1
Bug 1459299 - 2. Add pref setting methods for tests; r?snorp Add GeckoSessionTestRule.setPrefsUntilTestEnd and GeckoSessionTestRule.setPrefsDuringNextWait so tests can easily set prefs to get specific behavior. MozReview-Commit-ID: FquaonwfF5v
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/GeckoSessionTestRuleTest.kt
+++ b/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/GeckoSessionTestRuleTest.kt
@@ -1274,15 +1274,123 @@ class GeckoSessionTestRuleTest : BaseSes
     fun evaluateJS_throwOnChromeAccess() {
         sessionRule.session.evaluateJS("ChromeUtils")
     }
 
     @WithDevToolsAPI
     @Test fun evaluateChromeJS() {
         assertThat("Should be able to access ChromeUtils",
                    sessionRule.evaluateChromeJS("ChromeUtils"), notNullValue())
+
+        // We rely on Preferences.jsm for pref support.
+        assertThat("Should be able to access Preferences.jsm",
+                   sessionRule.evaluateChromeJS("""
+                       ChromeUtils.import('resource://gre/modules/Preferences.jsm',
+                                          {}).Preferences"""), notNullValue())
     }
 
     @Test(expected = AssertionError::class)
     fun evaluateChromeJS_throwOnNotWithDevTools() {
         sessionRule.evaluateChromeJS("0")
     }
+
+    @WithDevToolsAPI
+    @Test fun getPrefs_undefinedPrefReturnsNull() {
+        assertThat("Undefined pref should have null value",
+                   sessionRule.getPrefs("invalid.pref").asJSList(), equalTo(listOf(null)))
+    }
+
+    @Test(expected = AssertionError::class)
+    fun getPrefs_throwOnNotWithDevTools() {
+        sessionRule.getPrefs("invalid.pref")
+    }
+
+    @WithDevToolsAPI
+    @Test fun setPrefsUntilTestEnd() {
+        sessionRule.setPrefsUntilTestEnd(mapOf(
+                "test.pref.bool" to true,
+                "test.pref.int" to 1,
+                "test.pref.foo" to "foo"))
+
+        assertThat("Prefs should be set",
+                   sessionRule.getPrefs(
+                           "test.pref.bool",
+                           "test.pref.int",
+                           "test.pref.foo",
+                           "test.pref.bar").asJSList(),
+                   equalTo(listOf(true, 1.0, "foo", null)))
+
+        sessionRule.setPrefsUntilTestEnd(mapOf(
+                "test.pref.foo" to "bar",
+                "test.pref.bar" to "baz"))
+
+        assertThat("New prefs should be set",
+                   sessionRule.getPrefs(
+                           "test.pref.bool",
+                           "test.pref.int",
+                           "test.pref.foo",
+                           "test.pref.bar").asJSList(),
+                   equalTo(listOf(true, 1.0, "bar", "baz")))
+    }
+
+    @Test(expected = AssertionError::class)
+    fun setPrefsUntilTestEnd_throwOnNotWithDevTools() {
+        sessionRule.setPrefsUntilTestEnd(mapOf("invalid.pref" to true))
+    }
+
+    @WithDevToolsAPI
+    @Test fun setPrefsDuringNextWait() {
+        sessionRule.setPrefsDuringNextWait(mapOf(
+                "test.pref.bool" to true,
+                "test.pref.int" to 1,
+                "test.pref.foo" to "foo"))
+
+        assertThat("Prefs should be set before wait",
+                   sessionRule.getPrefs(
+                           "test.pref.bool",
+                           "test.pref.int",
+                           "test.pref.foo").asJSList(),
+                   equalTo(listOf(true, 1.0, "foo")))
+
+        sessionRule.session.reload()
+        sessionRule.session.waitForPageStop()
+
+        assertThat("Prefs should be cleared after wait",
+                   sessionRule.getPrefs(
+                           "test.pref.bool",
+                           "test.pref.int",
+                           "test.pref.foo").asJSList(),
+                   equalTo(listOf(null, null, null)))
+    }
+
+    @WithDevToolsAPI
+    @Test fun setPrefsDuringNextWait_hasPrecedence() {
+        sessionRule.setPrefsUntilTestEnd(mapOf(
+                "test.pref.int" to 1,
+                "test.pref.foo" to "foo"))
+
+        sessionRule.setPrefsDuringNextWait(mapOf(
+                "test.pref.foo" to "bar",
+                "test.pref.bar" to "baz"))
+
+        assertThat("Prefs should be overridden",
+                   sessionRule.getPrefs(
+                           "test.pref.int",
+                           "test.pref.foo",
+                           "test.pref.bar").asJSList(),
+                   equalTo(listOf(1.0, "bar", "baz")))
+
+        sessionRule.session.reload()
+        sessionRule.session.waitForPageStop()
+
+        assertThat("Overridden prefs should be restored",
+                   sessionRule.getPrefs(
+                           "test.pref.int",
+                           "test.pref.foo",
+                           "test.pref.bar").asJSList(),
+                   equalTo(listOf(1.0, "foo", null)))
+    }
+
+    @Test(expected = AssertionError::class)
+    fun setPrefsDuringNextWait_throwOnNotWithDevTools() {
+        sessionRule.setPrefsDuringNextWait(mapOf("invalid.pref" to true))
+    }
 }
--- 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
@@ -16,16 +16,18 @@ import org.mozilla.geckoview.test.rdp.Ta
 import org.mozilla.geckoview.test.util.Callbacks;
 
 import static org.hamcrest.Matchers.*;
 import static org.junit.Assert.assertThat;
 import static org.junit.Assert.fail;
 
 import org.hamcrest.Matcher;
 
+import org.json.JSONObject;
+
 import org.junit.rules.ErrorCollector;
 import org.junit.runner.Description;
 import org.junit.runners.model.Statement;
 
 import android.app.Instrumentation;
 import android.graphics.Point;
 import android.graphics.SurfaceTexture;
 import android.net.LocalSocketAddress;
@@ -476,16 +478,17 @@ public class GeckoSessionTestRule extend
         public String getCPUArch() {
             return BuildConfig.ANDROID_CPU_ARCH;
         }
     }
 
     protected class CallbackDelegates {
         private final Map<Pair<GeckoSession, Method>, MethodCall> mDelegates = new HashMap<>();
         private int mOrder;
+        private String mOldPrefs;
 
         public void delegate(final @Nullable GeckoSession session,
                              final @NonNull Object callback) {
             for (final Class<?> ifce : CALLBACK_CLASSES) {
                 if (!ifce.isInstance(callback)) {
                     continue;
                 }
                 assertThat("Cannot delegate null-delegate callbacks",
@@ -502,22 +505,96 @@ public class GeckoSessionTestRule extend
                     final MethodCall call = new MethodCall(
                             session, callbackMethod,
                             getAssertCalled(callbackMethod, callback), callback);
                     mDelegates.put(new Pair<>(session, method), call);
                 }
             }
         }
 
+        /** Generate a JS function to set new prefs and return a set of saved prefs. */
+        public void setPrefs(final @NonNull Map<String, ?> prefs) {
+            final String existingPrefs;
+            if (mOldPrefs == null) {
+                existingPrefs = "{}";
+            } else {
+                existingPrefs = String.format("JSON.parse(%s)", JSONObject.quote(mOldPrefs));
+            }
+
+            final StringBuilder newPrefs = new StringBuilder();
+            for (final Map.Entry<String, ?> pref : prefs.entrySet()) {
+                final String name = JSONObject.quote(pref.getKey());
+                final Object value = pref.getValue();
+                final String jsValue;
+                if (value instanceof Boolean) {
+                    jsValue = value.toString();
+                } else if (value instanceof Number) {
+                    jsValue = String.valueOf(((Number) value).intValue());
+                } else if (value instanceof CharSequence) {
+                    jsValue = JSONObject.quote(value.toString());
+                } else {
+                    throw new IllegalArgumentException("Unsupported pref value: " + value);
+                }
+                newPrefs.append(String.format("%s: %s,", name, jsValue));
+            }
+
+            final String prefSetter = String.format(
+                    "(function() {" +
+                    "  const prefs = ChromeUtils.import('resource://gre/modules/Preferences.jsm'," +
+                    "                                   {}).Preferences;" +
+                    "  const oldPrefs = %1$s;" +
+                    "  const newPrefs = {%2$s};" +
+                    "  Object.assign(oldPrefs," +
+                    "                ...Object.keys(newPrefs)" + // Save old prefs.
+                    "                         .filter(key => !(key in oldPrefs))" +
+                    "                         .map(key => ({[key]: prefs.get(key, null)})));" +
+                    "  prefs.set(newPrefs);" + // Set new prefs.
+                    "  return JSON.stringify(oldPrefs);" +
+                    "})()", existingPrefs, newPrefs.toString());
+
+            final Object oldPrefs = evaluateChromeJS(prefSetter);
+            assertThat("Old prefs should be JSON string",
+                       oldPrefs, instanceOf(String.class));
+            mOldPrefs = (String) oldPrefs;
+        }
+
+        /** Generate a JS function to set new prefs and reset a set of saved prefs. */
+        private void restorePrefs() {
+            if (mOldPrefs == null) {
+                return;
+            }
+
+            evaluateChromeJS(String.format(
+                    "(function() {" +
+                    "  const prefs = ChromeUtils.import('resource://gre/modules/Preferences.jsm'," +
+                    "                                   {}).Preferences;" +
+                    "  const oldPrefs = JSON.parse(%1$s);" +
+                    "  for (let [name, value] of Object.entries(oldPrefs)) {" +
+                    "    if (value === null) {" +
+                    "      prefs.reset(name);" +
+                    "    } else {" +
+                    "      prefs.set(name, value);" +
+                    "    }" +
+                    "  }" +
+                    "})()", JSONObject.quote(mOldPrefs)));
+            mOldPrefs = null;
+        }
+
         public void clear() {
+            mDelegates.clear();
+            mOrder = 0;
+
+            restorePrefs();
+        }
+
+        public void clearAndAssert() {
             final Collection<MethodCall> values = mDelegates.values();
             final MethodCall[] valuesArray = values.toArray(new MethodCall[values.size()]);
 
-            mDelegates.clear();
-            mOrder = 0;
+            clear();
 
             for (final MethodCall call : valuesArray) {
                 assertMatchesCount(call);
             }
         }
 
         public MethodCall prepareMethodCall(final GeckoSession session, final Method method) {
             MethodCall call = mDelegates.get(new Pair<>(session, method));
@@ -948,32 +1025,35 @@ public class GeckoSessionTestRule extend
             mCallRecordHandler = null;
         }
     }
 
     /**
      * Internal method to perform callback checks at the end of a test.
      */
     public void performTestEndCheck() {
-        mWaitScopeDelegates.clear();
-        mTestScopeDelegates.clear();
+        mWaitScopeDelegates.clearAndAssert();
+        mTestScopeDelegates.clearAndAssert();
     }
 
     protected void cleanupSession(final GeckoSession session) {
         final Tab tab = (mRDPTabs != null) ? mRDPTabs.get(session) : null;
         if (tab != null) {
             tab.detach();
             mRDPTabs.remove(session);
         }
         if (session.isOpen()) {
             session.close();
         }
     }
 
     protected void cleanupStatement() throws Throwable {
+        mWaitScopeDelegates.clear();
+        mTestScopeDelegates.clear();
+
         for (final GeckoSession session : mSubSessions) {
             cleanupSession(session);
         }
         cleanupSession(mMainSession);
 
         if (mDisplay != null) {
             mDisplay.surfaceDestroyed();
             mMainSession.releaseDisplay(mDisplay);
@@ -1338,17 +1418,17 @@ public class GeckoSessionTestRule extend
             final MethodCall methodCall = methodCalls.get(i);
             methodCall.incrementCounter();
             if (methodCall.allowUnlimitedCalls() || !methodCall.allowMoreCalls()) {
                 methodCalls.remove(i);
             }
         }
 
         mLastWaitEnd = index;
-        mWaitScopeDelegates.clear();
+        mWaitScopeDelegates.clearAndAssert();
     }
 
     /**
      * Playback callbacks that were made on all sessions during the previous wait. For any
      * methods annotated with {@link AssertCalled @AssertCalled}, assert that the
      * callbacks satisfy the specified requirements. If no {@link AssertCalled
      * @AssertCalled} annotations are found, assert any method has been called. Only
      * methods belonging to a GeckoSession callback are supported.
@@ -1657,9 +1737,60 @@ public class GeckoSessionTestRule extend
         if (mRDPChromeProcess == null) {
             mRDPChromeProcess = sRDPConnection.getChromeProcess();
             assertThat("Should have chrome process object",
                        mRDPChromeProcess, notNullValue());
             mRDPChromeProcess.attach();
         }
         return mRDPChromeProcess.getConsole().evaluateJS(js);
     }
+
+    /**
+     * Get a list of Gecko prefs. RDP must be enabled first using the {@link WithDevToolsAPI}
+     * annotation. Undefined prefs will return as null.
+     *
+     * @param prefs List of pref names.
+     * @return Pref values as a list of values.
+     */
+    public List<?> getPrefs(final @NonNull String... prefs) {
+        assertThat("Must enable RDP using @WithDevToolsAPI",
+                   mWithDevTools, equalTo(true));
+
+        final StringBuilder prefsList = new StringBuilder();
+        for (final String pref : prefs) {
+            prefsList.append(JSONObject.quote(pref)).append(',');
+        }
+
+        return (List<?>) evaluateChromeJS(String.format(
+                "(function() {" +
+                "  return ChromeUtils.import('resource://gre/modules/Preferences.jsm', {})" +
+                "                    .Preferences.get([%1$s]);" +
+                "})()", prefsList.toString()));
+    }
+
+    /**
+     * Set a list of Gecko prefs for the rest of the test. RDP must be enabled first using the
+     * {@link WithDevToolsAPI} annotation. Prefs set in {@link #setPrefsDuringNextWait} can
+     * temporarily take precedence over prefs set in {@code setPrefsUntilTestEnd}.
+     *
+     * @param prefs Map of pref names to values.
+     * @see #setPrefsDuringNextWait
+     */
+    public void setPrefsUntilTestEnd(final @NonNull Map<String, ?> prefs) {
+        assertThat("Must enable RDP using @WithDevToolsAPI",
+                   mWithDevTools, equalTo(true));
+        mTestScopeDelegates.setPrefs(prefs);
+    }
+
+    /**
+     * Set a list of Gecko prefs during the next wait. RDP must be enabled first using the
+     * {@link WithDevToolsAPI} annotation. Prefs set in {@code setPrefsDuringNextWait} can
+     * temporarily take precedence over prefs set in {@link #setPrefsUntilTestEnd}.
+     *
+     * @param prefs Map of pref names to values.
+     * @see #setPrefsUntilTestEnd
+     */
+    public void setPrefsDuringNextWait(final @NonNull Map<String, ?> prefs) {
+        assertThat("Must enable RDP using @WithDevToolsAPI",
+                   mWithDevTools, equalTo(true));
+        mWaitScopeDelegates.setPrefs(prefs);
+    }
 }