Bug 1207772: Add some better sanity checks for the bootstrap function calls. r=rhelmer

Unifies the methods we have to check that bootstrap add-ons are correctly loaded
and makes it easier to make changes to them all in the future without needing to
re-sign add-ons etc.

This code allows a bootstrap script to use a shared script in a single line of
code. The shared scripts sends out all the relevant info over the observer
service, the add-ons manager test harness receives and retains the current state
for every add-on also performing sanity checks like making sure an "install"
method is always called before any "startup" method etc. It also provides simple
functions to check the state of a given add-on.

--HG--
extra : commitid : 2mhI13iGMy6
extra : rebase_source : 1565c2909517c363a81b0516748c1c80a8890bdd
extra : amend_source : f8a1c8ad54d76109efbcad2ba5611cb89ab8e9a0
This commit is contained in:
Dave Townsend 2015-09-15 10:45:52 -07:00
Родитель 3c0a37662c
Коммит 0b2ce2f493
23 изменённых файлов: 472 добавлений и 478 удалений

Просмотреть файл

@ -2060,11 +2060,11 @@ this.XPIDatabaseReconcile = {
BOOTSTRAP_REASONS.ADDON_UPGRADE :
BOOTSTRAP_REASONS.ADDON_DOWNGRADE;
// If the previous add-on was in a different location, bootstrapped
// If the previous add-on was in a different path, bootstrapped
// and still exists then call its uninstall method.
if (previousAddon.bootstrap && previousAddon._installLocation &&
currentAddon._installLocation != previousAddon._installLocation &&
previousAddon._sourceBundle.exists()) {
previousAddon._sourceBundle.exists() &&
currentAddon._sourceBundle.path != previousAddon._sourceBundle.path) {
XPIProvider.callBootstrapMethod(previousAddon, previousAddon._sourceBundle,
"uninstall", installReason,
@ -2118,7 +2118,18 @@ this.XPIDatabaseReconcile = {
continue;
// This add-on vanished
// If the previous add-on was bootstrapped and still exists then call its
// uninstall method.
if (previousAddon.bootstrap && previousAddon._sourceBundle.exists()) {
XPIProvider.callBootstrapMethod(previousAddon, previousAddon._sourceBundle,
"uninstall", BOOTSTRAP_REASONS.ADDON_UNINSTALL);
XPIProvider.unloadBootstrapScope(previousAddon.id);
}
AddonManagerPrivate.addStartupChange(AddonManager.STARTUP_CHANGE_UNINSTALLED, id);
// Make sure to flush the cache when an old add-on has gone away
flushStartupCache();
}
// Make sure add-ons from hidden locations are marked invisible and inactive

Просмотреть файл

@ -1,32 +1 @@
Components.utils.import("resource://gre/modules/Services.jsm");
// Test steps chain from pref observers on *_reason,
// so always set that last
function install(data, reason) {
Components.utils.import(data.resourceURI.spec + "version.jsm");
Services.prefs.setIntPref("bootstraptest.installed_version", VERSION);
Services.prefs.setIntPref("bootstraptest.install_oldversion", data.oldVersion);
Components.utils.unload(data.resourceURI.spec + "version.jsm");
Services.prefs.setIntPref("bootstraptest.install_reason", reason);
}
function startup(data, reason) {
Components.utils.reportError("bootstrap startup");
Components.utils.import(data.resourceURI.spec + "version.jsm");
Services.prefs.setIntPref("bootstraptest.active_version", VERSION);
Services.prefs.setIntPref("bootstraptest.startup_oldversion", data.oldVersion);
Components.utils.unload(data.resourceURI.spec + "version.jsm");
Services.prefs.setIntPref("bootstraptest.startup_reason", reason);
}
function shutdown(data, reason) {
Services.prefs.setIntPref("bootstraptest.active_version", 0);
Services.prefs.setIntPref("bootstraptest.shutdown_newversion", data.newVersion);
Services.prefs.setIntPref("bootstraptest.shutdown_reason", reason);
}
function uninstall(data, reason) {
Services.prefs.setIntPref("bootstraptest.installed_version", 0);
Services.prefs.setIntPref("bootstraptest.uninstall_newversion", data.newVersion);
Services.prefs.setIntPref("bootstraptest.uninstall_reason", reason);
}
Components.utils.import("resource://xpcshell-data/BootstrapMonitor.jsm").monitor(this);

Просмотреть файл

@ -1,31 +1 @@
Components.utils.import("resource://gre/modules/Services.jsm");
// Test steps chain from pref observers on *_reason,
// so always set that last
function install(data, reason) {
Components.utils.import(data.resourceURI.spec + "version.jsm");
Services.prefs.setIntPref("bootstraptest.installed_version", VERSION);
Services.prefs.setIntPref("bootstraptest.install_oldversion", data.oldVersion);
Components.utils.unload(data.resourceURI.spec + "version.jsm");
Services.prefs.setIntPref("bootstraptest.install_reason", reason);
}
function startup(data, reason) {
Components.utils.import(data.resourceURI.spec + "version.jsm");
Services.prefs.setIntPref("bootstraptest.active_version", VERSION);
Services.prefs.setIntPref("bootstraptest.startup_oldversion", data.oldVersion);
Components.utils.unload(data.resourceURI.spec + "version.jsm");
Services.prefs.setIntPref("bootstraptest.startup_reason", reason);
}
function shutdown(data, reason) {
Services.prefs.setIntPref("bootstraptest.active_version", 0);
Services.prefs.setIntPref("bootstraptest.shutdown_newversion", data.newVersion);
Services.prefs.setIntPref("bootstraptest.shutdown_reason", reason);
}
function uninstall(data, reason) {
Services.prefs.setIntPref("bootstraptest.installed_version", 0);
Services.prefs.setIntPref("bootstraptest.uninstall_newversion", data.newVersion);
Services.prefs.setIntPref("bootstraptest.uninstall_reason", reason);
}
Components.utils.import("resource://xpcshell-data/BootstrapMonitor.jsm").monitor(this);

Просмотреть файл

@ -1,31 +1 @@
Components.utils.import("resource://gre/modules/Services.jsm");
// Test steps chain from pref observers on *_reason,
// so always set that last
function install(data, reason) {
Components.utils.import(data.resourceURI.spec + "version.jsm");
Services.prefs.setIntPref("bootstraptest.installed_version", VERSION);
Services.prefs.setIntPref("bootstraptest.install_oldversion", data.oldVersion);
Components.utils.unload(data.resourceURI.spec + "version.jsm");
Services.prefs.setIntPref("bootstraptest.install_reason", reason);
}
function startup(data, reason) {
Components.utils.import(data.resourceURI.spec + "version.jsm");
Services.prefs.setIntPref("bootstraptest.active_version", VERSION);
Services.prefs.setIntPref("bootstraptest.startup_oldversion", data.oldVersion);
Components.utils.unload(data.resourceURI.spec + "version.jsm");
Services.prefs.setIntPref("bootstraptest.startup_reason", reason);
}
function shutdown(data, reason) {
Services.prefs.setIntPref("bootstraptest.active_version", 0);
Services.prefs.setIntPref("bootstraptest.shutdown_newversion", data.newVersion);
Services.prefs.setIntPref("bootstraptest.shutdown_reason", reason);
}
function uninstall(data, reason) {
Services.prefs.setIntPref("bootstraptest.installed_version", 0);
Services.prefs.setIntPref("bootstraptest.uninstall_newversion", data.newVersion);
Services.prefs.setIntPref("bootstraptest.uninstall_reason", reason);
}
Components.utils.import("resource://xpcshell-data/BootstrapMonitor.jsm").monitor(this);

Просмотреть файл

@ -1,17 +1 @@
Components.utils.import("resource://gre/modules/Services.jsm");
function install(data, reason) {
Services.prefs.setIntPref("bootstraptest2.installed_version", 1);
}
function startup(data, reason) {
Services.prefs.setIntPref("bootstraptest2.active_version", 1);
}
function shutdown(data, reason) {
Services.prefs.setIntPref("bootstraptest2.active_version", 0);
}
function uninstall(data, reason) {
Services.prefs.setIntPref("bootstraptest2.installed_version", 0);
}
Components.utils.import("resource://xpcshell-data/BootstrapMonitor.jsm").monitor(this);

Просмотреть файл

@ -0,0 +1,28 @@
Components.utils.import("resource://gre/modules/Services.jsm");
this.EXPORTED_SYMBOLS = [ "monitor" ];
function notify(event, originalMethod, data, reason) {
let info = {
event,
data: Object.assign({}, data, {
installPath: data.installPath.path,
resourceURI: data.resourceURI.spec,
}),
reason
};
Services.obs.notifyObservers(null, "bootstrapmonitor-event", JSON.stringify(info));
// If the bootstrap scope already declares a method call it
if (originalMethod)
originalMethod(data, reason);
}
// Allows a simple one-line bootstrap script:
// Components.utils.import("resource://xpcshelldata/bootstrapmonitor.jsm").monitor(this);
this.monitor = function(scope) {
for (let event of ["install", "startup", "shutdown", "uninstall"]) {
scope[event] = notify.bind(null, event, scope[event]);
}
}

Просмотреть файл

@ -0,0 +1 @@
Components.utils.import("resource://xpcshell-data/BootstrapMonitor.jsm").monitor(this);

Двоичный файл не отображается.

Двоичный файл не отображается.

Двоичный файл не отображается.

Двоичный файл не отображается.

Двоичный файл не отображается.

Двоичный файл не отображается.

Двоичный файл не отображается.

Двоичный файл не отображается.

Двоичный файл не отображается.

Двоичный файл не отображается.

Двоичный файл не отображается.

Просмотреть файл

@ -61,6 +61,123 @@ var gUrlToFileMap = {};
var TEST_UNPACKED = false;
// Map resource://xpcshell-data/ to the data directory
let resHandler = Services.io.getProtocolHandler("resource")
.QueryInterface(AM_Ci.nsISubstitutingProtocolHandler);
// Allow non-existent files because of bug 1207735
let dataURI = NetUtil.newURI(do_get_file("data", true));
resHandler.setSubstitution("xpcshell-data", dataURI);
// Listens to messages from bootstrap.js telling us what add-ons were started
// and stopped etc. and performs some sanity checks that only installed add-ons
// are started etc.
this.BootstrapMonitor = {
// Contain the current state of add-ons in the system
installed: new Map(),
started: new Map(),
// Contain the last state of shutdown and uninstall calls for an add-on
stopped: new Map(),
uninstalled: new Map(),
startupPromises: [],
installPromises: [],
init() {
Services.obs.addObserver(this, "bootstrapmonitor-event", false);
},
clear(id) {
this.installed.delete(id);
this.started.delete(id);
this.stopped.delete(id);
this.uninstalled.delete(id);
},
promiseAddonStartup(id) {
return new Promise(resolve => {
this.startupPromises.push(resolve);
});
},
promiseAddonInstall(id) {
return new Promise(resolve => {
this.installPromises.push(resolve);
});
},
checkMatches(cached, current) {
do_check_neq(cached, undefined);
do_check_eq(current.data.version, cached.data.version)
do_check_eq(current.data.installPath, cached.data.installPath)
do_check_eq(current.data.resourceURI, cached.data.resourceURI)
},
checkAddonStarted(id, version = undefined) {
let started = this.started.get(id);
do_check_neq(started, undefined);
if (version != undefined)
do_check_eq(started.data.version, version);
},
checkAddonNotStarted(id) {
do_check_false(this.started.has(id));
},
checkAddonInstalled(id, version = undefined) {
let installed = this.installed.get(id);
do_check_neq(installed, undefined);
if (version != undefined)
do_check_eq(installed.data.version, version);
},
checkAddonNotInstalled(id) {
do_check_false(this.installed.has(id));
},
observe(subject, topic, data) {
let info = JSON.parse(data);
let id = info.data.id;
// If this is the install event the add-ons shouldn't already be installed
if (info.event == "install") {
this.checkAddonNotInstalled(id);
this.installed.set(id, info);
for (let resolve of this.installPromises)
resolve();
this.installPromises = [];
}
else {
this.checkMatches(this.installed.get(id), info);
}
// If this is the shutdown event than the add-on should already be started
if (info.event == "shutdown") {
this.checkMatches(this.started.get(id), info);
this.started.delete(id);
this.stopped.set(id, info);
}
else {
this.checkAddonNotStarted(id);
}
if (info.event == "uninstall") {
this.installed.delete(id);
this.uninstalled.set(id, info)
}
else if (info.event == "startup") {
this.started.set(id, info);
for (let resolve of this.startupPromises)
resolve();
this.startupPromises = [];
}
}
}
function isNightlyChannel() {
var channel = "default";
try {

Разница между файлами не показана из-за своего большого размера Загрузить разницу

Просмотреть файл

@ -19,6 +19,8 @@ userExtDir.append("extensions2");
userExtDir.append(gAppInfo.ID);
registerDirectory("XREUSysExt", userExtDir.parent);
BootstrapMonitor.init();
function TestProvider(result) {
this.result = result;
}
@ -50,18 +52,6 @@ function check_mapping(uri, id) {
do_check_eq(val.value, id);
}
function resetPrefs() {
Services.prefs.setIntPref("bootstraptest.active_version", -1);
}
function waitForPref(aPref, aCallback) {
function prefChanged() {
Services.prefs.removeObserver(aPref, prefChanged);
aCallback();
}
Services.prefs.addObserver(aPref, prefChanged, false);
}
function getActiveVersion() {
return Services.prefs.getIntPref("bootstraptest.active_version");
}
@ -69,8 +59,6 @@ function getActiveVersion() {
function run_test() {
do_test_pending();
resetPrefs();
run_test_early();
}
@ -148,7 +136,7 @@ function run_test_1() {
let uri = addon.getResourceURI(".");
check_mapping(uri, addon.id);
waitForPref("bootstraptest.active_version", function() {
BootstrapMonitor.promiseAddonStartup("bootstrap1@tests.mozilla.org").then(function() {
run_test_2(uri);
});
});

Просмотреть файл

@ -5,6 +5,8 @@ const PREF_SYSTEM_ADDON_SET = "extensions.systemAddonSet";
// Enable signature checks for these tests
Services.prefs.setBoolPref(PREF_XPI_SIGNATURES_REQUIRED, true);
BootstrapMonitor.init();
const featureDir = FileUtils.getDir("ProfD", ["features"]);
// Build the test sets
@ -61,8 +63,7 @@ function* check_installed(inProfile, ...versions) {
do_check_eq(addon.signedState, AddonManager.SIGNEDSTATE_SYSTEM);
// Verify the add-on actually started
let installed = Services.prefs.getCharPref("bootstraptest." + id + ".active_version");
do_check_eq(installed, versions[i]);
BootstrapMonitor.checkAddonStarted(id, versions[i]);
}
else {
if (inProfile) {
@ -74,12 +75,12 @@ function* check_installed(inProfile, ...versions) {
do_check_true(!addon || !addon.isActive);
}
try {
Services.prefs.getCharPref("bootstraptest." + id + ".active_version");
do_throw("Expected pref to be missing");
}
catch (e) {
}
BootstrapMonitor.checkAddonNotStarted(id);
if (addon)
BootstrapMonitor.checkAddonInstalled(id);
else
BootstrapMonitor.checkAddonNotInstalled(id);
}
}
}
@ -186,6 +187,9 @@ add_task(function* test_skips_additional() {
add_task(function* test_revert() {
manuallyUninstall(featureDir, "system2@tests.mozilla.org");
// With the add-on physically gone from disk we won't see uninstall events
BootstrapMonitor.clear("system2@tests.mozilla.org");
startupManager(false);
// With system add-on 2 gone the updated set is now invalid so it reverts to
@ -258,7 +262,7 @@ add_task(function* test_bad_app_cert() {
// Add-on will still be present just not active
let addon = yield promiseAddonByID("system1@tests.mozilla.org");
do_check_neq(addon, null);
do_check_eq(addon.signedState, AddonManager.SIGNEDSTATE_SIGNED);
do_check_eq(addon.signedState, AddonManager.SIGNEDSTATE_BROKEN);
yield check_installed(false, null, null, "1.0");

Просмотреть файл

@ -9,7 +9,9 @@ Components.utils.import("resource://testing-common/httpd.js");
const { computeHash } = Components.utils.import("resource://gre/modules/addons/ProductAddonChecker.jsm");
// Enable signature checks for these tests
//Services.prefs.setBoolPref(PREF_XPI_SIGNATURES_REQUIRED, true);
Services.prefs.setBoolPref(PREF_XPI_SIGNATURES_REQUIRED, true);
BootstrapMonitor.init();
const featureDir = FileUtils.getDir("ProfD", ["features"]);
@ -142,6 +144,8 @@ function* check_installed(inProfile, ...versions) {
let addon = yield promiseAddonByID(id);
if (versions[i]) {
do_print(`Checking state of add-on ${id}, expecting version ${versions[i]}`);
// Add-on should be installed
do_check_neq(addon, null);
do_check_eq(addon.version, versions[i]);
@ -159,13 +163,14 @@ function* check_installed(inProfile, ...versions) {
do_check_true(uri instanceof AM_Ci.nsIFileURL);
do_check_eq(uri.file.path, file.path);
//do_check_eq(addon.signedState, AddonManager.SIGNEDSTATE_SYSTEM);
do_check_eq(addon.signedState, AddonManager.SIGNEDSTATE_SYSTEM);
// Verify the add-on actually started
let installed = Services.prefs.getCharPref("bootstraptest." + id + ".active_version");
do_check_eq(installed, versions[i]);
BootstrapMonitor.checkAddonStarted(id, versions[i]);
}
else {
do_print(`Checking state of add-on ${id}, expecting it to be missing`);
if (inProfile) {
// Add-on should not be installed
do_check_eq(addon, null);
@ -175,12 +180,12 @@ function* check_installed(inProfile, ...versions) {
do_check_true(!addon || !addon.isActive);
}
try {
Services.prefs.getCharPref("bootstraptest." + id + ".active_version");
do_throw("Expected pref to be missing");
}
catch (e) {
}
BootstrapMonitor.checkAddonNotStarted(id);
if (addon)
BootstrapMonitor.checkAddonInstalled(id);
else
BootstrapMonitor.checkAddonNotInstalled(id);
}
}
}
@ -328,9 +333,9 @@ const TESTS = {
// Correct sizes and hashes should work
checkSizeHash: {
updateList: [
{ id: "system2@tests.mozilla.org", version: "3.0", path: "system2_3.xpi", size: 858 },
{ id: "system3@tests.mozilla.org", version: "3.0", path: "system3_3.xpi", hashFunction: "sha1", hashValue: "105a4c49bd513ebd30594e380c19e86bba1f83e2" },
{ id: "system5@tests.mozilla.org", version: "1.0", path: "system5_1.xpi", size: 857, hashFunction: "sha1", hashValue: "664e9218be3c9acbb9029e715c1e5d2fbb4ea2cc" }
{ id: "system2@tests.mozilla.org", version: "3.0", path: "system2_3.xpi", size: 4672 },
{ id: "system3@tests.mozilla.org", version: "3.0", path: "system3_3.xpi", hashFunction: "sha1", hashValue: "2df604b37b13766c0e04f1b7f59800e038f46cd5" },
{ id: "system5@tests.mozilla.org", version: "1.0", path: "system5_1.xpi", size: 4671, hashFunction: "sha1", hashValue: "f13dcaa8bfacaa222189bcbb0074972c05ceb621" }
],
finalState: [true, null, "3.0", "3.0", null, "1.0"]
},