/* Any copyright is dedicated to the Public Domain. http://creativecommons.org/publicdomain/zero/1.0/ */ const { TelemetryArchive } = ChromeUtils.importESModule( "resource://gre/modules/TelemetryArchive.sys.mjs" ); const { TelemetryController } = ChromeUtils.importESModule( "resource://gre/modules/TelemetryController.sys.mjs" ); const { TelemetryEnvironment } = ChromeUtils.importESModule( "resource://gre/modules/TelemetryEnvironment.sys.mjs" ); const MS_IN_ONE_HOUR = 60 * 60 * 1000; const MS_IN_ONE_DAY = 24 * MS_IN_ONE_HOUR; const PREF_BRANCH = "toolkit.telemetry."; const REASON_ABORTED_SESSION = "aborted-session"; const REASON_DAILY = "daily"; const REASON_ENVIRONMENT_CHANGE = "environment-change"; const REASON_SHUTDOWN = "shutdown"; var promiseValidateArchivedPings = async function (aExpectedReasons) { // The list of ping reasons which mark the session end (and must reset the subsession // count). const SESSION_END_PING_REASONS = new Set([ REASON_ABORTED_SESSION, REASON_SHUTDOWN, ]); let list = await TelemetryArchive.promiseArchivedPingList(); // We're just interested in the "main" pings. list = list.filter(p => p.type == "main"); Assert.equal( aExpectedReasons.length, list.length, "All the expected pings must be received." ); let previousPing = await TelemetryArchive.promiseArchivedPingById(list[0].id); Assert.equal( aExpectedReasons.shift(), previousPing.payload.info.reason, "Telemetry should only get pings with expected reasons." ); Assert.equal( previousPing.payload.info.previousSessionId, null, "The first session must report a null previous session id." ); Assert.equal( previousPing.payload.info.previousSubsessionId, null, "The first subsession must report a null previous subsession id." ); Assert.equal( previousPing.payload.info.profileSubsessionCounter, 1, "profileSubsessionCounter must be 1 the first time." ); Assert.equal( previousPing.payload.info.subsessionCounter, 1, "subsessionCounter must be 1 the first time." ); let expectedSubsessionCounter = 1; let expectedPreviousSessionId = previousPing.payload.info.sessionId; for (let i = 1; i < list.length; i++) { let currentPing = await TelemetryArchive.promiseArchivedPingById( list[i].id ); let currentInfo = currentPing.payload.info; let previousInfo = previousPing.payload.info; info( "Archive entry " + i + " - id: " + currentPing.id + ", reason: " + currentInfo.reason ); Assert.equal( aExpectedReasons.shift(), currentInfo.reason, "Telemetry should only get pings with expected reasons." ); Assert.equal( currentInfo.previousSessionId, expectedPreviousSessionId, "Telemetry must correctly chain session identifiers." ); Assert.equal( currentInfo.previousSubsessionId, previousInfo.subsessionId, "Telemetry must correctly chain subsession identifiers." ); Assert.equal( currentInfo.profileSubsessionCounter, previousInfo.profileSubsessionCounter + 1, "Telemetry must correctly track the profile subsessions count." ); Assert.equal( currentInfo.subsessionCounter, expectedSubsessionCounter, "The subsession counter should be monotonically increasing." ); // Store the current ping as previous. previousPing = currentPing; // Reset the expected subsession counter, if required. Otherwise increment the expected // subsession counter. // If this is the final subsession of a session we need to update expected values accordingly. if (SESSION_END_PING_REASONS.has(currentInfo.reason)) { expectedSubsessionCounter = 1; expectedPreviousSessionId = currentInfo.sessionId; } else { expectedSubsessionCounter++; } } }; add_task(async function test_setup() { do_test_pending(); // Addon manager needs a profile directory do_get_profile(); 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(); }); add_task(async function test_subsessionsChaining() { if (gIsAndroid) { // We don't support subsessions yet on Android, so skip the next checks. return; } const PREF_TEST = PREF_BRANCH + "test.pref1"; const PREFS_TO_WATCH = new Map([ [PREF_TEST, { what: TelemetryEnvironment.RECORD_PREF_VALUE }], ]); Services.prefs.clearUserPref(PREF_TEST); // Fake the clock data to manually trigger an aborted-session ping and a daily ping. // This is also helpful to make sure we get the archived pings in an expected order. let now = fakeNow(2009, 9, 18, 0, 0, 0); let monotonicNow = fakeMonotonicNow(1000); let moveClockForward = minutes => { let ms = minutes * MILLISECONDS_PER_MINUTE; now = fakeNow(futureDate(now, ms)); monotonicNow = fakeMonotonicNow(monotonicNow + ms); }; // Keep track of the ping reasons we're expecting in this test. let expectedReasons = []; // Start and shut down Telemetry. We expect a shutdown ping with profileSubsessionCounter: 1, // subsessionCounter: 1, subsessionId: A, and previousSubsessionId: null to be archived. await TelemetryController.testSetup(); await TelemetryController.testShutdown(); expectedReasons.push(REASON_SHUTDOWN); // Start Telemetry but don't wait for it to initialise before shutting down. We expect a // shutdown ping with profileSubsessionCounter: 2, subsessionCounter: 1, subsessionId: B // and previousSubsessionId: A to be archived. moveClockForward(30); TelemetryController.testReset(); await TelemetryController.testShutdown(); expectedReasons.push(REASON_SHUTDOWN); // Start Telemetry and simulate an aborted-session ping. We expect an aborted-session ping // with profileSubsessionCounter: 3, subsessionCounter: 1, subsessionId: C and // previousSubsessionId: B to be archived. let schedulerTickCallback = null; fakeSchedulerTimer( callback => (schedulerTickCallback = callback), () => {} ); await TelemetryController.testReset(); moveClockForward(6); // Trigger the an aborted session ping save. When testing,we are not saving the aborted-session // ping as soon as Telemetry starts, otherwise we would end up with unexpected pings being // sent when calling |TelemetryController.testReset()|, thus breaking some tests. Assert.ok(!!schedulerTickCallback); await schedulerTickCallback(); expectedReasons.push(REASON_ABORTED_SESSION); // Start Telemetry and trigger an environment change through a pref modification. We expect // an environment-change ping with profileSubsessionCounter: 4, subsessionCounter: 1, // subsessionId: D and previousSubsessionId: C to be archived. moveClockForward(30); await TelemetryController.testReset(); await TelemetryEnvironment.testWatchPreferences(PREFS_TO_WATCH); moveClockForward(30); Services.prefs.setIntPref(PREF_TEST, 1); expectedReasons.push(REASON_ENVIRONMENT_CHANGE); // Shut down Telemetry. We expect a shutdown ping with profileSubsessionCounter: 5, // subsessionCounter: 2, subsessionId: E and previousSubsessionId: D to be archived. moveClockForward(30); await TelemetryController.testShutdown(); expectedReasons.push(REASON_SHUTDOWN); // Start Telemetry and trigger a daily ping. We expect a daily ping with // profileSubsessionCounter: 6, subsessionCounter: 1, subsessionId: F and // previousSubsessionId: E to be archived. moveClockForward(30); await TelemetryController.testReset(); // Delay the callback around midnight. now = fakeNow(futureDate(now, MS_IN_ONE_DAY)); // Trigger the daily ping. await schedulerTickCallback(); expectedReasons.push(REASON_DAILY); // Trigger an environment change ping. We expect an environment-changed ping with // profileSubsessionCounter: 7, subsessionCounter: 2, subsessionId: G and // previousSubsessionId: F to be archived. moveClockForward(30); Services.prefs.setIntPref(PREF_TEST, 0); expectedReasons.push(REASON_ENVIRONMENT_CHANGE); // Shut down Telemetry and trigger a shutdown ping. moveClockForward(30); await TelemetryController.testShutdown(); expectedReasons.push(REASON_SHUTDOWN); // Start Telemetry and trigger an environment change. await TelemetryController.testReset(); await TelemetryEnvironment.testWatchPreferences(PREFS_TO_WATCH); moveClockForward(30); Services.prefs.setIntPref(PREF_TEST, 1); expectedReasons.push(REASON_ENVIRONMENT_CHANGE); // Don't shut down, instead trigger an aborted-session ping. moveClockForward(6); // Trigger the an aborted session ping save. await schedulerTickCallback(); expectedReasons.push(REASON_ABORTED_SESSION); // Start Telemetry and trigger a daily ping. moveClockForward(30); await TelemetryController.testReset(); // Delay the callback around midnight. now = futureDate(now, MS_IN_ONE_DAY); fakeNow(now); // Trigger the daily ping. await schedulerTickCallback(); expectedReasons.push(REASON_DAILY); // Trigger an environment change. moveClockForward(30); Services.prefs.setIntPref(PREF_TEST, 0); expectedReasons.push(REASON_ENVIRONMENT_CHANGE); // And an aborted-session ping again. moveClockForward(6); // Trigger the an aborted session ping save. await schedulerTickCallback(); expectedReasons.push(REASON_ABORTED_SESSION); // Make sure the aborted-session ping gets archived. await TelemetryController.testReset(); await promiseValidateArchivedPings(expectedReasons); }); add_task(async function () { await TelemetryController.testShutdown(); do_test_finished(); });