1122 lines
32 KiB
JavaScript
1122 lines
32 KiB
JavaScript
/* 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 { ClientID } = ChromeUtils.importESModule(
|
|
"resource://gre/modules/ClientID.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
|
|
);
|
|
});
|
|
|
|
// 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(expectedAnyValue, trigger) {
|
|
return new Promise(function (resolve, reject) {
|
|
let keys = new Map([
|
|
["TelemetryClientId", ClientID.getCachedClientID()],
|
|
["TelemetryProfileGroupId", ClientID.getCachedProfileGroupID()],
|
|
["TelemetryServerURL", "http://localhost:" + PingServer.port],
|
|
]);
|
|
|
|
let crs = {
|
|
QueryInterface: ChromeUtils.generateQI(["nsICrashReporter"]),
|
|
annotateCrashReport(key, value) {
|
|
let expectedValue = expectedAnyValue ? keys.get(key) : "";
|
|
|
|
if (!keys.delete(key)) {
|
|
MockRegistrar.unregister(gMockCrs);
|
|
reject(
|
|
Error(`Crash report annotation with unexpected key: "${key}".`)
|
|
);
|
|
}
|
|
|
|
if (value != expectedValue) {
|
|
MockRegistrar.unregister(gMockCrs);
|
|
reject(
|
|
Error(
|
|
`Expected ${key} annotation to be ${expectedValue} but was ${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();
|
|
});
|