Bug 1249491 - Write java/js integration test for retrieving telemetry client ID. r=sebastian
MozReview-Commit-ID: 4aczKEwRNkD
--- a/mobile/android/tests/browser/robocop/robocop.ini
+++ b/mobile/android/tests/browser/robocop/robocop.ini
@@ -102,16 +102,17 @@ skip-if = android_version == "18"
[src/org/mozilla/gecko/tests/testEventDispatcher.java]
[src/org/mozilla/gecko/tests/testGeckoRequest.java]
[src/org/mozilla/gecko/tests/testInputConnection.java]
[src/org/mozilla/gecko/tests/testJavascriptBridge.java]
[src/org/mozilla/gecko/tests/testNativeCrypto.java]
[src/org/mozilla/gecko/tests/testReaderModeTitle.java]
[src/org/mozilla/gecko/tests/testSessionHistory.java]
[src/org/mozilla/gecko/tests/testStateWhileLoading.java]
+[src/org/mozilla/gecko/tests/testUnifiedTelemetryClientId.java]
[src/org/mozilla/gecko/tests/testAccessibleCarets.java]
# testStumblerSetting disabled on Android 4.3, bug 1145846
[src/org/mozilla/gecko/tests/testStumblerSetting.java]
skip-if = android_version == "18"
[src/org/mozilla/gecko/tests/testLoginsProvider.java]
new file mode 100644
--- /dev/null
+++ b/mobile/android/tests/browser/robocop/src/org/mozilla/gecko/tests/testUnifiedTelemetryClientId.java
@@ -0,0 +1,134 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+package org.mozilla.gecko.tests;
+
+import static org.mozilla.gecko.tests.helpers.AssertionHelper.*;
+
+import org.mozilla.gecko.GeckoProfile;
+
+import java.io.File;
+import java.io.IOException;
+
+public class testUnifiedTelemetryClientId extends JavascriptBridgeTest {
+ private static final String TEST_JS = "testUnifiedTelemetryClientId.js";
+
+ private static final String CLIENT_ID_PATH = "datareporting/state.json";
+
+ private GeckoProfile profile;
+ private File profileDir;
+
+ public void setUp() throws Exception {
+ super.setUp();
+ profile = getTestProfile();
+ profileDir = profile.getDir(); // Assumes getDir is tested.
+
+ // In local testing, it's possible to ^C out of the harness and not have tearDown called,
+ // hence reset. We can't clear the cache because Gecko is not running yet.
+ resetTest(false);
+ }
+
+ public void tearDown() throws Exception {
+ // Don't clear cache because who knows what state Gecko is in.
+ resetTest(false);
+ getClientIdFile().delete();
+ super.tearDown();
+ }
+
+ private void resetTest(final boolean resetJSCache) {
+ if (resetJSCache) {
+ resetJSCache();
+ }
+ getClientIdFile().delete();
+ }
+
+ // TODO: If the intent service runs in the background, it could break this test. The service is disabled
+ // on non-official builds (e.g. this one) but that may not be the case on TBPL.
+ public void testUnifiedTelemetryClientId() throws Exception {
+ blockForReadyAndLoadJS(TEST_JS);
+ resetJSCache(); // Must be called after Gecko is loaded.
+ fAssertTrue("Profile directory exists", profileDir.exists());
+
+ // TODO: If these tests weren't so expensive to run in automation,
+ // this should be two separate tests to avoid storing state between tests.
+ testJavaCreatesClientId();
+ resetTest(true);
+ testJsCreatesClientId();
+
+ getJS().syncCall("endTest");
+ }
+
+ /**
+ * Scenario: Java creates client ID:
+ * * Fennec starts on fresh profile
+ * * Java code creates the client ID in datareporting/state.json
+ * * Js accesses client ID from the same file
+ * * Assert the client IDs are the same
+ */
+ private void testJavaCreatesClientId() throws Exception {
+ fAssertFalse("Client id file does not exist yet", getClientIdFile().exists());
+
+ final String clientIdFromJava = getClientIdFromJava();
+ final String clientIdFromJS = getClientIdFromJS();
+ fAssertEquals("Client ID from Java equals ID from JS", clientIdFromJava, clientIdFromJS);
+
+ final String clientIdFromJavaAgain = getClientIdFromJava();
+ final String clientIdFromJSCache = getClientIdFromJS();
+ resetJSCache();
+ final String clientIdFromJSFileAgain = getClientIdFromJS();
+ fAssertEquals("Same client ID retrieved from Java", clientIdFromJava, clientIdFromJavaAgain);
+ fAssertEquals("Same client ID retrieved from JS cache", clientIdFromJava, clientIdFromJSCache);
+ fAssertEquals("Same client ID retrieved from JS file", clientIdFromJava, clientIdFromJSFileAgain);
+ }
+
+ /**
+ * Scenario: JS creates client ID
+ * * Fennec starts on a fresh profile
+ * * Js creates the client ID in datareporting/state.json
+ * * Java access the client ID from the same file
+ * * Assert the client IDs are the same
+ */
+ private void testJsCreatesClientId() throws Exception {
+ fAssertFalse("Client id file does not exist yet", getClientIdFile().exists());
+
+ final String clientIdFromJS = getClientIdFromJS();
+ final String clientIdFromJava = getClientIdFromJava();
+ fAssertEquals("Client ID from JS equals ID from Java", clientIdFromJS, clientIdFromJava);
+
+ final String clientIdFromJSCache = getClientIdFromJS();
+ final String clientIdFromJavaAgain = getClientIdFromJava();
+ resetJSCache();
+ final String clientIdFromJSFileAgain = getClientIdFromJS();
+ fAssertEquals("Same client ID retrieved from JS cache", clientIdFromJS, clientIdFromJSCache);
+ fAssertEquals("Same client ID retrieved from JS file", clientIdFromJS, clientIdFromJSFileAgain);
+ fAssertEquals("Same client ID retrieved from Java", clientIdFromJS, clientIdFromJavaAgain);
+ }
+
+ private String getClientIdFromJava() throws IOException {
+ // This assumes implementation details: it assumes the client ID
+ // file is created when Java attempts to retrieve it if it does not exist.
+ final String clientId = profile.getClientId();
+ fAssertNotNull("Returned client ID is not null", clientId);
+ fAssertTrue("Client ID file exists after getClientId call", getClientIdFile().exists());
+ return clientId;
+ }
+
+ private String getClientIdFromJS() {
+ return getBlockingFromJsString("clientId");
+ }
+
+ /**
+ * Resets the client ID cache in ClientID.jsm. This method *must* be called after
+ * Gecko is loaded or else this method will hang.
+ */
+ private void resetJSCache() {
+ // HACK: the backing JS method is a promise with no return value. Rather than writing a method
+ // to handle this (for time reasons), I call the get String method and don't access the return value.
+ getBlockingFromJsString("reset");
+ }
+
+ private File getClientIdFile() {
+ return new File(profileDir, CLIENT_ID_PATH);
+ }
+}
new file mode 100644
--- /dev/null
+++ b/mobile/android/tests/browser/robocop/testUnifiedTelemetryClientId.js
@@ -0,0 +1,50 @@
+var { classes: Cc, interfaces: Ci, utils: Cu } = Components;
+
+Cu.import('resource://gre/modules/ClientID.jsm');
+
+var java = new JavaBridge(this);
+do_register_cleanup(() => {
+ java.disconnect();
+});
+do_test_pending();
+
+var isClientIDSet;
+var clientID;
+
+var isResetDone;
+
+function getAsyncClientId() {
+ isClientIDSet = false;
+ ClientID.getClientID().then(function (retClientID) {
+ // Ideally, we'd directly send the client ID back to Java but Java won't listen for
+ // js messages after we return from the containing function (bug 1253467).
+ //
+ // Note that my brief attempts to get synchronous Promise resolution (via Task.jsm)
+ // working failed - I have other things to focus on.
+ clientID = retClientID;
+ isClientIDSet = true;
+ }, function (fail) {
+ // Since Java doesn't listen to our messages (bug 1253467), I don't expect
+ // this throw to work correctly but we should timeout in Java.
+ do_throw('Could not retrieve client ID: ' + fail);
+ });
+}
+
+function pollGetAsyncClientId() {
+ java.asyncCall('blockingFromJsResponseString', isClientIDSet, clientID);
+}
+
+function getAsyncReset() {
+ isResetDone = false;
+ ClientID._reset().then(function () {
+ isResetDone = true;
+ });
+}
+
+function pollGetAsyncReset() {
+ java.asyncCall('blockingFromJsResponseString', isResetDone, '');
+}
+
+function endTest() {
+ do_test_finished();
+}