forked from mirrors/gecko-dev
		
	# ignore-this-changeset Differential Revision: https://phabricator.services.mozilla.com/D36042 --HG-- extra : source : d3afcafdce650a6f36cebbc126ee93b17f13cf52
		
			
				
	
	
		
			342 lines
		
	
	
	
		
			10 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			342 lines
		
	
	
	
		
			10 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
/* Any copyright is dedicated to the Public Domain.
 | 
						|
   http://creativecommons.org/publicdomain/zero/1.0/ */
 | 
						|
 | 
						|
// This tests are for a sessionstore.js atomic backup.
 | 
						|
// Each test will wait for a write to the Session Store
 | 
						|
// before executing.
 | 
						|
 | 
						|
var OS = ChromeUtils.import("resource://gre/modules/osfile.jsm", {}).OS;
 | 
						|
var { File, Constants, Path } = OS;
 | 
						|
 | 
						|
const PREF_SS_INTERVAL = "browser.sessionstore.interval";
 | 
						|
const Paths = SessionFile.Paths;
 | 
						|
 | 
						|
// A text decoder.
 | 
						|
var gDecoder = new TextDecoder();
 | 
						|
// Global variables that contain sessionstore.jsonlz4 and sessionstore.baklz4 data for
 | 
						|
// comparison between tests.
 | 
						|
var gSSData;
 | 
						|
var gSSBakData;
 | 
						|
 | 
						|
function promiseRead(path) {
 | 
						|
  return File.read(path, { encoding: "utf-8", compression: "lz4" });
 | 
						|
}
 | 
						|
 | 
						|
async function reInitSessionFile() {
 | 
						|
  await SessionFile.wipe();
 | 
						|
  await SessionFile.read();
 | 
						|
}
 | 
						|
 | 
						|
add_task(async function init() {
 | 
						|
  // Make sure that we are not racing with SessionSaver's time based
 | 
						|
  // saves.
 | 
						|
  Services.prefs.setIntPref(PREF_SS_INTERVAL, 10000000);
 | 
						|
  registerCleanupFunction(() => Services.prefs.clearUserPref(PREF_SS_INTERVAL));
 | 
						|
});
 | 
						|
 | 
						|
