diff options
Diffstat (limited to 'browser/components/sessionstore/test/unit')
12 files changed, 503 insertions, 0 deletions
diff --git a/browser/components/sessionstore/test/unit/.eslintrc.js b/browser/components/sessionstore/test/unit/.eslintrc.js new file mode 100644 index 000000000..d35787cd2 --- /dev/null +++ b/browser/components/sessionstore/test/unit/.eslintrc.js @@ -0,0 +1,7 @@ +"use strict"; + +module.exports = { + "extends": [ + "../../../../../testing/xpcshell/xpcshell.eslintrc.js" + ] +}; diff --git a/browser/components/sessionstore/test/unit/data/sessionCheckpoints_all.json b/browser/components/sessionstore/test/unit/data/sessionCheckpoints_all.json new file mode 100644 index 000000000..928de6a39 --- /dev/null +++ b/browser/components/sessionstore/test/unit/data/sessionCheckpoints_all.json @@ -0,0 +1 @@ +{"profile-after-change":true,"final-ui-startup":true,"sessionstore-windows-restored":true,"quit-application-granted":true,"quit-application":true,"sessionstore-final-state-write-complete":true,"profile-change-net-teardown":true,"profile-change-teardown":true,"profile-before-change":true}
\ No newline at end of file diff --git a/browser/components/sessionstore/test/unit/data/sessionstore_invalid.js b/browser/components/sessionstore/test/unit/data/sessionstore_invalid.js new file mode 100644 index 000000000..a8c3ff2ff --- /dev/null +++ b/browser/components/sessionstore/test/unit/data/sessionstore_invalid.js @@ -0,0 +1,3 @@ +{ + "windows": // invalid json +} diff --git a/browser/components/sessionstore/test/unit/data/sessionstore_valid.js b/browser/components/sessionstore/test/unit/data/sessionstore_valid.js new file mode 100644 index 000000000..f9511f29f --- /dev/null +++ b/browser/components/sessionstore/test/unit/data/sessionstore_valid.js @@ -0,0 +1,3 @@ +{ + "windows": [] +}
\ No newline at end of file diff --git a/browser/components/sessionstore/test/unit/head.js b/browser/components/sessionstore/test/unit/head.js new file mode 100644 index 000000000..b62856012 --- /dev/null +++ b/browser/components/sessionstore/test/unit/head.js @@ -0,0 +1,32 @@ +var Cu = Components.utils; +var Cc = Components.classes; +var Ci = Components.interfaces; + +Components.utils.import("resource://gre/modules/Services.jsm"); + +// Call a function once initialization of SessionStartup is complete +function afterSessionStartupInitialization(cb) { + do_print("Waiting for session startup initialization"); + let observer = function() { + try { + do_print("Session startup initialization observed"); + Services.obs.removeObserver(observer, "sessionstore-state-finalized"); + cb(); + } catch (ex) { + do_throw(ex); + } + }; + + // We need the Crash Monitor initialized for sessionstartup to run + // successfully. + Components.utils.import("resource://gre/modules/CrashMonitor.jsm"); + CrashMonitor.init(); + + // Start sessionstartup initialization. + let startup = Cc["@mozilla.org/browser/sessionstartup;1"]. + getService(Ci.nsIObserver); + Services.obs.addObserver(startup, "final-ui-startup", false); + Services.obs.addObserver(startup, "quit-application", false); + Services.obs.notifyObservers(null, "final-ui-startup", ""); + Services.obs.addObserver(observer, "sessionstore-state-finalized", false); +}; diff --git a/browser/components/sessionstore/test/unit/test_backup_once.js b/browser/components/sessionstore/test/unit/test_backup_once.js new file mode 100644 index 000000000..fff34ad58 --- /dev/null +++ b/browser/components/sessionstore/test/unit/test_backup_once.js @@ -0,0 +1,130 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +var {OS} = Cu.import("resource://gre/modules/osfile.jsm", {}); +var {XPCOMUtils} = Cu.import("resource://gre/modules/XPCOMUtils.jsm", {}); +var {Task} = Cu.import("resource://gre/modules/Task.jsm", {}); +var {SessionWorker} = Cu.import("resource:///modules/sessionstore/SessionWorker.jsm", {}); + +var File = OS.File; +var Paths; +var SessionFile; + +// We need a XULAppInfo to initialize SessionFile +Cu.import("resource://testing-common/AppInfo.jsm", this); +updateAppInfo({ + name: "SessionRestoreTest", + ID: "{230de50e-4cd1-11dc-8314-0800200c9a66}", + version: "1", + platformVersion: "", +}); + +function run_test() { + run_next_test(); +} + +add_task(function* init() { + // Make sure that we have a profile before initializing SessionFile + let profd = do_get_profile(); + SessionFile = Cu.import("resource:///modules/sessionstore/SessionFile.jsm", {}).SessionFile; + Paths = SessionFile.Paths; + + + let source = do_get_file("data/sessionstore_valid.js"); + source.copyTo(profd, "sessionstore.js"); + + // Finish initialization of SessionFile + yield SessionFile.read(); +}); + +var pathStore; +var pathBackup; +var decoder; + +function promise_check_exist(path, shouldExist) { + return Task.spawn(function*() { + do_print("Ensuring that " + path + (shouldExist?" exists":" does not exist")); + if ((yield OS.File.exists(path)) != shouldExist) { + throw new Error("File " + path + " should " + (shouldExist?"exist":"not exist")); + } + }); +} + +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"}); + 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 = 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, 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); + yield promise_check_exist(Paths.nextUpgradeBackup, true); + + yield promise_check_contents(Paths.recovery, new_content); + 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 = 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); + yield promise_check_exist(Paths.recovery, true); + yield promise_check_exist(Paths.nextUpgradeBackup, true); + + 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 = 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))); + yield promise_check_contents(Paths.clean, output); +}); diff --git a/browser/components/sessionstore/test/unit/test_histogram_corrupt_files.js b/browser/components/sessionstore/test/unit/test_histogram_corrupt_files.js new file mode 100644 index 000000000..c7d8b03ed --- /dev/null +++ b/browser/components/sessionstore/test/unit/test_histogram_corrupt_files.js @@ -0,0 +1,114 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/* + * The primary purpose of this test is to ensure that + * the sessionstore component records information about + * corrupted backup files into a histogram. + */ + +"use strict"; +Cu.import("resource://gre/modules/osfile.jsm", this); + +const Telemetry = Services.telemetry; +const Path = OS.Path; +const HistogramId = "FX_SESSION_RESTORE_ALL_FILES_CORRUPT"; + +// Prepare the session file. +var profd = do_get_profile(); +Cu.import("resource:///modules/sessionstore/SessionFile.jsm", this); + +/** + * A utility function for resetting the histogram and the contents + * of the backup directory. + */ +function reset_session(backups = {}) { + + // Reset the histogram. + Telemetry.getHistogramById(HistogramId).clear(); + + // Reset the contents of the backups directory + OS.File.makeDir(SessionFile.Paths.backups); + for (let key of SessionFile.Paths.loadOrder) { + if (backups.hasOwnProperty(key)) { + OS.File.copy(backups[key], SessionFile.Paths[key]); + } else { + OS.File.remove(SessionFile.Paths[key]); + } + } +} + +/** + * In order to use FX_SESSION_RESTORE_ALL_FILES_CORRUPT histogram + * it has to be registered in "toolkit/components/telemetry/Histograms.json". + * This test ensures that the histogram is registered and empty. + */ +add_task(function* test_ensure_histogram_exists_and_empty() { + let s = Telemetry.getHistogramById(HistogramId).snapshot(); + Assert.equal(s.sum, 0, "Initially, the sum of probes is 0"); +}); + +/** + * Makes sure that the histogram is negatively updated when no + * backup files are present. + */ +add_task(function* test_no_files_exist() { + // No session files are available to SessionFile. + reset_session(); + + yield SessionFile.read(); + // Checking if the histogram is updated negatively + let h = Telemetry.getHistogramById(HistogramId); + let s = h.snapshot(); + Assert.equal(s.counts[0], 1, "One probe for the 'false' bucket."); + Assert.equal(s.counts[1], 0, "No probes in the 'true' bucket."); +}); + +/** + * Makes sure that the histogram is negatively updated when at least one + * backup file is not corrupted. + */ +add_task(function* test_one_file_valid() { + // Corrupting some backup files. + let invalidSession = "data/sessionstore_invalid.js"; + let validSession = "data/sessionstore_valid.js"; + reset_session({ + clean : invalidSession, + cleanBackup: validSession, + recovery: invalidSession, + recoveryBackup: invalidSession + }); + + yield SessionFile.read(); + // Checking if the histogram is updated negatively. + let h = Telemetry.getHistogramById(HistogramId); + let s = h.snapshot(); + Assert.equal(s.counts[0], 1, "One probe for the 'false' bucket."); + Assert.equal(s.counts[1], 0, "No probes in the 'true' bucket."); +}); + +/** + * Makes sure that the histogram is positively updated when all + * backup files are corrupted. + */ +add_task(function* test_all_files_corrupt() { + // Corrupting all backup files. + let invalidSession = "data/sessionstore_invalid.js"; + reset_session({ + clean : invalidSession, + cleanBackup: invalidSession, + recovery: invalidSession, + recoveryBackup: invalidSession + }); + + yield SessionFile.read(); + // Checking if the histogram is positively updated. + let h = Telemetry.getHistogramById(HistogramId); + let s = h.snapshot(); + Assert.equal(s.counts[1], 1, "One probe for the 'true' bucket."); + Assert.equal(s.counts[0], 0, "No probes in the 'false' bucket."); +}); + +function run_test() { + run_next_test(); +} diff --git a/browser/components/sessionstore/test/unit/test_shutdown_cleanup.js b/browser/components/sessionstore/test/unit/test_shutdown_cleanup.js new file mode 100644 index 000000000..b99e566e9 --- /dev/null +++ b/browser/components/sessionstore/test/unit/test_shutdown_cleanup.js @@ -0,0 +1,127 @@ +"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 +Cu.import("resource://testing-common/AppInfo.jsm", this); +updateAppInfo({ + name: "SessionRestoreTest", + ID: "{230de50e-4cd1-11dc-8314-0800200c9a66}", + version: "1", + platformVersion: "", +}); + +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"); +}); diff --git a/browser/components/sessionstore/test/unit/test_startup_invalid_session.js b/browser/components/sessionstore/test/unit/test_startup_invalid_session.js new file mode 100644 index 000000000..9f6df8585 --- /dev/null +++ b/browser/components/sessionstore/test/unit/test_startup_invalid_session.js @@ -0,0 +1,21 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +function run_test() { + let profd = do_get_profile(); + + let sourceSession = do_get_file("data/sessionstore_invalid.js"); + sourceSession.copyTo(profd, "sessionstore.js"); + + let sourceCheckpoints = do_get_file("data/sessionCheckpoints_all.json"); + sourceCheckpoints.copyTo(profd, "sessionCheckpoints.json"); + + do_test_pending(); + let startup = Cc["@mozilla.org/browser/sessionstartup;1"]. + getService(Ci.nsISessionStartup); + + afterSessionStartupInitialization(function cb() { + do_check_eq(startup.sessionType, Ci.nsISessionStartup.NO_SESSION); + do_test_finished(); + }); +} diff --git a/browser/components/sessionstore/test/unit/test_startup_nosession_async.js b/browser/components/sessionstore/test/unit/test_startup_nosession_async.js new file mode 100644 index 000000000..5185b02d6 --- /dev/null +++ b/browser/components/sessionstore/test/unit/test_startup_nosession_async.js @@ -0,0 +1,22 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + + +// Test nsISessionStartup.sessionType in the following scenario: +// - no sessionstore.js; +// - the session store has been loaded, so no need to go +// through the synchronous fallback + +function run_test() { + do_get_profile(); + // Initialize the profile (the session startup uses it) + + do_test_pending(); + let startup = Cc["@mozilla.org/browser/sessionstartup;1"]. + getService(Ci.nsISessionStartup); + + afterSessionStartupInitialization(function cb() { + do_check_eq(startup.sessionType, Ci.nsISessionStartup.NO_SESSION); + do_test_finished(); + }); +}
\ No newline at end of file diff --git a/browser/components/sessionstore/test/unit/test_startup_session_async.js b/browser/components/sessionstore/test/unit/test_startup_session_async.js new file mode 100644 index 000000000..459acf885 --- /dev/null +++ b/browser/components/sessionstore/test/unit/test_startup_session_async.js @@ -0,0 +1,27 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + + +// Test nsISessionStartup.sessionType in the following scenario: +// - valid sessionstore.js; +// - valid sessionCheckpoints.json with all checkpoints; +// - the session store has been loaded + +function run_test() { + let profd = do_get_profile(); + + let sourceSession = do_get_file("data/sessionstore_valid.js"); + sourceSession.copyTo(profd, "sessionstore.js"); + + let sourceCheckpoints = do_get_file("data/sessionCheckpoints_all.json"); + sourceCheckpoints.copyTo(profd, "sessionCheckpoints.json"); + + do_test_pending(); + let startup = Cc["@mozilla.org/browser/sessionstartup;1"]. + getService(Ci.nsISessionStartup); + + afterSessionStartupInitialization(function cb() { + do_check_eq(startup.sessionType, Ci.nsISessionStartup.DEFER_SESSION); + do_test_finished(); + }); +} diff --git a/browser/components/sessionstore/test/unit/xpcshell.ini b/browser/components/sessionstore/test/unit/xpcshell.ini new file mode 100644 index 000000000..09980f877 --- /dev/null +++ b/browser/components/sessionstore/test/unit/xpcshell.ini @@ -0,0 +1,16 @@ +[DEFAULT] +head = head.js +tail = +firefox-appdir = browser +skip-if = toolkit == 'android' +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] |