services/healthreport/tests/xpcshell/test_healthreporter.js

branch
TOR_BUG_3246
changeset 5
4ab42b5ab56c
equal deleted inserted replaced
-1:000000000000 0:322647e8219c
1 /* Any copyright is dedicated to the Public Domain.
2 * http://creativecommons.org/publicdomain/zero/1.0/ */
3
4 "use strict";
5
6 const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
7
8 Cu.import("resource://services-common/observers.js");
9 Cu.import("resource://services-common/utils.js");
10 Cu.import("resource://gre/modules/Promise.jsm");
11 Cu.import("resource://gre/modules/Metrics.jsm");
12 Cu.import("resource://gre/modules/osfile.jsm");
13 Cu.import("resource://gre/modules/Preferences.jsm");
14 let bsp = Cu.import("resource://gre/modules/services/healthreport/healthreporter.jsm");
15 Cu.import("resource://gre/modules/services/healthreport/providers.jsm");
16 Cu.import("resource://gre/modules/services/datareporting/policy.jsm");
17 Cu.import("resource://gre/modules/Services.jsm");
18 Cu.import("resource://gre/modules/Task.jsm");
19 Cu.import("resource://testing-common/httpd.js");
20 Cu.import("resource://testing-common/services-common/bagheeraserver.js");
21 Cu.import("resource://testing-common/services/metrics/mocks.jsm");
22 Cu.import("resource://testing-common/services/healthreport/utils.jsm");
23 Cu.import("resource://testing-common/AppData.jsm");
24
25
26 const DUMMY_URI = "http://localhost:62013/";
27 const MILLISECONDS_PER_DAY = 24 * 60 * 60 * 1000;
28
29 const HealthReporterState = bsp.HealthReporterState;
30
31
32 function defineNow(policy, now) {
33 print("Adjusting fake system clock to " + now);
34 Object.defineProperty(policy, "now", {
35 value: function customNow() {
36 return now;
37 },
38 writable: true,
39 });
40 }
41
42 function getReporter(name, uri, inspected) {
43 return Task.spawn(function init() {
44 let reporter = getHealthReporter(name, uri, inspected);
45 yield reporter.init();
46
47 yield reporter._providerManager.registerProviderFromType(
48 HealthReportProvider);
49
50 throw new Task.Result(reporter);
51 });
52 }
53
54 function getReporterAndServer(name, namespace="test") {
55 return Task.spawn(function get() {
56 let server = new BagheeraServer();
57 server.createNamespace(namespace);
58 server.start();
59
60 let reporter = yield getReporter(name, server.serverURI);
61 reporter.serverNamespace = namespace;
62
63 throw new Task.Result([reporter, server]);
64 });
65 }
66
67 function shutdownServer(server) {
68 let deferred = Promise.defer();
69 server.stop(deferred.resolve.bind(deferred));
70
71 return deferred.promise;
72 }
73
74 function getHealthReportProviderValues(reporter, day=null) {
75 return Task.spawn(function getValues() {
76 let p = reporter.getProvider("org.mozilla.healthreport");
77 do_check_neq(p, null);
78 let m = p.getMeasurement("submissions", 2);
79 do_check_neq(m, null);
80
81 let data = yield reporter._storage.getMeasurementValues(m.id);
82 if (!day) {
83 throw new Task.Result(data);
84 }
85
86 do_check_true(data.days.hasDay(day));
87 let serializer = m.serializer(m.SERIALIZE_JSON)
88 let json = serializer.daily(data.days.getDay(day));
89 do_check_eq(json._v, 2);
90
91 throw new Task.Result(json);
92 });
93 }
94
95 function run_test() {
96 run_next_test();
97 }
98
99 // run_test() needs to finish synchronously, so we do async init here.
100 add_task(function test_init() {
101 yield makeFakeAppDir();
102 });
103
104 add_task(function test_constructor() {
105 let reporter = yield getReporter("constructor");
106
107 try {
108 do_check_eq(reporter.lastPingDate.getTime(), 0);
109 do_check_null(reporter.lastSubmitID);
110 do_check_eq(typeof(reporter._state), "object");
111 do_check_eq(reporter._state.lastPingDate.getTime(), 0);
112 do_check_eq(reporter._state.remoteIDs.length, 0);
113 do_check_eq(reporter._state.clientIDVersion, 1);
114 do_check_neq(reporter._state.clientID, null);
115
116 let failed = false;
117 try {
118 new HealthReporter("foo.bar");
119 } catch (ex) {
120 failed = true;
121 do_check_true(ex.message.startsWith("Branch must end"));
122 } finally {
123 do_check_true(failed);
124 failed = false;
125 }
126 } finally {
127 reporter._shutdown();
128 }
129 });
130
131 add_task(function test_shutdown_normal() {
132 let reporter = yield getReporter("shutdown_normal");
133
134 // We can't send "quit-application" notification because the xpcshell runner
135 // will shut down!
136 reporter._initiateShutdown();
137 reporter._waitForShutdown();
138 });
139
140 add_task(function test_shutdown_storage_in_progress() {
141 let reporter = yield getHealthReporter("shutdown_storage_in_progress", DUMMY_URI, true);
142
143 reporter.onStorageCreated = function () {
144 print("Faking shutdown during storage initialization.");
145 reporter._initiateShutdown();
146 };
147
148 reporter.init();
149
150 reporter._waitForShutdown();
151 do_check_eq(reporter.providerManagerShutdownCount, 0);
152 do_check_eq(reporter.storageCloseCount, 1);
153 });
154
155 // Ensure that a shutdown triggered while provider manager is initializing
156 // results in shutdown and storage closure.
157 add_task(function test_shutdown_provider_manager_in_progress() {
158 let reporter = yield getHealthReporter("shutdown_provider_manager_in_progress",
159 DUMMY_URI, true);
160
161 reporter.onProviderManagerInitialized = function () {
162 print("Faking shutdown during provider manager initialization.");
163 reporter._initiateShutdown();
164 };
165
166 reporter.init();
167
168 // This will hang if shutdown logic is busted.
169 reporter._waitForShutdown();
170 do_check_eq(reporter.providerManagerShutdownCount, 1);
171 do_check_eq(reporter.storageCloseCount, 1);
172 });
173
174 // Simulates an error during provider manager initialization and verifies we shut down.
175 add_task(function test_shutdown_when_provider_manager_errors() {
176 let reporter = yield getHealthReporter("shutdown_when_provider_manager_errors",
177 DUMMY_URI, true);
178
179 reporter.onInitializeProviderManagerFinished = function () {
180 print("Throwing fake error.");
181 throw new Error("Fake error during provider manager initialization.");
182 };
183
184 reporter.init();
185
186 // This will hang if shutdown logic is busted.
187 reporter._waitForShutdown();
188 do_check_eq(reporter.providerManagerShutdownCount, 1);
189 do_check_eq(reporter.storageCloseCount, 1);
190 });
191
192 // Pull-only providers are only initialized at collect time.
193 add_task(function test_pull_only_providers() {
194 const category = "healthreporter-constant-only";
195
196 let cm = Cc["@mozilla.org/categorymanager;1"]
197 .getService(Ci.nsICategoryManager);
198 cm.addCategoryEntry(category, "DummyProvider",
199 "resource://testing-common/services/metrics/mocks.jsm",
200 false, true);
201 cm.addCategoryEntry(category, "DummyConstantProvider",
202 "resource://testing-common/services/metrics/mocks.jsm",
203 false, true);
204
205 let reporter = yield getReporter("constant_only_providers");
206 try {
207 let initCount = reporter._providerManager.providers.length;
208 yield reporter._providerManager.registerProvidersFromCategoryManager(category);
209 do_check_eq(reporter._providerManager._providers.size, initCount + 1);
210 do_check_true(reporter._storage.hasProvider("DummyProvider"));
211 do_check_false(reporter._storage.hasProvider("DummyConstantProvider"));
212 do_check_neq(reporter.getProvider("DummyProvider"), null);
213 do_check_null(reporter.getProvider("DummyConstantProvider"));
214
215 yield reporter.collectMeasurements();
216
217 do_check_eq(reporter._providerManager._providers.size, initCount + 1);
218 do_check_true(reporter._storage.hasProvider("DummyConstantProvider"));
219
220 let mID = reporter._storage.measurementID("DummyConstantProvider", "DummyMeasurement", 1);
221 let values = yield reporter._storage.getMeasurementValues(mID);
222 do_check_true(values.singular.size > 0);
223 } finally {
224 reporter._shutdown();
225 }
226 });
227
228 add_task(function test_collect_daily() {
229 let reporter = yield getReporter("collect_daily");
230
231 try {
232 let now = new Date();
233 let provider = new DummyProvider();
234 yield reporter._providerManager.registerProvider(provider);
235 yield reporter.collectMeasurements();
236
237 do_check_eq(provider.collectConstantCount, 1);
238 do_check_eq(provider.collectDailyCount, 1);
239
240 yield reporter.collectMeasurements();
241 do_check_eq(provider.collectConstantCount, 1);
242 do_check_eq(provider.collectDailyCount, 1);
243
244 yield reporter.collectMeasurements();
245 do_check_eq(provider.collectDailyCount, 1); // Too soon.
246
247 reporter._lastDailyDate = now.getTime() - MILLISECONDS_PER_DAY - 1;
248 yield reporter.collectMeasurements();
249 do_check_eq(provider.collectDailyCount, 2);
250
251 reporter._lastDailyDate = null;
252 yield reporter.collectMeasurements();
253 do_check_eq(provider.collectDailyCount, 3);
254 } finally {
255 reporter._shutdown();
256 }
257 });
258
259 add_task(function test_remove_old_lastpayload() {
260 let reporter = getHealthReporter("remove-old-lastpayload");
261 let lastPayloadPath = reporter._state._lastPayloadPath;
262 let paths = [lastPayloadPath, lastPayloadPath + ".tmp"];
263 let createFiles = function () {
264 return Task.spawn(function createFiles() {
265 for (let path of paths) {
266 yield OS.File.writeAtomic(path, "delete-me", {tmpPath: path + ".tmp"});
267 do_check_true(yield OS.File.exists(path));
268 }
269 });
270 };
271 try {
272 do_check_true(!reporter._state.removedOutdatedLastpayload);
273 yield createFiles();
274 yield reporter.init();
275 for (let path of paths) {
276 do_check_false(yield OS.File.exists(path));
277 }
278 yield reporter._state.save();
279 reporter._shutdown();
280
281 let o = yield CommonUtils.readJSON(reporter._state._filename);
282 do_check_true(o.removedOutdatedLastpayload);
283
284 yield createFiles();
285 reporter = getHealthReporter("remove-old-lastpayload");
286 yield reporter.init();
287 for (let path of paths) {
288 do_check_true(yield OS.File.exists(path));
289 }
290 } finally {
291 reporter._shutdown();
292 }
293 });
294
295 add_task(function test_json_payload_simple() {
296 let reporter = yield getReporter("json_payload_simple");
297
298 let clientID = reporter._state.clientID;
299 do_check_neq(clientID, null);
300
301 try {
302 let now = new Date();
303 let payload = yield reporter.getJSONPayload();
304 do_check_eq(typeof payload, "string");
305 let original = JSON.parse(payload);
306
307 do_check_eq(original.version, 2);
308 do_check_eq(original.thisPingDate, reporter._formatDate(now));
309 do_check_eq(original.clientID, clientID);
310 do_check_eq(original.clientIDVersion, reporter._state.clientIDVersion);
311 do_check_eq(original.clientIDVersion, 1);
312 do_check_eq(Object.keys(original.data.last).length, 0);
313 do_check_eq(Object.keys(original.data.days).length, 0);
314 do_check_false("notInitialized" in original);
315
316 yield reporter._state.setLastPingDate(
317 new Date(now.getTime() - 24 * 60 * 60 * 1000 - 10));
318
319 original = JSON.parse(yield reporter.getJSONPayload());
320 do_check_eq(original.lastPingDate, reporter._formatDate(reporter.lastPingDate));
321 do_check_eq(original.clientID, clientID);
322
323 // This could fail if we cross UTC day boundaries at the exact instance the
324 // test is executed. Let's tempt fate.
325 do_check_eq(original.thisPingDate, reporter._formatDate(now));
326
327 payload = yield reporter.getJSONPayload(true);
328 do_check_eq(typeof payload, "object");
329 } finally {
330 reporter._shutdown();
331 }
332 });
333
334 add_task(function test_json_payload_dummy_provider() {
335 let reporter = yield getReporter("json_payload_dummy_provider");
336
337 try {
338 yield reporter._providerManager.registerProvider(new DummyProvider());
339 yield reporter.collectMeasurements();
340 let payload = yield reporter.getJSONPayload();
341 print(payload);
342 let o = JSON.parse(payload);
343
344 let name = "DummyProvider.DummyMeasurement";
345 do_check_eq(Object.keys(o.data.last).length, 1);
346 do_check_true(name in o.data.last);
347 do_check_eq(o.data.last[name]._v, 1);
348 } finally {
349 reporter._shutdown();
350 }
351 });
352
353 add_task(function test_collect_and_obtain_json_payload() {
354 let reporter = yield getReporter("collect_and_obtain_json_payload");
355
356 try {
357 yield reporter._providerManager.registerProvider(new DummyProvider());
358 let payload = yield reporter.collectAndObtainJSONPayload();
359 do_check_eq(typeof payload, "string");
360
361 let o = JSON.parse(payload);
362 do_check_true("DummyProvider.DummyMeasurement" in o.data.last);
363
364 payload = yield reporter.collectAndObtainJSONPayload(true);
365 do_check_eq(typeof payload, "object");
366 } finally {
367 reporter._shutdown();
368 }
369 });
370
371 // Ensure constant-only providers make their way into the JSON payload.
372 add_task(function test_constant_only_providers_in_json_payload() {
373 const category = "healthreporter-constant-only-in-payload";
374
375 let cm = Cc["@mozilla.org/categorymanager;1"]
376 .getService(Ci.nsICategoryManager);
377 cm.addCategoryEntry(category, "DummyProvider",
378 "resource://testing-common/services/metrics/mocks.jsm",
379 false, true);
380 cm.addCategoryEntry(category, "DummyConstantProvider",
381 "resource://testing-common/services/metrics/mocks.jsm",
382 false, true);
383
384 let reporter = yield getReporter("constant_only_providers_in_json_payload");
385 try {
386 let initCount = reporter._providerManager.providers.length;
387 yield reporter._providerManager.registerProvidersFromCategoryManager(category);
388
389 let payload = yield reporter.collectAndObtainJSONPayload();
390 let o = JSON.parse(payload);
391 do_check_true("DummyProvider.DummyMeasurement" in o.data.last);
392 do_check_true("DummyConstantProvider.DummyMeasurement" in o.data.last);
393
394 let providers = reporter._providerManager.providers;
395 do_check_eq(providers.length, initCount + 1);
396
397 // Do it again for good measure.
398 payload = yield reporter.collectAndObtainJSONPayload();
399 o = JSON.parse(payload);
400 do_check_true("DummyProvider.DummyMeasurement" in o.data.last);
401 do_check_true("DummyConstantProvider.DummyMeasurement" in o.data.last);
402
403 providers = reporter._providerManager.providers;
404 do_check_eq(providers.length, initCount + 1);
405
406 // Ensure throwing getJSONPayload is handled properly.
407 Object.defineProperty(reporter, "_getJSONPayload", {
408 value: function () {
409 throw new Error("Silly error.");
410 },
411 });
412
413 let deferred = Promise.defer();
414
415 reporter.collectAndObtainJSONPayload().then(do_throw, function onError() {
416 providers = reporter._providerManager.providers;
417 do_check_eq(providers.length, initCount + 1);
418 deferred.resolve();
419 });
420
421 yield deferred.promise;
422 } finally {
423 reporter._shutdown();
424 }
425 });
426
427 add_task(function test_json_payload_multiple_days() {
428 let reporter = yield getReporter("json_payload_multiple_days");
429
430 try {
431 let provider = new DummyProvider();
432 yield reporter._providerManager.registerProvider(provider);
433
434 let now = new Date();
435
436 let m = provider.getMeasurement("DummyMeasurement", 1);
437 for (let i = 0; i < 200; i++) {
438 let date = new Date(now.getTime() - i * MILLISECONDS_PER_DAY);
439 yield m.incrementDailyCounter("daily-counter", date);
440 }
441
442 // This test could fail if we cross a UTC day boundary when running. So,
443 // we ensure this doesn't occur.
444 Object.defineProperty(reporter, "_now", {
445 value: function () {
446 return now;
447 },
448 });
449
450 let payload = yield reporter.getJSONPayload();
451 print(payload);
452 let o = JSON.parse(payload);
453
454 do_check_eq(Object.keys(o.data.days).length, 180);
455 let today = reporter._formatDate(now);
456 do_check_true(today in o.data.days);
457 } finally {
458 reporter._shutdown();
459 }
460 });
461
462 add_task(function test_json_payload_newer_version_overwrites() {
463 let reporter = yield getReporter("json_payload_newer_version_overwrites");
464
465 try {
466 let now = new Date();
467 // Instead of hacking up the internals to ensure consistent order in Map
468 // iteration (which would be difficult), we instead opt to generate a lot
469 // of measurements of different versions and verify their iterable order
470 // is not increasing.
471 let versions = [1, 6, 3, 9, 2, 3, 7, 4, 10, 8];
472 let protos = [];
473 for (let version of versions) {
474 let m = function () {
475 Metrics.Measurement.call(this);
476 };
477 m.prototype = {
478 __proto__: DummyMeasurement.prototype,
479 name: "DummyMeasurement",
480 version: version,
481 };
482
483 protos.push(m);
484 }
485
486 let ctor = function () {
487 Metrics.Provider.call(this);
488 };
489 ctor.prototype = {
490 __proto__: DummyProvider.prototype,
491
492 name: "MultiMeasurementProvider",
493 measurementTypes: protos,
494 };
495
496 let provider = new ctor();
497
498 yield reporter._providerManager.registerProvider(provider);
499
500 let haveUnordered = false;
501 let last = -1;
502 let highestVersion = -1;
503 for (let [key, measurement] of provider.measurements) {
504 yield measurement.setDailyLastNumeric("daily-last-numeric",
505 measurement.version, now);
506 yield measurement.setLastNumeric("last-numeric",
507 measurement.version, now);
508
509 if (measurement.version > highestVersion) {
510 highestVersion = measurement.version;
511 }
512
513 if (measurement.version < last) {
514 haveUnordered = true;
515 }
516
517 last = measurement.version;
518 }
519
520 // Ensure Map traversal isn't ordered. If this ever fails, then we'll need
521 // to monkeypatch.
522 do_check_true(haveUnordered);
523
524 let payload = yield reporter.getJSONPayload();
525 let o = JSON.parse(payload);
526 do_check_true("MultiMeasurementProvider.DummyMeasurement" in o.data.last);
527 do_check_eq(o.data.last["MultiMeasurementProvider.DummyMeasurement"]._v, highestVersion);
528
529 let day = reporter._formatDate(now);
530 do_check_true(day in o.data.days);
531 do_check_true("MultiMeasurementProvider.DummyMeasurement" in o.data.days[day]);
532 do_check_eq(o.data.days[day]["MultiMeasurementProvider.DummyMeasurement"]._v, highestVersion);
533
534 } finally {
535 reporter._shutdown();
536 }
537 });
538
539 add_task(function test_idle_daily() {
540 let reporter = yield getReporter("idle_daily");
541 try {
542 let provider = new DummyProvider();
543 yield reporter._providerManager.registerProvider(provider);
544
545 let now = new Date();
546 let m = provider.getMeasurement("DummyMeasurement", 1);
547 for (let i = 0; i < 200; i++) {
548 let date = new Date(now.getTime() - i * MILLISECONDS_PER_DAY);
549 yield m.incrementDailyCounter("daily-counter", date);
550 }
551
552 let values = yield m.getValues();
553 do_check_eq(values.days.size, 200);
554
555 Services.obs.notifyObservers(null, "idle-daily", null);
556
557 values = yield m.getValues();
558 do_check_eq(values.days.size, 180);
559 } finally {
560 reporter._shutdown();
561 }
562 });
563
564 add_task(function test_data_submission_transport_failure() {
565 let reporter = yield getReporter("data_submission_transport_failure");
566 try {
567 reporter.serverURI = DUMMY_URI;
568 reporter.serverNamespace = "test00";
569
570 let deferred = Promise.defer();
571 let request = new DataSubmissionRequest(deferred, new Date(Date.now + 30000));
572 reporter.requestDataUpload(request);
573
574 yield deferred.promise;
575 do_check_eq(request.state, request.SUBMISSION_FAILURE_SOFT);
576
577 let data = yield getHealthReportProviderValues(reporter, new Date());
578 do_check_eq(data.firstDocumentUploadAttempt, 1);
579 do_check_eq(data.uploadTransportFailure, 1);
580 do_check_eq(Object.keys(data).length, 3);
581 } finally {
582 reporter._shutdown();
583 }
584 });
585
586 add_task(function test_data_submission_server_failure() {
587 let [reporter, server] = yield getReporterAndServer("data_submission_server_failure");
588 try {
589 Object.defineProperty(server, "_handleNamespaceSubmitPost", {
590 value: function (ns, id, request, response) {
591 throw HTTP_500;
592 },
593 writable: true,
594 });
595
596 let deferred = Promise.defer();
597 let now = new Date();
598 let request = new DataSubmissionRequest(deferred, now);
599 reporter.requestDataUpload(request);
600 yield deferred.promise;
601 do_check_eq(request.state, request.SUBMISSION_FAILURE_HARD);
602
603 let data = yield getHealthReportProviderValues(reporter, now);
604 do_check_eq(data.firstDocumentUploadAttempt, 1);
605 do_check_eq(data.uploadServerFailure, 1);
606 do_check_eq(Object.keys(data).length, 3);
607 } finally {
608 yield shutdownServer(server);
609 reporter._shutdown();
610 }
611 });
612
613 add_task(function test_data_submission_success() {
614 let [reporter, server] = yield getReporterAndServer("data_submission_success");
615 try {
616 yield reporter._providerManager.registerProviderFromType(DummyProvider);
617 yield reporter._providerManager.registerProviderFromType(DummyConstantProvider);
618
619 do_check_eq(reporter.lastPingDate.getTime(), 0);
620 do_check_false(reporter.haveRemoteData());
621
622 let deferred = Promise.defer();
623
624 let now = new Date();
625 let request = new DataSubmissionRequest(deferred, now);
626 reporter._state.addRemoteID("foo");
627 reporter.requestDataUpload(request);
628 yield deferred.promise;
629 do_check_eq(request.state, request.SUBMISSION_SUCCESS);
630 do_check_true(reporter.lastPingDate.getTime() > 0);
631 do_check_true(reporter.haveRemoteData());
632 for (let remoteID of reporter._state.remoteIDs) {
633 do_check_neq(remoteID, "foo");
634 }
635
636 // Ensure data from providers made it to payload.
637 let o = yield reporter.getJSONPayload(true);
638 do_check_true("DummyProvider.DummyMeasurement" in o.data.last);
639 do_check_true("DummyConstantProvider.DummyMeasurement" in o.data.last);
640
641 let data = yield getHealthReportProviderValues(reporter, now);
642 do_check_eq(data.continuationUploadAttempt, 1);
643 do_check_eq(data.uploadSuccess, 1);
644 do_check_eq(Object.keys(data).length, 3);
645
646 let d = reporter.lastPingDate;
647 let id = reporter.lastSubmitID;
648 let clientID = reporter._state.clientID;
649
650 reporter._shutdown();
651
652 // Ensure reloading state works.
653 reporter = yield getReporter("data_submission_success");
654 do_check_eq(reporter.lastSubmitID, id);
655 do_check_eq(reporter.lastPingDate.getTime(), d.getTime());
656 do_check_eq(reporter._state.clientID, clientID);
657
658 reporter._shutdown();
659 } finally {
660 yield shutdownServer(server);
661 }
662 });
663
664 add_task(function test_recurring_daily_pings() {
665 let [reporter, server] = yield getReporterAndServer("recurring_daily_pings");
666 try {
667 reporter._providerManager.registerProvider(new DummyProvider());
668
669 let policy = reporter._policy;
670
671 defineNow(policy, policy._futureDate(-24 * 60 * 68 * 1000));
672 policy.recordUserAcceptance();
673 defineNow(policy, policy.nextDataSubmissionDate);
674 let promise = policy.checkStateAndTrigger();
675 do_check_neq(promise, null);
676 yield promise;
677
678 let lastID = reporter.lastSubmitID;
679 do_check_neq(lastID, null);
680 do_check_true(server.hasDocument(reporter.serverNamespace, lastID));
681
682 // Skip forward to next scheduled submission time.
683 defineNow(policy, policy.nextDataSubmissionDate);
684 promise = policy.checkStateAndTrigger();
685 do_check_neq(promise, null);
686 yield promise;
687 do_check_neq(reporter.lastSubmitID, lastID);
688 do_check_true(server.hasDocument(reporter.serverNamespace, reporter.lastSubmitID));
689 do_check_false(server.hasDocument(reporter.serverNamespace, lastID));
690
691 // now() on the health reporter instance wasn't munged. So, we should see
692 // both requests attributed to the same day.
693 let data = yield getHealthReportProviderValues(reporter, new Date());
694 do_check_eq(data.firstDocumentUploadAttempt, 1);
695 do_check_eq(data.continuationUploadAttempt, 1);
696 do_check_eq(data.uploadSuccess, 2);
697 do_check_eq(Object.keys(data).length, 4);
698 } finally {
699 reporter._shutdown();
700 yield shutdownServer(server);
701 }
702 });
703
704 add_task(function test_request_remote_data_deletion() {
705 let [reporter, server] = yield getReporterAndServer("request_remote_data_deletion");
706
707 try {
708 let policy = reporter._policy;
709 defineNow(policy, policy._futureDate(-24 * 60 * 60 * 1000));
710 policy.recordUserAcceptance();
711 defineNow(policy, policy.nextDataSubmissionDate);
712 yield policy.checkStateAndTrigger();
713 let id = reporter.lastSubmitID;
714 do_check_neq(id, null);
715 do_check_true(server.hasDocument(reporter.serverNamespace, id));
716
717 let clientID = reporter._state.clientID;
718 do_check_neq(clientID, null);
719
720 defineNow(policy, policy._futureDate(10 * 1000));
721
722 let promise = reporter.requestDeleteRemoteData();
723 do_check_neq(promise, null);
724 yield promise;
725 do_check_null(reporter.lastSubmitID);
726 do_check_false(reporter.haveRemoteData());
727 do_check_false(server.hasDocument(reporter.serverNamespace, id));
728
729 // Client ID should be updated.
730 do_check_neq(reporter._state.clientID, null);
731 do_check_neq(reporter._state.clientID, clientID);
732 do_check_eq(reporter._state.clientIDVersion, 1);
733
734 // And it should be persisted to disk.
735 let o = yield CommonUtils.readJSON(reporter._state._filename);
736 do_check_eq(o.clientID, reporter._state.clientID);
737 do_check_eq(o.clientIDVersion, 1);
738 } finally {
739 reporter._shutdown();
740 yield shutdownServer(server);
741 }
742 });
743
744 add_task(function test_multiple_simultaneous_uploads() {
745 let [reporter, server] = yield getReporterAndServer("multiple_simultaneous_uploads");
746
747 try {
748 let d1 = Promise.defer();
749 let d2 = Promise.defer();
750 let t1 = new Date(Date.now() - 1000);
751 let t2 = new Date(t1.getTime() + 500);
752 let r1 = new DataSubmissionRequest(d1, t1);
753 let r2 = new DataSubmissionRequest(d2, t2);
754
755 let getPayloadDeferred = Promise.defer();
756
757 Object.defineProperty(reporter, "getJSONPayload", {
758 configurable: true,
759 value: () => {
760 getPayloadDeferred.resolve();
761 delete reporter["getJSONPayload"];
762 return reporter.getJSONPayload();
763 },
764 });
765
766 let p1 = reporter.requestDataUpload(r1);
767 yield getPayloadDeferred.promise;
768 do_check_true(reporter._uploadInProgress);
769 let p2 = reporter.requestDataUpload(r2);
770
771 yield p1;
772 yield p2;
773
774 do_check_eq(r1.state, r1.SUBMISSION_SUCCESS);
775 do_check_eq(r2.state, r2.UPLOAD_IN_PROGRESS);
776
777 // They should both be resolved already.
778 yield d1;
779 yield d2;
780
781 let data = yield getHealthReportProviderValues(reporter, t1);
782 do_check_eq(data.firstDocumentUploadAttempt, 1);
783 do_check_false("continuationUploadAttempt" in data);
784 do_check_eq(data.uploadSuccess, 1);
785 do_check_eq(data.uploadAlreadyInProgress, 1);
786 } finally {
787 reporter._shutdown();
788 yield shutdownServer(server);
789 }
790 });
791
792 add_task(function test_policy_accept_reject() {
793 let [reporter, server] = yield getReporterAndServer("policy_accept_reject");
794
795 try {
796 let policy = reporter._policy;
797
798 do_check_false(policy.dataSubmissionPolicyAccepted);
799 do_check_false(reporter.willUploadData);
800
801 policy.recordUserAcceptance();
802 do_check_true(policy.dataSubmissionPolicyAccepted);
803 do_check_true(reporter.willUploadData);
804
805 policy.recordUserRejection();
806 do_check_false(policy.dataSubmissionPolicyAccepted);
807 do_check_false(reporter.willUploadData);
808 } finally {
809 reporter._shutdown();
810 yield shutdownServer(server);
811 }
812 });
813
814 add_task(function test_error_message_scrubbing() {
815 let reporter = yield getReporter("error_message_scrubbing");
816
817 try {
818 let profile = Services.dirsvc.get("ProfD", Ci.nsIFile).path;
819 reporter._recordError("Foo " + profile);
820
821 do_check_eq(reporter._errors.length, 1);
822 do_check_eq(reporter._errors[0], "Foo <ProfilePath>");
823
824 reporter._errors = [];
825
826 let appdata = Services.dirsvc.get("UAppData", Ci.nsIFile);
827 let uri = Services.io.newFileURI(appdata);
828
829 reporter._recordError("Foo " + uri.spec);
830 do_check_eq(reporter._errors[0], "Foo <AppDataURI>");
831 } finally {
832 reporter._shutdown();
833 }
834 });
835
836 add_task(function test_basic_appinfo() {
837 function verify(d) {
838 do_check_eq(d["_v"], 1);
839 do_check_eq(d._v, 1);
840 do_check_eq(d.vendor, "Mozilla");
841 do_check_eq(d.name, "xpcshell");
842 do_check_eq(d.id, "xpcshell@tests.mozilla.org");
843 do_check_eq(d.version, "1");
844 do_check_eq(d.appBuildID, "20121107");
845 do_check_eq(d.platformVersion, "p-ver");
846 do_check_eq(d.platformBuildID, "20121106");
847 do_check_eq(d.os, "XPCShell");
848 do_check_eq(d.xpcomabi, "noarch-spidermonkey");
849 do_check_true("updateChannel" in d);
850 }
851 let reporter = yield getReporter("basic_appinfo");
852 try {
853 verify(reporter.obtainAppInfo());
854 let payload = yield reporter.collectAndObtainJSONPayload(true);
855 do_check_eq(payload["version"], 2);
856 verify(payload["geckoAppInfo"]);
857 } finally {
858 reporter._shutdown();
859 }
860 });
861
862 // Ensure collection occurs if upload is disabled.
863 add_task(function test_collect_when_upload_disabled() {
864 let reporter = getHealthReporter("collect_when_upload_disabled");
865 reporter._policy.recordHealthReportUploadEnabled(false, "testing-collect");
866 do_check_false(reporter._policy.healthReportUploadEnabled);
867
868 let name = "healthreport-testing-collect_when_upload_disabled-healthreport-lastDailyCollection";
869 let pref = "app.update.lastUpdateTime." + name;
870 do_check_false(Services.prefs.prefHasUserValue(pref));
871
872 try {
873 yield reporter.init();
874 do_check_true(Services.prefs.prefHasUserValue(pref));
875
876 // We would ideally ensure the timer fires and does the right thing.
877 // However, testing the update timer manager is quite involved.
878 } finally {
879 reporter._shutdown();
880 }
881 });
882
883 add_task(function test_failure_if_not_initialized() {
884 let reporter = yield getReporter("failure_if_not_initialized");
885 reporter._shutdown();
886
887 let error = false;
888 try {
889 yield reporter.requestDataUpload();
890 } catch (ex) {
891 error = true;
892 do_check_true(ex.message.contains("Not initialized."));
893 } finally {
894 do_check_true(error);
895 error = false;
896 }
897
898 try {
899 yield reporter.collectMeasurements();
900 } catch (ex) {
901 error = true;
902 do_check_true(ex.message.contains("Not initialized."));
903 } finally {
904 do_check_true(error);
905 error = false;
906 }
907
908 // getJSONPayload always works (to facilitate error upload).
909 yield reporter.getJSONPayload();
910 });
911
912 add_task(function test_upload_on_init_failure() {
913 let server = new BagheeraServer();
914 server.start();
915 let reporter = yield getHealthReporter("upload_on_init_failure", server.serverURI, true);
916 server.createNamespace(reporter.serverNamespace);
917
918 reporter.onInitializeProviderManagerFinished = function () {
919 throw new Error("Fake error during provider manager initialization.");
920 };
921
922 let deferred = Promise.defer();
923
924 let oldOnResult = reporter._onBagheeraResult;
925 Object.defineProperty(reporter, "_onBagheeraResult", {
926 value: function (request, isDelete, date, result) {
927 do_check_false(isDelete);
928 do_check_true(result.transportSuccess);
929 do_check_true(result.serverSuccess);
930
931 oldOnResult.call(reporter, request, isDelete, new Date(), result);
932 deferred.resolve();
933 },
934 });
935
936 reporter._policy.recordUserAcceptance();
937 let error = false;
938 try {
939 yield reporter.init();
940 } catch (ex) {
941 error = true;
942 } finally {
943 do_check_true(error);
944 }
945
946 // At this point the emergency upload should have been initiated. We
947 // wait for our monkeypatched response handler to signal request
948 // completion.
949 yield deferred.promise;
950
951 do_check_true(server.hasDocument(reporter.serverNamespace, reporter.lastSubmitID));
952 let doc = server.getDocument(reporter.serverNamespace, reporter.lastSubmitID);
953 do_check_true("notInitialized" in doc);
954 do_check_eq(doc.notInitialized, 1);
955 do_check_true("errors" in doc);
956 do_check_eq(doc.errors.length, 1);
957 do_check_true(doc.errors[0].contains("Fake error during provider manager initialization"));
958
959 reporter._shutdown();
960 yield shutdownServer(server);
961 });
962
963 add_task(function test_state_prefs_conversion_simple() {
964 let reporter = getHealthReporter("state_prefs_conversion");
965 let prefs = reporter._prefs;
966
967 let lastSubmit = new Date();
968 prefs.set("lastSubmitID", "lastID");
969 CommonUtils.setDatePref(prefs, "lastPingTime", lastSubmit);
970
971 try {
972 yield reporter.init();
973
974 do_check_eq(reporter._state.lastSubmitID, "lastID");
975 do_check_eq(reporter._state.remoteIDs.length, 1);
976 do_check_eq(reporter._state.lastPingDate.getTime(), lastSubmit.getTime());
977 do_check_eq(reporter._state.lastPingDate.getTime(), reporter.lastPingDate.getTime());
978 do_check_eq(reporter._state.lastSubmitID, reporter.lastSubmitID);
979 do_check_true(reporter.haveRemoteData());
980
981 // User set preferences should have been wiped out.
982 do_check_false(prefs.isSet("lastSubmitID"));
983 do_check_false(prefs.isSet("lastPingTime"));
984 } finally {
985 reporter._shutdown();
986 }
987 });
988
989 // If the saved JSON file does not contain an object, we should reset
990 // automatically.
991 add_task(function test_state_no_json_object() {
992 let reporter = getHealthReporter("state_shared");
993 yield CommonUtils.writeJSON("hello", reporter._state._filename);
994
995 try {
996 yield reporter.init();
997
998 do_check_eq(reporter.lastPingDate.getTime(), 0);
999 do_check_null(reporter.lastSubmitID);
1000
1001 let o = yield CommonUtils.readJSON(reporter._state._filename);
1002 do_check_eq(typeof(o), "object");
1003 do_check_eq(o.v, 1);
1004 do_check_eq(o.lastPingTime, 0);
1005 do_check_eq(o.remoteIDs.length, 0);
1006 } finally {
1007 reporter._shutdown();
1008 }
1009 });
1010
1011 // If we encounter a future version, we reset state to the current version.
1012 add_task(function test_state_future_version() {
1013 let reporter = getHealthReporter("state_shared");
1014 yield CommonUtils.writeJSON({v: 2, remoteIDs: ["foo"], lastPingTime: 2412},
1015 reporter._state._filename);
1016 try {
1017 yield reporter.init();
1018
1019 do_check_eq(reporter.lastPingDate.getTime(), 0);
1020 do_check_null(reporter.lastSubmitID);
1021
1022 // While the object is updated, we don't save the file.
1023 let o = yield CommonUtils.readJSON(reporter._state._filename);
1024 do_check_eq(o.v, 2);
1025 do_check_eq(o.lastPingTime, 2412);
1026 do_check_eq(o.remoteIDs.length, 1);
1027 } finally {
1028 reporter._shutdown();
1029 }
1030 });
1031
1032 // Test recovery if the state file contains invalid JSON.
1033 add_task(function test_state_invalid_json() {
1034 let reporter = getHealthReporter("state_shared");
1035
1036 let encoder = new TextEncoder();
1037 let arr = encoder.encode("{foo: bad value, 'bad': as2,}");
1038 let path = reporter._state._filename;
1039 yield OS.File.writeAtomic(path, arr, {tmpPath: path + ".tmp"});
1040
1041 try {
1042 yield reporter.init();
1043
1044 do_check_eq(reporter.lastPingDate.getTime(), 0);
1045 do_check_null(reporter.lastSubmitID);
1046 } finally {
1047 reporter._shutdown();
1048 }
1049 });
1050
1051 add_task(function test_state_multiple_remote_ids() {
1052 let [reporter, server] = yield getReporterAndServer("state_multiple_remote_ids");
1053 let documents = [
1054 [reporter.serverNamespace, "one", "{v:1}"],
1055 [reporter.serverNamespace, "two", "{v:2}"],
1056 ];
1057 let now = new Date(Date.now() - 5000);
1058
1059 try {
1060 for (let [ns, id, payload] of documents) {
1061 server.setDocument(ns, id, payload);
1062 do_check_true(server.hasDocument(ns, id));
1063 yield reporter._state.addRemoteID(id);
1064 do_check_eq(reporter._state.remoteIDs.indexOf(id), reporter._state.remoteIDs.length - 1);
1065 }
1066 yield reporter._state.setLastPingDate(now);
1067 do_check_eq(reporter._state.remoteIDs.length, 2);
1068 do_check_eq(reporter.lastSubmitID, documents[0][1]);
1069
1070 let deferred = Promise.defer();
1071 let request = new DataSubmissionRequest(deferred, now);
1072 reporter.requestDataUpload(request);
1073 yield deferred.promise;
1074
1075 do_check_eq(reporter._state.remoteIDs.length, 1);
1076 for (let [,id,] of documents) {
1077 do_check_eq(reporter._state.remoteIDs.indexOf(id), -1);
1078 do_check_false(server.hasDocument(reporter.serverNamespace, id));
1079 }
1080 do_check_true(reporter.lastPingDate.getTime() > now.getTime());
1081
1082 let o = yield CommonUtils.readJSON(reporter._state._filename);
1083 do_check_eq(o.remoteIDs.length, 1);
1084 do_check_eq(o.remoteIDs[0], reporter._state.remoteIDs[0]);
1085 do_check_eq(o.lastPingTime, reporter.lastPingDate.getTime());
1086 } finally {
1087 yield shutdownServer(server);
1088 reporter._shutdown();
1089 }
1090 });
1091
1092 // If we have a state file then downgrade to prefs, the prefs should be
1093 // reimported and should supplement existing state.
1094 add_task(function test_state_downgrade_upgrade() {
1095 let reporter = getHealthReporter("state_shared");
1096
1097 let now = new Date();
1098
1099 yield CommonUtils.writeJSON({v: 1, remoteIDs: ["id1", "id2"], lastPingTime: now.getTime()},
1100 reporter._state._filename);
1101
1102 let prefs = reporter._prefs;
1103 prefs.set("lastSubmitID", "prefID");
1104 prefs.set("lastPingTime", "" + (now.getTime() + 1000));
1105
1106 try {
1107 yield reporter.init();
1108
1109 do_check_eq(reporter.lastSubmitID, "id1");
1110 do_check_eq(reporter._state.remoteIDs.length, 3);
1111 do_check_eq(reporter._state.remoteIDs[2], "prefID");
1112 do_check_eq(reporter.lastPingDate.getTime(), now.getTime() + 1000);
1113 do_check_false(prefs.isSet("lastSubmitID"));
1114 do_check_false(prefs.isSet("lastPingTime"));
1115
1116 let o = yield CommonUtils.readJSON(reporter._state._filename);
1117 do_check_eq(o.remoteIDs.length, 3);
1118 do_check_eq(o.lastPingTime, now.getTime() + 1000);
1119 } finally {
1120 reporter._shutdown();
1121 }
1122 });
1123
1124 // Missing client ID in state should be created on state load.
1125 add_task(function* test_state_create_client_id() {
1126 let reporter = getHealthReporter("state_create_client_id");
1127
1128 yield CommonUtils.writeJSON({
1129 v: 1,
1130 remoteIDs: ["id1", "id2"],
1131 lastPingTime: Date.now(),
1132 removeOutdatedLastPayload: true,
1133 }, reporter._state._filename);
1134
1135 try {
1136 yield reporter.init();
1137
1138 do_check_eq(reporter.lastSubmitID, "id1");
1139 do_check_neq(reporter._state.clientID, null);
1140 do_check_eq(reporter._state.clientID.length, 36);
1141 do_check_eq(reporter._state.clientIDVersion, 1);
1142
1143 let clientID = reporter._state.clientID;
1144
1145 // The client ID should be persisted as soon as it is created.
1146 reporter._shutdown();
1147
1148 reporter = getHealthReporter("state_create_client_id");
1149 yield reporter.init();
1150 do_check_eq(reporter._state.clientID, clientID);
1151 } finally {
1152 reporter._shutdown();
1153 }
1154 });
1155
1156 // Invalid stored client ID is reset automatically.
1157 add_task(function* test_empty_client_id() {
1158 let reporter = getHealthReporter("state_empty_client_id");
1159
1160 yield CommonUtils.writeJSON({
1161 v: 1,
1162 clientID: "",
1163 remoteIDs: ["id1", "id2"],
1164 lastPingTime: Date.now(),
1165 removeOutdatedLastPayload: true,
1166 }, reporter._state._filename);
1167
1168 try {
1169 yield reporter.init();
1170
1171 do_check_neq(reporter._state.clientID, null);
1172 do_check_eq(reporter._state.clientID.length, 36);
1173 } finally {
1174 reporter._shutdown();
1175 }
1176 });
1177
1178 add_task(function* test_nonstring_client_id() {
1179 let reporter = getHealthReporter("state_nonstring_client_id");
1180
1181 yield CommonUtils.writeJSON({
1182 v: 1,
1183 clientID: 42,
1184 remoteIDs: ["id1", "id2"],
1185 lastPingTime: Date.now(),
1186 remoteOutdatedLastPayload: true,
1187 }, reporter._state._filename);
1188
1189 try {
1190 yield reporter.init();
1191
1192 do_check_neq(reporter._state.clientID, null);
1193 do_check_eq(reporter._state.clientID.length, 36);
1194 } finally {
1195 reporter._shutdown();
1196 }
1197 });

mercurial