services/healthreport/tests/xpcshell/test_healthreporter.js

changeset 0
6474c204b198
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/services/healthreport/tests/xpcshell/test_healthreporter.js	Wed Dec 31 06:09:35 2014 +0100
     1.3 @@ -0,0 +1,1197 @@
     1.4 +/* Any copyright is dedicated to the Public Domain.
     1.5 + * http://creativecommons.org/publicdomain/zero/1.0/ */
     1.6 +
     1.7 +"use strict";
     1.8 +
     1.9 +const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
    1.10 +
    1.11 +Cu.import("resource://services-common/observers.js");
    1.12 +Cu.import("resource://services-common/utils.js");
    1.13 +Cu.import("resource://gre/modules/Promise.jsm");
    1.14 +Cu.import("resource://gre/modules/Metrics.jsm");
    1.15 +Cu.import("resource://gre/modules/osfile.jsm");
    1.16 +Cu.import("resource://gre/modules/Preferences.jsm");
    1.17 +let bsp = Cu.import("resource://gre/modules/services/healthreport/healthreporter.jsm");
    1.18 +Cu.import("resource://gre/modules/services/healthreport/providers.jsm");
    1.19 +Cu.import("resource://gre/modules/services/datareporting/policy.jsm");
    1.20 +Cu.import("resource://gre/modules/Services.jsm");
    1.21 +Cu.import("resource://gre/modules/Task.jsm");
    1.22 +Cu.import("resource://testing-common/httpd.js");
    1.23 +Cu.import("resource://testing-common/services-common/bagheeraserver.js");
    1.24 +Cu.import("resource://testing-common/services/metrics/mocks.jsm");
    1.25 +Cu.import("resource://testing-common/services/healthreport/utils.jsm");
    1.26 +Cu.import("resource://testing-common/AppData.jsm");
    1.27 +
    1.28 +
    1.29 +const DUMMY_URI = "http://localhost:62013/";
    1.30 +const MILLISECONDS_PER_DAY = 24 * 60 * 60 * 1000;
    1.31 +
    1.32 +const HealthReporterState = bsp.HealthReporterState;
    1.33 +
    1.34 +
    1.35 +function defineNow(policy, now) {
    1.36 +  print("Adjusting fake system clock to " + now);
    1.37 +  Object.defineProperty(policy, "now", {
    1.38 +    value: function customNow() {
    1.39 +      return now;
    1.40 +    },
    1.41 +    writable: true,
    1.42 +  });
    1.43 +}
    1.44 +
    1.45 +function getReporter(name, uri, inspected) {
    1.46 +  return Task.spawn(function init() {
    1.47 +    let reporter = getHealthReporter(name, uri, inspected);
    1.48 +    yield reporter.init();
    1.49 +
    1.50 +    yield reporter._providerManager.registerProviderFromType(
    1.51 +      HealthReportProvider);
    1.52 +
    1.53 +    throw new Task.Result(reporter);
    1.54 +  });
    1.55 +}
    1.56 +
    1.57 +function getReporterAndServer(name, namespace="test") {
    1.58 +  return Task.spawn(function get() {
    1.59 +    let server = new BagheeraServer();
    1.60 +    server.createNamespace(namespace);
    1.61 +    server.start();
    1.62 +
    1.63 +    let reporter = yield getReporter(name, server.serverURI);
    1.64 +    reporter.serverNamespace = namespace;
    1.65 +
    1.66 +    throw new Task.Result([reporter, server]);
    1.67 +  });
    1.68 +}
    1.69 +
    1.70 +function shutdownServer(server) {
    1.71 +  let deferred = Promise.defer();
    1.72 +  server.stop(deferred.resolve.bind(deferred));
    1.73 +
    1.74 +  return deferred.promise;
    1.75 +}
    1.76 +
    1.77 +function getHealthReportProviderValues(reporter, day=null) {
    1.78 +  return Task.spawn(function getValues() {
    1.79 +    let p = reporter.getProvider("org.mozilla.healthreport");
    1.80 +    do_check_neq(p, null);
    1.81 +    let m = p.getMeasurement("submissions", 2);
    1.82 +    do_check_neq(m, null);
    1.83 +
    1.84 +    let data = yield reporter._storage.getMeasurementValues(m.id);
    1.85 +    if (!day) {
    1.86 +      throw new Task.Result(data);
    1.87 +    }
    1.88 +
    1.89 +    do_check_true(data.days.hasDay(day));
    1.90 +    let serializer = m.serializer(m.SERIALIZE_JSON)
    1.91 +    let json = serializer.daily(data.days.getDay(day));
    1.92 +    do_check_eq(json._v, 2);
    1.93 +
    1.94 +    throw new Task.Result(json);
    1.95 +  });
    1.96 +}
    1.97 +
    1.98 +function run_test() {
    1.99 +  run_next_test();
   1.100 +}
   1.101 +
   1.102 +// run_test() needs to finish synchronously, so we do async init here.
   1.103 +add_task(function test_init() {
   1.104 +  yield makeFakeAppDir();
   1.105 +});
   1.106 +
   1.107 +add_task(function test_constructor() {
   1.108 +  let reporter = yield getReporter("constructor");
   1.109 +
   1.110 +  try {
   1.111 +    do_check_eq(reporter.lastPingDate.getTime(), 0);
   1.112 +    do_check_null(reporter.lastSubmitID);
   1.113 +    do_check_eq(typeof(reporter._state), "object");
   1.114 +    do_check_eq(reporter._state.lastPingDate.getTime(), 0);
   1.115 +    do_check_eq(reporter._state.remoteIDs.length, 0);
   1.116 +    do_check_eq(reporter._state.clientIDVersion, 1);
   1.117 +    do_check_neq(reporter._state.clientID, null);
   1.118 +
   1.119 +    let failed = false;
   1.120 +    try {
   1.121 +      new HealthReporter("foo.bar");
   1.122 +    } catch (ex) {
   1.123 +      failed = true;
   1.124 +      do_check_true(ex.message.startsWith("Branch must end"));
   1.125 +    } finally {
   1.126 +      do_check_true(failed);
   1.127 +      failed = false;
   1.128 +    }
   1.129 +  } finally {
   1.130 +    reporter._shutdown();
   1.131 +  }
   1.132 +});
   1.133 +
   1.134 +add_task(function test_shutdown_normal() {
   1.135 +  let reporter = yield getReporter("shutdown_normal");
   1.136 +
   1.137 +  // We can't send "quit-application" notification because the xpcshell runner
   1.138 +  // will shut down!
   1.139 +  reporter._initiateShutdown();
   1.140 +  reporter._waitForShutdown();
   1.141 +});
   1.142 +
   1.143 +add_task(function test_shutdown_storage_in_progress() {
   1.144 +  let reporter = yield getHealthReporter("shutdown_storage_in_progress", DUMMY_URI, true);
   1.145 +
   1.146 +  reporter.onStorageCreated = function () {
   1.147 +    print("Faking shutdown during storage initialization.");
   1.148 +    reporter._initiateShutdown();
   1.149 +  };
   1.150 +
   1.151 +  reporter.init();
   1.152 +
   1.153 +  reporter._waitForShutdown();
   1.154 +  do_check_eq(reporter.providerManagerShutdownCount, 0);
   1.155 +  do_check_eq(reporter.storageCloseCount, 1);
   1.156 +});
   1.157 +
   1.158 +// Ensure that a shutdown triggered while provider manager is initializing
   1.159 +// results in shutdown and storage closure.
   1.160 +add_task(function test_shutdown_provider_manager_in_progress() {
   1.161 +  let reporter = yield getHealthReporter("shutdown_provider_manager_in_progress",
   1.162 +                                         DUMMY_URI, true);
   1.163 +
   1.164 +  reporter.onProviderManagerInitialized = function () {
   1.165 +    print("Faking shutdown during provider manager initialization.");
   1.166 +    reporter._initiateShutdown();
   1.167 +  };
   1.168 +
   1.169 +  reporter.init();
   1.170 +
   1.171 +  // This will hang if shutdown logic is busted.
   1.172 +  reporter._waitForShutdown();
   1.173 +  do_check_eq(reporter.providerManagerShutdownCount, 1);
   1.174 +  do_check_eq(reporter.storageCloseCount, 1);
   1.175 +});
   1.176 +
   1.177 +// Simulates an error during provider manager initialization and verifies we shut down.
   1.178 +add_task(function test_shutdown_when_provider_manager_errors() {
   1.179 +  let reporter = yield getHealthReporter("shutdown_when_provider_manager_errors",
   1.180 +                                       DUMMY_URI, true);
   1.181 +
   1.182 +  reporter.onInitializeProviderManagerFinished = function () {
   1.183 +    print("Throwing fake error.");
   1.184 +    throw new Error("Fake error during provider manager initialization.");
   1.185 +  };
   1.186 +
   1.187 +  reporter.init();
   1.188 +
   1.189 +  // This will hang if shutdown logic is busted.
   1.190 +  reporter._waitForShutdown();
   1.191 +  do_check_eq(reporter.providerManagerShutdownCount, 1);
   1.192 +  do_check_eq(reporter.storageCloseCount, 1);
   1.193 +});
   1.194 +
   1.195 +// Pull-only providers are only initialized at collect time.
   1.196 +add_task(function test_pull_only_providers() {
   1.197 +  const category = "healthreporter-constant-only";
   1.198 +
   1.199 +  let cm = Cc["@mozilla.org/categorymanager;1"]
   1.200 +             .getService(Ci.nsICategoryManager);
   1.201 +  cm.addCategoryEntry(category, "DummyProvider",
   1.202 +                      "resource://testing-common/services/metrics/mocks.jsm",
   1.203 +                      false, true);
   1.204 +  cm.addCategoryEntry(category, "DummyConstantProvider",
   1.205 +                      "resource://testing-common/services/metrics/mocks.jsm",
   1.206 +                      false, true);
   1.207 +
   1.208 +  let reporter = yield getReporter("constant_only_providers");
   1.209 +  try {
   1.210 +    let initCount = reporter._providerManager.providers.length;
   1.211 +    yield reporter._providerManager.registerProvidersFromCategoryManager(category);
   1.212 +    do_check_eq(reporter._providerManager._providers.size, initCount + 1);
   1.213 +    do_check_true(reporter._storage.hasProvider("DummyProvider"));
   1.214 +    do_check_false(reporter._storage.hasProvider("DummyConstantProvider"));
   1.215 +    do_check_neq(reporter.getProvider("DummyProvider"), null);
   1.216 +    do_check_null(reporter.getProvider("DummyConstantProvider"));
   1.217 +
   1.218 +    yield reporter.collectMeasurements();
   1.219 +
   1.220 +    do_check_eq(reporter._providerManager._providers.size, initCount + 1);
   1.221 +    do_check_true(reporter._storage.hasProvider("DummyConstantProvider"));
   1.222 +
   1.223 +    let mID = reporter._storage.measurementID("DummyConstantProvider", "DummyMeasurement", 1);
   1.224 +    let values = yield reporter._storage.getMeasurementValues(mID);
   1.225 +    do_check_true(values.singular.size > 0);
   1.226 +  } finally {
   1.227 +    reporter._shutdown();
   1.228 +  }
   1.229 +});
   1.230 +
   1.231 +add_task(function test_collect_daily() {
   1.232 +  let reporter = yield getReporter("collect_daily");
   1.233 +
   1.234 +  try {
   1.235 +    let now = new Date();
   1.236 +    let provider = new DummyProvider();
   1.237 +    yield reporter._providerManager.registerProvider(provider);
   1.238 +    yield reporter.collectMeasurements();
   1.239 +
   1.240 +    do_check_eq(provider.collectConstantCount, 1);
   1.241 +    do_check_eq(provider.collectDailyCount, 1);
   1.242 +
   1.243 +    yield reporter.collectMeasurements();
   1.244 +    do_check_eq(provider.collectConstantCount, 1);
   1.245 +    do_check_eq(provider.collectDailyCount, 1);
   1.246 +
   1.247 +    yield reporter.collectMeasurements();
   1.248 +    do_check_eq(provider.collectDailyCount, 1); // Too soon.
   1.249 +
   1.250 +    reporter._lastDailyDate = now.getTime() - MILLISECONDS_PER_DAY - 1;
   1.251 +    yield reporter.collectMeasurements();
   1.252 +    do_check_eq(provider.collectDailyCount, 2);
   1.253 +
   1.254 +    reporter._lastDailyDate = null;
   1.255 +    yield reporter.collectMeasurements();
   1.256 +    do_check_eq(provider.collectDailyCount, 3);
   1.257 +  } finally {
   1.258 +    reporter._shutdown();
   1.259 +  }
   1.260 +});
   1.261 +
   1.262 +add_task(function test_remove_old_lastpayload() {
   1.263 +  let reporter = getHealthReporter("remove-old-lastpayload");
   1.264 +  let lastPayloadPath = reporter._state._lastPayloadPath;
   1.265 +  let paths = [lastPayloadPath, lastPayloadPath + ".tmp"];
   1.266 +  let createFiles = function () {
   1.267 +    return Task.spawn(function createFiles() {
   1.268 +      for (let path of paths) {
   1.269 +        yield OS.File.writeAtomic(path, "delete-me", {tmpPath: path + ".tmp"});
   1.270 +        do_check_true(yield OS.File.exists(path));
   1.271 +      }
   1.272 +    });
   1.273 +  };
   1.274 +  try {
   1.275 +    do_check_true(!reporter._state.removedOutdatedLastpayload);
   1.276 +    yield createFiles();
   1.277 +    yield reporter.init();
   1.278 +    for (let path of paths) {
   1.279 +      do_check_false(yield OS.File.exists(path));
   1.280 +    }
   1.281 +    yield reporter._state.save();
   1.282 +    reporter._shutdown();
   1.283 +
   1.284 +    let o = yield CommonUtils.readJSON(reporter._state._filename);
   1.285 +    do_check_true(o.removedOutdatedLastpayload);
   1.286 +
   1.287 +    yield createFiles();
   1.288 +    reporter = getHealthReporter("remove-old-lastpayload");
   1.289 +    yield reporter.init();
   1.290 +    for (let path of paths) {
   1.291 +      do_check_true(yield OS.File.exists(path));
   1.292 +    }
   1.293 +  } finally {
   1.294 +    reporter._shutdown();
   1.295 +  }
   1.296 +});
   1.297 +
   1.298 +add_task(function test_json_payload_simple() {
   1.299 +  let reporter = yield getReporter("json_payload_simple");
   1.300 +
   1.301 +  let clientID = reporter._state.clientID;
   1.302 +  do_check_neq(clientID, null);
   1.303 +
   1.304 +  try {
   1.305 +    let now = new Date();
   1.306 +    let payload = yield reporter.getJSONPayload();
   1.307 +    do_check_eq(typeof payload, "string");
   1.308 +    let original = JSON.parse(payload);
   1.309 +
   1.310 +    do_check_eq(original.version, 2);
   1.311 +    do_check_eq(original.thisPingDate, reporter._formatDate(now));
   1.312 +    do_check_eq(original.clientID, clientID);
   1.313 +    do_check_eq(original.clientIDVersion, reporter._state.clientIDVersion);
   1.314 +    do_check_eq(original.clientIDVersion, 1);
   1.315 +    do_check_eq(Object.keys(original.data.last).length, 0);
   1.316 +    do_check_eq(Object.keys(original.data.days).length, 0);
   1.317 +    do_check_false("notInitialized" in original);
   1.318 +
   1.319 +    yield reporter._state.setLastPingDate(
   1.320 +      new Date(now.getTime() - 24 * 60 * 60 * 1000 - 10));
   1.321 +
   1.322 +    original = JSON.parse(yield reporter.getJSONPayload());
   1.323 +    do_check_eq(original.lastPingDate, reporter._formatDate(reporter.lastPingDate));
   1.324 +    do_check_eq(original.clientID, clientID);
   1.325 +
   1.326 +    // This could fail if we cross UTC day boundaries at the exact instance the
   1.327 +    // test is executed. Let's tempt fate.
   1.328 +    do_check_eq(original.thisPingDate, reporter._formatDate(now));
   1.329 +
   1.330 +    payload = yield reporter.getJSONPayload(true);
   1.331 +    do_check_eq(typeof payload, "object");
   1.332 +  } finally {
   1.333 +    reporter._shutdown();
   1.334 +  }
   1.335 +});
   1.336 +
   1.337 +add_task(function test_json_payload_dummy_provider() {
   1.338 +  let reporter = yield getReporter("json_payload_dummy_provider");
   1.339 +
   1.340 +  try {
   1.341 +    yield reporter._providerManager.registerProvider(new DummyProvider());
   1.342 +    yield reporter.collectMeasurements();
   1.343 +    let payload = yield reporter.getJSONPayload();
   1.344 +    print(payload);
   1.345 +    let o = JSON.parse(payload);
   1.346 +
   1.347 +    let name = "DummyProvider.DummyMeasurement";
   1.348 +    do_check_eq(Object.keys(o.data.last).length, 1);
   1.349 +    do_check_true(name in o.data.last);
   1.350 +    do_check_eq(o.data.last[name]._v, 1);
   1.351 +  } finally {
   1.352 +    reporter._shutdown();
   1.353 +  }
   1.354 +});
   1.355 +
   1.356 +add_task(function test_collect_and_obtain_json_payload() {
   1.357 +  let reporter = yield getReporter("collect_and_obtain_json_payload");
   1.358 +
   1.359 +  try {
   1.360 +    yield reporter._providerManager.registerProvider(new DummyProvider());
   1.361 +    let payload = yield reporter.collectAndObtainJSONPayload();
   1.362 +    do_check_eq(typeof payload, "string");
   1.363 +
   1.364 +    let o = JSON.parse(payload);
   1.365 +    do_check_true("DummyProvider.DummyMeasurement" in o.data.last);
   1.366 +
   1.367 +    payload = yield reporter.collectAndObtainJSONPayload(true);
   1.368 +    do_check_eq(typeof payload, "object");
   1.369 +  } finally {
   1.370 +    reporter._shutdown();
   1.371 +  }
   1.372 +});
   1.373 +
   1.374 +// Ensure constant-only providers make their way into the JSON payload.
   1.375 +add_task(function test_constant_only_providers_in_json_payload() {
   1.376 +  const category = "healthreporter-constant-only-in-payload";
   1.377 +
   1.378 +  let cm = Cc["@mozilla.org/categorymanager;1"]
   1.379 +             .getService(Ci.nsICategoryManager);
   1.380 +  cm.addCategoryEntry(category, "DummyProvider",
   1.381 +                      "resource://testing-common/services/metrics/mocks.jsm",
   1.382 +                      false, true);
   1.383 +  cm.addCategoryEntry(category, "DummyConstantProvider",
   1.384 +                      "resource://testing-common/services/metrics/mocks.jsm",
   1.385 +                      false, true);
   1.386 +
   1.387 +  let reporter = yield getReporter("constant_only_providers_in_json_payload");
   1.388 +  try {
   1.389 +    let initCount = reporter._providerManager.providers.length;
   1.390 +    yield reporter._providerManager.registerProvidersFromCategoryManager(category);
   1.391 +
   1.392 +    let payload = yield reporter.collectAndObtainJSONPayload();
   1.393 +    let o = JSON.parse(payload);
   1.394 +    do_check_true("DummyProvider.DummyMeasurement" in o.data.last);
   1.395 +    do_check_true("DummyConstantProvider.DummyMeasurement" in o.data.last);
   1.396 +
   1.397 +    let providers = reporter._providerManager.providers;
   1.398 +    do_check_eq(providers.length, initCount + 1);
   1.399 +
   1.400 +    // Do it again for good measure.
   1.401 +    payload = yield reporter.collectAndObtainJSONPayload();
   1.402 +    o = JSON.parse(payload);
   1.403 +    do_check_true("DummyProvider.DummyMeasurement" in o.data.last);
   1.404 +    do_check_true("DummyConstantProvider.DummyMeasurement" in o.data.last);
   1.405 +
   1.406 +    providers = reporter._providerManager.providers;
   1.407 +    do_check_eq(providers.length, initCount + 1);
   1.408 +
   1.409 +    // Ensure throwing getJSONPayload is handled properly.
   1.410 +    Object.defineProperty(reporter, "_getJSONPayload", {
   1.411 +      value: function () {
   1.412 +        throw new Error("Silly error.");
   1.413 +      },
   1.414 +    });
   1.415 +
   1.416 +    let deferred = Promise.defer();
   1.417 +
   1.418 +    reporter.collectAndObtainJSONPayload().then(do_throw, function onError() {
   1.419 +      providers = reporter._providerManager.providers;
   1.420 +      do_check_eq(providers.length, initCount + 1);
   1.421 +      deferred.resolve();
   1.422 +    });
   1.423 +
   1.424 +    yield deferred.promise;
   1.425 +  } finally {
   1.426 +    reporter._shutdown();
   1.427 +  }
   1.428 +});
   1.429 +
   1.430 +add_task(function test_json_payload_multiple_days() {
   1.431 +  let reporter = yield getReporter("json_payload_multiple_days");
   1.432 +
   1.433 +  try {
   1.434 +    let provider = new DummyProvider();
   1.435 +    yield reporter._providerManager.registerProvider(provider);
   1.436 +
   1.437 +    let now = new Date();
   1.438 +
   1.439 +    let m = provider.getMeasurement("DummyMeasurement", 1);
   1.440 +    for (let i = 0; i < 200; i++) {
   1.441 +      let date = new Date(now.getTime() - i * MILLISECONDS_PER_DAY);
   1.442 +      yield m.incrementDailyCounter("daily-counter", date);
   1.443 +    }
   1.444 +
   1.445 +    // This test could fail if we cross a UTC day boundary when running. So,
   1.446 +    // we ensure this doesn't occur.
   1.447 +    Object.defineProperty(reporter, "_now", {
   1.448 +      value: function () {
   1.449 +        return now;
   1.450 +      },
   1.451 +    });
   1.452 +
   1.453 +    let payload = yield reporter.getJSONPayload();
   1.454 +    print(payload);
   1.455 +    let o = JSON.parse(payload);
   1.456 +
   1.457 +    do_check_eq(Object.keys(o.data.days).length, 180);
   1.458 +    let today = reporter._formatDate(now);
   1.459 +    do_check_true(today in o.data.days);
   1.460 +  } finally {
   1.461 +    reporter._shutdown();
   1.462 +  }
   1.463 +});
   1.464 +
   1.465 +add_task(function test_json_payload_newer_version_overwrites() {
   1.466 +  let reporter = yield getReporter("json_payload_newer_version_overwrites");
   1.467 +
   1.468 +  try {
   1.469 +    let now = new Date();
   1.470 +    // Instead of hacking up the internals to ensure consistent order in Map
   1.471 +    // iteration (which would be difficult), we instead opt to generate a lot
   1.472 +    // of measurements of different versions and verify their iterable order
   1.473 +    // is not increasing.
   1.474 +    let versions = [1, 6, 3, 9, 2, 3, 7, 4, 10, 8];
   1.475 +    let protos = [];
   1.476 +    for (let version of versions) {
   1.477 +      let m = function () {
   1.478 +        Metrics.Measurement.call(this);
   1.479 +      };
   1.480 +      m.prototype = {
   1.481 +        __proto__: DummyMeasurement.prototype,
   1.482 +        name: "DummyMeasurement",
   1.483 +        version: version,
   1.484 +      };
   1.485 +
   1.486 +      protos.push(m);
   1.487 +    }
   1.488 +
   1.489 +    let ctor = function () {
   1.490 +      Metrics.Provider.call(this);
   1.491 +    };
   1.492 +    ctor.prototype = {
   1.493 +      __proto__: DummyProvider.prototype,
   1.494 +
   1.495 +      name: "MultiMeasurementProvider",
   1.496 +      measurementTypes: protos,
   1.497 +    };
   1.498 +
   1.499 +    let provider = new ctor();
   1.500 +
   1.501 +    yield reporter._providerManager.registerProvider(provider);
   1.502 +
   1.503 +    let haveUnordered = false;
   1.504 +    let last = -1;
   1.505 +    let highestVersion = -1;
   1.506 +    for (let [key, measurement] of provider.measurements) {
   1.507 +      yield measurement.setDailyLastNumeric("daily-last-numeric",
   1.508 +                                            measurement.version, now);
   1.509 +      yield measurement.setLastNumeric("last-numeric",
   1.510 +                                       measurement.version, now);
   1.511 +
   1.512 +      if (measurement.version > highestVersion) {
   1.513 +        highestVersion = measurement.version;
   1.514 +      }
   1.515 +
   1.516 +      if (measurement.version < last) {
   1.517 +        haveUnordered = true;
   1.518 +      }
   1.519 +
   1.520 +      last = measurement.version;
   1.521 +    }
   1.522 +
   1.523 +    // Ensure Map traversal isn't ordered. If this ever fails, then we'll need
   1.524 +    // to monkeypatch.
   1.525 +    do_check_true(haveUnordered);
   1.526 +
   1.527 +    let payload = yield reporter.getJSONPayload();
   1.528 +    let o = JSON.parse(payload);
   1.529 +    do_check_true("MultiMeasurementProvider.DummyMeasurement" in o.data.last);
   1.530 +    do_check_eq(o.data.last["MultiMeasurementProvider.DummyMeasurement"]._v, highestVersion);
   1.531 +
   1.532 +    let day = reporter._formatDate(now);
   1.533 +    do_check_true(day in o.data.days);
   1.534 +    do_check_true("MultiMeasurementProvider.DummyMeasurement" in o.data.days[day]);
   1.535 +    do_check_eq(o.data.days[day]["MultiMeasurementProvider.DummyMeasurement"]._v, highestVersion);
   1.536 +
   1.537 +  } finally {
   1.538 +    reporter._shutdown();
   1.539 +  }
   1.540 +});
   1.541 +
   1.542 +add_task(function test_idle_daily() {
   1.543 +  let reporter = yield getReporter("idle_daily");
   1.544 +  try {
   1.545 +    let provider = new DummyProvider();
   1.546 +    yield reporter._providerManager.registerProvider(provider);
   1.547 +
   1.548 +    let now = new Date();
   1.549 +    let m = provider.getMeasurement("DummyMeasurement", 1);
   1.550 +    for (let i = 0; i < 200; i++) {
   1.551 +      let date = new Date(now.getTime() - i * MILLISECONDS_PER_DAY);
   1.552 +      yield m.incrementDailyCounter("daily-counter", date);
   1.553 +    }
   1.554 +
   1.555 +    let values = yield m.getValues();
   1.556 +    do_check_eq(values.days.size, 200);
   1.557 +
   1.558 +    Services.obs.notifyObservers(null, "idle-daily", null);
   1.559 +
   1.560 +    values = yield m.getValues();
   1.561 +    do_check_eq(values.days.size, 180);
   1.562 +  } finally {
   1.563 +    reporter._shutdown();
   1.564 +  }
   1.565 +});
   1.566 +
   1.567 +add_task(function test_data_submission_transport_failure() {
   1.568 +  let reporter = yield getReporter("data_submission_transport_failure");
   1.569 +  try {
   1.570 +    reporter.serverURI = DUMMY_URI;
   1.571 +    reporter.serverNamespace = "test00";
   1.572 +
   1.573 +    let deferred = Promise.defer();
   1.574 +    let request = new DataSubmissionRequest(deferred, new Date(Date.now + 30000));
   1.575 +    reporter.requestDataUpload(request);
   1.576 +
   1.577 +    yield deferred.promise;
   1.578 +    do_check_eq(request.state, request.SUBMISSION_FAILURE_SOFT);
   1.579 +
   1.580 +    let data = yield getHealthReportProviderValues(reporter, new Date());
   1.581 +    do_check_eq(data.firstDocumentUploadAttempt, 1);
   1.582 +    do_check_eq(data.uploadTransportFailure, 1);
   1.583 +    do_check_eq(Object.keys(data).length, 3);
   1.584 +  } finally {
   1.585 +    reporter._shutdown();
   1.586 +  }
   1.587 +});
   1.588 +
   1.589 +add_task(function test_data_submission_server_failure() {
   1.590 +  let [reporter, server] = yield getReporterAndServer("data_submission_server_failure");
   1.591 +  try {
   1.592 +    Object.defineProperty(server, "_handleNamespaceSubmitPost", {
   1.593 +      value: function (ns, id, request, response) {
   1.594 +        throw HTTP_500;
   1.595 +      },
   1.596 +      writable: true,
   1.597 +    });
   1.598 +
   1.599 +    let deferred = Promise.defer();
   1.600 +    let now = new Date();
   1.601 +    let request = new DataSubmissionRequest(deferred, now);
   1.602 +    reporter.requestDataUpload(request);
   1.603 +    yield deferred.promise;
   1.604 +    do_check_eq(request.state, request.SUBMISSION_FAILURE_HARD);
   1.605 +
   1.606 +    let data = yield getHealthReportProviderValues(reporter, now);
   1.607 +    do_check_eq(data.firstDocumentUploadAttempt, 1);
   1.608 +    do_check_eq(data.uploadServerFailure, 1);
   1.609 +    do_check_eq(Object.keys(data).length, 3);
   1.610 +  } finally {
   1.611 +    yield shutdownServer(server);
   1.612 +    reporter._shutdown();
   1.613 +  }
   1.614 +});
   1.615 +
   1.616 +add_task(function test_data_submission_success() {
   1.617 +  let [reporter, server] = yield getReporterAndServer("data_submission_success");
   1.618 +  try {
   1.619 +    yield reporter._providerManager.registerProviderFromType(DummyProvider);
   1.620 +    yield reporter._providerManager.registerProviderFromType(DummyConstantProvider);
   1.621 +
   1.622 +    do_check_eq(reporter.lastPingDate.getTime(), 0);
   1.623 +    do_check_false(reporter.haveRemoteData());
   1.624 +
   1.625 +    let deferred = Promise.defer();
   1.626 +
   1.627 +    let now = new Date();
   1.628 +    let request = new DataSubmissionRequest(deferred, now);
   1.629 +    reporter._state.addRemoteID("foo");
   1.630 +    reporter.requestDataUpload(request);
   1.631 +    yield deferred.promise;
   1.632 +    do_check_eq(request.state, request.SUBMISSION_SUCCESS);
   1.633 +    do_check_true(reporter.lastPingDate.getTime() > 0);
   1.634 +    do_check_true(reporter.haveRemoteData());
   1.635 +    for (let remoteID of reporter._state.remoteIDs) {
   1.636 +      do_check_neq(remoteID, "foo");
   1.637 +    }
   1.638 +
   1.639 +    // Ensure data from providers made it to payload.
   1.640 +    let o = yield reporter.getJSONPayload(true);
   1.641 +    do_check_true("DummyProvider.DummyMeasurement" in o.data.last);
   1.642 +    do_check_true("DummyConstantProvider.DummyMeasurement" in o.data.last);
   1.643 +
   1.644 +    let data = yield getHealthReportProviderValues(reporter, now);
   1.645 +    do_check_eq(data.continuationUploadAttempt, 1);
   1.646 +    do_check_eq(data.uploadSuccess, 1);
   1.647 +    do_check_eq(Object.keys(data).length, 3);
   1.648 +
   1.649 +    let d = reporter.lastPingDate;
   1.650 +    let id = reporter.lastSubmitID;
   1.651 +    let clientID = reporter._state.clientID;
   1.652 +
   1.653 +    reporter._shutdown();
   1.654 +
   1.655 +    // Ensure reloading state works.
   1.656 +    reporter = yield getReporter("data_submission_success");
   1.657 +    do_check_eq(reporter.lastSubmitID, id);
   1.658 +    do_check_eq(reporter.lastPingDate.getTime(), d.getTime());
   1.659 +    do_check_eq(reporter._state.clientID, clientID);
   1.660 +
   1.661 +    reporter._shutdown();
   1.662 +  } finally {
   1.663 +    yield shutdownServer(server);
   1.664 +  }
   1.665 +});
   1.666 +
   1.667 +add_task(function test_recurring_daily_pings() {
   1.668 +  let [reporter, server] = yield getReporterAndServer("recurring_daily_pings");
   1.669 +  try {
   1.670 +    reporter._providerManager.registerProvider(new DummyProvider());
   1.671 +
   1.672 +    let policy = reporter._policy;
   1.673 +
   1.674 +    defineNow(policy, policy._futureDate(-24 * 60 * 68 * 1000));
   1.675 +    policy.recordUserAcceptance();
   1.676 +    defineNow(policy, policy.nextDataSubmissionDate);
   1.677 +    let promise = policy.checkStateAndTrigger();
   1.678 +    do_check_neq(promise, null);
   1.679 +    yield promise;
   1.680 +
   1.681 +    let lastID = reporter.lastSubmitID;
   1.682 +    do_check_neq(lastID, null);
   1.683 +    do_check_true(server.hasDocument(reporter.serverNamespace, lastID));
   1.684 +
   1.685 +    // Skip forward to next scheduled submission time.
   1.686 +    defineNow(policy, policy.nextDataSubmissionDate);
   1.687 +    promise = policy.checkStateAndTrigger();
   1.688 +    do_check_neq(promise, null);
   1.689 +    yield promise;
   1.690 +    do_check_neq(reporter.lastSubmitID, lastID);
   1.691 +    do_check_true(server.hasDocument(reporter.serverNamespace, reporter.lastSubmitID));
   1.692 +    do_check_false(server.hasDocument(reporter.serverNamespace, lastID));
   1.693 +
   1.694 +    // now() on the health reporter instance wasn't munged. So, we should see
   1.695 +    // both requests attributed to the same day.
   1.696 +    let data = yield getHealthReportProviderValues(reporter, new Date());
   1.697 +    do_check_eq(data.firstDocumentUploadAttempt, 1);
   1.698 +    do_check_eq(data.continuationUploadAttempt, 1);
   1.699 +    do_check_eq(data.uploadSuccess, 2);
   1.700 +    do_check_eq(Object.keys(data).length, 4);
   1.701 +  } finally {
   1.702 +    reporter._shutdown();
   1.703 +    yield shutdownServer(server);
   1.704 +  }
   1.705 +});
   1.706 +
   1.707 +add_task(function test_request_remote_data_deletion() {
   1.708 +  let [reporter, server] = yield getReporterAndServer("request_remote_data_deletion");
   1.709 +
   1.710 +  try {
   1.711 +    let policy = reporter._policy;
   1.712 +    defineNow(policy, policy._futureDate(-24 * 60 * 60 * 1000));
   1.713 +    policy.recordUserAcceptance();
   1.714 +    defineNow(policy, policy.nextDataSubmissionDate);
   1.715 +    yield policy.checkStateAndTrigger();
   1.716 +    let id = reporter.lastSubmitID;
   1.717 +    do_check_neq(id, null);
   1.718 +    do_check_true(server.hasDocument(reporter.serverNamespace, id));
   1.719 +
   1.720 +    let clientID = reporter._state.clientID;
   1.721 +    do_check_neq(clientID, null);
   1.722 +
   1.723 +    defineNow(policy, policy._futureDate(10 * 1000));
   1.724 +
   1.725 +    let promise = reporter.requestDeleteRemoteData();
   1.726 +    do_check_neq(promise, null);
   1.727 +    yield promise;
   1.728 +    do_check_null(reporter.lastSubmitID);
   1.729 +    do_check_false(reporter.haveRemoteData());
   1.730 +    do_check_false(server.hasDocument(reporter.serverNamespace, id));
   1.731 +
   1.732 +    // Client ID should be updated.
   1.733 +    do_check_neq(reporter._state.clientID, null);
   1.734 +    do_check_neq(reporter._state.clientID, clientID);
   1.735 +    do_check_eq(reporter._state.clientIDVersion, 1);
   1.736 +
   1.737 +    // And it should be persisted to disk.
   1.738 +    let o = yield CommonUtils.readJSON(reporter._state._filename);
   1.739 +    do_check_eq(o.clientID, reporter._state.clientID);
   1.740 +    do_check_eq(o.clientIDVersion, 1);
   1.741 +  } finally {
   1.742 +    reporter._shutdown();
   1.743 +    yield shutdownServer(server);
   1.744 +  }
   1.745 +});
   1.746 +
   1.747 +add_task(function test_multiple_simultaneous_uploads() {
   1.748 +  let [reporter, server] = yield getReporterAndServer("multiple_simultaneous_uploads");
   1.749 +
   1.750 +  try {
   1.751 +    let d1 = Promise.defer();
   1.752 +    let d2 = Promise.defer();
   1.753 +    let t1 = new Date(Date.now() - 1000);
   1.754 +    let t2 = new Date(t1.getTime() + 500);
   1.755 +    let r1 = new DataSubmissionRequest(d1, t1);
   1.756 +    let r2 = new DataSubmissionRequest(d2, t2);
   1.757 +
   1.758 +    let getPayloadDeferred = Promise.defer();
   1.759 +
   1.760 +    Object.defineProperty(reporter, "getJSONPayload", {
   1.761 +      configurable: true,
   1.762 +      value: () => {
   1.763 +        getPayloadDeferred.resolve();
   1.764 +        delete reporter["getJSONPayload"];
   1.765 +        return reporter.getJSONPayload();
   1.766 +      },
   1.767 +    });
   1.768 +
   1.769 +    let p1 = reporter.requestDataUpload(r1);
   1.770 +    yield getPayloadDeferred.promise;
   1.771 +    do_check_true(reporter._uploadInProgress);
   1.772 +    let p2 = reporter.requestDataUpload(r2);
   1.773 +
   1.774 +    yield p1;
   1.775 +    yield p2;
   1.776 +
   1.777 +    do_check_eq(r1.state, r1.SUBMISSION_SUCCESS);
   1.778 +    do_check_eq(r2.state, r2.UPLOAD_IN_PROGRESS);
   1.779 +
   1.780 +    // They should both be resolved already.
   1.781 +    yield d1;
   1.782 +    yield d2;
   1.783 +
   1.784 +    let data = yield getHealthReportProviderValues(reporter, t1);
   1.785 +    do_check_eq(data.firstDocumentUploadAttempt, 1);
   1.786 +    do_check_false("continuationUploadAttempt" in data);
   1.787 +    do_check_eq(data.uploadSuccess, 1);
   1.788 +    do_check_eq(data.uploadAlreadyInProgress, 1);
   1.789 +  } finally {
   1.790 +    reporter._shutdown();
   1.791 +    yield shutdownServer(server);
   1.792 +  }
   1.793 +});
   1.794 +
   1.795 +add_task(function test_policy_accept_reject() {
   1.796 +  let [reporter, server] = yield getReporterAndServer("policy_accept_reject");
   1.797 +
   1.798 +  try {
   1.799 +    let policy = reporter._policy;
   1.800 +
   1.801 +    do_check_false(policy.dataSubmissionPolicyAccepted);
   1.802 +    do_check_false(reporter.willUploadData);
   1.803 +
   1.804 +    policy.recordUserAcceptance();
   1.805 +    do_check_true(policy.dataSubmissionPolicyAccepted);
   1.806 +    do_check_true(reporter.willUploadData);
   1.807 +
   1.808 +    policy.recordUserRejection();
   1.809 +    do_check_false(policy.dataSubmissionPolicyAccepted);
   1.810 +    do_check_false(reporter.willUploadData);
   1.811 +  } finally {
   1.812 +    reporter._shutdown();
   1.813 +    yield shutdownServer(server);
   1.814 +  }
   1.815 +});
   1.816 +
   1.817 +add_task(function test_error_message_scrubbing() {
   1.818 +  let reporter = yield getReporter("error_message_scrubbing");
   1.819 +
   1.820 +  try {
   1.821 +    let profile = Services.dirsvc.get("ProfD", Ci.nsIFile).path;
   1.822 +    reporter._recordError("Foo " + profile);
   1.823 +
   1.824 +    do_check_eq(reporter._errors.length, 1);
   1.825 +    do_check_eq(reporter._errors[0], "Foo <ProfilePath>");
   1.826 +
   1.827 +    reporter._errors = [];
   1.828 +
   1.829 +    let appdata = Services.dirsvc.get("UAppData", Ci.nsIFile);
   1.830 +    let uri = Services.io.newFileURI(appdata);
   1.831 +
   1.832 +    reporter._recordError("Foo " + uri.spec);
   1.833 +    do_check_eq(reporter._errors[0], "Foo <AppDataURI>");
   1.834 +  } finally {
   1.835 +    reporter._shutdown();
   1.836 +  }
   1.837 +});
   1.838 +
   1.839 +add_task(function test_basic_appinfo() {
   1.840 +  function verify(d) {
   1.841 +    do_check_eq(d["_v"], 1);
   1.842 +    do_check_eq(d._v, 1);
   1.843 +    do_check_eq(d.vendor, "Mozilla");
   1.844 +    do_check_eq(d.name, "xpcshell");
   1.845 +    do_check_eq(d.id, "xpcshell@tests.mozilla.org");
   1.846 +    do_check_eq(d.version, "1");
   1.847 +    do_check_eq(d.appBuildID, "20121107");
   1.848 +    do_check_eq(d.platformVersion, "p-ver");
   1.849 +    do_check_eq(d.platformBuildID, "20121106");
   1.850 +    do_check_eq(d.os, "XPCShell");
   1.851 +    do_check_eq(d.xpcomabi, "noarch-spidermonkey");
   1.852 +    do_check_true("updateChannel" in d);
   1.853 +  }
   1.854 +  let reporter = yield getReporter("basic_appinfo");
   1.855 +  try {
   1.856 +    verify(reporter.obtainAppInfo());
   1.857 +    let payload = yield reporter.collectAndObtainJSONPayload(true);
   1.858 +    do_check_eq(payload["version"], 2);
   1.859 +    verify(payload["geckoAppInfo"]);
   1.860 +  } finally {
   1.861 +    reporter._shutdown();
   1.862 +  }
   1.863 +});
   1.864 +
   1.865 +// Ensure collection occurs if upload is disabled.
   1.866 +add_task(function test_collect_when_upload_disabled() {
   1.867 +  let reporter = getHealthReporter("collect_when_upload_disabled");
   1.868 +  reporter._policy.recordHealthReportUploadEnabled(false, "testing-collect");
   1.869 +  do_check_false(reporter._policy.healthReportUploadEnabled);
   1.870 +
   1.871 +  let name = "healthreport-testing-collect_when_upload_disabled-healthreport-lastDailyCollection";
   1.872 +  let pref = "app.update.lastUpdateTime." + name;
   1.873 +  do_check_false(Services.prefs.prefHasUserValue(pref));
   1.874 +
   1.875 +  try {
   1.876 +    yield reporter.init();
   1.877 +    do_check_true(Services.prefs.prefHasUserValue(pref));
   1.878 +
   1.879 +    // We would ideally ensure the timer fires and does the right thing.
   1.880 +    // However, testing the update timer manager is quite involved.
   1.881 +  } finally {
   1.882 +    reporter._shutdown();
   1.883 +  }
   1.884 +});
   1.885 +
   1.886 +add_task(function test_failure_if_not_initialized() {
   1.887 +  let reporter = yield getReporter("failure_if_not_initialized");
   1.888 +  reporter._shutdown();
   1.889 +
   1.890 +  let error = false;
   1.891 +  try {
   1.892 +    yield reporter.requestDataUpload();
   1.893 +  } catch (ex) {
   1.894 +    error = true;
   1.895 +    do_check_true(ex.message.contains("Not initialized."));
   1.896 +  } finally {
   1.897 +    do_check_true(error);
   1.898 +    error = false;
   1.899 +  }
   1.900 +
   1.901 +  try {
   1.902 +    yield reporter.collectMeasurements();
   1.903 +  } catch (ex) {
   1.904 +    error = true;
   1.905 +    do_check_true(ex.message.contains("Not initialized."));
   1.906 +  } finally {
   1.907 +    do_check_true(error);
   1.908 +    error = false;
   1.909 +  }
   1.910 +
   1.911 +  // getJSONPayload always works (to facilitate error upload).
   1.912 +  yield reporter.getJSONPayload();
   1.913 +});
   1.914 +
   1.915 +add_task(function test_upload_on_init_failure() {
   1.916 +  let server = new BagheeraServer();
   1.917 +  server.start();
   1.918 +  let reporter = yield getHealthReporter("upload_on_init_failure", server.serverURI, true);
   1.919 +  server.createNamespace(reporter.serverNamespace);
   1.920 +
   1.921 +  reporter.onInitializeProviderManagerFinished = function () {
   1.922 +    throw new Error("Fake error during provider manager initialization.");
   1.923 +  };
   1.924 +
   1.925 +  let deferred = Promise.defer();
   1.926 +
   1.927 +  let oldOnResult = reporter._onBagheeraResult;
   1.928 +  Object.defineProperty(reporter, "_onBagheeraResult", {
   1.929 +    value: function (request, isDelete, date, result) {
   1.930 +      do_check_false(isDelete);
   1.931 +      do_check_true(result.transportSuccess);
   1.932 +      do_check_true(result.serverSuccess);
   1.933 +
   1.934 +      oldOnResult.call(reporter, request, isDelete, new Date(), result);
   1.935 +      deferred.resolve();
   1.936 +    },
   1.937 +  });
   1.938 +
   1.939 +  reporter._policy.recordUserAcceptance();
   1.940 +  let error = false;
   1.941 +  try {
   1.942 +    yield reporter.init();
   1.943 +  } catch (ex) {
   1.944 +    error = true;
   1.945 +  } finally {
   1.946 +    do_check_true(error);
   1.947 +  }
   1.948 +
   1.949 +  // At this point the emergency upload should have been initiated. We
   1.950 +  // wait for our monkeypatched response handler to signal request
   1.951 +  // completion.
   1.952 +  yield deferred.promise;
   1.953 +
   1.954 +  do_check_true(server.hasDocument(reporter.serverNamespace, reporter.lastSubmitID));
   1.955 +  let doc = server.getDocument(reporter.serverNamespace, reporter.lastSubmitID);
   1.956 +  do_check_true("notInitialized" in doc);
   1.957 +  do_check_eq(doc.notInitialized, 1);
   1.958 +  do_check_true("errors" in doc);
   1.959 +  do_check_eq(doc.errors.length, 1);
   1.960 +  do_check_true(doc.errors[0].contains("Fake error during provider manager initialization"));
   1.961 +
   1.962 +  reporter._shutdown();
   1.963 +  yield shutdownServer(server);
   1.964 +});
   1.965 +
   1.966 +add_task(function test_state_prefs_conversion_simple() {
   1.967 +  let reporter = getHealthReporter("state_prefs_conversion");
   1.968 +  let prefs = reporter._prefs;
   1.969 +
   1.970 +  let lastSubmit = new Date();
   1.971 +  prefs.set("lastSubmitID", "lastID");
   1.972 +  CommonUtils.setDatePref(prefs, "lastPingTime", lastSubmit);
   1.973 +
   1.974 +  try {
   1.975 +    yield reporter.init();
   1.976 +
   1.977 +    do_check_eq(reporter._state.lastSubmitID, "lastID");
   1.978 +    do_check_eq(reporter._state.remoteIDs.length, 1);
   1.979 +    do_check_eq(reporter._state.lastPingDate.getTime(), lastSubmit.getTime());
   1.980 +    do_check_eq(reporter._state.lastPingDate.getTime(), reporter.lastPingDate.getTime());
   1.981 +    do_check_eq(reporter._state.lastSubmitID, reporter.lastSubmitID);
   1.982 +    do_check_true(reporter.haveRemoteData());
   1.983 +
   1.984 +    // User set preferences should have been wiped out.
   1.985 +    do_check_false(prefs.isSet("lastSubmitID"));
   1.986 +    do_check_false(prefs.isSet("lastPingTime"));
   1.987 +  } finally {
   1.988 +    reporter._shutdown();
   1.989 +  }
   1.990 +});
   1.991 +
   1.992 +// If the saved JSON file does not contain an object, we should reset
   1.993 +// automatically.
   1.994 +add_task(function test_state_no_json_object() {
   1.995 +  let reporter = getHealthReporter("state_shared");
   1.996 +  yield CommonUtils.writeJSON("hello", reporter._state._filename);
   1.997 +
   1.998 +  try {
   1.999 +    yield reporter.init();
  1.1000 +
  1.1001 +    do_check_eq(reporter.lastPingDate.getTime(), 0);
  1.1002 +    do_check_null(reporter.lastSubmitID);
  1.1003 +
  1.1004 +    let o = yield CommonUtils.readJSON(reporter._state._filename);
  1.1005 +    do_check_eq(typeof(o), "object");
  1.1006 +    do_check_eq(o.v, 1);
  1.1007 +    do_check_eq(o.lastPingTime, 0);
  1.1008 +    do_check_eq(o.remoteIDs.length, 0);
  1.1009 +  } finally {
  1.1010 +    reporter._shutdown();
  1.1011 +  }
  1.1012 +});
  1.1013 +
  1.1014 +// If we encounter a future version, we reset state to the current version.
  1.1015 +add_task(function test_state_future_version() {
  1.1016 +  let reporter = getHealthReporter("state_shared");
  1.1017 +  yield CommonUtils.writeJSON({v: 2, remoteIDs: ["foo"], lastPingTime: 2412},
  1.1018 +                              reporter._state._filename);
  1.1019 +  try {
  1.1020 +    yield reporter.init();
  1.1021 +
  1.1022 +    do_check_eq(reporter.lastPingDate.getTime(), 0);
  1.1023 +    do_check_null(reporter.lastSubmitID);
  1.1024 +
  1.1025 +    // While the object is updated, we don't save the file.
  1.1026 +    let o = yield CommonUtils.readJSON(reporter._state._filename);
  1.1027 +    do_check_eq(o.v, 2);
  1.1028 +    do_check_eq(o.lastPingTime, 2412);
  1.1029 +    do_check_eq(o.remoteIDs.length, 1);
  1.1030 +  } finally {
  1.1031 +    reporter._shutdown();
  1.1032 +  }
  1.1033 +});
  1.1034 +
  1.1035 +// Test recovery if the state file contains invalid JSON.
  1.1036 +add_task(function test_state_invalid_json() {
  1.1037 +  let reporter = getHealthReporter("state_shared");
  1.1038 +
  1.1039 +  let encoder = new TextEncoder();
  1.1040 +  let arr = encoder.encode("{foo: bad value, 'bad': as2,}");
  1.1041 +  let path = reporter._state._filename;
  1.1042 +  yield OS.File.writeAtomic(path, arr, {tmpPath: path + ".tmp"});
  1.1043 +
  1.1044 +  try {
  1.1045 +    yield reporter.init();
  1.1046 +
  1.1047 +    do_check_eq(reporter.lastPingDate.getTime(), 0);
  1.1048 +    do_check_null(reporter.lastSubmitID);
  1.1049 +  } finally {
  1.1050 +    reporter._shutdown();
  1.1051 +  }
  1.1052 +});
  1.1053 +
  1.1054 +add_task(function test_state_multiple_remote_ids() {
  1.1055 +  let [reporter, server] = yield getReporterAndServer("state_multiple_remote_ids");
  1.1056 +  let documents = [
  1.1057 +    [reporter.serverNamespace, "one", "{v:1}"],
  1.1058 +    [reporter.serverNamespace, "two", "{v:2}"],
  1.1059 +  ];
  1.1060 +  let now = new Date(Date.now() - 5000);
  1.1061 +
  1.1062 +  try {
  1.1063 +    for (let [ns, id, payload] of documents) {
  1.1064 +      server.setDocument(ns, id, payload);
  1.1065 +      do_check_true(server.hasDocument(ns, id));
  1.1066 +      yield reporter._state.addRemoteID(id);
  1.1067 +      do_check_eq(reporter._state.remoteIDs.indexOf(id), reporter._state.remoteIDs.length - 1);
  1.1068 +    }
  1.1069 +    yield reporter._state.setLastPingDate(now);
  1.1070 +    do_check_eq(reporter._state.remoteIDs.length, 2);
  1.1071 +    do_check_eq(reporter.lastSubmitID, documents[0][1]);
  1.1072 +
  1.1073 +    let deferred = Promise.defer();
  1.1074 +    let request = new DataSubmissionRequest(deferred, now);
  1.1075 +    reporter.requestDataUpload(request);
  1.1076 +    yield deferred.promise;
  1.1077 +
  1.1078 +    do_check_eq(reporter._state.remoteIDs.length, 1);
  1.1079 +    for (let [,id,] of documents) {
  1.1080 +      do_check_eq(reporter._state.remoteIDs.indexOf(id), -1);
  1.1081 +      do_check_false(server.hasDocument(reporter.serverNamespace, id));
  1.1082 +    }
  1.1083 +    do_check_true(reporter.lastPingDate.getTime() > now.getTime());
  1.1084 +
  1.1085 +    let o = yield CommonUtils.readJSON(reporter._state._filename);
  1.1086 +    do_check_eq(o.remoteIDs.length, 1);
  1.1087 +    do_check_eq(o.remoteIDs[0], reporter._state.remoteIDs[0]);
  1.1088 +    do_check_eq(o.lastPingTime, reporter.lastPingDate.getTime());
  1.1089 +  } finally {
  1.1090 +    yield shutdownServer(server);
  1.1091 +    reporter._shutdown();
  1.1092 +  }
  1.1093 +});
  1.1094 +
  1.1095 +// If we have a state file then downgrade to prefs, the prefs should be
  1.1096 +// reimported and should supplement existing state.
  1.1097 +add_task(function test_state_downgrade_upgrade() {
  1.1098 +  let reporter = getHealthReporter("state_shared");
  1.1099 +
  1.1100 +  let now = new Date();
  1.1101 +
  1.1102 +  yield CommonUtils.writeJSON({v: 1, remoteIDs: ["id1", "id2"], lastPingTime: now.getTime()},
  1.1103 +                              reporter._state._filename);
  1.1104 +
  1.1105 +  let prefs = reporter._prefs;
  1.1106 +  prefs.set("lastSubmitID", "prefID");
  1.1107 +  prefs.set("lastPingTime", "" + (now.getTime() + 1000));
  1.1108 +
  1.1109 +  try {
  1.1110 +    yield reporter.init();
  1.1111 +
  1.1112 +    do_check_eq(reporter.lastSubmitID, "id1");
  1.1113 +    do_check_eq(reporter._state.remoteIDs.length, 3);
  1.1114 +    do_check_eq(reporter._state.remoteIDs[2], "prefID");
  1.1115 +    do_check_eq(reporter.lastPingDate.getTime(), now.getTime() + 1000);
  1.1116 +    do_check_false(prefs.isSet("lastSubmitID"));
  1.1117 +    do_check_false(prefs.isSet("lastPingTime"));
  1.1118 +
  1.1119 +    let o = yield CommonUtils.readJSON(reporter._state._filename);
  1.1120 +    do_check_eq(o.remoteIDs.length, 3);
  1.1121 +    do_check_eq(o.lastPingTime, now.getTime() + 1000);
  1.1122 +  } finally {
  1.1123 +    reporter._shutdown();
  1.1124 +  }
  1.1125 +});
  1.1126 +
  1.1127 +// Missing client ID in state should be created on state load.
  1.1128 +add_task(function* test_state_create_client_id() {
  1.1129 +  let reporter = getHealthReporter("state_create_client_id");
  1.1130 +
  1.1131 +  yield CommonUtils.writeJSON({
  1.1132 +    v: 1,
  1.1133 +    remoteIDs: ["id1", "id2"],
  1.1134 +    lastPingTime: Date.now(),
  1.1135 +    removeOutdatedLastPayload: true,
  1.1136 +  }, reporter._state._filename);
  1.1137 +
  1.1138 +  try {
  1.1139 +    yield reporter.init();
  1.1140 +
  1.1141 +    do_check_eq(reporter.lastSubmitID, "id1");
  1.1142 +    do_check_neq(reporter._state.clientID, null);
  1.1143 +    do_check_eq(reporter._state.clientID.length, 36);
  1.1144 +    do_check_eq(reporter._state.clientIDVersion, 1);
  1.1145 +
  1.1146 +    let clientID = reporter._state.clientID;
  1.1147 +
  1.1148 +    // The client ID should be persisted as soon as it is created.
  1.1149 +    reporter._shutdown();
  1.1150 +
  1.1151 +    reporter = getHealthReporter("state_create_client_id");
  1.1152 +    yield reporter.init();
  1.1153 +    do_check_eq(reporter._state.clientID, clientID);
  1.1154 +  } finally {
  1.1155 +    reporter._shutdown();
  1.1156 +  }
  1.1157 +});
  1.1158 +
  1.1159 +// Invalid stored client ID is reset automatically.
  1.1160 +add_task(function* test_empty_client_id() {
  1.1161 +  let reporter = getHealthReporter("state_empty_client_id");
  1.1162 +
  1.1163 +  yield CommonUtils.writeJSON({
  1.1164 +    v: 1,
  1.1165 +    clientID: "",
  1.1166 +    remoteIDs: ["id1", "id2"],
  1.1167 +    lastPingTime: Date.now(),
  1.1168 +    removeOutdatedLastPayload: true,
  1.1169 +  }, reporter._state._filename);
  1.1170 +
  1.1171 +  try {
  1.1172 +    yield reporter.init();
  1.1173 +
  1.1174 +    do_check_neq(reporter._state.clientID, null);
  1.1175 +    do_check_eq(reporter._state.clientID.length, 36);
  1.1176 +  } finally {
  1.1177 +    reporter._shutdown();
  1.1178 +  }
  1.1179 +});
  1.1180 +
  1.1181 +add_task(function* test_nonstring_client_id() {
  1.1182 +  let reporter = getHealthReporter("state_nonstring_client_id");
  1.1183 +
  1.1184 +  yield CommonUtils.writeJSON({
  1.1185 +    v: 1,
  1.1186 +    clientID: 42,
  1.1187 +    remoteIDs: ["id1", "id2"],
  1.1188 +    lastPingTime: Date.now(),
  1.1189 +    remoteOutdatedLastPayload: true,
  1.1190 +  }, reporter._state._filename);
  1.1191 +
  1.1192 +  try {
  1.1193 +    yield reporter.init();
  1.1194 +
  1.1195 +    do_check_neq(reporter._state.clientID, null);
  1.1196 +    do_check_eq(reporter._state.clientID.length, 36);
  1.1197 +  } finally {
  1.1198 +    reporter._shutdown();
  1.1199 +  }
  1.1200 +});

mercurial