summaryrefslogtreecommitdiffstats
path: root/toolkit/components/telemetry/tests/unit/test_SubsessionChaining.js
blob: c86fb049988e97016e59df3ea3f241f2d11fa90a (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
/* Any copyright is dedicated to the Public Domain.
   http://creativecommons.org/publicdomain/zero/1.0/
*/

Cu.import("resource://gre/modules/Preferences.jsm", this);
Cu.import("resource://gre/modules/Promise.jsm", this);
Cu.import("resource://gre/modules/Task.jsm", this);
Cu.import("resource://gre/modules/TelemetryArchive.jsm", this);
Cu.import("resource://gre/modules/TelemetryController.jsm", this);
Cu.import("resource://gre/modules/TelemetryEnvironment.jsm", this);
Cu.import("resource://gre/modules/osfile.jsm", this);
Cu.import("resource://gre/modules/XPCOMUtils.jsm", this);

const MS_IN_ONE_HOUR  = 60 * 60 * 1000;
const MS_IN_ONE_DAY   = 24 * MS_IN_ONE_HOUR;

const PREF_BRANCH = "toolkit.telemetry.";
const PREF_ARCHIVE_ENABLED = PREF_BRANCH + "archive.enabled";

const REASON_ABORTED_SESSION = "aborted-session";
const REASON_DAILY = "daily";
const REASON_ENVIRONMENT_CHANGE = "environment-change";
const REASON_SHUTDOWN = "shutdown";

XPCOMUtils.defineLazyGetter(this, "DATAREPORTING_PATH", function() {
  return OS.Path.join(OS.Constants.Path.profileDir, "datareporting");
});

var promiseValidateArchivedPings = Task.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 = yield 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 = yield 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 = yield TelemetryArchive.promiseArchivedPingById(list[i].id);
    let currentInfo = currentPing.payload.info;
    let previousInfo = previousPing.payload.info;
    do_print("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(function* test_setup() {
  do_test_pending();

  // Addon manager needs a profile directory
  do_get_profile();
  loadAddonManager("xpcshell@tests.mozilla.org", "XPCShell", "1", "1.9.2");
  // Make sure we don't generate unexpected pings due to pref changes.
  yield setEmptyPrefWatchlist();

  Preferences.set(PREF_TELEMETRY_ENABLED, true);
});

add_task(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.
  yield TelemetryController.testSetup();
  yield 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();
  yield 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, () => {});
  yield 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);
  yield 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);
  yield TelemetryController.testReset();
  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);
  yield 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);
  yield TelemetryController.testReset();

  // Delay the callback around midnight.
  now = fakeNow(futureDate(now, MS_IN_ONE_DAY));
  // Trigger the daily ping.
  yield 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);
  yield TelemetryController.testShutdown();
  expectedReasons.push(REASON_SHUTDOWN);

  // Start Telemetry and trigger an environment change.
  yield TelemetryController.testReset();
  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.
  yield schedulerTickCallback();
  expectedReasons.push(REASON_ABORTED_SESSION);

  // Start Telemetry and trigger a daily ping.
  moveClockForward(30);
  yield TelemetryController.testReset();
  // Delay the callback around midnight.
  now = futureDate(now, MS_IN_ONE_DAY);
  fakeNow(now);
  // Trigger the daily ping.
  yield 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.
  yield schedulerTickCallback();
  expectedReasons.push(REASON_ABORTED_SESSION);

  // Make sure the aborted-session ping gets archived.
  yield TelemetryController.testReset();

  yield promiseValidateArchivedPings(expectedReasons);
});

add_task(function* () {
  yield TelemetryController.testShutdown();
  do_test_finished();
});