Bug 986040 - Telemetry experiments: Assure that no experiment addon is running yet before starting an experiment. r=Unfocused

--HG--
extra : rebase_source : 55be48f0b9cffe978c22baa8260b161f2cbc0562
This commit is contained in:
Georg Fritzsche 2014-03-27 14:26:59 -07:00
parent 75f0ab4595
commit a43d605ac0
3 changed files with 202 additions and 77 deletions

View File

@ -168,6 +168,51 @@ function telemetryEnabled() {
gPrefsTelemetry.get(PREF_TELEMETRY_PRERELEASE, false);
}
// Returns a promise that is resolved with the AddonInstall for that URL.
function addonInstallForURL(url, hash) {
let deferred = Promise.defer();
AddonManager.getInstallForURL(url, install => deferred.resolve(install),
"application/x-xpinstall", hash);
return deferred.promise;
}
// Returns a promise that is resolved with an Array<Addon> of the installed
// experiment addons.
function installedExperimentAddons() {
let deferred = Promise.defer();
AddonManager.getAddonsByTypes(["experiment"],
addons => deferred.resolve(addons));
return deferred.promise;
}
// Takes an Array<Addon> and returns a promise that is resolved when the
// addons are uninstalled.
function uninstallAddons(addons) {
let ids = new Set([a.id for (a of addons)]);
let deferred = Promise.defer();
let listener = {};
listener.onUninstalled = addon => {
if (!ids.has(addon.id)) {
return;
}
ids.delete(addon.id);
if (ids.size == 0) {
AddonManager.removeAddonListener(listener);
deferred.resolve();
}
};
AddonManager.addAddonListener(listener);
for (let addon of addons) {
addon.uninstall();
}
return deferred.promise;
}
/**
* The experiments module.
*/
@ -1300,68 +1345,91 @@ Experiments.ExperimentEntry.prototype = {
*/
start: function () {
gLogger.trace("ExperimentEntry::start() for " + this.id);
return Task.spawn(function* ExperimentEntry_start_task() {
let addons = yield installedExperimentAddons();
if (addons.length > 0) {
gLogger.error("ExperimentEntry::start() - there are already "
+ addons.length + " experiment addons installed");
yield uninstallAddons(addons);
}
yield this._installAddon();
}.bind(this));
},
// Async install of the addon for this experiment, part of the start task above.
_installAddon: function* () {
let deferred = Promise.defer();
let installCallback = install => {
let failureHandler = (install, handler) => {
let message = "AddonInstall " + handler + " for " + this.id + ", state=" +
(install.state || "?") + ", error=" + install.error;
gLogger.error("ExperimentEntry::start() - " + message);
this._failedStart = true;
let install = yield addonInstallForURL(this._manifestData.xpiURL,
this._manifestData.xpiHash);
let failureHandler = (install, handler) => {
let message = "AddonInstall " + handler + " for " + this.id + ", state=" +
(install.state || "?") + ", error=" + install.error;
gLogger.error("ExperimentEntry::_installAddon() - " + message);
this._failedStart = true;
TelemetryLog.log(TELEMETRY_LOG.ACTIVATION_KEY,
[TELEMETRY_LOG.ACTIVATION.INSTALL_FAILURE, this.id]);
TelemetryLog.log(TELEMETRY_LOG.ACTIVATION_KEY,
[TELEMETRY_LOG.ACTIVATION.INSTALL_FAILURE, this.id]);
deferred.reject(new Error(message));
};
let listener = {
onDownloadEnded: install => {
gLogger.trace("ExperimentEntry::start() - onDownloadEnded for " + this.id);
},
onInstallStarted: install => {
gLogger.trace("ExperimentEntry::start() - onInstallStarted for " + this.id);
if (install.addon.type !== "experiment") {
gLogger.error("ExperimentEntry::start() - wrong addon type");
failureHandler({state: -1, error: -1}, "onInstallStarted");
install.cancel();
return;
}
let addon = install.addon;
this._name = addon.name;
this._addonId = addon.id;
this._description = addon.description || "";
this._homepageURL = addon.homepageURL || "";
},
onInstallEnded: install => {
gLogger.trace("ExperimentEntry::start() - install ended for " + this.id);
this._lastChangedDate = this._policy.now();
this._startDate = this._policy.now();
this._enabled = true;
TelemetryLog.log(TELEMETRY_LOG.ACTIVATION_KEY,
[TELEMETRY_LOG.ACTIVATION.ACTIVATED, this.id]);
deferred.resolve();
},
};
["onDownloadCancelled", "onDownloadFailed", "onInstallCancelled", "onInstallFailed"]
.forEach(what => {
listener[what] = install => failureHandler(install, what)
});
install.addListener(listener);
install.install();
deferred.reject(new Error(message));
};
AddonManager.getInstallForURL(this._manifestData.xpiURL,
installCallback,
"application/x-xpinstall",
this._manifestData.xpiHash);
let listener = {
onDownloadEnded: install => {
gLogger.trace("ExperimentEntry::_installAddon() - onDownloadEnded for " + this.id);
if (install.existingAddon) {
gLogger.warn("ExperimentEntry::_installAddon() - onDownloadEnded, addon already installed");
}
if (install.addon.type !== "experiment") {
gLogger.error("ExperimentEntry::_installAddon() - onDownloadEnded, wrong addon type");
install.cancel();
}
},
onInstallStarted: install => {
gLogger.trace("ExperimentEntry::_installAddon() - onInstallStarted for " + this.id);
if (install.existingAddon) {
gLogger.warn("ExperimentEntry::_installAddon() - onInstallStarted, addon already installed");
}
if (install.addon.type !== "experiment") {
gLogger.error("ExperimentEntry::_installAddon() - onInstallStarted, wrong addon type");
return false;
}
},
onInstallEnded: install => {
gLogger.trace("ExperimentEntry::_installAddon() - install ended for " + this.id);
this._lastChangedDate = this._policy.now();
this._startDate = this._policy.now();
this._enabled = true;
TelemetryLog.log(TELEMETRY_LOG.ACTIVATION_KEY,
[TELEMETRY_LOG.ACTIVATION.ACTIVATED, this.id]);
let addon = install.addon;
this._name = addon.name;
this._addonId = addon.id;
this._description = addon.description || "";
this._homepageURL = addon.homepageURL || "";
deferred.resolve();
},
};
["onDownloadCancelled", "onDownloadFailed", "onInstallCancelled", "onInstallFailed"]
.forEach(what => {
listener[what] = install => failureHandler(install, what)
});
install.addListener(listener);
install.install();
return deferred.promise;
},
@ -1396,25 +1464,9 @@ Experiments.ExperimentEntry.prototype = {
return;
}
let listener = {};
let handler = addon => {
if (addon.id !== this._addonId) {
return;
}
updateDates();
this._logTermination(terminationKind, terminationReason);
AddonManager.removeAddonListener(listener);
deferred.resolve();
};
listener.onUninstalled = handler;
listener.onDisabled = handler;
AddonManager.addAddonListener(listener);
addon.uninstall();
updateDates();
this._logTermination(terminationKind, terminationReason);
deferred.resolve(uninstallAddons([addon]));
});
return deferred.promise;

