Bug 704642 - Rewrite TPS add-ons functionality; style improvements to TPS module; r=rnewman

This commit is contained in:
Gregory Szorc 2011-12-14 20:31:19 -08:00
parent 438e753b94
commit 61cb6a8c7f
7 changed files with 105 additions and 261 deletions

View File

@ -21,7 +21,11 @@
"test_privbrw_tabs.js",
"test_bookmarks_in_same_named_folder.js",
"test_client_wipe.js",
"test_special_tabs.js"
"test_special_tabs.js",
"test_addon_sanity.js",
"test_addon_restartless_xpi.js",
"test_addon_nonrestartless_xpi.js",
"test_addon_reconciling.js"
]
}

View File

@ -1,27 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<searchresults total_results="1">
<addon id="5617">
<name>Restartless Test XPI</name>
<type id="1">Extension</type>
<guid>restartless-xpi@tests.mozilla.org</guid>
<slug>restartless-xpi</slug>
<version>1.0</version>
<compatible_applications><application>
<name>Firefox</name>
<application_id>1</application_id>
<min_version>3.6</min_version>
<max_version>*</max_version>
<appID>{ec8030f7-c20a-464f-9b0e-13a3a9e97384}</appID>
</application></compatible_applications>
<all_compatible_os><os>ALL</os></all_compatible_os>
<install os="ALL" size="485">http://127.0.0.1:4567/restartless.xpi</install>
<created epoch="1252903662">
2009-09-14T04:47:42Z
</created>
<last_updated epoch="1315255329">
2011-09-05T20:42:09Z
</last_updated>
</addon>
</searchresults>

View File

