Bug 1134518 - Add xpcshell test for shistory caps on clean shutdown and fix existing tests r=Yoric
authorTim Taubert <ttaubert@mozilla.com>
Wed, 22 Apr 2015 20:26:49 +0200
changeset 240633 8a151d59bdec0cf0e946396771ff8dc2953be736
parent 240632 a0b3a91f096088c71849ab19c88c5fdc5394f16c
child 240634 d84b62b367b43f6544c7e79cfe4810b32cd6fbc5
push id12537
push userttaubert@mozilla.com
push dateThu, 23 Apr 2015 12:44:40 +0000
treeherderfx-team@8a151d59bdec [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersYoric
bugs1134518
milestone40.0a1
Bug 1134518 - Add xpcshell test for shistory caps on clean shutdown and fix existing tests r=Yoric
browser/components/sessionstore/test/browser.ini
browser/components/sessionstore/test/browser_history_cap.js
browser/components/sessionstore/test/unit/test_backup_once.js
browser/components/sessionstore/test/unit/test_shutdown_cleanup.js
browser/components/sessionstore/test/unit/xpcshell.ini
--- a/browser/components/sessionstore/test/browser.ini
+++ b/browser/components/sessionstore/test/browser.ini
@@ -78,17 +78,16 @@ skip-if = !e10s || !crashreporter
 [browser_form_restore_events.js]
 [browser_formdata.js]
 skip-if = buildapp == 'mulet'
 [browser_formdata_format.js]
 [browser_formdata_xpath.js]
 [browser_frametree.js]
 [browser_frame_history.js]
 [browser_global_store.js]
-[browser_history_cap.js]
 [browser_history_persist.js]
 [browser_label_and_icon.js]
 [browser_merge_closed_tabs.js]
 [browser_pageStyle.js]
 [browser_privatetabs.js]
 [browser_replace_load.js]
 [browser_restore_redirect.js]
 [browser_scrollPositions.js]
deleted file mode 100644
--- a/browser/components/sessionstore/test/browser_history_cap.js
+++ /dev/null
@@ -1,121 +0,0 @@
-/* Any copyright is dedicated to the Public Domain.
- * http://creativecommons.org/publicdomain/zero/1.0/ */
-
-"use strict";
-
-/**
- * This test ensures that the preferences (added in bug 943339) that control how
- * many back and forward button session history entries we store work correctly.
- *
- * It adds a number of entries to the session history, restores them and checks
- * that the restored state matches the preferences.
- */
-
-add_task(function *test_history_cap() {
-  const baseURL = "http://example.com/browser_history_cap#"
-  const maxEntries  = 9; // The number of generated session history entries.
-  const middleEntry = 4; // The zero-based index of the middle entry.
-
-  const maxBack1 = 2; // The history cap settings used for the first test,
-  const maxFwd1 = 3;  // where maxBack1 + 1 + maxFwd1 < maxEntries.
-
-  const maxBack2 = 5; // The history cap settings used for the other tests, 
-  const maxFwd2 = 5;  // where maxBack2 + 1 + maxFwd2 > maxEntries.
-
-  // Set the relevant preferences for the first test.
-  gPrefService.setIntPref("browser.sessionhistory.max_entries", maxEntries);
-  gPrefService.setIntPref("browser.sessionstore.max_serialize_back", maxBack1);
-  gPrefService.setIntPref("browser.sessionstore.max_serialize_forward", maxFwd1);
-
-  // Make sure the settings we modify are reset afterward.
-  registerCleanupFunction(() => {
-    gPrefService.clearUserPref("browser.sessionhistory.max_entries");
-    gPrefService.clearUserPref("browser.sessionstore.max_serialize_back");
-    gPrefService.clearUserPref("browser.sessionstore.max_serialize_forward");
-  });
-
-  let tab = gBrowser.addTab();
-  let browser = tab.linkedBrowser;
-  yield promiseBrowserLoaded(browser);
-
-  // Generate the tab state entries and set the one-based
-  // tab-state index to the middle session history entry.
-  let tabState = {entries: [], index: middleEntry + 1};
-  for (let i = 0; i < maxEntries; i++) {
-    tabState.entries.push({url: baseURL + i});
-  }
-
-  info("Testing situation where only a subset of session history entries should be restored.");
-
-  yield promiseTabState(tab, tabState);
-  TabState.flush(tab.linkedBrowser);
-
-  let restoredTabState = JSON.parse(ss.getTabState(tab));
-  is(restoredTabState.entries.length, maxBack1 + 1 + maxFwd1,
-    "The expected number of session history entries was restored.");
-  is(restoredTabState.index, maxBack1 + 1, "The restored tab-state index is correct");
-
-  let indexURLOffset = middleEntry - (restoredTabState.index - 1);
-  for (let i = 0; i < restoredTabState.entries.length; i++) {
-    is(restoredTabState.entries[i].url, baseURL + (i + indexURLOffset),
-        "URL of restored entry matches the expected URL.");
-  }
-
-  // Set the relevant preferences for the other tests.
-  gPrefService.setIntPref("browser.sessionstore.max_serialize_back", maxBack2);
-  gPrefService.setIntPref("browser.sessionstore.max_serialize_forward", maxFwd2);
-
-  info("Testing situation where all of the entries in the session history should be restored.");
-
-  yield promiseTabState(tab, tabState);
-  TabState.flush(tab.linkedBrowser);
-
-  restoredTabState = JSON.parse(ss.getTabState(tab));
-  is(restoredTabState.entries.length, maxEntries,
-    "The expected number of session history entries was restored.");
-  is(restoredTabState.index, middleEntry + 1, "The restored tab-state index is correct");
-
-  for (let i = middleEntry - 2; i <= middleEntry + 2; i++) {
-    is(restoredTabState.entries[i].url, baseURL + i,
-        "URL of restored entry matches the expected URL.");
-  }
-
-  info("Testing situation where only the 1 + maxFwd2 oldest entries should be restored.");
-
-  // Set the one-based tab-state index to the oldest session history entry.
-  tabState.index = 1;
-
-  yield promiseTabState(tab, tabState);
-  TabState.flush(tab.linkedBrowser);
-
-  restoredTabState = JSON.parse(ss.getTabState(tab));
-  is(restoredTabState.entries.length, 1 + maxFwd2,
-    "The expected number of session history entries was restored.");
-  is(restoredTabState.index, 1, "The restored tab-state index is correct");
-
-  for (let i = 0; i <= 2; i++) {
-    is(restoredTabState.entries[i].url, baseURL + i,
-        "URL of restored entry matches the expected URL.");
-  }
-
-  info("Testing situation where only the maxBack2 + 1 newest entries should be restored.");
-
-  // Set the one-based tab-state index to the newest session history entry.
-  tabState.index = maxEntries;
-
-  yield promiseTabState(tab, tabState);
-  TabState.flush(tab.linkedBrowser);
-
-  restoredTabState = JSON.parse(ss.getTabState(tab));
-  is(restoredTabState.entries.length, maxBack2 + 1,
-    "The expected number of session history entries was restored.");
-  is(restoredTabState.index, maxBack2 + 1, "The restored tab-state index is correct");
-
-  indexURLOffset = (maxEntries - 1) - maxBack2;
-  for (let i = maxBack2 - 2; i <= maxBack2; i++) {
-    is(restoredTabState.entries[i].url, baseURL + (i + indexURLOffset),
-        "URL of restored entry matches the expected URL.");
-  }
-
-  gBrowser.removeTab(tab);
-});
--- a/browser/components/sessionstore/test/unit/test_backup_once.js
+++ b/browser/components/sessionstore/test/unit/test_backup_once.js
@@ -74,34 +74,37 @@ function promise_check_exist(path, shoul
     }
   });
 }
 
 function promise_check_contents(path, expect) {
   return Task.spawn(function*() {
     do_print("Checking whether " + path + " has the right contents");
     let actual = yield OS.File.read(path, { encoding: "utf-8"});
-    if (actual != expect) {
-      throw new Error("File " + path + " should contain\n\t" + expect + "\nbut contains " + actual);
-    }
+    Assert.deepEqual(JSON.parse(actual), expect, `File ${path} contains the expected data.`);
   });
 }
 
