From c268a38982ac0cee9dc33c1c44a13b36cdeab175 Mon Sep 17 00:00:00 2001 From: Brian Nicholson Date: Mon, 11 Aug 2014 15:09:37 -0700 Subject: [PATCH] Bug 967325 - Implement GeckoRequest tests. r=jchen,mcomella --- mobile/android/base/tests/robocop.ini | 1 + .../android/base/tests/testGeckoRequest.java | 135 ++++++++++++++++++ mobile/android/base/tests/testGeckoRequest.js | 40 ++++++ mobile/android/base/util/GeckoRequest.java | 5 + 4 files changed, 181 insertions(+) create mode 100644 mobile/android/base/tests/testGeckoRequest.java create mode 100644 mobile/android/base/tests/testGeckoRequest.js diff --git a/mobile/android/base/tests/robocop.ini b/mobile/android/base/tests/robocop.ini index a586867da221..59866d8e7592 100644 --- a/mobile/android/base/tests/robocop.ini +++ b/mobile/android/base/tests/robocop.ini @@ -122,6 +122,7 @@ skip-if = android_version == "10" skip-if = android_version == "10" [testAppMenuPathways] [testEventDispatcher] +[testGeckoRequest] [testInputConnection] # disabled on Android 2.3; bug 1025968 skip-if = android_version == "10" diff --git a/mobile/android/base/tests/testGeckoRequest.java b/mobile/android/base/tests/testGeckoRequest.java new file mode 100644 index 000000000000..bc4416e87ff7 --- /dev/null +++ b/mobile/android/base/tests/testGeckoRequest.java @@ -0,0 +1,135 @@ +package org.mozilla.gecko.tests; + +import java.util.concurrent.atomic.AtomicBoolean; + +import com.jayway.android.robotium.solo.Condition; + +import org.mozilla.gecko.GeckoAppShell; +import org.mozilla.gecko.tests.helpers.AssertionHelper; +import org.mozilla.gecko.tests.helpers.GeckoHelper; +import org.mozilla.gecko.tests.helpers.JavascriptBridge; +import org.mozilla.gecko.tests.helpers.NavigationHelper; +import org.mozilla.gecko.tests.helpers.WaitHelper; +import org.mozilla.gecko.util.GeckoRequest; +import org.mozilla.gecko.util.NativeJSObject; + +/** + * Tests sending and receiving Gecko requests using the GeckoRequest API. + */ +public class testGeckoRequest extends UITest { + private static final String TEST_JS = "testGeckoRequest.js"; + private static final int MAX_WAIT_MS = 1000; + private static final String REQUEST_EVENT = "Robocop:GeckoRequest"; + private static final String REQUEST_EXCEPTION_EVENT = "Robocop:GeckoRequestException"; + + private JavascriptBridge js; + + @Override + public void setUp() throws Exception { + super.setUp(); + js = new JavascriptBridge(this); + } + + @Override + public void tearDown() throws Exception { + js.disconnect(); + super.tearDown(); + } + + public void testGeckoRequest() { + GeckoHelper.blockForReady(); + NavigationHelper.enterAndLoadUrl(StringHelper.ROBOCOP_JS_HARNESS_URL + "?path=" + TEST_JS); + + // Register a listener for this request. + js.syncCall("add_request_listener", REQUEST_EVENT); + + // Make sure we receive the expected response. + checkFooRequest(); + + // Try registering a second listener for this request, which should fail. + js.syncCall("add_second_request_listener", REQUEST_EVENT); + + // Unregister the listener for this request. + js.syncCall("remove_request_listener", REQUEST_EVENT); + + // Make sure we don't receive a response after removing the listener. + checkUnregisteredRequest(); + + // Check that we still receive a response for listeners that throw. + js.syncCall("add_exception_listener", REQUEST_EXCEPTION_EVENT); + checkExceptionRequest(); + js.syncCall("remove_request_listener", REQUEST_EXCEPTION_EVENT); + + js.syncCall("finish_test"); + } + + private void checkFooRequest() { + final AtomicBoolean responseReceived = new AtomicBoolean(false); + final String data = "foo"; + + GeckoAppShell.sendRequestToGecko(new GeckoRequest(REQUEST_EVENT, data) { + @Override + public void onResponse(NativeJSObject nativeJSObject) { + // Ensure we receive the expected response from Gecko. + final String result = nativeJSObject.getString("result"); + AssertionHelper.fAssertEquals("Sent and received request data", data + "bar", result); + responseReceived.set(true); + } + }); + + WaitHelper.waitFor("Received response for registered listener", new Condition() { + @Override + public boolean isSatisfied() { + return responseReceived.get(); + } + }, MAX_WAIT_MS); + } + + private void checkExceptionRequest() { + final AtomicBoolean responseReceived = new AtomicBoolean(false); + final AtomicBoolean errorReceived = new AtomicBoolean(false); + + GeckoAppShell.sendRequestToGecko(new GeckoRequest(REQUEST_EXCEPTION_EVENT, null) { + @Override + public void onResponse(NativeJSObject nativeJSObject) { + responseReceived.set(true); + } + + @Override + public void onError() { + errorReceived.set(true); + } + }); + + WaitHelper.waitFor("Received error for listener with exception", new Condition() { + @Override + public boolean isSatisfied() { + return errorReceived.get(); + } + }, MAX_WAIT_MS); + + AssertionHelper.fAssertTrue("onResponse not called for listener with exception", !responseReceived.get()); + } + + private void checkUnregisteredRequest() { + final AtomicBoolean responseReceived = new AtomicBoolean(false); + + GeckoAppShell.sendRequestToGecko(new GeckoRequest(REQUEST_EVENT, null) { + @Override + public void onResponse(NativeJSObject nativeJSObject) { + responseReceived.set(true); + } + }); + + // This check makes sure that we do *not* receive a response for an unregistered listener, + // meaning waitForCondition() should always time out. + getSolo().waitForCondition(new Condition() { + @Override + public boolean isSatisfied() { + return responseReceived.get(); + } + }, MAX_WAIT_MS); + + AssertionHelper.fAssertTrue("Did not receive response for unregistered listener", !responseReceived.get()); + } +} diff --git a/mobile/android/base/tests/testGeckoRequest.js b/mobile/android/base/tests/testGeckoRequest.js new file mode 100644 index 000000000000..e30f6d5e18e5 --- /dev/null +++ b/mobile/android/base/tests/testGeckoRequest.js @@ -0,0 +1,40 @@ +Components.utils.import("resource://gre/modules/Messaging.jsm"); + +let java = new JavaBridge(this); + +do_register_cleanup(() => { + java.disconnect(); +}); +do_test_pending(); + +function add_request_listener(message) { + RequestService.addListener(function (data) { + return { result: data + "bar" }; + }, message); +} + +function add_exception_listener(message) { + RequestService.addListener(function (data) { + throw "error!"; + }, message); +} + +function add_second_request_listener(message) { + let exceptionCaught = false; + + try { + RequestService.addListener(() => {}, message); + } catch (e) { + exceptionCaught = true; + } + + do_check_true(exceptionCaught); +} + +function remove_request_listener(message) { + RequestService.removeListener(message); +} + +function finish_test() { + do_test_finished(); +} diff --git a/mobile/android/base/util/GeckoRequest.java b/mobile/android/base/util/GeckoRequest.java index 192ca9fa8b83..1e0ade69c923 100644 --- a/mobile/android/base/util/GeckoRequest.java +++ b/mobile/android/base/util/GeckoRequest.java @@ -5,6 +5,8 @@ import java.util.concurrent.atomic.AtomicInteger; import org.json.JSONException; import org.json.JSONObject; +import org.mozilla.gecko.mozglue.RobocopTarget; + import android.util.Log; public abstract class GeckoRequest { @@ -24,6 +26,7 @@ public abstract class GeckoRequest { * @param data Data to send with this request, which can be any object serializeable by * {@link JSONObject#put(String, Object)}. */ + @RobocopTarget public GeckoRequest(String name, Object data) { this.name = name; final JSONObject message = new JSONObject(); @@ -68,6 +71,7 @@ public abstract class GeckoRequest { * * @param nativeJSObject The response data from Gecko */ + @RobocopTarget public abstract void onResponse(NativeJSObject nativeJSObject); /** @@ -80,6 +84,7 @@ public abstract class GeckoRequest { * * @throws RuntimeException */ + @RobocopTarget public void onError() { throw new RuntimeException("Unhandled error for GeckoRequest: " + name); }