Bug 1168835 - Part 2 - Add test coverage for pending-pings quota. r=gfritzsche,a=ritu
authorAlessio Placitelli <alessio.placitelli@gmail.com>
Mon, 20 Jul 2015 08:46:00 +0200
changeset 281835 16c778f1122d1473d07d68ec7474fdabf0151b06
parent 281834 5329b924ab80425605a5ef5a3d9cca628c168bcb
child 281836 7742c5799277857cb770240deb3608a6a312273d
push id4932
push userjlund@mozilla.com
push dateMon, 10 Aug 2015 18:23:06 +0000
treeherdermozilla-beta@6dd5a4f5f745 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersgfritzsche, ritu
bugs1168835
milestone41.0a2
Bug 1168835 - Part 2 - Add test coverage for pending-pings quota. r=gfritzsche,a=ritu
testing/xpcshell/head.js
toolkit/components/telemetry/TelemetryStorage.jsm
toolkit/components/telemetry/tests/unit/test_TelemetryEnvironment.js
toolkit/components/telemetry/tests/unit/test_TelemetrySendOldPings.js
--- a/testing/xpcshell/head.js
+++ b/testing/xpcshell/head.js
@@ -1499,16 +1499,17 @@ try {
 try {
   if (runningInParent) {
     let prefs = Components.classes["@mozilla.org/preferences-service;1"]
       .getService(Components.interfaces.nsIPrefBranch);
 
     prefs.setCharPref("media.gmp-manager.url.override", "http://%(server)s/dummy-gmp-manager.xml");
     prefs.setCharPref("browser.selfsupport.url", "https://%(server)s/selfsupport-dummy/");
     prefs.setCharPref("toolkit.telemetry.server", "https://%(server)s/telemetry-dummy");
+    prefs.setCharPref("browser.search.geoip.url", "https://%(server)s/geoip-dummy");
   }
 } catch (e) { }
 
 // Make tests run consistently on DevEdition (which has a lightweight theme
 // selected by default).
 try {
   if (runningInParent) {
     let prefs = Components.classes["@mozilla.org/preferences-service;1"]
--- a/toolkit/components/telemetry/TelemetryStorage.jsm
+++ b/toolkit/components/telemetry/TelemetryStorage.jsm
@@ -168,16 +168,23 @@ this.TelemetryStorage = {
   /**
    * Test method that allows waiting on the archive clean task to finish.
    */
   testCleanupTaskPromise: function() {
     return (TelemetryStorageImpl._cleanArchiveTask || Promise.resolve());
   },
 
   /**
+   * Test method that allows waiting on the pending pings quota task to finish.
+   */
+  testPendingQuotaTaskPromise: function() {
+    return (TelemetryStorageImpl._enforcePendingPingsQuotaTask || Promise.resolve());
+  },
+
+  /**
    * Save a pending - outgoing - ping to disk and track it.
    *
    * @param {Object} ping The ping data.
    * @return {Promise} Resolved when the ping was saved.
    */
   savePendingPing: function(ping) {
     return TelemetryStorageImpl.savePendingPing(ping);
   },
@@ -1099,17 +1106,18 @@ let TelemetryStorageImpl = {
       this._log.trace("savePendingPing - saved ping with id " + ping.id);
     });
   },
 
   loadPendingPing: function(id) {
     this._log.trace("loadPendingPing - id: " + id);
     let info = this._pendingPings.get(id);
     if (!info) {
-      return;
+      this._log.trace("loadPendingPing - unknown id " + id);
+      return Promise.reject(new Error("TelemetryStorage.loadPendingPing - no ping with id " + id));
     }
 
     return this.loadPingFile(info.path, false);
   },
 
   removePendingPing: function(id) {
     let info = this._pendingPings.get(id);
     if (!info) {
--- a/toolkit/components/telemetry/tests/unit/test_TelemetryEnvironment.js
+++ b/toolkit/components/telemetry/tests/unit/test_TelemetryEnvironment.js
@@ -1012,19 +1012,17 @@ add_task(function* test_defaultSearchEng
   Assert.ok(!("defaultSearchEngineData" in data.settings));
 
   // Load the engines definitions from a custom JAR file: that's needed so that
   // the search provider reports an engine identifier.
   let defaultBranch = Services.prefs.getDefaultBranch(null);
   defaultBranch.setCharPref("browser.search.jarURIs", "chrome://testsearchplugin/locale/searchplugins/");
   defaultBranch.setBoolPref("browser.search.loadFromJars", true);
 
-  // Initialize the search service and disable geoip lookup, so we don't get unwanted
-  // network connections.
-  Preferences.set("browser.search.geoip.url", "");
+  // Initialize the search service.
   yield new Promise(resolve => Services.search.init(resolve));
 
   // Our default engine from the JAR file has an identifier. Check if it is correctly
   // reported.
   data = TelemetryEnvironment.currentEnvironment;
   checkEnvironmentData(data);
   Assert.equal(data.settings.defaultSearchEngine, "telemetrySearchIdentifier");
   let expectedSearchEngineData = {
--- a/toolkit/components/telemetry/tests/unit/test_TelemetrySendOldPings.js
+++ b/toolkit/components/telemetry/tests/unit/test_TelemetrySendOldPings.js
@@ -30,17 +30,16 @@ XPCOMUtils.defineLazyGetter(this, "gData
 const ONE_MINUTE_MS = 60 * 1000;
 const OVERDUE_PING_FILE_AGE = TelemetrySend.OVERDUE_PING_FILE_AGE + ONE_MINUTE_MS;
 
 const PING_SAVE_FOLDER = "saved-telemetry-pings";
 const PING_TIMEOUT_LENGTH = 5000;
 const OVERDUE_PINGS = 6;
 const OLD_FORMAT_PINGS = 4;
 const RECENT_PINGS = 4;
-const LRU_PINGS = TelemetrySend.MAX_LRU_PINGS;
 
 const TOTAL_EXPECTED_PINGS = OVERDUE_PINGS + RECENT_PINGS + OLD_FORMAT_PINGS;
 
 let gCreatedPings = 0;
 let gSeenPings = 0;
 
 /**
  * Creates some Telemetry pings for the and saves them to disk. Each ping gets a
@@ -85,16 +84,25 @@ let createSavedPings = Task.async(functi
  */
 let clearPings = Task.async(function* (aPingIds) {
   for (let pingId of aPingIds) {
     yield TelemetryStorage.removePendingPing(pingId);
   }
 });
 
 /**
+ * Fakes the pending pings storage quota.
+ * @param {Integer} aPendingQuota The new quota, in bytes.
+ */
+function fakePendingPingsQuota(aPendingQuota) {
+  let storage = Cu.import("resource://gre/modules/TelemetryStorage.jsm");
+  storage.Policy.getPendingPingsQuota = () => aPendingQuota;
+}
+
+/**
  * Returns a handle for the file that a ping should be
  * stored in locally.
  *
  * @returns path
  */
 function getSavePathForPingId(aPingId) {
   return Path.join(Constants.Path.profileDir, PING_SAVE_FOLDER, aPingId);
 }
@@ -144,24 +152,16 @@ function pingHandler(aRequest) {
  */
 let clearPendingPings = Task.async(function*() {
   const pending = yield TelemetryStorage.loadPendingPingList();
   for (let p of pending) {
     yield TelemetryStorage.removePendingPing(p.id);
   }
 });
 
-/**
- * Creates and returns a TelemetryController instance in "testing"
- * mode.
- */
-function startTelemetry() {
-  return TelemetryController.setup();
-}
-
 function run_test() {
   PingServer.start();
   PingServer.registerPingHandler(pingHandler);
   do_get_profile();
   loadAddonManager("xpcshell@tests.mozilla.org", "XPCShell", "1", "1.9.2");
 
   // Send the needed startup notifications to the datareporting service
   // to ensure that it has been initialized.
@@ -339,13 +339,107 @@ add_task(function* test_overdue_old_form
     receivedPings++;
   });
 
   yield TelemetryController.reset();
   yield TelemetrySend.testWaitOnOutgoingPings();
   Assert.equal(receivedPings, 1, "We must receive a ping in the old format.");
 
   yield clearPendingPings();
+  PingServer.resetPingHandler();
+});
+
+add_task(function* test_pendingPingsQuota() {
+  const PING_TYPE = "foo";
+  const PREF_FHR_UPLOAD = "datareporting.healthreport.uploadEnabled";
+
+  // Disable upload so pings don't get sent and removed from the pending pings directory.
+  Services.prefs.setBoolPref(PREF_FHR_UPLOAD, false);
+
+  // Remove all the pending pings then startup and wait for the cleanup task to complete.
+  // There should be nothing to remove.
+  yield clearPendingPings();
+  yield TelemetryController.reset();
+  yield TelemetrySend.testWaitOnOutgoingPings();
+  yield TelemetryStorage.testPendingQuotaTaskPromise();
+
+  // Remove the pending deletion ping generated when flipping FHR upload off.
+  yield clearPendingPings();
+
+  let expectedPrunedPings = [];
+  let expectedNotPrunedPings = [];
+
+  let checkPendingPings = Task.async(function*() {
+    // Check that the pruned pings are not on disk anymore.
+    for (let prunedPingId of expectedPrunedPings) {
+      yield Assert.rejects(TelemetryStorage.loadPendingPing(prunedPingId),
+                           "Ping " + prunedPingId + " should have been pruned.");
+      const pingPath = getSavePathForPingId(prunedPingId);
+      Assert.ok(!(yield OS.File.exists(pingPath)), "The ping should not be on the disk anymore.");
+    }
+
+    // Check that the expected pings are there.
+    for (let expectedPingId of expectedNotPrunedPings) {
+      Assert.ok((yield TelemetryStorage.loadPendingPing(expectedPingId)),
+                "Ping" + expectedPingId + " should be among the pending pings.");
+    }
+  });
+
+  let pendingPingsInfo = [];
+  let pingsSizeInBytes = 0;
+
+  // Create 10 pings to test the pending pings quota.
+  for (let days = 1; days < 11; days++) {
+    const date = fakeNow(2010, 1, days, 1, 1, 0);
+    const pingId = yield TelemetryController.addPendingPing(PING_TYPE, {}, {});
+
+    // Find the size of the ping.
+    const pingFilePath = getSavePathForPingId(pingId);
+    const pingSize = (yield OS.File.stat(pingFilePath)).size;
+    // Add the info at the beginning of the array, so that most recent pings come first.
+    pendingPingsInfo.unshift({id: pingId, size: pingSize, timestamp: date.getTime() });
+
+    // Set the last modification date.
+    yield OS.File.setDates(pingFilePath, null, date.getTime());
+
+    // Add it to the pending ping directory size.
+    pingsSizeInBytes += pingSize;
+  }
+
+  // Set the quota to 80% of the space.
+  const testQuotaInBytes = pingsSizeInBytes * 0.8;
+  fakePendingPingsQuota(testQuotaInBytes);
+
+  // The storage prunes pending pings until we reach 90% of the requested storage quota.
+  // Based on that, find how many pings should be kept.
+  const safeQuotaSize = Math.round(testQuotaInBytes * 0.9);
+  let sizeInBytes = 0;
+  let pingsWithinQuota = [];
+  let pingsOutsideQuota = [];
+
+  for (let pingInfo of pendingPingsInfo) {
+    sizeInBytes += pingInfo.size;
+    if (sizeInBytes >= safeQuotaSize) {
+      pingsOutsideQuota.push(pingInfo.id);
+      continue;
+    }
+    pingsWithinQuota.push(pingInfo.id);
+  }
+
+  expectedNotPrunedPings = pingsWithinQuota;
+  expectedPrunedPings = pingsOutsideQuota;
+
+  // Reset TelemetryController to start the pending pings cleanup.
+  yield TelemetryController.reset();
+  yield TelemetryStorage.testPendingQuotaTaskPromise();
+  yield checkPendingPings();
+
+  // Trigger a cleanup again and make sure we're not removing anything.
+  yield TelemetryController.reset();
+  yield TelemetryStorage.testPendingQuotaTaskPromise();
+  yield checkPendingPings();
+
+  Services.prefs.setBoolPref(PREF_FHR_UPLOAD, true);
 });
 
 add_task(function* teardown() {
   yield PingServer.stop();
 });