+function generateFileContents(id) {
+  let url = `http://example.com/test_backup_once#${id}_${Math.random()}`;
+  return {windows: [{tabs: [{entries: [{url}], index: 1}]}]}
+}
+
 // Write to the store, and check that it creates:
 // - $Path.recovery with the new data
 // - $Path.nextUpgradeBackup with the old data
 add_task(function* test_first_write_backup() {
-  let initial_content = "initial content " + Math.random();
-  let new_content = "test_1 " + Math.random();
+  let initial_content = generateFileContents("initial");
+  let new_content = generateFileContents("test_1");
 
   do_print("Before the first write, none of the files should exist");
   yield promise_check_exist(Paths.backups, false);
 
   yield File.makeDir(Paths.backups);
-  yield File.writeAtomic(Paths.clean, initial_content, { encoding: "utf-8" });
+  yield File.writeAtomic(Paths.clean, JSON.stringify(initial_content), { encoding: "utf-8" });
   yield SessionFile.write(new_content);
 
   do_print("After first write, a few files should have been created");
   yield promise_check_exist(Paths.backups, true);
   yield promise_check_exist(Paths.clean, false);
   yield promise_check_exist(Paths.cleanBackup, true);
   yield promise_check_exist(Paths.recovery, true);
   yield promise_check_exist(Paths.recoveryBackup, false);
@@ -111,18 +114,19 @@ add_task(function* test_first_write_back
   yield promise_check_contents(Paths.nextUpgradeBackup, initial_content);
 });
 
 // Write to the store again, and check that
 // - $Path.clean is not written
 // - $Path.recovery contains the new data
 // - $Path.recoveryBackup contains the previous data
 add_task(function* test_second_write_no_backup() {
-  let new_content = "test_2 " + Math.random();
+  let new_content = generateFileContents("test_2");
   let previous_backup_content = yield File.read(Paths.recovery, { encoding: "utf-8" });
+  previous_backup_content = JSON.parse(previous_backup_content);
 
   yield OS.File.remove(Paths.cleanBackup);
 
   yield SessionFile.write(new_content);
 
   yield promise_check_exist(Paths.backups, true);
   yield promise_check_exist(Paths.clean, false);
   yield promise_check_exist(Paths.cleanBackup, false);
@@ -131,21 +135,19 @@ add_task(function* test_second_write_no_
 
   yield promise_check_contents(Paths.recovery, new_content);
   yield promise_check_contents(Paths.recoveryBackup, previous_backup_content);
 });
 
 // Make sure that we create $Paths.clean and remove $Paths.recovery*
 // upon shutdown
 add_task(function* test_shutdown() {
-  let output = "test_3 " + Math.random();
+  let output = generateFileContents("test_3");
 
   yield File.writeAtomic(Paths.recovery, "I should disappear");
   yield File.writeAtomic(Paths.recoveryBackup, "I should also disappear");
 
   yield SessionWorker.post("write", [output, { isFinalWrite: true, performShutdownCleanup: true}]);
 
   do_check_false((yield File.exists(Paths.recovery)));
   do_check_false((yield File.exists(Paths.recoveryBackup)));
-  let input = yield File.read(Paths.clean, { encoding: "utf-8"});
-  do_check_eq(input, output);
-
+  yield promise_check_contents(Paths.clean, output);
 });
new file mode 100644
--- /dev/null
+++ b/browser/components/sessionstore/test/unit/test_shutdown_cleanup.js
@@ -0,0 +1,151 @@
+"use strict";
+
+/**
+ * This test ensures that we correctly clean up the session state before
+ * writing to disk a last time on shutdown. For now it only tests that each
+ * tab's shistory is capped to a maximum number of preceding and succeeding
+ * entries.
+ */
+
+const {XPCOMUtils} = Cu.import("resource://gre/modules/XPCOMUtils.jsm", {});
+const {Task} = Cu.import("resource://gre/modules/Task.jsm", {});
+const {SessionWorker} = Cu.import("resource:///modules/sessionstore/SessionWorker.jsm", {});
+
+const profd = do_get_profile();
+const {SessionFile} = Cu.import("resource:///modules/sessionstore/SessionFile.jsm", {});
+const {Paths} = SessionFile;
+
+const {OS} = Cu.import("resource://gre/modules/osfile.jsm", {});
+const {File} = OS;
+
+const MAX_ENTRIES = 9;
+const URL = "http://example.com/#";
+
+// We need a XULAppInfo to initialize SessionFile
+let XULAppInfo = {
+  vendor: "Mozilla",
+  name: "SessionRestoreTest",
+  ID: "{230de50e-4cd1-11dc-8314-0800200c9a66}",
+  version: "1",
+  appBuildID: "2007010101",
+  platformVersion: "",
+  platformBuildID: "2007010101",
+  inSafeMode: false,
+  logConsoleErrors: true,
+  OS: "XPCShell",
+  XPCOMABI: "noarch-spidermonkey",
+
+  QueryInterface: XPCOMUtils.generateQI([
+    Ci.nsIXULAppInfo,
+    Ci.nsIXULRuntime,
+  ])
+};
+
+let XULAppInfoFactory = {
+  createInstance: function (outer, iid) {
+    if (outer != null)
+      throw Cr.NS_ERROR_NO_AGGREGATION;
+    return XULAppInfo.QueryInterface(iid);
+  }
+};
+
+let registrar = Components.manager.QueryInterface(Ci.nsIComponentRegistrar);
+registrar.registerFactory(Components.ID("{fbfae60b-64a4-44ef-a911-08ceb70b9f31}"),
+                          "XULAppInfo", "@mozilla.org/xre/app-info;1",
+                          XULAppInfoFactory);
+
+add_task(function* setup() {
+  let source = do_get_file("data/sessionstore_valid.js");
+  source.copyTo(profd, "sessionstore.js");
+
+  // Finish SessionFile initialization.
+  yield SessionFile.read();
+
+  // Reset prefs on cleanup.
+  do_register_cleanup(() => {
+    Services.prefs.clearUserPref("browser.sessionstore.max_serialize_back");
+    Services.prefs.clearUserPref("browser.sessionstore.max_serialize_forward");
+  });
+});
+
+function createSessionState(index) {
+  // Generate the tab state entries and set the one-based
+  // tab-state index to the middle session history entry.
+  let tabState = {entries: [], index};
+  for (let i = 0; i < MAX_ENTRIES; i++) {
+    tabState.entries.push({url: URL + i});
+  }
+
+  return {windows: [{tabs: [tabState]}]};
+}
+
+function* setMaxBackForward(back, fwd) {
+  Services.prefs.setIntPref("browser.sessionstore.max_serialize_back", back);
+  Services.prefs.setIntPref("browser.sessionstore.max_serialize_forward", fwd);
+  yield SessionFile.read();
+}
+
+function* writeAndParse(state, path, options = {}) {
+  yield SessionWorker.post("write", [state, options]);
+  return JSON.parse(yield File.read(path, {encoding: "utf-8"}));
+}
+
+add_task(function* test_shistory_cap_none() {
+  let state = createSessionState(5);
+
+  // Don't limit the number of shistory entries.
+  yield setMaxBackForward(-1, -1);
+
+  // Check that no caps are applied.
+  let diskState = yield writeAndParse(state, Paths.clean, {isFinalWrite: true});
+  Assert.deepEqual(state, diskState, "no cap applied");
+});
+
+add_task(function* test_shistory_cap_middle() {
+  let state = createSessionState(5);
+  yield setMaxBackForward(2, 3);
+
+  // Cap is only applied on clean shutdown.
+  let diskState = yield writeAndParse(state, Paths.recovery);
+  Assert.deepEqual(state, diskState, "no cap applied");
+
+  // Check that the right number of shistory entries was discarded
+  // and the shistory index updated accordingly.
+  diskState = yield writeAndParse(state, Paths.clean, {isFinalWrite: true});
+  let tabState = state.windows[0].tabs[0];
+  tabState.entries = tabState.entries.slice(2, 8);
+  tabState.index = 3;
+  Assert.deepEqual(state, diskState, "cap applied");
+});
+
+add_task(function* test_shistory_cap_lower_bound() {
+  let state = createSessionState(1);
+  yield setMaxBackForward(5, 5);
+
+  // Cap is only applied on clean shutdown.
+  let diskState = yield writeAndParse(state, Paths.recovery);
+  Assert.deepEqual(state, diskState, "no cap applied");
+
+  // Check that the right number of shistory entries was discarded.
+  diskState = yield writeAndParse(state, Paths.clean, {isFinalWrite: true});
+  let tabState = state.windows[0].tabs[0];
+  tabState.entries = tabState.entries.slice(0, 6);
+  Assert.deepEqual(state, diskState, "cap applied");
+});
+
+add_task(function* test_shistory_cap_upper_bound() {
+  let state = createSessionState(MAX_ENTRIES);
+  yield setMaxBackForward(5, 5);
+
+  // Cap is only applied on clean shutdown.
+  let diskState = yield writeAndParse(state, Paths.recovery);
+  Assert.deepEqual(state, diskState, "no cap applied");
+
+  // Check that the right number of shistory entries was discarded
+  // and the shistory index updated accordingly.
+  diskState = yield writeAndParse(state, Paths.clean, {isFinalWrite: true});
+  let tabState = state.windows[0].tabs[0];
+  tabState.entries = tabState.entries.slice(3);
+  tabState.index = 6;
+  Assert.deepEqual(state, diskState, "cap applied");
+});
--- a/browser/components/sessionstore/test/unit/xpcshell.ini
+++ b/browser/components/sessionstore/test/unit/xpcshell.ini
@@ -4,12 +4,13 @@ tail =
 firefox-appdir = browser
 skip-if = toolkit == 'android' || toolkit == 'gonk'
 support-files =
   data/sessionCheckpoints_all.json
   data/sessionstore_invalid.js
   data/sessionstore_valid.js
 
 [test_backup_once.js]
+[test_histogram_corrupt_files.js]
+[test_shutdown_cleanup.js]
 [test_startup_nosession_async.js]
 [test_startup_session_async.js]
 [test_startup_invalid_session.js]
-[test_histogram_corrupt_files.js]