View File

@ -160,11 +160,11 @@ function uninstallAddon(id) {
return deferred.promise;
}
function createAppInfo(options) {
function createAppInfo(optionsIn) {
const XULAPPINFO_CONTRACTID = "@mozilla.org/xre/app-info;1";
const XULAPPINFO_CID = Components.ID("{c763b610-9d49-455a-bbd2-ede71682a1ac}");
let options = options || {};
let options = optionsIn || {};
let id = options.id || "xpcshell@tests.mozilla.org";
let name = options.name || "XPCShell";
let version = options.version || "1.0";

View File

@ -263,6 +263,79 @@ add_task(function* test_getExperiments() {
yield removeCacheFile();
});
// Test that we handle the experiments addon already being
// installed properly.
// We should just pave over them.
add_task(function* test_addonAlreadyInstalled() {
const OBSERVER_TOPIC = "experiments-changed";
let observerFireCount = 0;
let expectedObserverFireCount = 0;
let observer = () => ++observerFireCount;
Services.obs.addObserver(observer, OBSERVER_TOPIC, false);
// Dates the following tests are based on.
let baseDate = new Date(2014, 5, 1, 12);
let startDate = futureDate(baseDate, 100 * MS_IN_ONE_DAY);
let endDate = futureDate(baseDate, 10000 * MS_IN_ONE_DAY);
// The manifest data we test with.
gManifestObject = {
"version": 1,
experiments: [
{
id: EXPERIMENT1_ID,
xpiURL: gDataRoot + EXPERIMENT1_XPI_NAME,
xpiHash: EXPERIMENT1_XPI_SHA1,
startTime: dateToSeconds(startDate),
endTime: dateToSeconds(endDate),
maxActiveSeconds: 10 * SEC_IN_ONE_DAY,
appName: ["XPCShell"],
channel: ["nightly"],
},
],
};
let experiments = new Experiments.Experiments(gPolicy);
// Trigger update, clock set to before any activation.
let now = baseDate;
defineNow(gPolicy, now);
yield experiments.updateManifest();
Assert.equal(observerFireCount, 0,
"Experiments observer should not have been called yet.");
let list = yield experiments.getExperiments();
Assert.equal(list.length, 0, "Experiment list should be empty.");
// Install conflicting addon.
let installed = yield installAddon(gDataRoot + EXPERIMENT1_XPI_NAME, EXPERIMENT1_XPI_SHA1);
Assert.ok(installed, "Addon should have been installed.");
// Trigger update, clock set for the experiment to start.
now = futureDate(startDate, 10 * MS_IN_ONE_DAY);
defineNow(gPolicy, now);
yield experiments.updateManifest();
Assert.equal(observerFireCount, ++expectedObserverFireCount,
"Experiments observer should have been called.");
list = yield experiments.getExperiments();
list = yield experiments.getExperiments();
Assert.equal(list.length, 1, "Experiment list should have 1 entry now.");
Assert.equal(list[0].id, EXPERIMENT1_ID, "Experiment 1 should be the sole entry.");
Assert.equal(list[0].active, true, "Experiment 1 should be active.");
// Cleanup.
Services.obs.removeObserver(observer, OBSERVER_TOPIC);
yield experiments.uninit();
yield removeCacheFile();
});
add_task(function* test_lastActiveToday() {
let experiments = new Experiments.Experiments(gPolicy);