summaryrefslogtreecommitdiffstats
path: root/toolkit/components/telemetry/tests/unit/test_SubsessionChaining.js
blob: 4ffa10f879eb5b969bf67e2dcc1cc6d7c62456b6 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
/* Any copyright is dedicated to the Public Domain.
   http://creativecommons.org/publicdomain/zero/1.0/
*/

const { Preferences } = ChromeUtils.importESModule(
  "resource://gre/modules/Preferences.sys.mjs"
);
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 }],
  ]);
  Preferences.reset(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);
  Preferences.set(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);
  Preferences.set(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);
  Preferences.set(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);
  Preferences.set(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();
});