@ -7,44 +7,23 @@
* testrunner (no single quotes, extra comma's, etc).
*/
var phases = { "phase1": "profile1",
"phase2": "profile1",
"phase3": "profile1",
"phase4": "profile1",
"phase5": "profile1" };
EnableEngines(["addons"]);
/*
* Test phases
*/
let phases = { "phase1": "profile1",
"phase2": "profile1" };
Phase('phase1', [
[Addons.install, ['unsigned-1.0.xml']],
[Addons.verify, ['unsigned-xpi@tests.mozilla.org'], STATE_DISABLED],
[Sync, SYNC_WIPE_SERVER],
const id = "unsigned-xpi@tests.mozilla.org";
Phase("phase1", [
[Addons.install, [id]],
// Non-restartless add-on shouldn't be found after install.
[Addons.verifyNot, [id]],
// But it should be marked for Sync.
[Sync]
]);
Phase('phase2', [
[Sync],
[Addons.verify, ['unsigned-xpi@tests.mozilla.org'], STATE_ENABLED],
[Addons.setState, ['unsigned-xpi@tests.mozilla.org'], STATE_DISABLED],
[Sync],
]);
Phase('phase3', [
[Sync],
[Addons.verify, ['unsigned-xpi@tests.mozilla.org'], STATE_DISABLED],
[Addons.setState, ['unsigned-xpi@tests.mozilla.org'], STATE_ENABLED],
[Sync],
]);
Phase('phase4', [
[Sync],
[Addons.verify, ['unsigned-xpi@tests.mozilla.org'], STATE_ENABLED],
[Addons.uninstall, ['unsigned-xpi@tests.mozilla.org']],
[Sync],
]);
Phase('phase5', [
[Sync],
[Addons.verifyNot, ['unsigned-xpi@tests.mozilla.org']],
Phase("phase2", [
// Add-on should be present after restart
[Addons.verify, [id], STATE_ENABLED]
]);

View File

@ -1,27 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<searchresults total_results="1">
<addon id="5612">
<name>Unsigned Test XPI</name>
<type id="1">Extension</type>
<guid>unsigned-xpi@tests.mozilla.org</guid>
<slug>unsigned-xpi</slug>
<version>1.0</version>
<compatible_applications><application>
<name>Firefox</name>
<application_id>1</application_id>
<min_version>3.6</min_version>
<max_version>*</max_version>
<appID>{ec8030f7-c20a-464f-9b0e-13a3a9e97384}</appID>
</application></compatible_applications>
<all_compatible_os><os>ALL</os></all_compatible_os>
<install os="ALL" size="452">http://127.0.0.1:4567/unsigned-1.0.xpi</install>
<created epoch="1252903662">
2009-09-14T04:47:42Z
</created>
<last_updated epoch="1315255329">
2011-09-05T20:42:09Z
</last_updated>
</addon>
</searchresults>

View File

@ -33,21 +33,19 @@
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
"use strict";
var EXPORTED_SYMBOLS = ["Addon", "STATE_ENABLED", "STATE_DISABLED"];
let EXPORTED_SYMBOLS = ["Addon", "STATE_ENABLED", "STATE_DISABLED"];
const CC = Components.classes;
const CI = Components.interfaces;
const CU = Components.utils;
const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
CU.import("resource://gre/modules/AddonManager.jsm");
CU.import("resource://gre/modules/AddonRepository.jsm");
CU.import("resource://gre/modules/Services.jsm");
CU.import("resource://services-sync/async.js");
CU.import("resource://services-sync/util.js");
CU.import("resource://tps/logger.jsm");
var XPIProvider = CU.import("resource://gre/modules/XPIProvider.jsm")
.XPIProvider;
Cu.import("resource://gre/modules/AddonManager.jsm");
Cu.import("resource://gre/modules/AddonRepository.jsm");
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://services-sync/async.js");
Cu.import("resource://services-sync/engines.js");
Cu.import("resource://services-sync/util.js");
Cu.import("resource://tps/logger.jsm");
const ADDONSGETURL = 'http://127.0.0.1:4567/';
const STATE_ENABLED = 1;
@ -57,14 +55,14 @@ function GetFileAsText(file)
{
let channel = Services.io.newChannel(file, null, null);
let inputStream = channel.open();
if (channel instanceof CI.nsIHttpChannel &&
if (channel instanceof Ci.nsIHttpChannel &&
channel.responseStatus != 200) {
return "";
}
let streamBuf = "";
let sis = CC["@mozilla.org/scriptableinputstream;1"]
.createInstance(CI.nsIScriptableInputStream);
let sis = Cc["@mozilla.org/scriptableinputstream;1"]
.createInstance(Ci.nsIScriptableInputStream);
sis.init(inputStream);
let available;
@ -82,171 +80,84 @@ function Addon(TPS, id) {
}
Addon.prototype = {
_addons_requiring_restart: [],
_addons_pending_install: [],
addon: null,
Delete: function() {
uninstall: function uninstall() {
// find our addon locally
let cb = Async.makeSyncCallback();
XPIProvider.getAddonsByTypes(null, cb);
let results = Async.waitForSyncCallback(cb);
var addon;
var id = this.id;
results.forEach(function(result) {
if (result.id == id) {
addon = result;
}
});
AddonManager.getAddonByID(this.id, cb);
let addon = Async.waitForSyncCallback(cb);
Logger.AssertTrue(!!addon, 'could not find addon ' + this.id + ' to uninstall');
addon.uninstall();
cb = Async.makeSpinningCallback();
let store = Engines.get("addons")._store;
store.uninstallAddon(addon, cb);
cb.wait();
},
Find: function(state) {
find: function find(state) {
let cb = Async.makeSyncCallback();
let addon_found = false;
var that = this;
AddonManager.getAddonByID(this.id, cb);
let addon = Async.waitForSyncCallback(cb);
var log_addon = function(addon) {
that.addon = addon;
Logger.logInfo('addon ' + addon.id + ' found, isActive: ' + addon.isActive);
if (state == STATE_ENABLED || state == STATE_DISABLED) {
Logger.AssertEqual(addon.isActive,
state == STATE_ENABLED ? true : false,
"addon " + that.id + " has an incorrect enabled state");
}
};
// first look in the list of all addons
XPIProvider.getAddonsByTypes(null, cb);
let addonlist = Async.waitForSyncCallback(cb);
addonlist.forEach(function(addon) {
if (addon.id == that.id) {
addon_found = true;
log_addon.call(that, addon);
}
});
if (!addon_found) {
// then look in the list of recent installs
cb = Async.makeSyncCallback();
XPIProvider.getInstallsByTypes(null, cb);
addonlist = Async.waitForSyncCallback(cb);
for (var i in addonlist) {
if (addonlist[i].addon && addonlist[i].addon.id == that.id &&
addonlist[i].state == AddonManager.STATE_INSTALLED) {
addon_found = true;
log_addon.call(that, addonlist[i].addon);
}
}
if (!addon) {
Logger.logInfo("Could not find add-on with ID: " + this.id);
return false;
}
return addon_found;
this.addon = addon;
Logger.logInfo("add-on found: " + addon.id + ", enabled: " +
!addon.userDisabled);
if (state == STATE_ENABLED) {
Logger.AssertFalse(addon.userDisabled, "add-on is disabled: " + addon.id);
return true;
} else if (state == STATE_DISABLED) {
Logger.AssertTrue(addon.userDisabled, "add-on is enabled: " + addon.id);
return true;
} else if (state) {
throw Error("Don't know how to handle state: " + state);
} else {
// No state, so just checking that it exists.
return true;
}
},
Install: function() {
install: function install() {
// For Install, the id parameter initially passed is really the filename
// for the addon's install .xml; we'll read the actual id from the .xml.
let url = this.id;
// set the url used by getAddonsByIDs
var prefs = CC["@mozilla.org/preferences-service;1"]
.getService(CI.nsIPrefBranch);
prefs.setCharPref('extensions.getAddons.get.url', ADDONSGETURL + url);
let cb = Async.makeSpinningCallback();
// We call the store's APIs for installation because it is simpler. If that
// API is broken, it should ideally be caught by an xpcshell test. But, if
// TPS tests fail, it's all the same: a genuite reported error.
let store = Engines.get("addons")._store;
store.installAddonsFromIDs([this.id], cb);
let result = cb.wait();
// read the XML and find the addon id
xml = GetFileAsText(ADDONSGETURL + url);
Logger.AssertTrue(xml.indexOf("<guid>") > -1, 'guid not found in ' + url);
this.id = xml.substring(xml.indexOf("<guid>") + 6, xml.indexOf("</guid"));
Logger.logInfo('addon XML = ' + this.id);
Logger.AssertEqual(1, result.installedIDs.length, "Exactly 1 add-on was installed.");
Logger.AssertEqual(this.id, result.installedIDs[0],
"Add-on was installed successfully: " + this.id);
},
// find our addon on 'AMO'
let cb = Async.makeSyncCallback();
AddonRepository.getAddonsByIDs([this.id], {
searchSucceeded: cb,
searchFailed: cb
}, false);
setEnabled: function setEnabled(flag) {
Logger.AssertTrue(this.find(), "Add-on is available.");
// Result will be array of addons on searchSucceeded or undefined on
// searchFailed.
let install_addons = Async.waitForSyncCallback(cb);
Logger.AssertTrue(install_addons,
"no addons found for id " + this.id);
Logger.AssertEqual(install_addons.length,
1,
"multiple addons found for id " + this.id);
let addon = install_addons[0];
Logger.logInfo(JSON.stringify(addon), null, ' ');
if (XPIProvider.installRequiresRestart(addon)) {
this._addons_requiring_restart.push(addon.id);
let userDisabled;
if (flag == STATE_ENABLED) {
userDisabled = false;
} else if (flag == STATE_DISABLED) {
userDisabled = true;
} else {
throw new Error("Unknown flag to setEnabled: " + flag);
}
// Start installing the addon asynchronously; finish up in
// onInstallEnded(), onInstallFailed(), or onDownloadFailed().
this._addons_pending_install.push(addon.id);
this.TPS.StartAsyncOperation();
Utils.nextTick(function() {
let callback = function(aInstall) {
addon.install = aInstall;
Logger.logInfo("addon install: " + addon.install);
Logger.AssertTrue(addon.install,
"could not get install object for id " + this.id);
addon.install.addListener(this);
addon.install.install();
};
AddonManager.getInstallForURL(addon.sourceURI.spec,
callback.bind(this),
"application/x-xpinstall");
}, this);
},
SetState: function(state) {
if (!this.Find())
return false;
this.addon.userDisabled = state == STATE_ENABLED ? false : true;
return true;
},
// addon installation callbacks
onInstallEnded: function(addon) {
try {
Logger.logInfo('--------- event observed: addon onInstallEnded');
Logger.AssertTrue(addon.addon,
"No addon object in addon instance passed to onInstallEnded");
Logger.AssertTrue(this._addons_pending_install.indexOf(addon.addon.id) > -1,
"onInstallEnded received for unexpected addon " + addon.addon.id);
this._addons_pending_install.splice(
this._addons_pending_install.indexOf(addon.addon.id),
1);
}
catch(e) {
// We can't throw during a callback, as it will just get eaten by
// the callback's caller.
Utils.nextTick(function() {
this.DumpError(e);
}, this);
return;
}
this.TPS.FinishAsyncOperation();
},
onInstallFailed: function(addon) {
Logger.logInfo('--------- event observed: addon onInstallFailed');
Utils.nextTick(function() {
this.DumpError('Installation failed for addon ' +
(addon.addon && addon.addon.id ? addon.addon.id : 'unknown'));
}, this);
},
onDownloadFailed: function(addon) {
Logger.logInfo('--------- event observed: addon onDownloadFailed');
Utils.nextTick(function() {
this.DumpError('Download failed for addon ' +
(addon.addon && addon.addon.id ? addon.addon.id : 'unknown'));
}, this);
},
let store = Engines.get("addons")._store;
let cb = Async.makeSpinningCallback();
store.updateUserDisabled(this.addon, userDisabled, cb);
cb.wait();
return true;
}
};

View File

@ -341,26 +341,28 @@ let TPS =
},
HandleAddons: function (addons, action, state) {
for (var i in addons) {
for each (let entry in addons) {
Logger.logInfo("executing action " + action.toUpperCase() +
" on addon " + JSON.stringify(addons[i]));
var addon = new Addon(this, addons[i]);
" on addon " + JSON.stringify(entry));
let addon = new Addon(this, entry);
switch(action) {
case ACTION_ADD:
addon.Install();
addon.install();
break;
case ACTION_DELETE:
addon.Delete();
addon.uninstall();
break;
case ACTION_VERIFY:
Logger.AssertTrue(addon.Find(state), 'addon ' + addon.id + ' not found');
Logger.AssertTrue(addon.find(state), 'addon ' + addon.id + ' not found');
break;
case ACTION_VERIFY_NOT:
Logger.AssertTrue(!addon.Find(state), 'addon ' + addon.id + " is present, but it shouldn't be");
Logger.AssertFalse(addon.find(state), 'addon ' + addon.id + " is present, but it shouldn't be");
break;
case ACTION_SETSTATE:
Logger.AssertTrue(addon.SetState(state), 'addon ' + addon.id + ' not found');
case ACTION_SET_ENABLED:
Logger.AssertTrue(addon.setEnabled(state), 'addon ' + addon.id + ' not found');
break;
default:
throw new Error("Unknown action for add-on: " + action);
}
}
Logger.logPass("executing action " + action.toUpperCase() +

View File

@ -96,9 +96,11 @@ class TPSTestRunner(object):
'browser.tabs.warnOnClose' : False,
'browser.warnOnQuit': False,
'browser.sessionstore.resume_from_crash': False,
'services.sync.addons.ignoreRepositoryChecking': True,
'services.sync.firstSync': 'notReady',
'services.sync.lastversion': '1.0',
'services.sync.log.rootLogger': 'Trace',
'services.sync.log.logger.engine.addons': 'Trace',
'services.sync.log.logger.service.main': 'Trace',
'services.sync.log.logger.engine.bookmarks': 'Trace',
'services.sync.log.appender.console': 'Trace',