305 lines
9.5 KiB
JavaScript
305 lines
9.5 KiB
JavaScript
let Cu = Components.utils;
|
|
|
|
Cu.import("resource://gre/modules/Services.jsm");
|
|
Cu.import("resource://gre/modules/Promise.jsm");
|
|
Cu.import("resource://gre/modules/AsyncShutdown.jsm");
|
|
|
|
Services.prefs.setBoolPref("toolkit.asyncshutdown.testing", true);
|
|
|
|
function run_test() {
|
|
run_next_test();
|
|
}
|
|
|
|
function get_exn(f) {
|
|
try {
|
|
f();
|
|
return null;
|
|
} catch (ex) {
|
|
return ex;
|
|
}
|
|
}
|
|
|
|
function do_check_exn(exn, constructor) {
|
|
do_check_neq(exn, null);
|
|
if (exn.name == constructor) {
|
|
do_check_eq(exn.constructor.name, constructor);
|
|
return;
|
|
}
|
|
do_print("Wrong error constructor");
|
|
do_print(exn.constructor.name);
|
|
do_print(exn.stack);
|
|
do_check_true(false);
|
|
}
|
|
|
|
/**
|
|
* Utility function used to provide the same API for both AsyncShutdown
|
|
* phases and AsyncShutdown barriers.
|
|
*
|
|
* @param {bool} heavy If |true|, use a AsyncShutdown phase, otherwise
|
|
* a AsyncShutdown barrier.
|
|
*
|
|
* @return An object with the following methods:
|
|
* - addBlocker() - the same method as AsyncShutdown phases and barrier clients
|
|
* - wait() - trigger the resolution of the lock
|
|
*/
|
|
function makeLock(heavy) {
|
|
if (heavy) {
|
|
let topic = "test-Phase-" + ++makeLock.counter;
|
|
let phase = AsyncShutdown._getPhase(topic);
|
|
return {
|
|
addBlocker: function(...args) {
|
|
return phase.addBlocker(...args);
|
|
},
|
|
removeBlocker: function(blocker) {
|
|
return phase.removeBlocker(blocker);
|
|
},
|
|
wait: function() {
|
|
Services.obs.notifyObservers(null, topic, null);
|
|
return Promise.resolve();
|
|
}
|
|
};
|
|
} else {
|
|
let name = "test-Barrier-" + ++makeLock.counter;
|
|
let barrier = new AsyncShutdown.Barrier(name);
|
|
return {
|
|
addBlocker: barrier.client.addBlocker,
|
|
removeBlocker: barrier.client.removeBlocker,
|
|
wait: function() {
|
|
return barrier.wait();
|
|
}
|
|
};
|
|
}
|
|
}
|
|
makeLock.counter = 0;
|
|
|
|
/**
|
|
* An asynchronous task that takes several ticks to complete.
|
|
*
|
|
* @param {*=} resolution The value with which the resulting promise will be
|
|
* resolved once the task is complete. This may be a rejected promise,
|
|
* in which case the resulting promise will itself be rejected.
|
|
* @param {object=} outResult An object modified by side-effect during the task.
|
|
* Initially, its field |isFinished| is set to |false|. Once the task is
|
|
* complete, its field |isFinished| is set to |true|.
|
|
*
|
|
* @return {promise} A promise fulfilled once the task is complete
|
|
*/
|
|
function longRunningAsyncTask(resolution = undefined, outResult = {}) {
|
|
outResult.isFinished = false;
|
|
if (!("countFinished" in outResult)) {
|
|
outResult.countFinished = 0;
|
|
}
|
|
let deferred = Promise.defer();
|
|
do_timeout(100, function() {
|
|
++outResult.countFinished;
|
|
outResult.isFinished = true;
|
|
deferred.resolve(resolution);
|
|
});
|
|
return deferred.promise;
|
|
}
|
|
|
|
|
|
//////// Tests on AsyncShutdown phases
|
|
|
|
/*add_task*/(function* test_no_condition() {
|
|
for (let heavy of [false, true]) {
|
|
do_print("Testing a barrier with no condition (" + heavy?"heavy":"light" + ")");
|
|
let lock = makeLock(heavy);
|
|
yield lock.wait();
|
|
do_print("Barrier with no condition didn't lock");
|
|
}
|
|
});
|
|
|
|
/*add_task*/(function* test_phase_simple_async() {
|
|
do_print("Testing various combinations of a phase with a single condition");
|
|
for (let heavy of [false, true]) {
|
|
for (let arg of [undefined, null, "foo", 100, new Error("BOOM")]) {
|
|
for (let resolution of [arg, Promise.reject(arg)]) {
|
|
for (let success of [false, true]) {
|
|
for (let state of [[null],
|
|
[],
|
|
[() => "some state"],
|
|
[function() {
|
|
throw new Error("State BOOM"); }],
|
|
[function() {
|
|
return {
|
|
toJSON: function() {
|
|
throw new Error("State.toJSON BOOM");
|
|
}
|
|
};
|
|
}]]) {
|
|
// Asynchronous phase
|
|
do_print("Asynchronous test with " + arg + ", " + resolution + ", " + heavy);
|
|
let lock = makeLock(heavy);
|
|
let outParam = { isFinished: false };
|
|
lock.addBlocker(
|
|
"Async test",
|
|
function() {
|
|
if (success) {
|
|
return longRunningAsyncTask(resolution, outParam);
|
|
} else {
|
|
throw resolution;
|
|
}
|
|
},
|
|
...state
|
|
);
|
|
do_check_false(outParam.isFinished);
|
|
yield lock.wait();
|
|
do_check_eq(outParam.isFinished, success);
|
|
}
|
|
}
|
|
|
|
// Synchronous phase - just test that we don't throw/freeze
|
|
do_print("Synchronous test with " + arg + ", " + resolution + ", " + heavy);
|
|
let lock = makeLock(heavy);
|
|
lock.addBlocker(
|
|
"Sync test",
|
|
resolution
|
|
);
|
|
yield lock.wait();
|
|
}
|
|
}
|
|
}
|
|
});
|
|
|
|
/*add_task*/(function* test_phase_many() {
|
|
do_print("Testing various combinations of a phase with many conditions");
|
|
for (let heavy of [false, true]) {
|
|
let lock = makeLock(heavy);
|
|
let outParams = [];
|
|
for (let arg of [undefined, null, "foo", 100, new Error("BOOM")]) {
|
|
for (let resolve of [true, false]) {
|
|
do_print("Testing with " + heavy + ", " + arg + ", " + resolve);
|
|
let resolution = resolve ? arg : Promise.reject(arg);
|
|
let outParam = { isFinished: false };
|
|
lock.addBlocker(
|
|
"Test",
|
|
() => longRunningAsyncTask(resolution, outParam)
|
|
);
|
|
}
|
|
}
|
|
do_check_true(outParams.every((x) => !x.isFinished));
|
|
yield lock.wait();
|
|
do_check_true(outParams.every((x) => x.isFinished));
|
|
}
|
|
});
|
|
|
|
|
|
|
|
/*add_task*/(function* test_phase_various_failures() {
|
|
do_print("Ensure that we cannot add a condition for a phase once notification has been received");
|
|
for (let heavy of [false, true]) {
|
|
let lock = makeLock(heavy);
|
|
lock.wait(); // Don't actually wait for the promise to be resolved
|
|
let exn = get_exn(() => lock.addBlocker("Test", true));
|
|
do_check_true(!!exn);
|
|
|
|
do_print("Ensure that an incomplete blocker causes a TypeError");
|
|
|
|
lock = makeLock(heavy);
|
|
exn = get_exn(() => lock.addBlocker());
|
|
do_check_exn(exn, "TypeError");
|
|
|
|
exn = get_exn(() => lock.addBlocker(null, true));
|
|
do_check_exn(exn, "TypeError");
|
|
|
|
exn = get_exn(() => lock.addBlocker("Test 2", () => true, "not a function"));
|
|
do_check_exn(exn, "TypeError");
|
|
}
|
|
});
|
|
|
|
|
|
add_task(function* test_phase_removeBlocker() {
|
|
do_print("Testing that we can call removeBlocker before, during and after the call to wait()");
|
|
|
|
for (let heavy of [false, true]) {
|
|
|
|
do_print("Attempt to add then remove a blocker before wait()");
|
|
let lock = makeLock(heavy);
|
|
let blocker = () => {
|
|
do_print("This promise will never be resolved");
|
|
return Promise.defer().promise;
|
|
};
|
|
|
|
lock.addBlocker("Wait forever", blocker);
|
|
do_check_true(lock.removeBlocker(blocker));
|
|
do_check_false(lock.removeBlocker(blocker));
|
|
do_print("Attempt to remove non-registered blockers before wait()");
|
|
do_check_false(lock.removeBlocker("foo"));
|
|
do_check_false(lock.removeBlocker(null));
|
|
do_print("Waiting (should lift immediately)");
|
|
yield lock.wait();
|
|
|
|
do_print("Attempt to add a blocker then remove it during wait()");
|
|
lock = makeLock(heavy);
|
|
let blockers = [
|
|
() => {
|
|
do_print("This blocker will self-destruct");
|
|
do_check_true(lock.removeBlocker(blockers[0]));
|
|
return Promise.defer().promise;
|
|
},
|
|
() => {
|
|
do_print("This blocker will self-destruct twice");
|
|
do_check_true(lock.removeBlocker(blockers[1]));
|
|
do_check_false(lock.removeBlocker(blockers[1]));
|
|
return Promise.defer().promise;
|
|
},
|
|
() => {
|
|
do_print("Attempt to remove non-registered blockers during wait()");
|
|
do_check_false(lock.removeBlocker("foo"));
|
|
do_check_false(lock.removeBlocker(null));
|
|
}
|
|
];
|
|
for (let i in blockers) {
|
|
lock.addBlocker("Wait forever again: " + i, blockers[i]);
|
|
}
|
|
do_print("Waiting (should lift very quickly)");
|
|
yield lock.wait();
|
|
do_check_false(lock.removeBlocker(blockers[0]));
|
|
|
|
|
|
do_print("Attempt to remove a blocker after wait");
|
|
lock = makeLock(heavy);
|
|
blocker = Promise.resolve;
|
|
yield lock.wait();
|
|
|
|
do_print("Attempt to remove non-registered blocker after wait()");
|
|
do_check_false(lock.removeBlocker("foo"));
|
|
do_check_false(lock.removeBlocker(null));
|
|
}
|
|
|
|
});
|
|
|
|
add_task(function* test_state() {
|
|
do_print("Testing information contained in `state`");
|
|
|
|
let BLOCKER_NAME = "test_state blocker " + Math.random();
|
|
|
|
// Set up the barrier. Note that we cannot test `barrier.state`
|
|
// immediately, as it initially contains "Not started"
|
|
let barrier = new AsyncShutdown.Barrier("test_filename");
|
|
let deferred = Promise.defer();
|
|
let {filename, lineNumber} = Components.stack;
|
|
barrier.client.addBlocker(BLOCKER_NAME,
|
|
function() {
|
|
return deferred.promise;
|
|
});
|
|
|
|
let promiseDone = barrier.wait();
|
|
|
|
// Now that we have called `wait()`, the state contains interesting things
|
|
let state = barrier.state[0];
|
|
do_print("State: " + JSON.stringify(barrier.state, null, "\t"));
|
|
Assert.equal(state.filename, filename);
|
|
Assert.equal(state.lineNumber, lineNumber + 2);
|
|
Assert.equal(state.name, BLOCKER_NAME);
|
|
|
|
deferred.resolve();
|
|
yield promiseDone;
|
|
});
|
|
|
|
add_task(function*() {
|
|
Services.prefs.clearUserPref("toolkit.asyncshutdown.testing");
|
|
});
|
|
|