michael@0: /* Any copyright is dedicated to the Public Domain. michael@0: http://creativecommons.org/publicdomain/zero/1.0/ */ michael@0: michael@0: /** michael@0: * This file tests the Task.jsm module. michael@0: */ michael@0: michael@0: //////////////////////////////////////////////////////////////////////////////// michael@0: /// Globals michael@0: michael@0: const Cc = Components.classes; michael@0: const Ci = Components.interfaces; michael@0: const Cu = Components.utils; michael@0: const Cr = Components.results; michael@0: michael@0: Cu.import("resource://gre/modules/XPCOMUtils.jsm"); michael@0: michael@0: XPCOMUtils.defineLazyModuleGetter(this, "Promise", michael@0: "resource://gre/modules/Promise.jsm"); michael@0: XPCOMUtils.defineLazyModuleGetter(this, "Services", michael@0: "resource://gre/modules/Services.jsm"); michael@0: XPCOMUtils.defineLazyModuleGetter(this, "Task", michael@0: "resource://gre/modules/Task.jsm"); michael@0: michael@0: /** michael@0: * Returns a promise that will be resolved with the given value, when an event michael@0: * posted on the event loop of the main thread is processed. michael@0: */ michael@0: function promiseResolvedLater(aValue) { michael@0: let deferred = Promise.defer(); michael@0: Services.tm.mainThread.dispatch(function () deferred.resolve(aValue), michael@0: Ci.nsIThread.DISPATCH_NORMAL); michael@0: return deferred.promise; michael@0: } michael@0: michael@0: //////////////////////////////////////////////////////////////////////////////// michael@0: /// Tests michael@0: michael@0: function run_test() michael@0: { michael@0: run_next_test(); michael@0: } michael@0: michael@0: add_test(function test_normal() michael@0: { michael@0: Task.spawn(function () { michael@0: let result = yield Promise.resolve("Value"); michael@0: for (let i = 0; i < 3; i++) { michael@0: result += yield promiseResolvedLater("!"); michael@0: } michael@0: throw new Task.Result("Task result: " + result); michael@0: }).then(function (result) { michael@0: do_check_eq("Task result: Value!!!", result); michael@0: run_next_test(); michael@0: }, function (ex) { michael@0: do_throw("Unexpected error: " + ex); michael@0: }); michael@0: }); michael@0: michael@0: add_test(function test_exceptions() michael@0: { michael@0: Task.spawn(function () { michael@0: try { michael@0: yield Promise.reject("Rejection result by promise."); michael@0: do_throw("Exception expected because the promise was rejected."); michael@0: } catch (ex) { michael@0: // We catch this exception now, we will throw a different one later. michael@0: do_check_eq("Rejection result by promise.", ex); michael@0: } michael@0: throw new Error("Exception uncaught by task."); michael@0: }).then(function (result) { michael@0: do_throw("Unexpected success!"); michael@0: }, function (ex) { michael@0: do_check_eq("Exception uncaught by task.", ex.message); michael@0: run_next_test(); michael@0: }); michael@0: }); michael@0: michael@0: add_test(function test_recursion() michael@0: { michael@0: function task_fibonacci(n) { michael@0: throw new Task.Result(n < 2 ? n : (yield task_fibonacci(n - 1)) + michael@0: (yield task_fibonacci(n - 2))); michael@0: }; michael@0: michael@0: Task.spawn(task_fibonacci(6)).then(function (result) { michael@0: do_check_eq(8, result); michael@0: run_next_test(); michael@0: }, function (ex) { michael@0: do_throw("Unexpected error: " + ex); michael@0: }); michael@0: }); michael@0: michael@0: add_test(function test_spawn_primitive() michael@0: { michael@0: function fibonacci(n) { michael@0: return n < 2 ? n : fibonacci(n - 1) + fibonacci(n - 2); michael@0: }; michael@0: michael@0: // Polymorphism between task and non-task functions (see "test_recursion"). michael@0: Task.spawn(fibonacci(6)).then(function (result) { michael@0: do_check_eq(8, result); michael@0: run_next_test(); michael@0: }, function (ex) { michael@0: do_throw("Unexpected error: " + ex); michael@0: }); michael@0: }); michael@0: michael@0: add_test(function test_spawn_function() michael@0: { michael@0: Task.spawn(function () { michael@0: return "This is not a generator."; michael@0: }).then(function (result) { michael@0: do_check_eq("This is not a generator.", result); michael@0: run_next_test(); michael@0: }, function (ex) { michael@0: do_throw("Unexpected error: " + ex); michael@0: }); michael@0: }); michael@0: michael@0: add_test(function test_spawn_function_this() michael@0: { michael@0: Task.spawn(function () { michael@0: return this; michael@0: }).then(function (result) { michael@0: // Since the task function wasn't defined in strict mode, its "this" object michael@0: // should be the same as the "this" object in this function, i.e. the global michael@0: // object. michael@0: do_check_eq(result, this); michael@0: run_next_test(); michael@0: }, function (ex) { michael@0: do_throw("Unexpected error: " + ex); michael@0: }); michael@0: }); michael@0: michael@0: add_test(function test_spawn_function_this_strict() michael@0: { michael@0: "use strict"; michael@0: Task.spawn(function () { michael@0: return this; michael@0: }).then(function (result) { michael@0: // Since the task function was defined in strict mode, its "this" object michael@0: // should be undefined. michael@0: do_check_eq(typeof(result), "undefined"); michael@0: run_next_test(); michael@0: }, function (ex) { michael@0: do_throw("Unexpected error: " + ex); michael@0: }); michael@0: }); michael@0: michael@0: add_test(function test_spawn_function_returning_promise() michael@0: { michael@0: Task.spawn(function () { michael@0: return promiseResolvedLater("Resolution value."); michael@0: }).then(function (result) { michael@0: do_check_eq("Resolution value.", result); michael@0: run_next_test(); michael@0: }, function (ex) { michael@0: do_throw("Unexpected error: " + ex); michael@0: }); michael@0: }); michael@0: michael@0: add_test(function test_spawn_function_exceptions() michael@0: { michael@0: Task.spawn(function () { michael@0: throw new Error("Exception uncaught by task."); michael@0: }).then(function (result) { michael@0: do_throw("Unexpected success!"); michael@0: }, function (ex) { michael@0: do_check_eq("Exception uncaught by task.", ex.message); michael@0: run_next_test(); michael@0: }); michael@0: }); michael@0: michael@0: add_test(function test_spawn_function_taskresult() michael@0: { michael@0: Task.spawn(function () { michael@0: throw new Task.Result("Task result"); michael@0: }).then(function (result) { michael@0: do_check_eq("Task result", result); michael@0: run_next_test(); michael@0: }, function (ex) { michael@0: do_throw("Unexpected error: " + ex); michael@0: }); michael@0: }); michael@0: michael@0: add_test(function test_yielded_undefined() michael@0: { michael@0: Task.spawn(function () { michael@0: yield; michael@0: throw new Task.Result("We continued correctly."); michael@0: }).then(function (result) { michael@0: do_check_eq("We continued correctly.", result); michael@0: run_next_test(); michael@0: }, function (ex) { michael@0: do_throw("Unexpected error: " + ex); michael@0: }); michael@0: }); michael@0: michael@0: add_test(function test_yielded_primitive() michael@0: { michael@0: Task.spawn(function () { michael@0: throw new Task.Result("Primitive " + (yield "value.")); michael@0: }).then(function (result) { michael@0: do_check_eq("Primitive value.", result); michael@0: run_next_test(); michael@0: }, function (ex) { michael@0: do_throw("Unexpected error: " + ex); michael@0: }); michael@0: }); michael@0: michael@0: add_test(function test_star_normal() michael@0: { michael@0: Task.spawn(function* () { michael@0: let result = yield Promise.resolve("Value"); michael@0: for (let i = 0; i < 3; i++) { michael@0: result += yield promiseResolvedLater("!"); michael@0: } michael@0: return "Task result: " + result; michael@0: }).then(function (result) { michael@0: do_check_eq("Task result: Value!!!", result); michael@0: run_next_test(); michael@0: }, function (ex) { michael@0: do_throw("Unexpected error: " + ex); michael@0: }); michael@0: }); michael@0: michael@0: add_test(function test_star_exceptions() michael@0: { michael@0: Task.spawn(function* () { michael@0: try { michael@0: yield Promise.reject("Rejection result by promise."); michael@0: do_throw("Exception expected because the promise was rejected."); michael@0: } catch (ex) { michael@0: // We catch this exception now, we will throw a different one later. michael@0: do_check_eq("Rejection result by promise.", ex); michael@0: } michael@0: throw new Error("Exception uncaught by task."); michael@0: }).then(function (result) { michael@0: do_throw("Unexpected success!"); michael@0: }, function (ex) { michael@0: do_check_eq("Exception uncaught by task.", ex.message); michael@0: run_next_test(); michael@0: }); michael@0: }); michael@0: michael@0: add_test(function test_star_recursion() michael@0: { michael@0: function* task_fibonacci(n) { michael@0: return n < 2 ? n : (yield task_fibonacci(n - 1)) + michael@0: (yield task_fibonacci(n - 2)); michael@0: }; michael@0: michael@0: Task.spawn(task_fibonacci(6)).then(function (result) { michael@0: do_check_eq(8, result); michael@0: run_next_test(); michael@0: }, function (ex) { michael@0: do_throw("Unexpected error: " + ex); michael@0: }); michael@0: }); michael@0: michael@0: add_test(function test_mixed_legacy_and_star() michael@0: { michael@0: Task.spawn(function* () { michael@0: return yield (function() { michael@0: throw new Task.Result(yield 5); michael@0: })(); michael@0: }).then(function (result) { michael@0: do_check_eq(5, result); michael@0: run_next_test(); michael@0: }, function (ex) { michael@0: do_throw("Unexpected error: " + ex); michael@0: }); michael@0: }); michael@0: michael@0: add_test(function test_async_function_from_generator() michael@0: { michael@0: Task.spawn(function* () { michael@0: let object = { michael@0: asyncFunction: Task.async(function* (param) { michael@0: do_check_eq(this, object); michael@0: return param; michael@0: }) michael@0: }; michael@0: michael@0: // Ensure the async function returns a promise that resolves as expected. michael@0: do_check_eq((yield object.asyncFunction(1)), 1); michael@0: michael@0: // Ensure a second call to the async function also returns such a promise. michael@0: do_check_eq((yield object.asyncFunction(3)), 3); michael@0: }).then(function () { michael@0: run_next_test(); michael@0: }, function (ex) { michael@0: do_throw("Unexpected error: " + ex); michael@0: }); michael@0: }); michael@0: michael@0: add_test(function test_async_function_from_function() michael@0: { michael@0: Task.spawn(function* () { michael@0: return Task.spawn(function* () { michael@0: let object = { michael@0: asyncFunction: Task.async(function (param) { michael@0: do_check_eq(this, object); michael@0: return param; michael@0: }) michael@0: }; michael@0: michael@0: // Ensure the async function returns a promise that resolves as expected. michael@0: do_check_eq((yield object.asyncFunction(5)), 5); michael@0: michael@0: // Ensure a second call to the async function also returns such a promise. michael@0: do_check_eq((yield object.asyncFunction(7)), 7); michael@0: }); michael@0: }).then(function () { michael@0: run_next_test(); michael@0: }, function (ex) { michael@0: do_throw("Unexpected error: " + ex); michael@0: }); michael@0: }); michael@0: michael@0: add_test(function test_async_function_that_throws_rejects_promise() michael@0: { michael@0: Task.spawn(function* () { michael@0: let object = { michael@0: asyncFunction: Task.async(function* () { michael@0: throw "Rejected!"; michael@0: }) michael@0: }; michael@0: michael@0: yield object.asyncFunction(); michael@0: }).then(function () { michael@0: do_throw("unexpected success calling async function that throws error"); michael@0: }, function (ex) { michael@0: do_check_eq(ex, "Rejected!"); michael@0: run_next_test(); michael@0: }); michael@0: }); michael@0: michael@0: add_test(function test_async_return_function() michael@0: { michael@0: Task.spawn(function* () { michael@0: // Ensure an async function that returns a function resolves to the function michael@0: // itself instead of calling the function and resolving to its return value. michael@0: return Task.spawn(function* () { michael@0: let returnValue = function () { michael@0: return "These aren't the droids you're looking for."; michael@0: }; michael@0: michael@0: let asyncFunction = Task.async(function () { michael@0: return returnValue; michael@0: }); michael@0: michael@0: do_check_eq((yield asyncFunction()), returnValue); michael@0: }); michael@0: }).then(function () { michael@0: run_next_test(); michael@0: }, function (ex) { michael@0: do_throw("Unexpected error: " + ex); michael@0: }); michael@0: }); michael@0: michael@0: add_test(function test_async_throw_argument_not_function() michael@0: { michael@0: Task.spawn(function* () { michael@0: // Ensure Task.async throws if its aTask argument is not a function. michael@0: Assert.throws(() => Task.async("not a function"), michael@0: /aTask argument must be a function/); michael@0: }).then(function () { michael@0: run_next_test(); michael@0: }, function (ex) { michael@0: do_throw("Unexpected error: " + ex); michael@0: }); michael@0: }); michael@0: michael@0: add_test(function test_async_throw_on_function_in_place_of_promise() michael@0: { michael@0: Task.spawn(function* () { michael@0: // Ensure Task.spawn throws if passed an async function. michael@0: Assert.throws(() => Task.spawn(Task.async(function* () {})), michael@0: /Cannot use an async function in place of a promise/); michael@0: }).then(function () { michael@0: run_next_test(); michael@0: }, function (ex) { michael@0: do_throw("Unexpected error: " + ex); michael@0: }); michael@0: });