add_task(async function test_creation() {
 | 
						|
  // Cancel all pending session saves so they won't get in our way.
 | 
						|
  SessionSaver.cancel();
 | 
						|
 | 
						|
  // Create dummy sessionstore backups
 | 
						|
  let OLD_BACKUP = Path.join(Constants.Path.profileDir, "sessionstore.baklz4");
 | 
						|
  let OLD_UPGRADE_BACKUP = Path.join(
 | 
						|
    Constants.Path.profileDir,
 | 
						|
    "sessionstore.baklz4-0000000"
 | 
						|
  );
 | 
						|
 | 
						|
  await File.writeAtomic(OLD_BACKUP, "sessionstore.bak");
 | 
						|
  await File.writeAtomic(OLD_UPGRADE_BACKUP, "sessionstore upgrade backup");
 | 
						|
 | 
						|
  await reInitSessionFile();
 | 
						|
 | 
						|
  // Ensure none of the sessionstore files and backups exists
 | 
						|
  for (let k of Paths.loadOrder) {
 | 
						|
    ok(
 | 
						|
      !(await File.exists(Paths[k])),
 | 
						|
      "After wipe " + k + " sessionstore file doesn't exist"
 | 
						|
    );
 | 
						|
  }
 | 
						|
  ok(!(await File.exists(OLD_BACKUP)), "After wipe, old backup doesn't exist");
 | 
						|
  ok(
 | 
						|
    !(await File.exists(OLD_UPGRADE_BACKUP)),
 | 
						|
    "After wipe, old upgrade backup doesn't exist"
 | 
						|
  );
 | 
						|
 | 
						|
  // Open a new tab, save session, ensure that the correct files exist.
 | 
						|
  let URL_BASE =
 | 
						|
    "http://example.com/?atomic_backup_test_creation=" + Math.random();
 | 
						|
  let URL = URL_BASE + "?first_write";
 | 
						|
  let tab = BrowserTestUtils.addTab(gBrowser, URL);
 | 
						|
 | 
						|
  info("Testing situation after a single write");
 | 
						|
  await promiseBrowserLoaded(tab.linkedBrowser);
 | 
						|
  await TabStateFlusher.flush(tab.linkedBrowser);
 | 
						|
  await SessionSaver.run();
 | 
						|
 | 
						|
  ok(
 | 
						|
    await File.exists(Paths.recovery),
 | 
						|
    "After write, recovery sessionstore file exists again"
 | 
						|
  );
 | 
						|
  ok(
 | 
						|
    !(await File.exists(Paths.recoveryBackup)),
 | 
						|
    "After write, recoveryBackup sessionstore doesn't exist"
 | 
						|
  );
 | 
						|
  ok(
 | 
						|
    (await promiseRead(Paths.recovery)).includes(URL),
 | 
						|
    "Recovery sessionstore file contains the required tab"
 | 
						|
  );
 | 
						|
  ok(
 | 
						|
    !(await File.exists(Paths.clean)),
 | 
						|
    "After first write, clean shutdown " +
 | 
						|
      "sessionstore doesn't exist, since we haven't shutdown yet"
 | 
						|
  );
 | 
						|
 | 
						|
  // Open a second tab, save session, ensure that the correct files exist.
 | 
						|
  info("Testing situation after a second write");
 | 
						|
  let URL2 = URL_BASE + "?second_write";
 | 
						|
  BrowserTestUtils.loadURI(tab.linkedBrowser, URL2);
 | 
						|
  await promiseBrowserLoaded(tab.linkedBrowser);
 | 
						|
  await TabStateFlusher.flush(tab.linkedBrowser);
 | 
						|
  await SessionSaver.run();
 | 
						|
 | 
						|
  ok(
 | 
						|
    await File.exists(Paths.recovery),
 | 
						|
    "After second write, recovery sessionstore file still exists"
 | 
						|
  );
 | 
						|
  ok(
 | 
						|
    (await promiseRead(Paths.recovery)).includes(URL2),
 | 
						|
    "Recovery sessionstore file contains the latest url"
 | 
						|
  );
 | 
						|
  ok(
 | 
						|
    await File.exists(Paths.recoveryBackup),
 | 
						|
    "After write, recoveryBackup sessionstore now exists"
 | 
						|
  );
 | 
						|
  let backup = await promiseRead(Paths.recoveryBackup);
 | 
						|
  ok(!backup.includes(URL2), "Recovery backup doesn't contain the latest url");
 | 
						|
  ok(backup.includes(URL), "Recovery backup contains the original url");
 | 
						|
  ok(
 | 
						|
    !(await File.exists(Paths.clean)),
 | 
						|
    "After first write, clean shutdown " +
 | 
						|
      "sessionstore doesn't exist, since we haven't shutdown yet"
 | 
						|
  );
 | 
						|
 | 
						|
  info("Reinitialize, ensure that we haven't leaked sensitive files");
 | 
						|
  await SessionFile.read(); // Reinitializes SessionFile
 | 
						|
  await SessionSaver.run();
 | 
						|
  ok(
 | 
						|
    !(await File.exists(Paths.clean)),
 | 
						|
    "After second write, clean shutdown " +
 | 
						|
      "sessionstore doesn't exist, since we haven't shutdown yet"
 | 
						|
  );
 | 
						|
  ok(
 | 
						|
    !(await File.exists(Paths.upgradeBackup)),
 | 
						|
    "After second write, clean " +
 | 
						|
      "shutdown sessionstore doesn't exist, since we haven't shutdown yet"
 | 
						|
  );
 | 
						|
  ok(
 | 
						|
    !(await File.exists(Paths.nextUpgradeBackup)),
 | 
						|
    "After second write, clean " +
 | 
						|
      "shutdown sessionstore doesn't exist, since we haven't shutdown yet"
 | 
						|
  );
 | 
						|
 | 
						|
  gBrowser.removeTab(tab);
 | 
						|
});
 | 
						|
 | 
						|
var promiseSource = async function(name) {
 | 
						|
  let URL =
 | 
						|
    "http://example.com/?atomic_backup_test_recovery=" +
 | 
						|
    Math.random() +
 | 
						|
    "&name=" +
 | 
						|
    name;
 | 
						|
  let tab = BrowserTestUtils.addTab(gBrowser, URL);
 | 
						|
 | 
						|
  await promiseBrowserLoaded(tab.linkedBrowser);
 | 
						|
  await TabStateFlusher.flush(tab.linkedBrowser);
 | 
						|
  await SessionSaver.run();
 | 
						|
  gBrowser.removeTab(tab);
 | 
						|
 | 
						|
  let SOURCE = await promiseRead(Paths.recovery);
 | 
						|
  await SessionFile.wipe();
 | 
						|
  return SOURCE;
 | 
						|
};
 | 
						|
 | 
						|
