diff --git a/mobile/android/base/TelemetryContract.java b/mobile/android/base/TelemetryContract.java index a9a095ace22..143c9ece8b6 100644 --- a/mobile/android/base/TelemetryContract.java +++ b/mobile/android/base/TelemetryContract.java @@ -5,11 +5,17 @@ package org.mozilla.gecko; +import org.mozilla.gecko.mozglue.RobocopTarget; + /** * Holds data definitions for our UI Telemetry implementation. * + * Note that enum values of "_TEST*" are reserved for testing and + * should not be changed without changing the associated tests. + * * See mobile/android/base/docs/index.rst for a full dictionary. */ +@RobocopTarget public interface TelemetryContract { /** @@ -64,6 +70,12 @@ public interface TelemetryContract { // Stop holding a resource (reader, bookmark, etc) for viewing later. // Note: Only used in JavaScript for now, but here for completeness. UNSAVE("unsave.1"), + + // VALUES BELOW THIS LINE ARE EXCLUSIVE TO TESTING. + _TEST1("_test_event_1.1"), + _TEST2("_test_event_2.1"), + _TEST3("_test_event_3.1"), + _TEST4("_test_event_4.1"), ; private final String string; @@ -124,6 +136,10 @@ public interface TelemetryContract { // Action triggered from a suggestion provided to the user. SUGGESTION("suggestion"), + + // VALUES BELOW THIS LINE ARE EXCLUSIVE TO TESTING. + _TEST1("_test_method_1"), + _TEST2("_test_method_2"), ; private final String string; @@ -164,6 +180,10 @@ public interface TelemetryContract { // Started when a Reader viewer becomes active in the foreground. // Note: Only used in JavaScript for now, but here for completeness. READER("reader.1"), + + // VALUES BELOW THIS LINE ARE EXCLUSIVE TO TESTING. + _TEST_STARTED_TWICE("_test_session_started_twice.1"), + _TEST_STOPPED_TWICE("_test_session_stopped_twice.1"), ; private final String string; @@ -190,6 +210,11 @@ public interface TelemetryContract { // No reason is specified. NONE(null), + + // VALUES BELOW THIS LINE ARE EXCLUSIVE TO TESTING. + _TEST1("_test_reason_1"), + _TEST2("_test_reason_2"), + _TEST_IGNORED("_test_reason_ignored"), ; private final String string; diff --git a/mobile/android/base/tests/testUITelemetry.java b/mobile/android/base/tests/testUITelemetry.java index 291991193c0..e90d3a13975 100644 --- a/mobile/android/base/tests/testUITelemetry.java +++ b/mobile/android/base/tests/testUITelemetry.java @@ -3,14 +3,14 @@ package org.mozilla.gecko.tests; import org.mozilla.gecko.AppConstants; import org.mozilla.gecko.PrefsHelper; import org.mozilla.gecko.Telemetry; +import org.mozilla.gecko.TelemetryContract.Event; +import org.mozilla.gecko.TelemetryContract.Method; +import org.mozilla.gecko.TelemetryContract.Reason; +import org.mozilla.gecko.TelemetryContract.Session; import android.util.Log; public class testUITelemetry extends JavascriptTest { - // Prefix used to distinguish test events and sessions from - // real ones. Used by the javascript part of the test. - static final String TEST_PREFIX = "TEST-"; - public testUITelemetry() { super("testUITelemetry.js"); } @@ -26,18 +26,24 @@ public class testUITelemetry extends JavascriptTest { Log.i("GeckoTest", "Adding telemetry events."); try { - Telemetry.sendUIEvent(TEST_PREFIX + "enone", "method0"); - Telemetry.startUISession(TEST_PREFIX + "foo"); - Telemetry.sendUIEvent(TEST_PREFIX + "efoo", "method1"); - Telemetry.startUISession(TEST_PREFIX + "foo"); - Telemetry.sendUIEvent(TEST_PREFIX + "efoo", "method2"); - Telemetry.startUISession(TEST_PREFIX + "bar"); - Telemetry.sendUIEvent(TEST_PREFIX + "efoobar", "method3", "foobarextras"); - Telemetry.stopUISession(TEST_PREFIX + "foo", "reasonfoo"); - Telemetry.sendUIEvent(TEST_PREFIX + "ebar", "method4", "barextras"); - Telemetry.stopUISession(TEST_PREFIX + "bar", "reasonbar"); - Telemetry.stopUISession(TEST_PREFIX + "bar", "reasonbar2"); - Telemetry.sendUIEvent(TEST_PREFIX + "enone", "method5"); + Telemetry.sendUIEvent(Event._TEST1, Method._TEST1); + Telemetry.startUISession(Session._TEST_STARTED_TWICE); + Telemetry.sendUIEvent(Event._TEST2, Method._TEST1); + + // We can only start one session per name, so this call should be ignored. + Telemetry.startUISession(Session._TEST_STARTED_TWICE); + + Telemetry.sendUIEvent(Event._TEST2, Method._TEST2); + Telemetry.startUISession(Session._TEST_STOPPED_TWICE); + Telemetry.sendUIEvent(Event._TEST3, Method._TEST1, "foobarextras"); + Telemetry.stopUISession(Session._TEST_STARTED_TWICE, Reason._TEST1); + Telemetry.sendUIEvent(Event._TEST4, Method._TEST1, "barextras"); + Telemetry.stopUISession(Session._TEST_STOPPED_TWICE, Reason._TEST2); + + // This session is already stopped, so this call should be ignored. + Telemetry.stopUISession(Session._TEST_STOPPED_TWICE, Reason._TEST_IGNORED); + + Telemetry.sendUIEvent(Event._TEST1, Method._TEST1); } catch (Exception e) { Log.e("GeckoTest", "Oops.", e); } diff --git a/mobile/android/base/tests/testUITelemetry.js b/mobile/android/base/tests/testUITelemetry.js index 850843ca536..8b97e209342 100644 --- a/mobile/android/base/tests/testUITelemetry.js +++ b/mobile/android/base/tests/testUITelemetry.js @@ -9,8 +9,19 @@ const Cu = Components.utils; Cu.import("resource://gre/modules/Services.jsm"); -const TEST_PREFIX = "TEST-"; -const TEST_REGEX = new RegExp("^" + TEST_PREFIX); +const EVENT_TEST1 = "_test_event_1.1"; +const EVENT_TEST2 = "_test_event_2.1"; +const EVENT_TEST3 = "_test_event_3.1"; +const EVENT_TEST4 = "_test_event_4.1"; + +const METHOD_TEST1 = "_test_method_1"; +const METHOD_TEST2 = "_test_method_2"; + +const REASON_TEST1 = "_test_reason_1"; +const REASON_TEST2 = "_test_reason_2"; + +const SESSION_STARTED_TWICE = "_test_session_started_twice.1"; +const SESSION_STOPPED_TWICE = "_test_session_stopped_twice.1"; function do_check_array_eq(a1, a2) { do_check_eq(a1.length, a2.length); @@ -19,6 +30,31 @@ function do_check_array_eq(a1, a2) { } } +/** + * Asserts that the given measurements are equal. Assumes that measurements + * of type "event" have their sessions arrays sorted. + */ +function do_check_measurement_eq(m1, m2) { + do_check_eq(m1.type, m2.type); + + switch (m1.type) { + case "event": + do_check_eq(m1.action, m2.action); + do_check_eq(m1.method, m2.method); + do_check_array_eq(m1.sessions, m2.sessions); + do_check_eq(m1.extras, m2.extras); + break; + + case "session": + do_check_eq(m1.name, m2.name); + do_check_eq(m1.reason, m2.reason); + break; + + default: + do_throw("Unknown event type: " + m1.type); + } +} + function getObserver() { let bridge = Cc["@mozilla.org/android/bridge;1"] .getService(Ci.nsIAndroidBridge); @@ -39,49 +75,73 @@ add_test(function test_enabled() { }); add_test(function test_telemetry_events() { + let expected = expectedArraysToObjs([ + ["event", EVENT_TEST1, METHOD_TEST1, [], undefined], + ["event", EVENT_TEST2, METHOD_TEST1, [SESSION_STARTED_TWICE], undefined], + ["event", EVENT_TEST2, METHOD_TEST2, [SESSION_STARTED_TWICE], undefined], + ["event", EVENT_TEST3, METHOD_TEST1, [SESSION_STARTED_TWICE, SESSION_STOPPED_TWICE], "foobarextras"], + ["session", SESSION_STARTED_TWICE, REASON_TEST1], + ["event", EVENT_TEST4, METHOD_TEST1, [SESSION_STOPPED_TWICE], "barextras"], + ["session", SESSION_STOPPED_TWICE, REASON_TEST2], + ["event", EVENT_TEST1, METHOD_TEST1, [], undefined], + ]); + let obs = getObserver(); - let measurements = obs.getUIMeasurements().filter(function(m) { - // Only want events and sessions that were generated by - // the Java-side of the test. - return TEST_REGEX.test(m.type == "event" ? m.action : m.name); + let measurements = removeNonTestMeasurements(obs.getUIMeasurements()); + + measurements.forEach(function (m, i) { + if (m.type === "event") { + m.sessions = removeNonTestSessions(m.sessions); + m.sessions.sort(); // Mutates. + } + + do_check_measurement_eq(expected[i], m); }); - let expected = [ - ["event", TEST_PREFIX + "enone", "method0", [], null], - ["event", TEST_PREFIX + "efoo", "method1", [TEST_PREFIX + "foo"], null], - ["event", TEST_PREFIX + "efoo", "method2", [TEST_PREFIX + "foo"], null], - ["event", TEST_PREFIX + "efoobar", "method3", [TEST_PREFIX + "foo", TEST_PREFIX + "bar"], "foobarextras"], - ["session", TEST_PREFIX + "foo", "reasonfoo"], - ["event", TEST_PREFIX + "ebar", "method4", [TEST_PREFIX + "bar"], "barextras"], - ["session", TEST_PREFIX + "bar", "reasonbar"], - ["event", TEST_PREFIX + "enone", "method5", [], null], - ]; - - do_check_eq(expected.length, measurements.length); - - for (let i = 0; i < measurements.length; ++i) { - let m = measurements[i]; - - let type = m.type; - if (type == "event") { - let [type, action, method, sessions, extras] = expected[i]; - do_check_eq(m.action, action); - do_check_eq(m.method, method); - // might receive real sessions in addition to the test ones -- remove the real ones - do_check_array_eq(m.sessions.filter(s => TEST_REGEX.test(s)), sessions); - do_check_eq(m.extras, extras); - continue; - } - - if (type == "session") { - let [type, name, reason] = expected[i]; - do_check_eq(m.name, name); - do_check_eq(m.reason, reason); - continue; - } - } - run_next_test(); }); +/** + * Converts the expected value arrays to objects, + * for less typing when initializing the expected arrays. + */ +function expectedArraysToObjs(expectedArrays) { + return expectedArrays.map(function (arr) { + let type = arr[0]; + if (type === "event") { + return { + type: type, + action: arr[1], + method: arr[2], + sessions: arr[3].sort(), // Sort, just in case it's not sorted by hand! + extras: arr[4], + }; + + } else if (type === "session") { + return { + type: type, + name: arr[1], + reason: arr[2], + }; + } + }); +} + +function removeNonTestMeasurements(measurements) { + return measurements.filter(function (measurement) { + if (measurement.type === "event") { + return measurement.action.startsWith("_test_event_"); + } else if (measurement.type === "session") { + return measurement.name.startsWith("_test_session_"); + } + return false; + }); +} + +function removeNonTestSessions(sessions) { + return sessions.filter(function (sessionName) { + return sessionName.startsWith("_test_session_"); + }); +} + run_next_test();