michael@0: let Cu = Components.utils; michael@0: michael@0: Cu.import("resource://gre/modules/Services.jsm"); michael@0: Cu.import("resource://gre/modules/Promise.jsm"); michael@0: Cu.import("resource://gre/modules/AsyncShutdown.jsm"); michael@0: michael@0: Services.prefs.setBoolPref("toolkit.asyncshutdown.testing", true); michael@0: michael@0: /** michael@0: * An asynchronous task that takes several ticks to complete. michael@0: * michael@0: * @param {*=} resolution The value with which the resulting promise will be michael@0: * resolved once the task is complete. This may be a rejected promise, michael@0: * in which case the resulting promise will itself be rejected. michael@0: * @param {object=} outResult An object modified by side-effect during the task. michael@0: * Initially, its field |isFinished| is set to |false|. Once the task is michael@0: * complete, its field |isFinished| is set to |true|. michael@0: * michael@0: * @return {promise} A promise fulfilled once the task is complete michael@0: */ michael@0: function longRunningAsyncTask(resolution = undefined, outResult = {}) { michael@0: outResult.isFinished = false; michael@0: if (!("countFinished" in outResult)) { michael@0: outResult.countFinished = 0; michael@0: } michael@0: let deferred = Promise.defer(); michael@0: do_timeout(100, function() { michael@0: ++outResult.countFinished; michael@0: outResult.isFinished = true; michael@0: deferred.resolve(resolution); michael@0: }); michael@0: return deferred.promise; michael@0: } michael@0: michael@0: /** michael@0: * Generate a unique notification topic. michael@0: */ michael@0: function getUniqueTopic() { michael@0: const PREFIX = "testing-phases-"; michael@0: return PREFIX + ++getUniqueTopic.counter; michael@0: } michael@0: getUniqueTopic.counter = 0; michael@0: michael@0: add_task(function test_no_condition() { michael@0: do_print("Testing a phase with no condition"); michael@0: let topic = getUniqueTopic(); michael@0: AsyncShutdown._getPhase(topic); michael@0: Services.obs.notifyObservers(null, topic, null); michael@0: do_print("Phase with no condition didn't lock"); michael@0: }); michael@0: michael@0: add_task(function test_simple_async() { michael@0: do_print("Testing various combinations of a phase with a single condition"); michael@0: for (let arg of [undefined, null, "foo", 100, new Error("BOOM")]) { michael@0: for (let resolution of [arg, Promise.reject(arg)]) { michael@0: for (let success of [false, true]) { michael@0: for (let state of [[null], michael@0: [], michael@0: [() => "some state"], michael@0: [function() { michael@0: throw new Error("State BOOM"); }], michael@0: [function() { michael@0: return { michael@0: toJSON: function() { michael@0: throw new Error("State.toJSON BOOM"); michael@0: } michael@0: }; michael@0: }]]) { michael@0: // Asynchronous phase michael@0: do_print("Asynchronous test with " + arg + ", " + resolution); michael@0: let topic = getUniqueTopic(); michael@0: let outParam = { isFinished: false }; michael@0: AsyncShutdown._getPhase(topic).addBlocker( michael@0: "Async test", michael@0: function() { michael@0: if (success) { michael@0: return longRunningAsyncTask(resolution, outParam); michael@0: } else { michael@0: throw resolution; michael@0: } michael@0: }, michael@0: ...state michael@0: ); michael@0: do_check_false(outParam.isFinished); michael@0: Services.obs.notifyObservers(null, topic, null); michael@0: do_check_eq(outParam.isFinished, success); michael@0: } michael@0: } michael@0: michael@0: // Synchronous phase - just test that we don't throw/freeze michael@0: do_print("Synchronous test with " + arg + ", " + resolution); michael@0: let topic = getUniqueTopic(); michael@0: AsyncShutdown._getPhase(topic).addBlocker( michael@0: "Sync test", michael@0: resolution michael@0: ); michael@0: Services.obs.notifyObservers(null, topic, null); michael@0: } michael@0: } michael@0: }); michael@0: michael@0: add_task(function test_many() { michael@0: do_print("Testing various combinations of a phase with many conditions"); michael@0: let topic = getUniqueTopic(); michael@0: let phase = AsyncShutdown._getPhase(topic); michael@0: let outParams = []; michael@0: for (let arg of [undefined, null, "foo", 100, new Error("BOOM")]) { michael@0: for (let resolution of [arg, Promise.reject(arg)]) { michael@0: let outParam = { isFinished: false }; michael@0: phase.addBlocker( michael@0: "Test", michael@0: () => longRunningAsyncTask(resolution, outParam) michael@0: ); michael@0: } michael@0: } michael@0: do_check_true(outParams.every((x) => !x.isFinished)); michael@0: Services.obs.notifyObservers(null, topic, null); michael@0: do_check_true(outParams.every((x) => x.isFinished)); michael@0: }); michael@0: michael@0: function get_exn(f) { michael@0: try { michael@0: f(); michael@0: return null; michael@0: } catch (ex) { michael@0: return ex; michael@0: } michael@0: } michael@0: michael@0: add_task(function test_various_failures() { michael@0: do_print("Ensure that we cannot add a condition for a phase that is already complete"); michael@0: let topic = getUniqueTopic(); michael@0: let phase = AsyncShutdown._getPhase(topic); michael@0: Services.obs.notifyObservers(null, topic, null); michael@0: let exn = get_exn(() => phase.addBlocker("Test", true)); michael@0: do_check_true(!!exn); michael@0: michael@0: do_print("Ensure that an incomplete blocker causes a TypeError"); michael@0: michael@0: exn = get_exn(() => phase.addBlocker()); michael@0: do_check_eq(exn.name, "TypeError"); michael@0: michael@0: exn = get_exn(() => phase.addBlocker(null, true)); michael@0: do_check_eq(exn.name, "TypeError"); michael@0: michael@0: exn = get_exn(() => phase.addBlocker("Test 2", () => true, "not a function")); michael@0: do_check_eq(exn.name, "TypeError"); michael@0: }); michael@0: michael@0: add_task(function() { michael@0: Services.prefs.clearUserPref("toolkit.asyncshutdown.testing"); michael@0: }); michael@0: michael@0: function run_test() { michael@0: run_next_test(); michael@0: }