add_task(async function test_recovery() {
 | 
						|
  await reInitSessionFile();
 | 
						|
  info("Attempting to recover from the recovery file");
 | 
						|
 | 
						|
  // Create Paths.recovery, ensure that we can recover from it.
 | 
						|
  let SOURCE = await promiseSource("Paths.recovery");
 | 
						|
  await File.makeDir(Paths.backups);
 | 
						|
  await File.writeAtomic(Paths.recovery, SOURCE, {
 | 
						|
    encoding: "utf-8",
 | 
						|
    compression: "lz4",
 | 
						|
  });
 | 
						|
  is(
 | 
						|
    (await SessionFile.read()).source,
 | 
						|
    SOURCE,
 | 
						|
    "Recovered the correct source from the recovery file"
 | 
						|
  );
 | 
						|
 | 
						|
  info("Corrupting recovery file, attempting to recover from recovery backup");
 | 
						|
  SOURCE = await promiseSource("Paths.recoveryBackup");
 | 
						|
  await File.makeDir(Paths.backups);
 | 
						|
  await File.writeAtomic(Paths.recoveryBackup, SOURCE, {
 | 
						|
    encoding: "utf-8",
 | 
						|
    compression: "lz4",
 | 
						|
  });
 | 
						|
  await File.writeAtomic(Paths.recovery, "<Invalid JSON>", {
 | 
						|
    encoding: "utf-8",
 | 
						|
    compression: "lz4",
 | 
						|
  });
 | 
						|
  is(
 | 
						|
    (await SessionFile.read()).source,
 | 
						|
    SOURCE,
 | 
						|
    "Recovered the correct source from the recovery file"
 | 
						|
  );
 | 
						|
});
 | 
						|
 | 
						|
add_task(async function test_recovery_inaccessible() {
 | 
						|
  // Can't do chmod() on non-UNIX platforms, we need that for this test.
 | 
						|
  if (AppConstants.platform != "macosx" && AppConstants.platform != "linux") {
 | 
						|
    return;
 | 
						|
  }
 | 
						|
 | 
						|
  await reInitSessionFile();
 | 
						|
  info(
 | 
						|
    "Making recovery file inaccessible, attempting to recover from recovery backup"
 | 
						|
  );
 | 
						|
  let SOURCE_RECOVERY = await promiseSource("Paths.recovery");
 | 
						|
  let SOURCE = await promiseSource("Paths.recoveryBackup");
 | 
						|
  await File.makeDir(Paths.backups);
 | 
						|
  await File.writeAtomic(Paths.recoveryBackup, SOURCE, {
 | 
						|
    encoding: "utf-8",
 | 
						|
    compression: "lz4",
 | 
						|
  });
 | 
						|
 | 
						|
  // Write a valid recovery file but make it inaccessible.
 | 
						|
  await File.writeAtomic(Paths.recovery, SOURCE_RECOVERY, {
 | 
						|
    encoding: "utf-8",
 | 
						|
    compression: "lz4",
 | 
						|
  });
 | 
						|
  await File.setPermissions(Paths.recovery, { unixMode: 0 });
 | 
						|
 | 
						|
  is(
 | 
						|
    (await SessionFile.read()).source,
 | 
						|
    SOURCE,
 | 
						|
    "Recovered the correct source from the recovery file"
 | 
						|
  );
 | 
						|
  await File.setPermissions(Paths.recovery, { unixMode: 0o644 });
 | 
						|
});
 | 
						|
 | 
						|
add_task(async function test_clean() {
 | 
						|
  await reInitSessionFile();
 | 
						|
  let SOURCE = await promiseSource("Paths.clean");
 | 
						|
  await File.writeAtomic(Paths.clean, SOURCE, {
 | 
						|
    encoding: "utf-8",
 | 
						|
    compression: "lz4",
 | 
						|
  });
 | 
						|
  await SessionFile.read();
 | 
						|
  await SessionSaver.run();
 | 
						|
  is(
 | 
						|
    await promiseRead(Paths.cleanBackup),
 | 
						|
    SOURCE,
 | 
						|
    "After first read/write, " +
 | 
						|
      "clean shutdown file has been moved to cleanBackup"
 | 
						|
  );
 | 
						|
});
 | 
						|
 | 
						|
