Files
tubestation/toolkit/modules/tests/xpcshell/test_AsyncShutdown.js

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");
});