|
1 /* Any copyright is dedicated to the Public Domain. |
|
2 * http://creativecommons.org/publicdomain/zero/1.0/ */ |
|
3 |
|
4 "use strict"; |
|
5 |
|
6 Cu.import("resource://testing-common/httpd.js"); |
|
7 Cu.import("resource://gre/modules/TelemetryLog.jsm"); |
|
8 let bsp = Cu.import("resource:///modules/experiments/Experiments.jsm"); |
|
9 |
|
10 |
|
11 const FILE_MANIFEST = "experiments.manifest"; |
|
12 const MANIFEST_HANDLER = "manifests/handler"; |
|
13 |
|
14 const SEC_IN_ONE_DAY = 24 * 60 * 60; |
|
15 const MS_IN_ONE_DAY = SEC_IN_ONE_DAY * 1000; |
|
16 |
|
17 |
|
18 let gProfileDir = null; |
|
19 let gHttpServer = null; |
|
20 let gHttpRoot = null; |
|
21 let gDataRoot = null; |
|
22 let gReporter = null; |
|
23 let gPolicy = null; |
|
24 let gManifestObject = null; |
|
25 let gManifestHandlerURI = null; |
|
26 |
|
27 const TLOG = bsp.TELEMETRY_LOG; |
|
28 |
|
29 let gGlobalScope = this; |
|
30 function loadAddonManager() { |
|
31 let ns = {}; |
|
32 Cu.import("resource://gre/modules/Services.jsm", ns); |
|
33 let head = "../../../../toolkit/mozapps/extensions/test/xpcshell/head_addons.js"; |
|
34 let file = do_get_file(head); |
|
35 let uri = ns.Services.io.newFileURI(file); |
|
36 ns.Services.scriptloader.loadSubScript(uri.spec, gGlobalScope); |
|
37 createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "1.9.2"); |
|
38 startupManager(); |
|
39 } |
|
40 |
|
41 function checkEvent(event, id, data) |
|
42 { |
|
43 do_print("Checking message " + id); |
|
44 Assert.equal(event[0], id, "id should match"); |
|
45 Assert.ok(event[1] > 0, "timestamp should be greater than 0"); |
|
46 |
|
47 if (data === undefined) { |
|
48 Assert.equal(event.length, 2, "event array should have 2 entries"); |
|
49 } else { |
|
50 Assert.equal(event.length, data.length + 2, "event entry count should match expected count"); |
|
51 for (var i = 0; i < data.length; ++i) { |
|
52 Assert.equal(typeof(event[i + 2]), "string", "event entry should be a string"); |
|
53 Assert.equal(event[i + 2], data[i], "event entry should match expected entry"); |
|
54 } |
|
55 } |
|
56 } |
|
57 |
|
58 function run_test() { |
|
59 run_next_test(); |
|
60 } |
|
61 |
|
62 add_task(function* test_setup() { |
|
63 loadAddonManager(); |
|
64 gProfileDir = do_get_profile(); |
|
65 |
|
66 gHttpServer = new HttpServer(); |
|
67 gHttpServer.start(-1); |
|
68 let port = gHttpServer.identity.primaryPort; |
|
69 gHttpRoot = "http://localhost:" + port + "/"; |
|
70 gDataRoot = gHttpRoot + "data/"; |
|
71 gManifestHandlerURI = gHttpRoot + MANIFEST_HANDLER; |
|
72 gHttpServer.registerDirectory("/data/", do_get_cwd()); |
|
73 gHttpServer.registerPathHandler("/" + MANIFEST_HANDLER, (request, response) => { |
|
74 response.setStatusLine(null, 200, "OK"); |
|
75 response.write(JSON.stringify(gManifestObject)); |
|
76 response.processAsync(); |
|
77 response.finish(); |
|
78 }); |
|
79 do_register_cleanup(() => gHttpServer.stop(() => {})); |
|
80 |
|
81 disableCertificateChecks(); |
|
82 |
|
83 Services.prefs.setBoolPref(PREF_EXPERIMENTS_ENABLED, true); |
|
84 Services.prefs.setIntPref(PREF_LOGGING_LEVEL, 0); |
|
85 Services.prefs.setBoolPref(PREF_LOGGING_DUMP, true); |
|
86 Services.prefs.setCharPref(PREF_MANIFEST_URI, gManifestHandlerURI); |
|
87 Services.prefs.setIntPref(PREF_FETCHINTERVAL, 0); |
|
88 |
|
89 gReporter = yield getReporter("json_payload_simple"); |
|
90 yield gReporter.collectMeasurements(); |
|
91 let payload = yield gReporter.getJSONPayload(true); |
|
92 do_register_cleanup(() => gReporter._shutdown()); |
|
93 |
|
94 gPolicy = new Experiments.Policy(); |
|
95 let dummyTimer = { cancel: () => {}, clear: () => {} }; |
|
96 patchPolicy(gPolicy, { |
|
97 updatechannel: () => "nightly", |
|
98 healthReportPayload: () => Promise.resolve(payload), |
|
99 oneshotTimer: (callback, timeout, thisObj, name) => dummyTimer, |
|
100 }); |
|
101 |
|
102 yield removeCacheFile(); |
|
103 }); |
|
104 |
|
105 // Test basic starting and stopping of experiments. |
|
106 |
|
107 add_task(function* test_telemetryBasics() { |
|
108 // Check TelemetryLog instead of TelemetryPing.getPayload().log because |
|
109 // TelemetryPing gets Experiments.instance() and side-effects log entries. |
|
110 |
|
111 const OBSERVER_TOPIC = "experiments-changed"; |
|
112 let observerFireCount = 0; |
|
113 let expectedLogLength = 0; |
|
114 |
|
115 // Dates the following tests are based on. |
|
116 |
|
117 let baseDate = new Date(2014, 5, 1, 12); |
|
118 let startDate1 = futureDate(baseDate, 50 * MS_IN_ONE_DAY); |
|
119 let endDate1 = futureDate(baseDate, 100 * MS_IN_ONE_DAY); |
|
120 let startDate2 = futureDate(baseDate, 150 * MS_IN_ONE_DAY); |
|
121 let endDate2 = futureDate(baseDate, 200 * MS_IN_ONE_DAY); |
|
122 |
|
123 // The manifest data we test with. |
|
124 |
|
125 gManifestObject = { |
|
126 "version": 1, |
|
127 experiments: [ |
|
128 { |
|
129 id: EXPERIMENT1_ID, |
|
130 xpiURL: gDataRoot + EXPERIMENT1_XPI_NAME, |
|
131 xpiHash: EXPERIMENT1_XPI_SHA1, |
|
132 startTime: dateToSeconds(startDate1), |
|
133 endTime: dateToSeconds(endDate1), |
|
134 maxActiveSeconds: 10 * SEC_IN_ONE_DAY, |
|
135 appName: ["XPCShell"], |
|
136 channel: ["nightly"], |
|
137 }, |
|
138 { |
|
139 id: EXPERIMENT2_ID, |
|
140 xpiURL: gDataRoot + EXPERIMENT2_XPI_NAME, |
|
141 xpiHash: EXPERIMENT2_XPI_SHA1, |
|
142 startTime: dateToSeconds(startDate2), |
|
143 endTime: dateToSeconds(endDate2), |
|
144 maxActiveSeconds: 10 * SEC_IN_ONE_DAY, |
|
145 appName: ["XPCShell"], |
|
146 channel: ["nightly"], |
|
147 }, |
|
148 ], |
|
149 }; |
|
150 |
|
151 // Data to compare the result of Experiments.getExperiments() against. |
|
152 |
|
153 let experimentListData = [ |
|
154 { |
|
155 id: EXPERIMENT2_ID, |
|
156 name: "Test experiment 2", |
|
157 description: "And yet another experiment that experiments experimentally.", |
|
158 }, |
|
159 { |
|
160 id: EXPERIMENT1_ID, |
|
161 name: EXPERIMENT1_NAME, |
|
162 description: "Yet another experiment that experiments experimentally.", |
|
163 }, |
|
164 ]; |
|
165 |
|
166 let experiments = new Experiments.Experiments(gPolicy); |
|
167 |
|
168 // Trigger update, clock set to before any activation. |
|
169 // Use updateManifest() to provide for coverage of that path. |
|
170 |
|
171 let now = baseDate; |
|
172 defineNow(gPolicy, now); |
|
173 |
|
174 yield experiments.updateManifest(); |
|
175 let list = yield experiments.getExperiments(); |
|
176 Assert.equal(list.length, 0, "Experiment list should be empty."); |
|
177 |
|
178 expectedLogLength += 2; |
|
179 let log = TelemetryLog.entries(); |
|
180 Assert.equal(log.length, expectedLogLength, "Telemetry log should have " + expectedLogLength + " entries."); |
|
181 checkEvent(log[log.length-2], TLOG.ACTIVATION_KEY, |
|
182 [TLOG.ACTIVATION.REJECTED, EXPERIMENT1_ID, "startTime"]); |
|
183 checkEvent(log[log.length-1], TLOG.ACTIVATION_KEY, |
|
184 [TLOG.ACTIVATION.REJECTED, EXPERIMENT2_ID, "startTime"]); |
|
185 |
|
186 // Trigger update, clock set for experiment 1 to start. |
|
187 |
|
188 now = futureDate(startDate1, 5 * MS_IN_ONE_DAY); |
|
189 defineNow(gPolicy, now); |
|
190 |
|
191 yield experiments.updateManifest(); |
|
192 list = yield experiments.getExperiments(); |
|
193 Assert.equal(list.length, 1, "Experiment list should have 1 entry now."); |
|
194 |
|
195 expectedLogLength += 1; |
|
196 log = TelemetryLog.entries(); |
|
197 Assert.equal(log.length, expectedLogLength, "Telemetry log should have " + expectedLogLength + " entries. Got " + log.toSource()); |
|
198 checkEvent(log[log.length-1], TLOG.ACTIVATION_KEY, |
|
199 [TLOG.ACTIVATION.ACTIVATED, EXPERIMENT1_ID]); |
|
200 |
|
201 // Trigger update, clock set for experiment 1 to stop. |
|
202 |
|
203 now = futureDate(endDate1, 1000); |
|
204 defineNow(gPolicy, now); |
|
205 |
|
206 yield experiments.updateManifest(); |
|
207 list = yield experiments.getExperiments(); |
|
208 Assert.equal(list.length, 1, "Experiment list should have 1 entry."); |
|
209 |
|
210 expectedLogLength += 2; |
|
211 log = TelemetryLog.entries(); |
|
212 Assert.equal(log.length, expectedLogLength, "Telemetry log should have " + expectedLogLength + " entries."); |
|
213 checkEvent(log[log.length-2], TLOG.TERMINATION_KEY, |
|
214 [TLOG.TERMINATION.EXPIRED, EXPERIMENT1_ID]); |
|
215 checkEvent(log[log.length-1], TLOG.ACTIVATION_KEY, |
|
216 [TLOG.ACTIVATION.REJECTED, EXPERIMENT2_ID, "startTime"]); |
|
217 |
|
218 // Trigger update, clock set for experiment 2 to start with invalid hash. |
|
219 |
|
220 now = startDate2; |
|
221 defineNow(gPolicy, now); |
|
222 gManifestObject.experiments[1].xpiHash = "sha1:0000000000000000000000000000000000000000"; |
|
223 |
|
224 yield experiments.updateManifest(); |
|
225 list = yield experiments.getExperiments(); |
|
226 Assert.equal(list.length, 1, "Experiment list should have 1 entries."); |
|
227 |
|
228 expectedLogLength += 1; |
|
229 log = TelemetryLog.entries(); |
|
230 Assert.equal(log.length, expectedLogLength, "Telemetry log should have " + expectedLogLength + " entries."); |
|
231 checkEvent(log[log.length-1], TLOG.ACTIVATION_KEY, |
|
232 [TLOG.ACTIVATION.INSTALL_FAILURE, EXPERIMENT2_ID]); |
|
233 |
|
234 // Trigger update, clock set for experiment 2 to properly start now. |
|
235 |
|
236 now = futureDate(now, MS_IN_ONE_DAY); |
|
237 defineNow(gPolicy, now); |
|
238 gManifestObject.experiments[1].xpiHash = EXPERIMENT2_XPI_SHA1; |
|
239 |
|
240 yield experiments.updateManifest(); |
|
241 list = yield experiments.getExperiments(); |
|
242 Assert.equal(list.length, 2, "Experiment list should have 2 entries."); |
|
243 |
|
244 expectedLogLength += 1; |
|
245 log = TelemetryLog.entries(); |
|
246 Assert.equal(log.length, expectedLogLength, "Telemetry log should have " + expectedLogLength + " entries."); |
|
247 checkEvent(log[log.length-1], TLOG.ACTIVATION_KEY, |
|
248 [TLOG.ACTIVATION.ACTIVATED, EXPERIMENT2_ID]); |
|
249 |
|
250 // Fake user uninstall of experiment via add-on manager. |
|
251 |
|
252 now = futureDate(now, MS_IN_ONE_DAY); |
|
253 defineNow(gPolicy, now); |
|
254 |
|
255 yield experiments.disableExperiment(TLOG.TERMINATION.ADDON_UNINSTALLED); |
|
256 list = yield experiments.getExperiments(); |
|
257 Assert.equal(list.length, 2, "Experiment list should have 2 entries."); |
|
258 |
|
259 expectedLogLength += 1; |
|
260 log = TelemetryLog.entries(); |
|
261 Assert.equal(log.length, expectedLogLength, "Telemetry log should have " + expectedLogLength + " entries."); |
|
262 checkEvent(log[log.length-1], TLOG.TERMINATION_KEY, |
|
263 [TLOG.TERMINATION.ADDON_UNINSTALLED, EXPERIMENT2_ID]); |
|
264 |
|
265 // Trigger update with experiment 1a ready to start. |
|
266 |
|
267 now = futureDate(now, MS_IN_ONE_DAY); |
|
268 defineNow(gPolicy, now); |
|
269 gManifestObject.experiments[0].id = EXPERIMENT3_ID; |
|
270 gManifestObject.experiments[0].endTime = dateToSeconds(futureDate(now, 50 * MS_IN_ONE_DAY)); |
|
271 |
|
272 yield experiments.updateManifest(); |
|
273 list = yield experiments.getExperiments(); |
|
274 Assert.equal(list.length, 3, "Experiment list should have 3 entries."); |
|
275 |
|
276 expectedLogLength += 1; |
|
277 log = TelemetryLog.entries(); |
|
278 Assert.equal(log.length, expectedLogLength, "Telemetry log should have " + expectedLogLength + " entries."); |
|
279 checkEvent(log[log.length-1], TLOG.ACTIVATION_KEY, |
|
280 [TLOG.ACTIVATION.ACTIVATED, EXPERIMENT3_ID]); |
|
281 |
|
282 // Trigger disable of an experiment via the API. |
|
283 |
|
284 now = futureDate(now, MS_IN_ONE_DAY); |
|
285 defineNow(gPolicy, now); |
|
286 |
|
287 yield experiments.disableExperiment(TLOG.TERMINATION.FROM_API); |
|
288 list = yield experiments.getExperiments(); |
|
289 Assert.equal(list.length, 3, "Experiment list should have 3 entries."); |
|
290 |
|
291 expectedLogLength += 1; |
|
292 log = TelemetryLog.entries(); |
|
293 Assert.equal(log.length, expectedLogLength, "Telemetry log should have " + expectedLogLength + " entries."); |
|
294 checkEvent(log[log.length-1], TLOG.TERMINATION_KEY, |
|
295 [TLOG.TERMINATION.FROM_API, EXPERIMENT3_ID]); |
|
296 |
|
297 // Trigger update with experiment 1a ready to start. |
|
298 |
|
299 now = futureDate(now, MS_IN_ONE_DAY); |
|
300 defineNow(gPolicy, now); |
|
301 gManifestObject.experiments[0].id = EXPERIMENT4_ID; |
|
302 gManifestObject.experiments[0].endTime = dateToSeconds(futureDate(now, 50 * MS_IN_ONE_DAY)); |
|
303 |
|
304 yield experiments.updateManifest(); |
|
305 list = yield experiments.getExperiments(); |
|
306 Assert.equal(list.length, 4, "Experiment list should have 4 entries."); |
|
307 |
|
308 expectedLogLength += 1; |
|
309 log = TelemetryLog.entries(); |
|
310 Assert.equal(log.length, expectedLogLength, "Telemetry log should have " + expectedLogLength + " entries."); |
|
311 checkEvent(log[log.length-1], TLOG.ACTIVATION_KEY, |
|
312 [TLOG.ACTIVATION.ACTIVATED, EXPERIMENT4_ID]); |
|
313 |
|
314 // Trigger experiment termination by something other than expiry via the manifest. |
|
315 |
|
316 now = futureDate(now, MS_IN_ONE_DAY); |
|
317 defineNow(gPolicy, now); |
|
318 gManifestObject.experiments[0].os = "Plan9"; |
|
319 |
|
320 yield experiments.updateManifest(); |
|
321 list = yield experiments.getExperiments(); |
|
322 Assert.equal(list.length, 4, "Experiment list should have 4 entries."); |
|
323 |
|
324 expectedLogLength += 1; |
|
325 log = TelemetryLog.entries(); |
|
326 Assert.equal(log.length, expectedLogLength, "Telemetry log should have " + expectedLogLength + " entries."); |
|
327 checkEvent(log[log.length-1], TLOG.TERMINATION_KEY, |
|
328 [TLOG.TERMINATION.RECHECK, EXPERIMENT4_ID, "os"]); |
|
329 |
|
330 // Cleanup. |
|
331 |
|
332 yield experiments.uninit(); |
|
333 yield removeCacheFile(); |
|
334 }); |