gecko-dev/toolkit/components/telemetry/tests/unit/test_EcosystemTelemetry.js
2019-04-26 19:19:16 +00:00

249 lines
9.3 KiB
JavaScript

/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/
*/
"use strict";
ChromeUtils.import("resource://gre/modules/TelemetryController.jsm", this);
ChromeUtils.import("resource://gre/modules/TelemetryUtils.jsm", this);
ChromeUtils.import("resource://gre/modules/Preferences.jsm", this);
XPCOMUtils.defineLazyModuleGetters(this, {
ONLOGIN_NOTIFICATION: "resource://gre/modules/FxAccountsCommon.js",
ONLOGOUT_NOTIFICATION: "resource://gre/modules/FxAccountsCommon.js",
});
ChromeUtils.defineModuleGetter(this, "EcosystemTelemetry",
"resource://gre/modules/EcosystemTelemetry.jsm");
const WEAVE_EVENT = "weave:service:login:change";
const TEST_PING_TYPE = "test-ping-type";
function fakeIdleNotification(topic) {
let scheduler = ChromeUtils.import("resource://gre/modules/TelemetryScheduler.jsm", null);
return scheduler.TelemetryScheduler.observe(null, topic, null);
}
function checkPingStructure(ping, type, reason) {
Assert.equal(ping.type, type, "Should be an ecosystem ping.");
Assert.ok(!("clientId" in ping), "Ping must not contain a client ID.");
Assert.ok("environment" in ping, "Ping must contain an environment.");
let environment = ping.environment;
// Check that the environment is indeed minimal
const ALLOWED_ENVIRONMENT_KEYS = ["settings", "system", "profile"];
Assert.deepEqual(ALLOWED_ENVIRONMENT_KEYS, Object.keys(environment), "Environment should only contain a limited set of keys.");
// Check that fields of the environment are indeed minimal
Assert.deepEqual(["locale"], Object.keys(environment.settings), "Settings environment should only contain locale");
Assert.deepEqual(["cpu", "memoryMB", "os"], Object.keys(environment.system).sort(),
"System environment should contain a limited set of keys");
Assert.deepEqual(["locale", "name", "version"], Object.keys(environment.system.os).sort(),
"system.environment.os should contain a limited set of keys");
// Check the payload for required fields.
let payload = ping.payload;
Assert.equal(payload.reason, reason, "Ping reason must match.");
Assert.ok(payload.duration >= 0, "Payload must have a duration greater or equal to 0");
Assert.ok("ecosystemClientId" in payload, "Payload must contain the ecosystem client ID");
Assert.ok("scalars" in payload, "Payload must contain scalars");
Assert.ok("keyedScalars" in payload, "Payload must contain keyed scalars");
Assert.ok("histograms" in payload, "Payload must contain histograms");
Assert.ok("keyedHistograms" in payload, "Payload must contain keyed histograms");
Assert.ok("telemetry.ecosystem_old_send_time" in payload.scalars.parent, "Old send time should be set");
Assert.ok("telemetry.ecosystem_new_send_time" in payload.scalars.parent, "New send time should be set");
}
function sendPing() {
return TelemetryController.submitExternalPing(TEST_PING_TYPE, {}, {});
}
function fakeFxaUid(fn) {
const m = ChromeUtils.import("resource://gre/modules/EcosystemTelemetry.jsm", null);
let oldFn = m.Policy.fxaUid;
m.Policy.fxaUid = fn;
return oldFn;
}
registerCleanupFunction(function() {
PingServer.stop();
});
add_task(async function setup() {
// Trigger a proper telemetry init.
do_get_profile(true);
// Make sure we don't generate unexpected pings due to pref changes.
await setEmptyPrefWatchlist();
// Start the local ping server and setup Telemetry to use it during the tests.
PingServer.start();
Preferences.set(TelemetryUtils.Preferences.Server, "http://localhost:" + PingServer.port);
TelemetrySend.setServer("http://localhost:" + PingServer.port);
await TelemetryController.testSetup();
});
// We make absolute sure the Ecosystem ping is never triggered on Fennec/Non-unified Telemetry
add_task({
skip_if: () => !gIsAndroid,
}, async function test_no_ecosystem_ping_on_fennec() {
// Force preference to true, we should have an additional check on Android/Unified Telemetry
Preferences.set(TelemetryUtils.Preferences.EcosystemTelemetryEnabled, true);
EcosystemTelemetry.testReset();
// This is invoked in regular intervals by the timer.
// Would trigger ping sending.
EcosystemTelemetry.periodicPing();
// Let's send a test ping to ensure we receive _something_.
sendPing();
let ping = await PingServer.promiseNextPing();
Assert.equal(ping.type, TEST_PING_TYPE, "Should be a test ping.");
});
add_task({
skip_if: () => gIsAndroid,
}, async function test_nosending_if_disabled() {
Preferences.set(TelemetryUtils.Preferences.EcosystemTelemetryEnabled, false);
EcosystemTelemetry.testReset();
// This is invoked in regular intervals by the timer.
// Would trigger ping sending.
EcosystemTelemetry.periodicPing();
// Let's send a test ping to ensure we receive _something_.
sendPing();
let ping = await PingServer.promiseNextPing();
Assert.equal(ping.type, TEST_PING_TYPE, "Should be a test ping.");
});
add_task({
skip_if: () => gIsAndroid,
}, async function test_simple_send() {
Preferences.set(TelemetryUtils.Preferences.EcosystemTelemetryEnabled, true);
EcosystemTelemetry.testReset();
// This is invoked in regular intervals by the timer.
EcosystemTelemetry.periodicPing();
let ping = await PingServer.promiseNextPing();
checkPingStructure(ping, "pre-account", "periodic");
});
add_task({
skip_if: () => gIsAndroid,
}, async function test_login_workflow() {
// Fake the whole login/logout workflow by triggering the events directly.
Preferences.set(TelemetryUtils.Preferences.EcosystemTelemetryEnabled, true);
EcosystemTelemetry.testReset();
let originalFxaUid = fakeFxaUid(() => null);
let ping;
// 1. No user, timer invoked
EcosystemTelemetry.periodicPing();
ping = await PingServer.promiseNextPing();
checkPingStructure(ping, "pre-account", "periodic");
Assert.ok(!("uid" in ping.payload), "Ping should not contain a UID");
// 2. User logs in, no uid available.
// No ping will be generated.
EcosystemTelemetry.observe(null, ONLOGIN_NOTIFICATION, null);
sendPing();
ping = await PingServer.promiseNextPing();
Assert.equal(ping.type, TEST_PING_TYPE, "Should be a test ping.");
// 3. uid becomes available, weave syncs.
fakeFxaUid(() => "hashed-id");
EcosystemTelemetry.observe(null, WEAVE_EVENT, null);
ping = await PingServer.promiseNextPing();
checkPingStructure(ping, "pre-account", "login");
Assert.ok("uid" in ping.payload, "Ping should contain hashed ID");
// 4. User is logged in, timer invokes different ping
EcosystemTelemetry.periodicPing();
ping = await PingServer.promiseNextPing();
checkPingStructure(ping, "pre-account", "periodic");
Assert.ok("uid" in ping.payload, "Ping should contain hashed ID");
// 5. User disconnects account, no uid available
fakeFxaUid(() => null);
EcosystemTelemetry.observe(null, ONLOGOUT_NOTIFICATION, null);
ping = await PingServer.promiseNextPing();
checkPingStructure(ping, "pre-account", "logout");
Assert.ok(!("uid" in ping.payload), "Ping should not contain a UID");
// 6. No user, timer invoked
EcosystemTelemetry.periodicPing();
ping = await PingServer.promiseNextPing();
checkPingStructure(ping, "pre-account", "periodic");
Assert.ok(!("uid" in ping.payload), "Ping should not contain a UID");
// 7. Shutdown
EcosystemTelemetry.shutdown();
ping = await PingServer.promiseNextPing();
checkPingStructure(ping, "pre-account", "shutdown");
Assert.ok(!("uid" in ping.payload), "Ping should not contain a UID");
// Reset policy.
fakeFxaUid(originalFxaUid);
});
// Test that a periodic ping is triggered by the scheduler at midnight
//
// Based on `test_TelemetrySession#test_DailyDueAndIdle`.
add_task({
skip_if: () => gIsAndroid,
}, async function test_periodic_ping() {
await TelemetryStorage.testClearPendingPings();
PingServer.clearRequests();
const pingType = "pre-account";
let receivedPing = null;
// Register a ping handler that will assert when receiving multiple ecosystem pings.
// We can ignore other pings, such as the periodic ping.
PingServer.registerPingHandler(req => {
const ping = decodeRequestPayload(req);
if (ping.type == pingType) {
Assert.ok(!receivedPing, "Telemetry must only send one periodic ecosystem ping.");
receivedPing = ping;
}
});
// Faking scheduler timer has to happen before resetting TelemetryController
// to be effective.
let schedulerTickCallback = null;
let now = new Date(2040, 1, 1, 0, 0, 0);
fakeNow(now);
// Fake scheduler functions to control periodic collection flow in tests.
fakeSchedulerTimer(callback => schedulerTickCallback = callback, () => {});
await TelemetryController.testReset();
Preferences.set(TelemetryUtils.Preferences.EcosystemTelemetryEnabled, true);
EcosystemTelemetry.testReset();
// Trigger the periodic ecosystem ping.
let firstPeriodicDue = new Date(2040, 1, 2, 0, 0, 0);
fakeNow(firstPeriodicDue);
// Run a scheduler tick: it should trigger the periodic ping.
Assert.ok(!!schedulerTickCallback);
let tickPromise = schedulerTickCallback();
// Send an idle and then an active user notification.
fakeIdleNotification("idle");
fakeIdleNotification("active");
// Wait on the tick promise.
await tickPromise;
await TelemetrySend.testWaitOnOutgoingPings();
// Decode the ping contained in the request and check that's a periodic ping.
Assert.ok(receivedPing, "Telemetry must send one ecosystem periodic ping.");
checkPingStructure(receivedPing, pingType, "periodic");
});