Bug 1249491 - Write java/js integration test for retrieving telemetry client ID. r=sebastian
authorMichael Comella <michael.l.comella@gmail.com>
Thu, 24 Mar 2016 15:39:31 -0700
changeset 290799 1e683499ef1155866eb34b57d01b0e8c0e337fa3
parent 290798 08685d4867990cf629a57baaeeb24f66575cd296
child 290800 416d7925fc2df25bb85e26bee3fd78605a312566
push id19656
push usergwagner@mozilla.com
push dateMon, 04 Apr 2016 13:43:23 +0000
treeherderb2g-inbound@e99061fde28a [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerssebastian
bugs1249491
milestone48.0a1
Bug 1249491 - Write java/js integration test for retrieving telemetry client ID. r=sebastian MozReview-Commit-ID: 4aczKEwRNkD
mobile/android/tests/browser/robocop/robocop.ini
mobile/android/tests/browser/robocop/src/org/mozilla/gecko/tests/testUnifiedTelemetryClientId.java
mobile/android/tests/browser/robocop/testUnifiedTelemetryClientId.js
--- 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();
+}