/**
 | 
						|
 * Tests loading of sessionstore when format version is known.
 | 
						|
 */
 | 
						|
add_task(async function test_version() {
 | 
						|
  info("Preparing sessionstore");
 | 
						|
  let SOURCE = await promiseSource("Paths.clean");
 | 
						|
 | 
						|
  // Check there's a format version number
 | 
						|
  is(
 | 
						|
    JSON.parse(SOURCE).version[0],
 | 
						|
    "sessionrestore",
 | 
						|
    "Found sessionstore format version"
 | 
						|
  );
 | 
						|
 | 
						|
  // Create Paths.clean file
 | 
						|
  await File.makeDir(Paths.backups);
 | 
						|
  await File.writeAtomic(Paths.clean, SOURCE, {
 | 
						|
    encoding: "utf-8",
 | 
						|
    compression: "lz4",
 | 
						|
  });
 | 
						|
 | 
						|
  info("Attempting to recover from the clean file");
 | 
						|
  // Ensure that we can recover from Paths.recovery
 | 
						|
  is(
 | 
						|
    (await SessionFile.read()).source,
 | 
						|
    SOURCE,
 | 
						|
    "Recovered the correct source from the clean file"
 | 
						|
  );
 | 
						|
});
 | 
						|
 | 
						|
/**
 | 
						|
 * Tests fallback to previous backups if format version is unknown.
 | 
						|
 */
 | 
						|
add_task(async function test_version_fallback() {
 | 
						|
  await reInitSessionFile();
 | 
						|
  info("Preparing data, making sure that it has a version number");
 | 
						|
  let SOURCE = await promiseSource("Paths.clean");
 | 
						|
  let BACKUP_SOURCE = await promiseSource("Paths.cleanBackup");
 | 
						|
 | 
						|
  is(
 | 
						|
    JSON.parse(SOURCE).version[0],
 | 
						|
    "sessionrestore",
 | 
						|
    "Found sessionstore format version"
 | 
						|
  );
 | 
						|
  is(
 | 
						|
    JSON.parse(BACKUP_SOURCE).version[0],
 | 
						|
    "sessionrestore",
 | 
						|
    "Found backup sessionstore format version"
 | 
						|
  );
 | 
						|
 | 
						|
  await File.makeDir(Paths.backups);
 | 
						|
 | 
						|
  info(
 | 
						|
    "Modifying format version number to something incorrect, to make sure that we disregard the file."
 | 
						|
  );
 | 
						|
  let parsedSource = JSON.parse(SOURCE);
 | 
						|
  parsedSource.version[0] = "bookmarks";
 | 
						|
  await File.writeAtomic(Paths.clean, JSON.stringify(parsedSource), {
 | 
						|
    encoding: "utf-8",
 | 
						|
    compression: "lz4",
 | 
						|
  });
 | 
						|
  await File.writeAtomic(Paths.cleanBackup, BACKUP_SOURCE, {
 | 
						|
    encoding: "utf-8",
 | 
						|
    compression: "lz4",
 | 
						|
  });
 | 
						|
  is(
 | 
						|
    (await SessionFile.read()).source,
 | 
						|
    BACKUP_SOURCE,
 | 
						|
    "Recovered the correct source from the backup recovery file"
 | 
						|
  );
 | 
						|
 | 
						|
  info(
 | 
						|
    "Modifying format version number to a future version, to make sure that we disregard the file."
 | 
						|
  );
 | 
						|
  parsedSource = JSON.parse(SOURCE);
 | 
						|
  parsedSource.version[1] = Number.MAX_SAFE_INTEGER;
 | 
						|
  await File.writeAtomic(Paths.clean, JSON.stringify(parsedSource), {
 | 
						|
    encoding: "utf-8",
 | 
						|
    compression: "lz4",
 | 
						|
  });
 | 
						|
  await File.writeAtomic(Paths.cleanBackup, BACKUP_SOURCE, {
 | 
						|
    encoding: "utf-8",
 | 
						|
    compression: "lz4",
 | 
						|
  });
 | 
						|
  is(
 | 
						|
    (await SessionFile.read()).source,
 | 
						|
    BACKUP_SOURCE,
 | 
						|
    "Recovered the correct source from the backup recovery file"
 | 
						|
  );
 | 
						|
});
 | 
						|
 | 
						|
add_task(async function cleanup() {
 | 
						|
  await reInitSessionFile();
 | 
						|
});
 |