summaryrefslogtreecommitdiffstats
path: root/toolkit/components/telemetry/tests/unit/test_TelemetrySend.js
diff options
context:
space:
mode:
Diffstat (limited to 'toolkit/components/telemetry/tests/unit/test_TelemetrySend.js')
-rw-r--r--toolkit/components/telemetry/tests/unit/test_TelemetrySend.js1110
1 files changed, 1110 insertions, 0 deletions
diff --git a/toolkit/components/telemetry/tests/unit/test_TelemetrySend.js b/toolkit/components/telemetry/tests/unit/test_TelemetrySend.js
new file mode 100644
index 0000000000..5bcb69d5a0
--- /dev/null
+++ b/toolkit/components/telemetry/tests/unit/test_TelemetrySend.js
@@ -0,0 +1,1110 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/
+*/
+
+// This tests the public Telemetry API for submitting pings.
+
+"use strict";
+
+const { TelemetryController } = ChromeUtils.importESModule(
+ "resource://gre/modules/TelemetryController.sys.mjs"
+);
+const { MockRegistrar } = ChromeUtils.importESModule(
+ "resource://testing-common/MockRegistrar.sys.mjs"
+);
+const { TelemetrySend } = ChromeUtils.importESModule(
+ "resource://gre/modules/TelemetrySend.sys.mjs"
+);
+const { TelemetryStorage } = ChromeUtils.importESModule(
+ "resource://gre/modules/TelemetryStorage.sys.mjs"
+);
+const { TelemetryUtils } = ChromeUtils.importESModule(
+ "resource://gre/modules/TelemetryUtils.sys.mjs"
+);
+
+ChromeUtils.defineESModuleGetters(this, {
+ TelemetryHealthPing: "resource://gre/modules/HealthPing.sys.mjs",
+});
+
+const MS_IN_A_MINUTE = 60 * 1000;
+
+function countPingTypes(pings) {
+ let countByType = new Map();
+ for (let p of pings) {
+ countByType.set(p.type, 1 + (countByType.get(p.type) || 0));
+ }
+ return countByType;
+}
+
+function setPingLastModified(id, timestamp) {
+ const path = PathUtils.join(TelemetryStorage.pingDirectoryPath, id);
+ return IOUtils.setModificationTime(path, timestamp);
+}
+
+// Mock out the send timer activity.
+function waitForTimer() {
+ return new Promise(resolve => {
+ fakePingSendTimer(
+ (callback, timeout) => {
+ resolve([callback, timeout]);
+ },
+ () => {}
+ );
+ });
+}
+
+function sendPing(aSendClientId, aSendEnvironment) {
+ const TEST_PING_TYPE = "test-ping-type";
+
+ if (PingServer.started) {
+ TelemetrySend.setServer("http://localhost:" + PingServer.port);
+ } else {
+ TelemetrySend.setServer("http://doesnotexist");
+ }
+
+ let options = {
+ addClientId: aSendClientId,
+ addEnvironment: aSendEnvironment,
+ };
+ return TelemetryController.submitExternalPing(TEST_PING_TYPE, {}, options);
+}
+
+// Allow easy faking of readable ping ids.
+// This helps with debugging issues with e.g. ordering in the send logic.
+function fakePingId(type, number) {
+ const HEAD = "93bd0011-2c8f-4e1c-bee0-";
+ const TAIL = "000000000000";
+ const N = String(number);
+ const id = HEAD + type + TAIL.slice(type.length, -N.length) + N;
+ fakeGeneratePingId(() => id);
+ return id;
+}
+
+var checkPingsSaved = async function (pingIds) {
+ let allFound = true;
+ for (let id of pingIds) {
+ const path = PathUtils.join(TelemetryStorage.pingDirectoryPath, id);
+ let exists = false;
+ try {
+ exists = await IOUtils.exists(path);
+ } catch (ex) {}
+
+ if (!exists) {
+ dump("checkPingsSaved - failed to find ping: " + path + "\n");
+ allFound = false;
+ }
+ }
+
+ return allFound;
+};
+
+function histogramValueCount(h) {
+ return Object.values(h.values).reduce((a, b) => a + b, 0);
+}
+
+add_task(async function test_setup() {
+ // Trigger a proper telemetry init.
+ do_get_profile(true);
+
+ // Addon manager needs a profile directory.
+ await loadAddonManager(
+ "xpcshell@tests.mozilla.org",
+ "XPCShell",
+ "1",
+ "1.9.2"
+ );
+ finishAddonManagerStartup();
+ fakeIntlReady();
+
+ // Make sure we don't generate unexpected pings due to pref changes.
+ await setEmptyPrefWatchlist();
+ Services.prefs.setBoolPref(
+ TelemetryUtils.Preferences.HealthPingEnabled,
+ true
+ );
+ TelemetryStopwatch.setTestModeEnabled(true);
+});
+
+// Test the ping sending logic.
+add_task(async function test_sendPendingPings() {
+ const TYPE_PREFIX = "test-sendPendingPings-";
+ const TEST_TYPE_A = TYPE_PREFIX + "A";
+ const TEST_TYPE_B = TYPE_PREFIX + "B";
+
+ const TYPE_A_COUNT = 20;
+ const TYPE_B_COUNT = 5;
+
+ let histSuccess = Telemetry.getHistogramById("TELEMETRY_SUCCESS");
+ let histSendTimeSuccess = Telemetry.getHistogramById(
+ "TELEMETRY_SEND_SUCCESS"
+ );
+ let histSendTimeFail = Telemetry.getHistogramById("TELEMETRY_SEND_FAILURE");
+ histSuccess.clear();
+ histSendTimeSuccess.clear();
+ histSendTimeFail.clear();
+
+ // Fake a current date.
+ let now = TelemetryUtils.truncateToDays(new Date());
+ now = fakeNow(futureDate(now, 10 * 60 * MS_IN_A_MINUTE));
+
+ // Enable test-mode for TelemetrySend, otherwise we won't store pending pings
+ // before the module is fully initialized later.
+ TelemetrySend.setTestModeEnabled(true);
+
+ // Submit some pings without the server and telemetry started yet.
+ for (let i = 0; i < TYPE_A_COUNT; ++i) {
+ fakePingId("a", i);
+ const id = await TelemetryController.submitExternalPing(TEST_TYPE_A, {});
+ await setPingLastModified(id, now.getTime() + i * 1000);
+ }
+
+ Assert.equal(
+ TelemetrySend.pendingPingCount,
+ TYPE_A_COUNT,
+ "Should have correct pending ping count"
+ );
+
+ // Submit some more pings of a different type.
+ now = fakeNow(futureDate(now, 5 * MS_IN_A_MINUTE));
+ for (let i = 0; i < TYPE_B_COUNT; ++i) {
+ fakePingId("b", i);
+ const id = await TelemetryController.submitExternalPing(TEST_TYPE_B, {});
+ await setPingLastModified(id, now.getTime() + i * 1000);
+ }
+
+ Assert.equal(
+ TelemetrySend.pendingPingCount,
+ TYPE_A_COUNT + TYPE_B_COUNT,
+ "Should have correct pending ping count"
+ );
+
+ Assert.deepEqual(
+ histSuccess.snapshot().values,
+ {},
+ "Should not have recorded any sending in histograms yet."
+ );
+ Assert.equal(
+ histSendTimeSuccess.snapshot().sum,
+ 0,
+ "Should not have recorded any sending in histograms yet."
+ );
+ Assert.equal(
+ histSendTimeFail.snapshot().sum,
+ 0,
+ "Should not have recorded any sending in histograms yet."
+ );
+
+ // Now enable sending to the ping server.
+ now = fakeNow(futureDate(now, MS_IN_A_MINUTE));
+ PingServer.start();
+ Services.prefs.setStringPref(
+ TelemetryUtils.Preferences.Server,
+ "http://localhost:" + PingServer.port
+ );
+
+ let timerPromise = waitForTimer();
+ await TelemetryController.testReset();
+ let [pingSendTimerCallback, pingSendTimeout] = await timerPromise;
+ Assert.ok(!!pingSendTimerCallback, "Should have a timer callback");
+
+ // We should have received 10 pings from the first send batch:
+ // 5 of type B and 5 of type A, as sending is newest-first.
+ // The other pings should be delayed by the 10-pings-per-minute limit.
+ let pings = await PingServer.promiseNextPings(10);
+ Assert.equal(
+ TelemetrySend.pendingPingCount,
+ TYPE_A_COUNT - 5,
+ "Should have correct pending ping count"
+ );
+ PingServer.registerPingHandler(() =>
+ Assert.ok(false, "Should not have received any pings now")
+ );
+ let countByType = countPingTypes(pings);
+
+ Assert.equal(
+ countByType.get(TEST_TYPE_B),
+ TYPE_B_COUNT,
+ "Should have received the correct amount of type B pings"
+ );
+ Assert.equal(
+ countByType.get(TEST_TYPE_A),
+ 10 - TYPE_B_COUNT,
+ "Should have received the correct amount of type A pings"
+ );
+
+ Assert.deepEqual(
+ histSuccess.snapshot().values,
+ { 0: 0, 1: 10, 2: 0 },
+ "Should have recorded sending success in histograms."
+ );
+ Assert.equal(
+ histogramValueCount(histSendTimeSuccess.snapshot()),
+ 10,
+ "Should have recorded successful send times in histograms."
+ );
+ Assert.equal(
+ histogramValueCount(histSendTimeFail.snapshot()),
+ 0,
+ "Should not have recorded any failed sending in histograms yet."
+ );
+
+ // As we hit the ping send limit and still have pending pings, a send tick should
+ // be scheduled in a minute.
+ Assert.ok(!!pingSendTimerCallback, "Timer callback should be set");
+ Assert.equal(
+ pingSendTimeout,
+ MS_IN_A_MINUTE,
+ "Send tick timeout should be correct"
+ );
+
+ // Trigger the next tick - we should receive the next 10 type A pings.
+ PingServer.resetPingHandler();
+ now = fakeNow(futureDate(now, pingSendTimeout));
+ timerPromise = waitForTimer();
+ pingSendTimerCallback();
+ [pingSendTimerCallback, pingSendTimeout] = await timerPromise;
+
+ pings = await PingServer.promiseNextPings(10);
+ PingServer.registerPingHandler(() =>
+ Assert.ok(false, "Should not have received any pings now")
+ );
+ countByType = countPingTypes(pings);
+
+ Assert.equal(
+ countByType.get(TEST_TYPE_A),
+ 10,
+ "Should have received the correct amount of type A pings"
+ );
+
+ // We hit the ping send limit again and still have pending pings, a send tick should
+ // be scheduled in a minute.
+ Assert.equal(
+ pingSendTimeout,
+ MS_IN_A_MINUTE,
+ "Send tick timeout should be correct"
+ );
+
+ // Trigger the next tick - we should receive the remaining type A pings.
+ PingServer.resetPingHandler();
+ now = fakeNow(futureDate(now, pingSendTimeout));
+ await pingSendTimerCallback();
+
+ pings = await PingServer.promiseNextPings(5);
+ PingServer.registerPingHandler(() =>
+ Assert.ok(false, "Should not have received any pings now")
+ );
+ countByType = countPingTypes(pings);
+
+ Assert.equal(
+ countByType.get(TEST_TYPE_A),
+ 5,
+ "Should have received the correct amount of type A pings"
+ );
+
+ await TelemetrySend.testWaitOnOutgoingPings();
+ PingServer.resetPingHandler();
+ // Restore the default ping id generator.
+ fakeGeneratePingId(() => TelemetryUtils.generateUUID());
+});
+
+add_task(async function test_sendDateHeader() {
+ fakeNow(new Date(Date.UTC(2011, 1, 1, 11, 0, 0)));
+ await TelemetrySend.reset();
+
+ let pingId = await TelemetryController.submitExternalPing(
+ "test-send-date-header",
+ {}
+ );
+ let req = await PingServer.promiseNextRequest();
+ let ping = decodeRequestPayload(req);
+ Assert.equal(
+ req.getHeader("Date"),
+ "Tue, 01 Feb 2011 11:00:00 GMT",
+ "Telemetry should send the correct Date header with requests."
+ );
+ Assert.equal(ping.id, pingId, "Should have received the correct ping id.");
+});
+
+// Test the backoff timeout behavior after send failures.
+add_task(async function test_backoffTimeout() {
+ const TYPE_PREFIX = "test-backoffTimeout-";
+ const TEST_TYPE_C = TYPE_PREFIX + "C";
+ const TEST_TYPE_D = TYPE_PREFIX + "D";
+ const TEST_TYPE_E = TYPE_PREFIX + "E";
+
+ let histSuccess = Telemetry.getHistogramById("TELEMETRY_SUCCESS");
+ let histSendTimeSuccess = Telemetry.getHistogramById(
+ "TELEMETRY_SEND_SUCCESS"
+ );
+ let histSendTimeFail = Telemetry.getHistogramById("TELEMETRY_SEND_FAILURE");
+
+ // Failing a ping send now should trigger backoff behavior.
+ let now = fakeNow(2010, 1, 1, 11, 0, 0);
+ await TelemetrySend.reset();
+ PingServer.stop();
+
+ histSuccess.clear();
+ histSendTimeSuccess.clear();
+ histSendTimeFail.clear();
+
+ fakePingId("c", 0);
+ now = fakeNow(futureDate(now, MS_IN_A_MINUTE));
+ let sendAttempts = 0;
+ let timerPromise = waitForTimer();
+ await TelemetryController.submitExternalPing(TEST_TYPE_C, {});
+ let [pingSendTimerCallback, pingSendTimeout] = await timerPromise;
+ Assert.equal(
+ TelemetrySend.pendingPingCount,
+ 1,
+ "Should have one pending ping."
+ );
+ ++sendAttempts;
+
+ const MAX_BACKOFF_TIMEOUT = 120 * MS_IN_A_MINUTE;
+ for (
+ let timeout = 2 * MS_IN_A_MINUTE;
+ timeout <= MAX_BACKOFF_TIMEOUT;
+ timeout *= 2
+ ) {
+ Assert.ok(!!pingSendTimerCallback, "Should have received a timer callback");
+ Assert.equal(
+ pingSendTimeout,
+ timeout,
+ "Send tick timeout should be correct"
+ );
+
+ let callback = pingSendTimerCallback;
+ now = fakeNow(futureDate(now, pingSendTimeout));
+ timerPromise = waitForTimer();
+ await callback();
+ [pingSendTimerCallback, pingSendTimeout] = await timerPromise;
+ ++sendAttempts;
+ }
+
+ timerPromise = waitForTimer();
+ await pingSendTimerCallback();
+ [pingSendTimerCallback, pingSendTimeout] = await timerPromise;
+ Assert.equal(
+ pingSendTimeout,
+ MAX_BACKOFF_TIMEOUT,
+ "Tick timeout should be capped"
+ );
+ ++sendAttempts;
+
+ Assert.deepEqual(
+ histSuccess.snapshot().values,
+ { 0: sendAttempts, 1: 0 },
+ "Should have recorded sending failure in histograms."
+ );
+ Assert.equal(
+ histSendTimeSuccess.snapshot().sum,
+ 0,
+ "Should not have recorded any sending success in histograms yet."
+ );
+ Assert.greaterOrEqual(
+ histSendTimeFail.snapshot().sum,
+ 0,
+ "Should have recorded send failure times in histograms."
+ );
+ Assert.equal(
+ histogramValueCount(histSendTimeFail.snapshot()),
+ sendAttempts,
+ "Should have recorded send failure times in histograms."
+ );
+
+ // Submitting a new ping should reset the backoff behavior.
+ fakePingId("d", 0);
+ now = fakeNow(futureDate(now, MS_IN_A_MINUTE));
+ timerPromise = waitForTimer();
+ await TelemetryController.submitExternalPing(TEST_TYPE_D, {});
+ [pingSendTimerCallback, pingSendTimeout] = await timerPromise;
+ Assert.equal(
+ pingSendTimeout,
+ 2 * MS_IN_A_MINUTE,
+ "Send tick timeout should be correct"
+ );
+ sendAttempts += 2;
+
+ // With the server running again, we should send out the pending pings immediately
+ // when a new ping is submitted.
+ PingServer.start();
+ TelemetrySend.setServer("http://localhost:" + PingServer.port);
+ fakePingId("e", 0);
+ now = fakeNow(futureDate(now, MS_IN_A_MINUTE));
+ timerPromise = waitForTimer();
+ await TelemetryController.submitExternalPing(TEST_TYPE_E, {});
+
+ let pings = await PingServer.promiseNextPings(3);
+ let countByType = countPingTypes(pings);
+
+ Assert.equal(
+ countByType.get(TEST_TYPE_C),
+ 1,
+ "Should have received the correct amount of type C pings"
+ );
+ Assert.equal(
+ countByType.get(TEST_TYPE_D),
+ 1,
+ "Should have received the correct amount of type D pings"
+ );
+ Assert.equal(
+ countByType.get(TEST_TYPE_E),
+ 1,
+ "Should have received the correct amount of type E pings"
+ );
+
+ await TelemetrySend.testWaitOnOutgoingPings();
+ Assert.equal(
+ TelemetrySend.pendingPingCount,
+ 0,
+ "Should have no pending pings left"
+ );
+
+ Assert.deepEqual(
+ histSuccess.snapshot().values,
+ { 0: sendAttempts, 1: 3, 2: 0 },
+ "Should have recorded sending failure in histograms."
+ );
+ Assert.greaterOrEqual(
+ histSendTimeSuccess.snapshot().sum,
+ 0,
+ "Should have recorded sending success in histograms."
+ );
+ Assert.equal(
+ histogramValueCount(histSendTimeSuccess.snapshot()),
+ 3,
+ "Should have recorded sending success in histograms."
+ );
+ Assert.equal(
+ histogramValueCount(histSendTimeFail.snapshot()),
+ sendAttempts,
+ "Should have recorded send failure times in histograms."
+ );
+
+ // Restore the default ping id generator.
+ fakeGeneratePingId(() => TelemetryUtils.generateUUID());
+});
+
+add_task(async function test_discardBigPings() {
+ const TEST_PING_TYPE = "test-ping-type";
+
+ let histSizeExceeded = Telemetry.getHistogramById(
+ "TELEMETRY_PING_SIZE_EXCEEDED_SEND"
+ );
+ let histDiscardedSize = Telemetry.getHistogramById(
+ "TELEMETRY_DISCARDED_SEND_PINGS_SIZE_MB"
+ );
+ let histSuccess = Telemetry.getHistogramById("TELEMETRY_SUCCESS");
+ let histSendTimeSuccess = Telemetry.getHistogramById(
+ "TELEMETRY_SEND_SUCCESS"
+ );
+ let histSendTimeFail = Telemetry.getHistogramById("TELEMETRY_SEND_FAILURE");
+ for (let h of [
+ histSizeExceeded,
+ histDiscardedSize,
+ histSuccess,
+ histSendTimeSuccess,
+ histSendTimeFail,
+ ]) {
+ h.clear();
+ }
+
+ // Submit a ping of a normal size and check that we don't count it in the histogram.
+ await TelemetryController.submitExternalPing(TEST_PING_TYPE, {
+ test: "test",
+ });
+ await TelemetrySend.testWaitOnOutgoingPings();
+ await PingServer.promiseNextPing();
+
+ Assert.equal(
+ histSizeExceeded.snapshot().sum,
+ 0,
+ "Telemetry must report no oversized ping submitted."
+ );
+ Assert.equal(
+ histDiscardedSize.snapshot().sum,
+ 0,
+ "Telemetry must report no oversized pings."
+ );
+ Assert.deepEqual(
+ histSuccess.snapshot().values,
+ { 0: 0, 1: 1, 2: 0 },
+ "Should have recorded sending success."
+ );
+ Assert.equal(
+ histogramValueCount(histSendTimeSuccess.snapshot()),
+ 1,
+ "Should have recorded send success time."
+ );
+ Assert.greaterOrEqual(
+ histSendTimeSuccess.snapshot().sum,
+ 0,
+ "Should have recorded send success time."
+ );
+ Assert.equal(
+ histogramValueCount(histSendTimeFail.snapshot()),
+ 0,
+ "Should not have recorded send failure time."
+ );
+
+ // Submit an oversized ping and check that it gets discarded.
+ TelemetryHealthPing.testReset();
+ // Ensure next ping has a 2 MB gzipped payload.
+ fakeGzipCompressStringForNextPing(2 * 1024 * 1024);
+ const OVERSIZED_PAYLOAD = {
+ data: "empty on purpose - policy takes care of size",
+ };
+ await TelemetryController.submitExternalPing(
+ TEST_PING_TYPE,
+ OVERSIZED_PAYLOAD
+ );
+ await TelemetrySend.testWaitOnOutgoingPings();
+ let ping = await PingServer.promiseNextPing();
+
+ Assert.equal(
+ ping.type,
+ TelemetryHealthPing.HEALTH_PING_TYPE,
+ "Should have received a health ping."
+ );
+ Assert.equal(
+ ping.payload.reason,
+ TelemetryHealthPing.Reason.IMMEDIATE,
+ "Health ping should have the right reason."
+ );
+ Assert.deepEqual(
+ ping.payload[TelemetryHealthPing.FailureType.DISCARDED_FOR_SIZE],
+ { [TEST_PING_TYPE]: 1 },
+ "Should have recorded correct type of oversized ping."
+ );
+ Assert.deepEqual(
+ ping.payload.os,
+ TelemetryHealthPing.OsInfo,
+ "Should have correct os info."
+ );
+
+ Assert.equal(
+ histSizeExceeded.snapshot().sum,
+ 1,
+ "Telemetry must report 1 oversized ping submitted."
+ );
+ Assert.equal(
+ histDiscardedSize.snapshot().values[2],
+ 1,
+ "Telemetry must report a 2MB, oversized, ping submitted."
+ );
+
+ Assert.deepEqual(
+ histSuccess.snapshot().values,
+ { 0: 0, 1: 2, 2: 0 },
+ "Should have recorded sending success."
+ );
+ Assert.equal(
+ histogramValueCount(histSendTimeSuccess.snapshot()),
+ 2,
+ "Should have recorded send success time."
+ );
+ Assert.greaterOrEqual(
+ histSendTimeSuccess.snapshot().sum,
+ 0,
+ "Should have recorded send success time."
+ );
+ Assert.equal(
+ histogramValueCount(histSendTimeFail.snapshot()),
+ 0,
+ "Should not have recorded send failure time."
+ );
+});
+
+add_task(async function test_largeButWithinLimit() {
+ const TEST_PING_TYPE = "test-ping-type";
+
+ let histSuccess = Telemetry.getHistogramById("TELEMETRY_SUCCESS");
+ histSuccess.clear();
+
+ // Next ping will have a 900KB gzip payload.
+ fakeGzipCompressStringForNextPing(900 * 1024);
+ const LARGE_PAYLOAD = {
+ data: "empty on purpose - policy takes care of size",
+ };
+
+ await TelemetryController.submitExternalPing(TEST_PING_TYPE, LARGE_PAYLOAD);
+ await TelemetrySend.testWaitOnOutgoingPings();
+ await PingServer.promiseNextRequest();
+
+ Assert.deepEqual(
+ histSuccess.snapshot().values,
+ { 0: 0, 1: 1, 2: 0 },
+ "Should have sent large ping."
+ );
+});
+
+add_task(async function test_evictedOnServerErrors() {
+ const TEST_TYPE = "test-evicted";
+
+ await TelemetrySend.reset();
+
+ let histEvicted = Telemetry.getHistogramById(
+ "TELEMETRY_PING_EVICTED_FOR_SERVER_ERRORS"
+ );
+ let histSuccess = Telemetry.getHistogramById("TELEMETRY_SUCCESS");
+ let histSendTimeSuccess = Telemetry.getHistogramById(
+ "TELEMETRY_SEND_SUCCESS"
+ );
+ let histSendTimeFail = Telemetry.getHistogramById("TELEMETRY_SEND_FAILURE");
+ for (let h of [
+ histEvicted,
+ histSuccess,
+ histSendTimeSuccess,
+ histSendTimeFail,
+ ]) {
+ h.clear();
+ }
+
+ // Write a custom ping handler which will return 403. This will trigger ping eviction
+ // on client side.
+ PingServer.registerPingHandler((req, res) => {
+ res.setStatusLine(null, 403, "Forbidden");
+ res.processAsync();
+ res.finish();
+ });
+
+ // Clear the histogram and submit a ping.
+ let pingId = await TelemetryController.submitExternalPing(TEST_TYPE, {});
+ await TelemetrySend.testWaitOnOutgoingPings();
+
+ Assert.equal(
+ histEvicted.snapshot().sum,
+ 1,
+ "Telemetry must report a ping evicted due to server errors"
+ );
+ Assert.deepEqual(histSuccess.snapshot().values, { 0: 0, 1: 1, 2: 0 });
+ Assert.equal(histogramValueCount(histSendTimeSuccess.snapshot()), 1);
+ Assert.greaterOrEqual(histSendTimeSuccess.snapshot().sum, 0);
+ Assert.equal(histogramValueCount(histSendTimeFail.snapshot()), 0);
+
+ // The ping should not be persisted.
+ await Assert.rejects(
+ TelemetryStorage.loadPendingPing(pingId),
+ /TelemetryStorage.loadPendingPing - no ping with id/,
+ "The ping must not be persisted."
+ );
+
+ // Reset the ping handler and submit a new ping.
+ PingServer.resetPingHandler();
+ pingId = await TelemetryController.submitExternalPing(TEST_TYPE, {});
+
+ let ping = await PingServer.promiseNextPings(1);
+ Assert.equal(ping[0].id, pingId, "The correct ping must be received");
+
+ // We should not have updated the error histogram.
+ await TelemetrySend.testWaitOnOutgoingPings();
+ Assert.equal(
+ histEvicted.snapshot().sum,
+ 1,
+ "Telemetry must report only one ping evicted due to server errors"
+ );
+ Assert.deepEqual(histSuccess.snapshot().values, { 0: 0, 1: 2, 2: 0 });
+ Assert.equal(histogramValueCount(histSendTimeSuccess.snapshot()), 2);
+ Assert.equal(histogramValueCount(histSendTimeFail.snapshot()), 0);
+});
+
+add_task(async function test_tooLateToSend() {
+ Assert.ok(true, "TEST BEGIN");
+ const TEST_TYPE = "test-too-late-to-send";
+
+ await TelemetrySend.reset();
+ PingServer.start();
+ PingServer.registerPingHandler(() =>
+ Assert.ok(false, "Should not have received any pings now")
+ );
+
+ Assert.equal(
+ TelemetrySend.pendingPingCount,
+ 0,
+ "Should have no pending pings yet"
+ );
+
+ TelemetrySend.testTooLateToSend(true);
+
+ const id = await TelemetryController.submitExternalPing(TEST_TYPE, {});
+
+ // Triggering a shutdown should persist the pings
+ await TelemetrySend.shutdown();
+ const pendingPings = TelemetryStorage.getPendingPingList();
+ Assert.equal(pendingPings.length, 1, "Should have a pending ping in storage");
+ Assert.equal(pendingPings[0].id, id, "Should have pended our test's ping");
+
+ Assert.equal(
+ Telemetry.getHistogramById("TELEMETRY_SEND_FAILURE_TYPE").snapshot()
+ .values[7],
+ 1,
+ "Should have registered the failed attempt to send"
+ );
+ Assert.equal(
+ Telemetry.getKeyedHistogramById(
+ "TELEMETRY_SEND_FAILURE_TYPE_PER_PING"
+ ).snapshot()[TEST_TYPE].values[7],
+ 1,
+ "Should have registered the failed attempt to send TEST_TYPE ping"
+ );
+ await TelemetryStorage.reset();
+ Assert.equal(
+ TelemetrySend.pendingPingCount,
+ 0,
+ "Should clean up after yourself"
+ );
+});
+
+add_task(
+ { skip_if: () => gIsAndroid },
+ async function test_pingSenderShutdownBatch() {
+ const TEST_TYPE = "test-ping-sender-batch";
+
+ await TelemetrySend.reset();
+ PingServer.start();
+ PingServer.registerPingHandler(() =>
+ Assert.ok(false, "Should not have received any pings at this time.")
+ );
+
+ Assert.equal(
+ TelemetrySend.pendingPingCount,
+ 0,
+ "Should have no pending pings yet"
+ );
+
+ TelemetrySend.testTooLateToSend(true);
+
+ const id = await TelemetryController.submitExternalPing(
+ TEST_TYPE,
+ { payload: false },
+ { usePingSender: true }
+ );
+ const id2 = await TelemetryController.submitExternalPing(
+ TEST_TYPE,
+ { payload: false },
+ { usePingSender: true }
+ );
+
+ Assert.equal(
+ TelemetrySend.pendingPingCount,
+ 2,
+ "Should have stored these two pings in pending land."
+ );
+
+ // Permit pings to be received.
+ PingServer.resetPingHandler();
+
+ TelemetrySend.flushPingSenderBatch();
+
+ // Pings don't have to be sent in the order they're submitted.
+ const ping = await PingServer.promiseNextPing();
+ const ping2 = await PingServer.promiseNextPing();
+ Assert.ok(
+ (ping.id == id && ping2.id == id2) || (ping.id == id2 && ping2.id == id)
+ );
+ Assert.equal(ping.type, TEST_TYPE);
+ Assert.equal(ping2.type, TEST_TYPE);
+
+ await TelemetryStorage.reset();
+ Assert.equal(
+ TelemetrySend.pendingPingCount,
+ 0,
+ "Should clean up after yourself"
+ );
+ }
+);
+
+// Test that the current, non-persisted pending pings are properly saved on shutdown.
+add_task(async function test_persistCurrentPingsOnShutdown() {
+ const TEST_TYPE = "test-persistCurrentPingsOnShutdown";
+ const PING_COUNT = 5;
+ await TelemetrySend.reset();
+ PingServer.stop();
+ Assert.equal(
+ TelemetrySend.pendingPingCount,
+ 0,
+ "Should have no pending pings yet"
+ );
+
+ // Submit new pings that shouldn't be persisted yet.
+ let ids = [];
+ for (let i = 0; i < 5; ++i) {
+ ids.push(fakePingId("f", i));
+ TelemetryController.submitExternalPing(TEST_TYPE, {});
+ }
+
+ Assert.equal(
+ TelemetrySend.pendingPingCount,
+ PING_COUNT,
+ "Should have the correct pending ping count"
+ );
+
+ // Triggering a shutdown should persist the pings.
+ await TelemetrySend.shutdown();
+ Assert.ok(
+ await checkPingsSaved(ids),
+ "All pending pings should have been persisted"
+ );
+
+ // After a restart the pings should have been found when scanning.
+ await TelemetrySend.reset();
+ Assert.equal(
+ TelemetrySend.pendingPingCount,
+ PING_COUNT,
+ "Should have the correct pending ping count"
+ );
+
+ // Restore the default ping id generator.
+ fakeGeneratePingId(() => TelemetryUtils.generateUUID());
+});
+
+add_task(async function test_sendCheckOverride() {
+ const TEST_PING_TYPE = "test-sendCheckOverride";
+
+ // Disable "health" ping. It can sneak into the test.
+ Services.prefs.setBoolPref(
+ TelemetryUtils.Preferences.HealthPingEnabled,
+ false
+ );
+
+ // Clear any pending pings.
+ await TelemetryController.testShutdown();
+ await TelemetryStorage.testClearPendingPings();
+
+ // Enable the ping server.
+ PingServer.start();
+ Services.prefs.setStringPref(
+ TelemetryUtils.Preferences.Server,
+ "http://localhost:" + PingServer.port
+ );
+
+ // Start Telemetry and disable the test-mode so pings don't get
+ // sent unless we enable the override.
+ await TelemetryController.testReset();
+
+ // Submit a test ping and make sure it doesn't get sent. We only do
+ // that if we're on unofficial builds: pings will always get sent otherwise.
+ if (!Services.telemetry.isOfficialTelemetry) {
+ TelemetrySend.setTestModeEnabled(false);
+ PingServer.registerPingHandler(() =>
+ Assert.ok(false, "Should not have received any pings now")
+ );
+
+ await TelemetryController.submitExternalPing(TEST_PING_TYPE, {
+ test: "test",
+ });
+ Assert.equal(
+ TelemetrySend.pendingPingCount,
+ 0,
+ "Should have no pending pings"
+ );
+ }
+
+ // Enable the override and try to send again.
+ Services.prefs.setBoolPref(
+ TelemetryUtils.Preferences.OverrideOfficialCheck,
+ true
+ );
+ PingServer.resetPingHandler();
+ await TelemetrySend.reset();
+ await TelemetryController.submitExternalPing(TEST_PING_TYPE, {
+ test: "test",
+ });
+
+ // Make sure we received the ping.
+ const ping = await PingServer.promiseNextPing();
+ Assert.equal(
+ ping.type,
+ TEST_PING_TYPE,
+ "Must receive a ping of the expected type"
+ );
+
+ // Restore the test mode and disable the override.
+ TelemetrySend.setTestModeEnabled(true);
+ Services.prefs.clearUserPref(
+ TelemetryUtils.Preferences.OverrideOfficialCheck
+ );
+});
+
+add_task(async function test_submissionPath() {
+ const PING_FORMAT_VERSION = 4;
+ const TEST_PING_TYPE = "test-ping-type";
+
+ await TelemetrySend.reset();
+ PingServer.clearRequests();
+
+ await sendPing(false, false);
+
+ // Fetch the request from the server.
+ let request = await PingServer.promiseNextRequest();
+
+ // Get the payload.
+ let ping = decodeRequestPayload(request);
+ checkPingFormat(ping, TEST_PING_TYPE, false, false);
+
+ let app = ping.application;
+ let pathComponents = [
+ ping.id,
+ ping.type,
+ app.name,
+ app.version,
+ app.channel,
+ app.buildId,
+ ];
+
+ let urlComponents = request.path.split("/");
+
+ for (let i = 0; i < pathComponents.length; i++) {
+ Assert.ok(
+ urlComponents.includes(pathComponents[i]),
+ `Path should include ${pathComponents[i]}`
+ );
+ }
+
+ // Check that we have a version query parameter in the URL.
+ Assert.notEqual(request.queryString, "");
+
+ // Make sure the version in the query string matches the new ping format version.
+ let params = request.queryString.split("&");
+ Assert.ok(params.find(p => p == "v=" + PING_FORMAT_VERSION));
+});
+
+add_task(async function testCookies() {
+ const TEST_TYPE = "test-cookies";
+
+ await TelemetrySend.reset();
+ PingServer.clearRequests();
+
+ let uri = Services.io.newURI("http://localhost:" + PingServer.port);
+ let channel = NetUtil.newChannel({
+ uri,
+ loadUsingSystemPrincipal: true,
+ contentPolicyType: Ci.nsIContentPolicy.TYPE_DOCUMENT,
+ });
+ Services.cookies.QueryInterface(Ci.nsICookieService);
+ Services.cookies.setCookieStringFromHttp(uri, "cookie-time=yes", channel);
+
+ const id = await TelemetryController.submitExternalPing(TEST_TYPE, {});
+ let foundit = false;
+ while (!foundit) {
+ var request = await PingServer.promiseNextRequest();
+ var ping = decodeRequestPayload(request);
+ foundit = id === ping.id;
+ }
+ Assert.equal(id, ping.id, "We're testing the right ping's request, right?");
+ Assert.equal(
+ false,
+ request.hasHeader("Cookie"),
+ "Request should not have Cookie header"
+ );
+});
+
+add_task(async function test_pref_observer() {
+ // This test requires the presence of the crash reporter component.
+ let registrar = Components.manager.QueryInterface(Ci.nsIComponentRegistrar);
+ if (
+ !registrar.isContractIDRegistered("@mozilla.org/toolkit/crash-reporter;1")
+ ) {
+ return;
+ }
+
+ await TelemetrySend.setup(true);
+
+ const IS_UNIFIED_TELEMETRY = Services.prefs.getBoolPref(
+ TelemetryUtils.Preferences.Unified,
+ false
+ );
+
+ let origTelemetryEnabled = Services.prefs.getBoolPref(
+ TelemetryUtils.Preferences.TelemetryEnabled
+ );
+ let origFhrUploadEnabled = Services.prefs.getBoolPref(
+ TelemetryUtils.Preferences.FhrUploadEnabled
+ );
+
+ if (!IS_UNIFIED_TELEMETRY) {
+ Services.prefs.setBoolPref(
+ TelemetryUtils.Preferences.TelemetryEnabled,
+ true
+ );
+ }
+ Services.prefs.setBoolPref(TelemetryUtils.Preferences.FhrUploadEnabled, true);
+
+ function waitAnnotateCrashReport(expectedValue, trigger) {
+ return new Promise(function (resolve, reject) {
+ let keys = new Set(["TelemetryClientId", "TelemetryServerURL"]);
+
+ let crs = {
+ QueryInterface: ChromeUtils.generateQI(["nsICrashReporter"]),
+ annotateCrashReport(key, value) {
+ if (!keys.delete(key)) {
+ MockRegistrar.unregister(gMockCrs);
+ reject(
+ Error(`Crash report annotation with unexpected key: "${key}".`)
+ );
+ }
+
+ if (expectedValue && value == "") {
+ MockRegistrar.unregister(gMockCrs);
+ reject(Error("Crash report annotation without expected value."));
+ }
+
+ if (keys.size == 0) {
+ MockRegistrar.unregister(gMockCrs);
+ resolve();
+ }
+ },
+ removeCrashReportAnnotation(key) {
+ if (!keys.delete(key)) {
+ MockRegistrar.unregister(gMockCrs);
+ }
+
+ if (keys.size == 0) {
+ MockRegistrar.unregister(gMockCrs);
+ resolve();
+ }
+ },
+ UpdateCrashEventsDir() {},
+ };
+
+ let gMockCrs = MockRegistrar.register(
+ "@mozilla.org/toolkit/crash-reporter;1",
+ crs
+ );
+ registerCleanupFunction(function () {
+ MockRegistrar.unregister(gMockCrs);
+ });
+
+ trigger();
+ });
+ }
+
+ await waitAnnotateCrashReport(!IS_UNIFIED_TELEMETRY, () =>
+ Services.prefs.setBoolPref(
+ TelemetryUtils.Preferences.FhrUploadEnabled,
+ false
+ )
+ );
+
+ await waitAnnotateCrashReport(true, () =>
+ Services.prefs.setBoolPref(
+ TelemetryUtils.Preferences.FhrUploadEnabled,
+ true
+ )
+ );
+
+ if (!IS_UNIFIED_TELEMETRY) {
+ Services.prefs.setBoolPref(
+ TelemetryUtils.Preferences.TelemetryEnabled,
+ origTelemetryEnabled
+ );
+ }
+ Services.prefs.setBoolPref(
+ TelemetryUtils.Preferences.FhrUploadEnabled,
+ origFhrUploadEnabled
+ );
+});
+
+add_task(async function cleanup() {
+ await PingServer.stop();
+});