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
--- 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);
+ }
}