Source code
Revision control
Copy as Markdown
Other Tools
Test Info: Warnings
- This test gets skipped with pattern: verify && os == 'win' OR os == 'android' && processor == 'x86_64'
- Manifest: toolkit/components/telemetry/tests/unit/xpcshell.toml
/* Any copyright is dedicated to the Public Domain.
*/
// This tests the public Telemetry API for submitting Health pings.
"use strict";
ChromeUtils.defineESModuleGetters(this, {
TelemetryHealthPing: "resource://gre/modules/HealthPing.sys.mjs",
});
function checkHealthPingStructure(ping, expectedFailuresDict) {
let payload = ping.payload;
Assert.equal(
ping.type,
TelemetryHealthPing.HEALTH_PING_TYPE,
"Should have recorded a health ping."
);
for (let [key, value] of Object.entries(expectedFailuresDict)) {
Assert.deepEqual(
payload[key],
value,
"Should have recorded correct entry with key: " + key
);
}
}
function fakeHealthSchedulerTimer(set, clear) {
let { Policy } = ChromeUtils.importESModule(
"resource://gre/modules/HealthPing.sys.mjs"
);
Policy.setSchedulerTickTimeout = set;
Policy.clearSchedulerTickTimeout = clear;
}
add_setup(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();
Services.prefs.setBoolPref(
TelemetryUtils.Preferences.HealthPingEnabled,
true
);
await TelemetryController.testSetup();
PingServer.start();
Services.prefs.setStringPref(
TelemetryUtils.Preferences.Server,
);
});
registerCleanupFunction(async function cleanup() {
await PingServer.stop();
});
add_task(async function test_sendImmediately() {
PingServer.clearRequests();
TelemetryHealthPing.testReset();
await TelemetryHealthPing.recordSendFailure("testProblem");
let ping = await PingServer.promiseNextPing();
checkHealthPingStructure(ping, {
[TelemetryHealthPing.FailureType.SEND_FAILURE]: {
testProblem: 1,
},
os: TelemetryHealthPing.OsInfo,
reason: TelemetryHealthPing.Reason.IMMEDIATE,
});
});
add_task(async function test_sendOnDelay() {
PingServer.clearRequests();
TelemetryHealthPing.testReset();
// This first failure should immediately trigger a ping. After this, subsequent failures should be throttled.
await TelemetryHealthPing.recordSendFailure("testFailure");
let testPing = await PingServer.promiseNextPing();
Assert.equal(
testPing.type,
TelemetryHealthPing.HEALTH_PING_TYPE,
"Should have recorded a health ping."
);
// Retrieve delayed call back.
let pingSubmissionCallBack = null;
fakeHealthSchedulerTimer(
callBack => (pingSubmissionCallBack = callBack),
() => {}
);
// Record two failures, health ping must not be send now.
await TelemetryHealthPing.recordSendFailure("testFailure");
await TelemetryHealthPing.recordSendFailure("testFailure");
// Wait for sending delayed health ping.
await pingSubmissionCallBack();
let ping = await PingServer.promiseNextPing();
checkHealthPingStructure(ping, {
[TelemetryHealthPing.FailureType.SEND_FAILURE]: {
testFailure: 2,
},
os: TelemetryHealthPing.OsInfo,
reason: TelemetryHealthPing.Reason.DELAYED,
});
});
add_task(async function test_sendOverSizedPing() {
TelemetryHealthPing.testReset();
PingServer.clearRequests();
let OVER_SIZED_PING_TYPE = "over-sized-ping";
let overSizedData = generateRandomString(2 * 1024 * 1024);
await TelemetryController.submitExternalPing(OVER_SIZED_PING_TYPE, {
data: overSizedData,
});
let ping = await PingServer.promiseNextPing();
checkHealthPingStructure(ping, {
[TelemetryHealthPing.FailureType.DISCARDED_FOR_SIZE]: {
[OVER_SIZED_PING_TYPE]: 1,
},
os: TelemetryHealthPing.OsInfo,
reason: TelemetryHealthPing.Reason.IMMEDIATE,
});
});
add_task(async function test_sendOnlyTopTenDiscardedPings() {
TelemetryHealthPing.testReset();
await TelemetrySend.reset();
PingServer.clearRequests();
let PING_TYPE = "sort-discarded";
// This first failure should immediately trigger a ping. After this, subsequent failures should be throttled.
await TelemetryHealthPing.recordSendFailure("testFailure");
let testPing = await PingServer.promiseNextPing();
Assert.equal(
testPing.type,
TelemetryHealthPing.HEALTH_PING_TYPE,
"Should have recorded a health ping."
);
// Retrieve delayed call back.
let pingSubmissionCallBack = null;
fakeHealthSchedulerTimer(
callBack => (pingSubmissionCallBack = callBack),
() => {}
);
// Add failures
for (let i = 1; i < 12; i++) {
for (let j = 1; j < i; j++) {
TelemetryHealthPing.recordDiscardedPing(PING_TYPE + i);
}
}
await TelemetrySend.reset();
await pingSubmissionCallBack();
let ping = await PingServer.promiseNextPing();
checkHealthPingStructure(ping, {
os: TelemetryHealthPing.OsInfo,
reason: TelemetryHealthPing.Reason.DELAYED,
[TelemetryHealthPing.FailureType.DISCARDED_FOR_SIZE]: {
[PING_TYPE + 11]: 10,
[PING_TYPE + 10]: 9,
[PING_TYPE + 9]: 8,
[PING_TYPE + 8]: 7,
[PING_TYPE + 7]: 6,
[PING_TYPE + 6]: 5,
[PING_TYPE + 5]: 4,
[PING_TYPE + 4]: 3,
[PING_TYPE + 3]: 2,
[PING_TYPE + 2]: 1,
},
});
});
add_task(async function test_discardedForSizePending() {
TelemetryHealthPing.testReset();
PingServer.clearRequests();
const PING_TYPE = "discarded-for-size-pending";
const OVERSIZED_PING_ID = "9b21ec8f-f762-4d28-a2c1-44e1c4694f24";
// Create a pending oversized ping.
let overSizedPayload = generateRandomString(2 * 1024 * 1024);
const OVERSIZED_PING = {
id: OVERSIZED_PING_ID,
type: PING_TYPE,
creationDate: new Date().toISOString(),
// Generate a 2MB string to use as the ping payload.
payload: overSizedPayload,
};
// Test loadPendingPing.
await TelemetryStorage.savePendingPing(OVERSIZED_PING);
// Try to manually load the oversized ping.
await Assert.rejects(
TelemetryStorage.loadPendingPing(OVERSIZED_PING_ID),
/loadPendingPing - exceeded the maximum ping size/,
"The oversized ping should have been pruned."
);
let ping = await PingServer.promiseNextPing();
checkHealthPingStructure(ping, {
[TelemetryHealthPing.FailureType.DISCARDED_FOR_SIZE]: {
"<unknown>": 1,
},
os: TelemetryHealthPing.OsInfo,
reason: TelemetryHealthPing.Reason.IMMEDIATE,
});
// Test _scanPendingPings.
TelemetryHealthPing.testReset();
await TelemetryStorage.savePendingPing(OVERSIZED_PING);
await TelemetryStorage.loadPendingPingList();
ping = await PingServer.promiseNextPing();
checkHealthPingStructure(ping, {
[TelemetryHealthPing.FailureType.DISCARDED_FOR_SIZE]: {
"<unknown>": 1,
},
os: TelemetryHealthPing.OsInfo,
reason: TelemetryHealthPing.Reason.IMMEDIATE,
});
});
add_task(async function test_usePingSenderOnShutdown() {
if (
gIsAndroid ||
(AppConstants.platform == "linux" && !Services.appinfo.is64Bit)
) {
// We also don't support the pingsender testing on Treeherder for
// Linux 32 bit (due to missing libraries). So skip it there too.
return;
}
TelemetryHealthPing.testReset();
await TelemetrySend.reset();
PingServer.clearRequests();
// This first failure should immediately trigger a ping.
// After this, subsequent failures should be throttled.
await TelemetryHealthPing.recordSendFailure("testFailure");
await PingServer.promiseNextPing();
TelemetryHealthPing.recordSendFailure("testFailure");
let nextRequest = PingServer.promiseNextRequest();
await TelemetryController.testReset();
await TelemetryController.testShutdown();
let request = await nextRequest;
let ping = decodeRequestPayload(request);
checkHealthPingStructure(ping, {
[TelemetryHealthPing.FailureType.SEND_FAILURE]: {
testFailure: 1,
},
os: TelemetryHealthPing.OsInfo,
reason: TelemetryHealthPing.Reason.SHUT_DOWN,
});
// Check that the health ping is sent at shutdown using the pingsender.
Assert.equal(
request.getHeader("User-Agent"),
"pingsender/1.0",
"Should have received the correct user agent string."
);
Assert.equal(
request.getHeader("X-PingSender-Version"),
"1.0",
"Should have received the correct PingSender version string."
);
});