diff -r 000000000000 -r 6474c204b198 toolkit/modules/tests/xpcshell/test_AsyncShutdown.js --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/toolkit/modules/tests/xpcshell/test_AsyncShutdown.js Wed Dec 31 06:09:35 2014 +0100 @@ -0,0 +1,156 @@ +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); + +/** + * 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; +} + +/** + * Generate a unique notification topic. + */ +function getUniqueTopic() { + const PREFIX = "testing-phases-"; + return PREFIX + ++getUniqueTopic.counter; +} +getUniqueTopic.counter = 0; + +add_task(function test_no_condition() { + do_print("Testing a phase with no condition"); + let topic = getUniqueTopic(); + AsyncShutdown._getPhase(topic); + Services.obs.notifyObservers(null, topic, null); + do_print("Phase with no condition didn't lock"); +}); + +add_task(function test_simple_async() { + do_print("Testing various combinations of a phase with a single condition"); + 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); + let topic = getUniqueTopic(); + let outParam = { isFinished: false }; + AsyncShutdown._getPhase(topic).addBlocker( + "Async test", + function() { + if (success) { + return longRunningAsyncTask(resolution, outParam); + } else { + throw resolution; + } + }, + ...state + ); + do_check_false(outParam.isFinished); + Services.obs.notifyObservers(null, topic, null); + do_check_eq(outParam.isFinished, success); + } + } + + // Synchronous phase - just test that we don't throw/freeze + do_print("Synchronous test with " + arg + ", " + resolution); + let topic = getUniqueTopic(); + AsyncShutdown._getPhase(topic).addBlocker( + "Sync test", + resolution + ); + Services.obs.notifyObservers(null, topic, null); + } + } +}); + +add_task(function test_many() { + do_print("Testing various combinations of a phase with many conditions"); + let topic = getUniqueTopic(); + let phase = AsyncShutdown._getPhase(topic); + let outParams = []; + for (let arg of [undefined, null, "foo", 100, new Error("BOOM")]) { + for (let resolution of [arg, Promise.reject(arg)]) { + let outParam = { isFinished: false }; + phase.addBlocker( + "Test", + () => longRunningAsyncTask(resolution, outParam) + ); + } + } + do_check_true(outParams.every((x) => !x.isFinished)); + Services.obs.notifyObservers(null, topic, null); + do_check_true(outParams.every((x) => x.isFinished)); +}); + +function get_exn(f) { + try { + f(); + return null; + } catch (ex) { + return ex; + } +} + +add_task(function test_various_failures() { + do_print("Ensure that we cannot add a condition for a phase that is already complete"); + let topic = getUniqueTopic(); + let phase = AsyncShutdown._getPhase(topic); + Services.obs.notifyObservers(null, topic, null); + let exn = get_exn(() => phase.addBlocker("Test", true)); + do_check_true(!!exn); + + do_print("Ensure that an incomplete blocker causes a TypeError"); + + exn = get_exn(() => phase.addBlocker()); + do_check_eq(exn.name, "TypeError"); + + exn = get_exn(() => phase.addBlocker(null, true)); + do_check_eq(exn.name, "TypeError"); + + exn = get_exn(() => phase.addBlocker("Test 2", () => true, "not a function")); + do_check_eq(exn.name, "TypeError"); +}); + +add_task(function() { + Services.prefs.clearUserPref("toolkit.asyncshutdown.testing"); +}); + +function run_test() { + run_next_test(); +}