Bug 978876 - Part 1: handle declined engines in desktop Sync meta/global. r=gps

* * *
Bug 978876 - Part 2: refactor out meta/global upload.
This commit is contained in:
Richard Newman 2014-03-13 16:37:25 -07:00
Родитель e5a7647359
Коммит 9138511a11
12 изменённых файлов: 434 добавлений и 59 удалений

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

@ -51,6 +51,7 @@ sync_engine_modules := \
sync_stage_modules := \
cluster.js \
declined.js \
enginesync.js \
$(NULL)

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

@ -445,9 +445,11 @@ this.EngineManager = function EngineManager(service) {
this.service = service;
this._engines = {};
// This will be populated by Service on startup.
this._declined = new Set();
this._log = Log.repository.getLogger("Sync.EngineManager");
this._log.level = Log.Level[Svc.Prefs.get(
"log.logger.service.engines", "Debug")];
this._log.level = Log.Level[Svc.Prefs.get("log.logger.service.engines", "Debug")];
}
EngineManager.prototype = {
get: function (name) {
@ -477,10 +479,69 @@ EngineManager.prototype = {
return [engine for ([name, engine] in Iterator(this._engines))];
},
/**
* N.B., does not pay attention to the declined list.
*/
getEnabled: function () {
return this.getAll().filter(function(engine) engine.enabled);
},
get enabledEngineNames() {
return [e.name for each (e in this.getEnabled())];
},
persistDeclined: function () {
Svc.Prefs.set("declinedEngines", [...this._declined].join(","));
},
/**
* Returns an array.
*/
getDeclined: function () {
return [...this._declined];
},
setDeclined: function (engines) {
this._declined = new Set(engines);
this.persistDeclined();
},
isDeclined: function (engineName) {
return this._declined.has(engineName);
},
/**
* Accepts a Set or an array.
*/
decline: function (engines) {
for (let e of engines) {
this._declined.add(e);
}
this.persistDeclined();
},
undecline: function (engines) {
for (let e of engines) {
this._declined.delete(e);
}
this.persistDeclined();
},
/**
* Mark any non-enabled engines as declined.
*
* This is useful after initial customization during setup.
*/
declineDisabled: function () {
for (let e of this.getAll()) {
if (!e.enabled) {
this._log.debug("Declining disabled engine " + e.name);
this._declined.add(e.name);
}
}
this.persistDeclined();
},
/**
* Register an Engine to the service. Alternatively, give an array of engine
* objects to register.
@ -638,16 +699,16 @@ SyncEngine.prototype = {
__proto__: Engine.prototype,
_recordObj: CryptoWrapper,
version: 1,
// How many records to pull in a single sync. This is primarily to avoid very
// long first syncs against profiles with many history records.
downloadLimit: null,
// How many records to pull at one time when specifying IDs. This is to avoid
// URI length limitations.
guidFetchBatchSize: DEFAULT_GUID_FETCH_BATCH_SIZE,
mobileGUIDFetchBatchSize: DEFAULT_MOBILE_GUID_FETCH_BATCH_SIZE,
// How many records to process in a single batch.
applyIncomingBatchSize: DEFAULT_STORE_BATCH_SIZE,
@ -863,7 +924,7 @@ SyncEngine.prototype = {
newitems.newer = this.lastSync;
newitems.full = true;
newitems.limit = batchSize;
// applied => number of items that should be applied.
// failed => number of items that failed in this sync.
// newFailed => number of items that failed for the first time in this sync.

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

@ -553,5 +553,9 @@ IdentityManager.prototype = {
createClusterManager: function(service) {
Cu.import("resource://services-sync/stages/cluster.js");
return new ClusterManager(service);
}
},
offerSyncOptions: function () {
// TODO
},
};

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

@ -31,6 +31,7 @@ Cu.import("resource://services-sync/record.js");
Cu.import("resource://services-sync/resource.js");
Cu.import("resource://services-sync/rest.js");
Cu.import("resource://services-sync/stages/enginesync.js");
Cu.import("resource://services-sync/stages/declined.js");
Cu.import("resource://services-sync/status.js");
Cu.import("resource://services-sync/userapi.js");
Cu.import("resource://services-sync/util.js");
@ -65,10 +66,6 @@ Sync11Service.prototype = {
metaURL: null,
cryptoKeyURL: null,
get enabledEngineNames() {
return [e.name for each (e in this.engineManager.getEnabled())];
},
get serverURL() Svc.Prefs.get("serverURL"),
set serverURL(value) {
if (!value.endsWith("/")) {
@ -430,6 +427,12 @@ Sync11Service.prototype = {
engines = pref.split(",");
}
let declined = [];
pref = Svc.Prefs.get("declinedEngines");
if (pref) {
declined = pref.split(",");
}
this.clientsEngine = new ClientEngine(this);
for (let name of engines) {
@ -448,12 +451,14 @@ Sync11Service.prototype = {
continue;
}
this.engineManager.register(ns[engineName], this);
this.engineManager.register(ns[engineName]);
} catch (ex) {
this._log.warn("Could not register engine " + name + ": " +
CommonUtils.exceptionStr(ex));
}
}
this.engineManager.setDeclined(declined);
},
QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver,
@ -1062,6 +1067,7 @@ Sync11Service.prototype = {
newMeta = new WBORecord("meta", "global");
newMeta.payload.syncID = this.syncID;
newMeta.payload.storageVersion = STORAGE_VERSION;
newMeta.payload.declined = this.engineManager.getDeclined();
newMeta.isNew = true;
@ -1245,9 +1251,43 @@ Sync11Service.prototype = {
// wait() throws if the first argument is truthy, which is exactly what
// we want.
let result = cb.wait();
// We successfully synchronized. Now let's update our declined engines.
let meta = this.recordManager.get(this.metaURL);
if (!meta) {
this._log.warn("No meta/global; can't update declined state.");
return;
}
let declinedEngines = new DeclinedEngines(this);
let didChange = declinedEngines.updateDeclined(meta, this.engineManager);
if (!didChange) {
this._log.info("No change to declined engines. Not reuploading meta/global.");
return;
}
this.uploadMetaGlobal(meta);
}))();
},
/**
* Upload meta/global, throwing the response on failure.
*/
uploadMetaGlobal: function (meta) {
this._log.debug("Uploading meta/global: " + JSON.stringify(meta));
// It would be good to set the X-If-Unmodified-Since header to `timestamp`
// for this PUT to ensure at least some level of transactionality.
// Unfortunately, the servers don't support it after a wipe right now
// (bug 693893), so we're going to defer this until bug 692700.
let res = this.resource(this.metaURL);
let response = res.put(meta);
if (!response.success) {
throw response;
}
this.recordManager.set(this.metaURL, meta);
},
/**
* If we have a passphrase, rather than a 25-alphadigit sync key,
* use the provided sync ID to bootstrap it using PBKDF2.
@ -1303,26 +1343,19 @@ Sync11Service.prototype = {
let meta = new WBORecord("meta", "global");
meta.payload.syncID = this.syncID;
meta.payload.storageVersion = STORAGE_VERSION;
meta.payload.declined = this.engineManager.getDeclined();
meta.isNew = true;
this._log.debug("New metadata record: " + JSON.stringify(meta.payload));
let res = this.resource(this.metaURL);
// It would be good to set the X-If-Unmodified-Since header to `timestamp`
// for this PUT to ensure at least some level of transactionality.
// Unfortunately, the servers don't support it after a wipe right now
// (bug 693893), so we're going to defer this until bug 692700.
let resp = res.put(meta);
if (!resp.success) {
// If we got into a race condition, we'll abort the sync this way, too.
// That's fine. We'll just wait till the next sync. The client that we're
// racing is probably busy uploading stuff right now anyway.
throw resp;
}
this.recordManager.set(this.metaURL, meta);
// uploadMetaGlobal throws on failure -- including race conditions.
// If we got into a race condition, we'll abort the sync this way, too.
// That's fine. We'll just wait till the next sync. The client that we're
// racing is probably busy uploading stuff right now anyway.
this.uploadMetaGlobal(meta);
// Wipe everything we know about except meta because we just uploaded it
let engines = [this.clientsEngine].concat(this.engineManager.getAll());
let collections = [engine.name for each (engine in engines)];
// TODO: there's a bug here. We should be calling resetClient, no?
// Generate, upload, and download new keys. Do this last so we don't wipe
// them...

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

@ -1,6 +1,6 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
this.EXPORTED_SYMBOLS = ["ClusterManager"];

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

@ -0,0 +1,76 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
/**
* This file contains code for maintaining the set of declined engines,
* in conjunction with EngineManager.
*/
"use strict";
this.EXPORTED_SYMBOLS = ["DeclinedEngines"];
const {utils: Cu} = Components;
Cu.import("resource://services-sync/constants.js");
Cu.import("resource://gre/modules/Log.jsm");
Cu.import("resource://services-common/utils.js");
Cu.import("resource://services-common/observers.js");
Cu.import("resource://gre/modules/Preferences.jsm");
this.DeclinedEngines = function (service) {
this._log = Log.repository.getLogger("Sync.Declined");
this._log.level = Log.Level[new Preferences(PREFS_BRANCH).get("log.logger.declined")];
this.service = service;
}
this.DeclinedEngines.prototype = {
updateDeclined: function (meta, engineManager=this.service.engineManager) {
let enabled = new Set([e.name for each (e in engineManager.getEnabled())]);
let known = new Set([e.name for each (e in engineManager.getAll())]);
let remoteDeclined = new Set(meta.payload.declined || []);
let localDeclined = new Set(engineManager.getDeclined());
this._log.debug("Handling remote declined: " + JSON.stringify([...remoteDeclined]));
this._log.debug("Handling local declined: " + JSON.stringify([...localDeclined]));
// Any engines that are locally enabled should be removed from the remote
// declined list.
//
// Any engines that are locally declined should be added to the remote
// declined list.
let newDeclined = CommonUtils.union(localDeclined, CommonUtils.difference(remoteDeclined, enabled));
// If our declined set has changed, put it into the meta object and mark
// it as changed.
let declinedChanged = !CommonUtils.setEqual(newDeclined, remoteDeclined);
this._log.debug("Declined changed? " + declinedChanged);
if (declinedChanged) {
meta.changed = true;
meta.payload.declined = [...newDeclined];
}
// Update the engine manager regardless.
engineManager.setDeclined(newDeclined);
// Any engines that are locally known, locally disabled, and not remotely
// or locally declined, are candidates for enablement.
let undecided = CommonUtils.difference(CommonUtils.difference(known, enabled), newDeclined);
if (undecided.size) {
let subject = {
declined: newDeclined,
enabled: enabled,
known: known,
undecided: undecided,
};
CommonUtils.nextTick(() => {
Observers.notify("weave:engines:notdeclined", subject);
});
}
return declinedChanged;
},
};

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

@ -1,6 +1,6 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
/**
* This file contains code for synchronizing engines.
@ -71,11 +71,13 @@ EngineSynchronizer.prototype = {
Svc.Prefs.set("lastPing", now);
}
let engineManager = this.service.engineManager;
// Figure out what the last modified time is for each collection
let info = this.service._fetchInfo(infoURL);
// Convert the response to an object and read out the modified times
for (let engine of [this.service.clientsEngine].concat(this.service.engineManager.getAll())) {
for (let engine of [this.service.clientsEngine].concat(engineManager.getAll())) {
engine.lastModified = info.obj[engine.name] || 0;
}
@ -97,13 +99,13 @@ EngineSynchronizer.prototype = {
// Wipe data in the desired direction if necessary
switch (Svc.Prefs.get("firstSync")) {
case "resetClient":
this.service.resetClient(this.service.enabledEngineNames);
this.service.resetClient(engineManager.enabledEngineNames);
break;
case "wipeClient":
this.service.wipeClient(this.service.enabledEngineNames);
this.service.wipeClient(engineManager.enabledEngineNames);
break;
case "wipeRemote":
this.service.wipeRemote(this.service.enabledEngineNames);
this.service.wipeRemote(engineManager.enabledEngineNames);
break;
}
@ -142,7 +144,7 @@ EngineSynchronizer.prototype = {
}
try {
for each (let engine in this.service.engineManager.getEnabled()) {
for (let engine of engineManager.getEnabled()) {
// If there's any problems with syncing the engine, report the failure
if (!(this._syncEngine(engine)) || this.service.status.enforceBackoff) {
this._log.info("Aborting sync for failure in " + engine.name);
@ -160,12 +162,17 @@ EngineSynchronizer.prototype = {
return;
}
// Upload meta/global if any engines changed anything
// Upload meta/global if any engines changed anything.
let meta = this.service.recordManager.get(this.service.metaURL);
if (meta.isNew || meta.changed) {
this.service.resource(this.service.metaURL).put(meta);
delete meta.isNew;
delete meta.changed;
this._log.info("meta/global changed locally: reuploading.");
try {
this.service.uploadMetaGlobal(meta);
delete meta.isNew;
delete meta.changed;
} catch (error) {
this._log.error("Unable to upload meta/global. Leaving marked as new.");
}
}
// If there were no sync engine failures
@ -205,17 +212,19 @@ EngineSynchronizer.prototype = {
return true;
},
_updateEnabledEngines: function _updateEnabledEngines() {
_updateEnabledFromMeta: function (meta, numClients, engineManager=this.service.engineManager) {
this._log.info("Updating enabled engines: " +
this.service.scheduler.numClients + " clients.");
let meta = this.service.recordManager.get(this.service.metaURL);
if (meta.isNew || !meta.payload.engines)
numClients + " clients.");
if (meta.isNew || !meta.payload.engines) {
this._log.debug("meta/global isn't new, or is missing engines. Not updating enabled state.");
return;
}
// If we're the only client, and no engines are marked as enabled,
// thumb our noses at the server data: it can't be right.
// Belt-and-suspenders approach to Bug 615926.
if ((this.service.scheduler.numClients <= 1) &&
if ((numClients <= 1) &&
([e for (e in meta.payload.engines) if (e != "clients")].length == 0)) {
this._log.info("One client and no enabled engines: not touching local engine status.");
return;
@ -223,7 +232,11 @@ EngineSynchronizer.prototype = {
this.service._ignorePrefObserver = true;
let enabled = this.service.enabledEngineNames;
let enabled = engineManager.enabledEngineNames;
let toDecline = new Set();
let toUndecline = new Set();
for (let engineName in meta.payload.engines) {
if (engineName == "clients") {
// Clients is special.
@ -235,7 +248,7 @@ EngineSynchronizer.prototype = {
enabled.splice(index, 1);
continue;
}
let engine = this.service.engineManager.get(engineName);
let engine = engineManager.get(engineName);
if (!engine) {
// The engine doesn't exist locally. Nothing to do.
continue;
@ -247,9 +260,17 @@ EngineSynchronizer.prototype = {
this._log.trace("Wiping data for " + engineName + " engine.");
engine.wipeServer();
delete meta.payload.engines[engineName];
meta.changed = true;
meta.changed = true; // TODO: Should we still do this?
// We also here mark the engine as declined, because the pref
// was explicitly changed to false.
// This will be reflected in meta/global in the next stage.
this._log.trace("Engine " + engineName + " was disabled locally. Marking as declined.");
toDecline.add(engineName);
} else {
// The engine was enabled remotely. Enable it locally.
this._log.trace("Engine " + engineName + " was enabled. Marking as non-declined.");
toUndecline.add(engineName);
this._log.trace(engineName + " engine was enabled remotely.");
engine.enabled = true;
}
@ -257,18 +278,31 @@ EngineSynchronizer.prototype = {
// Any remaining engines were either enabled locally or disabled remotely.
for each (let engineName in enabled) {
let engine = this.service.engineManager.get(engineName);
let engine = engineManager.get(engineName);
if (Svc.Prefs.get("engineStatusChanged." + engine.prefName, false)) {
this._log.trace("The " + engineName + " engine was enabled locally.");
toUndecline.add(engineName);
} else {
this._log.trace("The " + engineName + " engine was disabled remotely.");
// Don't automatically mark it as declined!
engine.enabled = false;
}
}
this.service.engineManager.decline(toDecline);
this.service.engineManager.undecline(toUndecline);
Svc.Prefs.resetBranch("engineStatusChanged.");
this.service._ignorePrefObserver = false;
},
_updateEnabledEngines: function () {
let meta = this.service.recordManager.get(this.service.metaURL);
let numClients = this.service.scheduler.numClients;
let engineManager = this.service.engineManager;
this._updateEnabledFromMeta(meta, numClients, engineManager);
},
};
Object.freeze(EngineSynchronizer.prototype);

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

@ -57,6 +57,7 @@ pref("services.sync.log.appender.file.logOnSuccess", false);
pref("services.sync.log.appender.file.maxErrorAge", 864000); // 10 days
pref("services.sync.log.rootLogger", "Debug");
pref("services.sync.log.logger.addonutils", "Debug");
pref("services.sync.log.logger.declined", "Debug");
pref("services.sync.log.logger.service.main", "Debug");
pref("services.sync.log.logger.status", "Debug");
pref("services.sync.log.logger.authenticator", "Debug");

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

@ -0,0 +1,153 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
Cu.import("resource://services-sync/stages/declined.js");
Cu.import("resource://services-sync/stages/enginesync.js");
Cu.import("resource://services-sync/engines.js");
Cu.import("resource://services-sync/service.js");
Cu.import("resource://services-common/observers.js");
function run_test() {
run_next_test();
}
function PetrolEngine() {}
PetrolEngine.prototype.name = "petrol";
function DieselEngine() {}
DieselEngine.prototype.name = "diesel";
function DummyEngine() {}
DummyEngine.prototype.name = "dummy";
function ActualEngine() {}
ActualEngine.prototype = {__proto__: Engine.prototype,
name: 'actual'};
function getEngineManager() {
let manager = new EngineManager(Service);
Service.engineManager = manager;
manager._engines = {
"petrol": new PetrolEngine(),
"diesel": new DieselEngine(),
"dummy": new DummyEngine(),
"actual": new ActualEngine(),
};
return manager;
}
/**
* 'Fetch' a meta/global record that doesn't mention declined.
*
* Push it into the EngineSynchronizer to set enabled; verify that those are
* correct.
*
* Then push it into DeclinedEngines to set declined; verify that none are
* declined, and a notification is sent for our locally disabled-but-not-
* declined engines.
*/
add_test(function testOldMeta() {
let meta = {
payload: {
engines: {
"petrol": 1,
"diesel": 2,
"nonlocal": 3, // Enabled but not supported.
},
},
};
_("Record: " + JSON.stringify(meta));
let manager = getEngineManager();
// Update enabled from meta/global.
let engineSync = new EngineSynchronizer(Service);
engineSync._updateEnabledFromMeta(meta, 3, manager);
Assert.ok(manager._engines["petrol"].enabled, "'petrol' locally enabled.");
Assert.ok(manager._engines["diesel"].enabled, "'diesel' locally enabled.");
Assert.ok(!("nonlocal" in manager._engines), "We don't know anything about the 'nonlocal' engine.");
Assert.ok(!manager._engines["actual"].enabled, "'actual' not locally enabled.");
Assert.ok(!manager.isDeclined("actual"), "'actual' not declined, though.");
let declinedEngines = new DeclinedEngines(Service);
function onNotDeclined(subject, topic, data) {
Observers.remove("weave:engines:notdeclined", onNotDeclined);
Assert.ok(subject.undecided.has("actual"), "EngineManager observed that 'actual' was undecided.");
let declined = manager.getDeclined();
_("Declined: " + JSON.stringify(declined));
Assert.ok(!meta.changed, "No need to upload a new meta/global.");
run_next_test();
}
Observers.add("weave:engines:notdeclined", onNotDeclined);
declinedEngines.updateDeclined(meta, manager);
});
/**
* 'Fetch' a meta/global that declines an engine we don't
* recognize. Ensure that we track that declined engine along
* with any we locally declined, and that the meta/global
* record is marked as changed and includes all declined
* engines.
*/
add_test(function testDeclinedMeta() {
let meta = {
payload: {
engines: {
"petrol": 1,
"diesel": 2,
"nonlocal": 3, // Enabled but not supported.
},
declined: ["nonexistent"], // Declined and not supported.
},
};
_("Record: " + JSON.stringify(meta));
let manager = getEngineManager();
manager._engines["petrol"].enabled = true;
manager._engines["diesel"].enabled = true;
manager._engines["dummy"].enabled = true;
manager._engines["actual"].enabled = false; // Disabled but not declined.
manager.decline(["localdecline"]); // Declined and not supported.
let declinedEngines = new DeclinedEngines(Service);
function onNotDeclined(subject, topic, data) {
Observers.remove("weave:engines:notdeclined", onNotDeclined);
Assert.ok(subject.undecided.has("actual"), "EngineManager observed that 'actual' was undecided.");
let declined = manager.getDeclined();
_("Declined: " + JSON.stringify(declined));
Assert.equal(declined.indexOf("actual"), -1, "'actual' is locally disabled, but not marked as declined.");
Assert.equal(declined.indexOf("clients"), -1, "'clients' is enabled and not remotely declined.");
Assert.equal(declined.indexOf("petrol"), -1, "'petrol' is enabled and not remotely declined.");
Assert.equal(declined.indexOf("diesel"), -1, "'diesel' is enabled and not remotely declined.");
Assert.equal(declined.indexOf("dummy"), -1, "'dummy' is enabled and not remotely declined.");
Assert.ok(0 <= declined.indexOf("nonexistent"), "'nonexistent' was declined on the server.");
Assert.ok(0 <= declined.indexOf("localdecline"), "'localdecline' was declined locally.");
// The meta/global is modified, too.
Assert.ok(0 <= meta.payload.declined.indexOf("nonexistent"), "meta/global's declined contains 'nonexistent'.");
Assert.ok(0 <= meta.payload.declined.indexOf("localdecline"), "meta/global's declined contains 'localdecline'.");
Assert.strictEqual(true, meta.changed, "meta/global was changed.");
run_next_test();
}
Observers.add("weave:engines:notdeclined", onNotDeclined);
declinedEngines.updateDeclined(meta, manager);
});

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

@ -5,6 +5,23 @@ Cu.import("resource://services-sync/engines.js");
Cu.import("resource://services-sync/service.js");
function run_test() {
run_next_test();
}
function PetrolEngine() {}
PetrolEngine.prototype.name = "petrol";
function DieselEngine() {}
DieselEngine.prototype.name = "diesel";
function DummyEngine() {}
DummyEngine.prototype.name = "dummy";
function ActualEngine() {}
ActualEngine.prototype = {__proto__: Engine.prototype,
name: 'actual'};
add_test(function test_basics() {
_("We start out with a clean slate");
let manager = new EngineManager(Service);
@ -14,8 +31,6 @@ function run_test() {
do_check_eq(manager.get('dummy'), undefined);
_("Register an engine");
function DummyEngine() {}
DummyEngine.prototype.name = "dummy";
manager.register(DummyEngine);
let dummy = manager.get('dummy');
do_check_true(dummy instanceof DummyEngine);
@ -29,11 +44,6 @@ function run_test() {
do_check_eq(manager.get('dummy'), dummy);
_("Register multiple engines in one go");
function PetrolEngine() {}
PetrolEngine.prototype.name = "petrol";
function DieselEngine() {}
DieselEngine.prototype.name = "diesel";
manager.register([PetrolEngine, DieselEngine]);
let petrol = manager.get('petrol');
let diesel = manager.get('diesel');
@ -74,9 +84,6 @@ function run_test() {
_("Unregister an engine by value");
// manager.unregister() checks for instanceof Engine, so let's make one:
function ActualEngine() {}
ActualEngine.prototype = {__proto__: Engine.prototype,
name: 'actual'};
manager.register(ActualEngine);
let actual = manager.get('actual');
do_check_true(actual instanceof ActualEngine);
@ -84,4 +91,7 @@ function run_test() {
manager.unregister(actual);
do_check_eq(manager.get('actual'), undefined);
}
run_next_test();
});

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

@ -26,6 +26,7 @@ const modules = [
"rest.js",
"service.js",
"stages/cluster.js",
"stages/declined.js",
"stages/enginesync.js",
"status.js",
"userapi.js",

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

@ -102,6 +102,7 @@ skip-if = os == "android"
skip-if = os == "mac" || os == "linux"
[test_corrupt_keys.js]
[test_declined.js]
[test_errorhandler.js]
[test_errorhandler_filelog.js]
# Bug 676978: test hangs on Android (see also testing/xpcshell/xpcshell.ini)