diff options
Diffstat (limited to 'toolkit/modules/tests/xpcshell/test_task.js')
-rw-r--r-- | toolkit/modules/tests/xpcshell/test_task.js | 642 |
1 files changed, 642 insertions, 0 deletions
diff --git a/toolkit/modules/tests/xpcshell/test_task.js b/toolkit/modules/tests/xpcshell/test_task.js new file mode 100644 index 000000000..fdcd56514 --- /dev/null +++ b/toolkit/modules/tests/xpcshell/test_task.js @@ -0,0 +1,642 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * This file tests the Task.jsm module. + */ + +//////////////////////////////////////////////////////////////////////////////// +/// Globals + +var Cc = Components.classes; +var Ci = Components.interfaces; +var Cu = Components.utils; +var Cr = Components.results; + +Cu.import("resource://gre/modules/XPCOMUtils.jsm"); + +XPCOMUtils.defineLazyModuleGetter(this, "Promise", + "resource://gre/modules/Promise.jsm"); +XPCOMUtils.defineLazyModuleGetter(this, "Services", + "resource://gre/modules/Services.jsm"); +XPCOMUtils.defineLazyModuleGetter(this, "Task", + "resource://gre/modules/Task.jsm"); + +/** + * Returns a promise that will be resolved with the given value, when an event + * posted on the event loop of the main thread is processed. + */ +function promiseResolvedLater(aValue) { + let deferred = Promise.defer(); + Services.tm.mainThread.dispatch(() => deferred.resolve(aValue), + Ci.nsIThread.DISPATCH_NORMAL); + return deferred.promise; +} + +//////////////////////////////////////////////////////////////////////////////// +/// Tests + +function run_test() +{ + run_next_test(); +} + +add_test(function test_normal() +{ + Task.spawn(function () { + let result = yield Promise.resolve("Value"); + for (let i = 0; i < 3; i++) { + result += yield promiseResolvedLater("!"); + } + throw new Task.Result("Task result: " + result); + }).then(function (result) { + do_check_eq("Task result: Value!!!", result); + run_next_test(); + }, function (ex) { + do_throw("Unexpected error: " + ex); + }); +}); + +add_test(function test_exceptions() +{ + Task.spawn(function () { + try { + yield Promise.reject("Rejection result by promise."); + do_throw("Exception expected because the promise was rejected."); + } catch (ex) { + // We catch this exception now, we will throw a different one later. + do_check_eq("Rejection result by promise.", ex); + } + throw new Error("Exception uncaught by task."); + }).then(function (result) { + do_throw("Unexpected success!"); + }, function (ex) { + do_check_eq("Exception uncaught by task.", ex.message); + run_next_test(); + }); +}); + +add_test(function test_recursion() +{ + function task_fibonacci(n) { + throw new Task.Result(n < 2 ? n : (yield task_fibonacci(n - 1)) + + (yield task_fibonacci(n - 2))); + }; + + Task.spawn(task_fibonacci(6)).then(function (result) { + do_check_eq(8, result); + run_next_test(); + }, function (ex) { + do_throw("Unexpected error: " + ex); + }); +}); + +add_test(function test_spawn_primitive() +{ + function fibonacci(n) { + return n < 2 ? n : fibonacci(n - 1) + fibonacci(n - 2); + }; + + // Polymorphism between task and non-task functions (see "test_recursion"). + Task.spawn(fibonacci(6)).then(function (result) { + do_check_eq(8, result); + run_next_test(); + }, function (ex) { + do_throw("Unexpected error: " + ex); + }); +}); + +add_test(function test_spawn_function() +{ + Task.spawn(function () { + return "This is not a generator."; + }).then(function (result) { + do_check_eq("This is not a generator.", result); + run_next_test(); + }, function (ex) { + do_throw("Unexpected error: " + ex); + }); +}); + +add_test(function test_spawn_function_this() +{ + Task.spawn(function () { + return this; + }).then(function (result) { + // Since the task function wasn't defined in strict mode, its "this" object + // should be the same as the "this" object in this function, i.e. the global + // object. + do_check_eq(result, this); + run_next_test(); + }, function (ex) { + do_throw("Unexpected error: " + ex); + }); +}); + +add_test(function test_spawn_function_this_strict() +{ + "use strict"; + Task.spawn(function () { + return this; + }).then(function (result) { + // Since the task function was defined in strict mode, its "this" object + // should be undefined. + do_check_eq(typeof(result), "undefined"); + run_next_test(); + }, function (ex) { + do_throw("Unexpected error: " + ex); + }); +}); + +add_test(function test_spawn_function_returning_promise() +{ + Task.spawn(function () { + return promiseResolvedLater("Resolution value."); + }).then(function (result) { + do_check_eq("Resolution value.", result); + run_next_test(); + }, function (ex) { + do_throw("Unexpected error: " + ex); + }); +}); + +add_test(function test_spawn_function_exceptions() +{ + Task.spawn(function () { + throw new Error("Exception uncaught by task."); + }).then(function (result) { + do_throw("Unexpected success!"); + }, function (ex) { + do_check_eq("Exception uncaught by task.", ex.message); + run_next_test(); + }); +}); + +add_test(function test_spawn_function_taskresult() +{ + Task.spawn(function () { + throw new Task.Result("Task result"); + }).then(function (result) { + do_check_eq("Task result", result); + run_next_test(); + }, function (ex) { + do_throw("Unexpected error: " + ex); + }); +}); + +add_test(function test_yielded_undefined() +{ + Task.spawn(function () { + yield; + throw new Task.Result("We continued correctly."); + }).then(function (result) { + do_check_eq("We continued correctly.", result); + run_next_test(); + }, function (ex) { + do_throw("Unexpected error: " + ex); + }); +}); + +add_test(function test_yielded_primitive() +{ + Task.spawn(function () { + throw new Task.Result("Primitive " + (yield "value.")); + }).then(function (result) { + do_check_eq("Primitive value.", result); + run_next_test(); + }, function (ex) { + do_throw("Unexpected error: " + ex); + }); +}); + +add_test(function test_star_normal() +{ + Task.spawn(function* () { + let result = yield Promise.resolve("Value"); + for (let i = 0; i < 3; i++) { + result += yield promiseResolvedLater("!"); + } + return "Task result: " + result; + }).then(function (result) { + do_check_eq("Task result: Value!!!", result); + run_next_test(); + }, function (ex) { + do_throw("Unexpected error: " + ex); + }); +}); + +add_test(function test_star_exceptions() +{ + Task.spawn(function* () { + try { + yield Promise.reject("Rejection result by promise."); + do_throw("Exception expected because the promise was rejected."); + } catch (ex) { + // We catch this exception now, we will throw a different one later. + do_check_eq("Rejection result by promise.", ex); + } + throw new Error("Exception uncaught by task."); + }).then(function (result) { + do_throw("Unexpected success!"); + }, function (ex) { + do_check_eq("Exception uncaught by task.", ex.message); + run_next_test(); + }); +}); + +add_test(function test_star_recursion() +{ + function* task_fibonacci(n) { + return n < 2 ? n : (yield task_fibonacci(n - 1)) + + (yield task_fibonacci(n - 2)); + }; + + Task.spawn(task_fibonacci(6)).then(function (result) { + do_check_eq(8, result); + run_next_test(); + }, function (ex) { + do_throw("Unexpected error: " + ex); + }); +}); + +add_test(function test_mixed_legacy_and_star() +{ + Task.spawn(function* () { + return yield (function() { + throw new Task.Result(yield 5); + })(); + }).then(function (result) { + do_check_eq(5, result); + run_next_test(); + }, function (ex) { + do_throw("Unexpected error: " + ex); + }); +}); + +add_test(function test_async_function_from_generator() +{ + Task.spawn(function* () { + let object = { + asyncFunction: Task.async(function* (param) { + do_check_eq(this, object); + return param; + }) + }; + + // Ensure the async function returns a promise that resolves as expected. + do_check_eq((yield object.asyncFunction(1)), 1); + + // Ensure a second call to the async function also returns such a promise. + do_check_eq((yield object.asyncFunction(3)), 3); + }).then(function () { + run_next_test(); + }, function (ex) { + do_throw("Unexpected error: " + ex); + }); +}); + +add_test(function test_async_function_from_function() +{ + Task.spawn(function* () { + return Task.spawn(function* () { + let object = { + asyncFunction: Task.async(function (param) { + do_check_eq(this, object); + return param; + }) + }; + + // Ensure the async function returns a promise that resolves as expected. + do_check_eq((yield object.asyncFunction(5)), 5); + + // Ensure a second call to the async function also returns such a promise. + do_check_eq((yield object.asyncFunction(7)), 7); + }); + }).then(function () { + run_next_test(); + }, function (ex) { + do_throw("Unexpected error: " + ex); + }); +}); + +add_test(function test_async_function_that_throws_rejects_promise() +{ + Task.spawn(function* () { + let object = { + asyncFunction: Task.async(function* () { + throw "Rejected!"; + }) + }; + + yield object.asyncFunction(); + }).then(function () { + do_throw("unexpected success calling async function that throws error"); + }, function (ex) { + do_check_eq(ex, "Rejected!"); + run_next_test(); + }); +}); + +add_test(function test_async_return_function() +{ + Task.spawn(function* () { + // Ensure an async function that returns a function resolves to the function + // itself instead of calling the function and resolving to its return value. + return Task.spawn(function* () { + let returnValue = function () { + return "These aren't the droids you're looking for."; + }; + + let asyncFunction = Task.async(function () { + return returnValue; + }); + + do_check_eq((yield asyncFunction()), returnValue); + }); + }).then(function () { + run_next_test(); + }, function (ex) { + do_throw("Unexpected error: " + ex); + }); +}); + +add_test(function test_async_throw_argument_not_function() +{ + Task.spawn(function* () { + // Ensure Task.async throws if its aTask argument is not a function. + Assert.throws(() => Task.async("not a function"), + /aTask argument must be a function/); + }).then(function () { + run_next_test(); + }, function (ex) { + do_throw("Unexpected error: " + ex); + }); +}); + +add_test(function test_async_throw_on_function_in_place_of_promise() +{ + Task.spawn(function* () { + // Ensure Task.spawn throws if passed an async function. + Assert.throws(() => Task.spawn(Task.async(function* () {})), + /Cannot use an async function in place of a promise/); + }).then(function () { + run_next_test(); + }, function (ex) { + do_throw("Unexpected error: " + ex); + }); +}); + + +////////////////// Test rewriting of stack traces + +// Backup Task.Debuggin.maintainStack. +// Will be restored by `exit_stack_tests`. +var maintainStack; +add_test(function enter_stack_tests() { + maintainStack = Task.Debugging.maintainStack; + Task.Debugging.maintainStack = true; + run_next_test(); +}); + + +/** + * Ensure that a list of frames appear in a stack, in the right order + */ +function do_check_rewritten_stack(frames, ex) { + do_print("Checking that the expected frames appear in the right order"); + do_print(frames.join(", ")); + let stack = ex.stack; + do_print(stack); + + let framesFound = 0; + let lineNumber = 0; + let reLine = /([^\r\n])+/g; + let match; + while (framesFound < frames.length && (match = reLine.exec(stack))) { + let line = match[0]; + let frame = frames[framesFound]; + do_print("Searching for " + frame + " in line " + line); + if (line.indexOf(frame) != -1) { + do_print("Found " + frame); + ++framesFound; + } else { + do_print("Didn't find " + frame); + } + } + + if (framesFound >= frames.length) { + return; + } + do_throw("Did not find: " + frames.slice(framesFound).join(", ") + + " in " + stack.substr(reLine.lastIndex)); + + do_print("Ensuring that we have removed Task.jsm, Promise.jsm"); + do_check_true(stack.indexOf("Task.jsm") == -1); + do_check_true(stack.indexOf("Promise.jsm") == -1); + do_check_true(stack.indexOf("Promise-backend.js") == -1); +} + + +// Test that we get an acceptable rewritten stack when we launch +// an error in a Task.spawn. +add_test(function test_spawn_throw_stack() { + Task.spawn(function* task_spawn_throw_stack() { + for (let i = 0; i < 5; ++i) { + yield Promise.resolve(); // Without stack rewrite, this would lose valuable information + } + throw new Error("BOOM"); + }).then(do_throw, function(ex) { + do_check_rewritten_stack(["task_spawn_throw_stack", + "test_spawn_throw_stack"], + ex); + run_next_test(); + }); +}); + +// Test that we get an acceptable rewritten stack when we yield +// a rejection in a Task.spawn. +add_test(function test_spawn_yield_reject_stack() { + Task.spawn(function* task_spawn_yield_reject_stack() { + for (let i = 0; i < 5; ++i) { + yield Promise.resolve(); // Without stack rewrite, this would lose valuable information + } + yield Promise.reject(new Error("BOOM")); + }).then(do_throw, function(ex) { + do_check_rewritten_stack(["task_spawn_yield_reject_stack", + "test_spawn_yield_reject_stack"], + ex); + run_next_test(); + }); +}); + +// Test that we get an acceptable rewritten stack when we launch +// an error in a Task.async function. +add_test(function test_async_function_throw_stack() { + let task_async_function_throw_stack = Task.async(function*() { + for (let i = 0; i < 5; ++i) { + yield Promise.resolve(); // Without stack rewrite, this would lose valuable information + } + throw new Error("BOOM"); + })().then(do_throw, function(ex) { + do_check_rewritten_stack(["task_async_function_throw_stack", + "test_async_function_throw_stack"], + ex); + run_next_test(); + }); +}); + +// Test that we get an acceptable rewritten stack when we launch +// an error in a Task.async function. +add_test(function test_async_function_yield_reject_stack() { + let task_async_function_yield_reject_stack = Task.async(function*() { + for (let i = 0; i < 5; ++i) { + yield Promise.resolve(); // Without stack rewrite, this would lose valuable information + } + yield Promise.reject(new Error("BOOM")); + })().then(do_throw, function(ex) { + do_check_rewritten_stack(["task_async_function_yield_reject_stack", + "test_async_function_yield_reject_stack"], + ex); + run_next_test(); + }); +}); + +// Test that we get an acceptable rewritten stack when we launch +// an error in a Task.async function. +add_test(function test_async_method_throw_stack() { + let object = { + task_async_method_throw_stack: Task.async(function*() { + for (let i = 0; i < 5; ++i) { + yield Promise.resolve(); // Without stack rewrite, this would lose valuable information + } + throw new Error("BOOM"); + }) + }; + object.task_async_method_throw_stack().then(do_throw, function(ex) { + do_check_rewritten_stack(["task_async_method_throw_stack", + "test_async_method_throw_stack"], + ex); + run_next_test(); + }); +}); + +// Test that we get an acceptable rewritten stack when we launch +// an error in a Task.async function. +add_test(function test_async_method_yield_reject_stack() { + let object = { + task_async_method_yield_reject_stack: Task.async(function*() { + for (let i = 0; i < 5; ++i) { + yield Promise.resolve(); // Without stack rewrite, this would lose valuable information + } + yield Promise.reject(new Error("BOOM")); + }) + }; + object.task_async_method_yield_reject_stack().then(do_throw, function(ex) { + do_check_rewritten_stack(["task_async_method_yield_reject_stack", + "test_async_method_yield_reject_stack"], + ex); + run_next_test(); + }); +}); + +// Test that two tasks whose execution takes place interleaved do not capture each other's stack. +add_test(function test_throw_stack_do_not_capture_the_wrong_task() { + for (let iter_a of [3, 4, 5]) { // Vary the interleaving + for (let iter_b of [3, 4, 5]) { + Task.spawn(function* task_a() { + for (let i = 0; i < iter_a; ++i) { + yield Promise.resolve(); + } + throw new Error("BOOM"); + }).then(do_throw, function(ex) { + do_check_rewritten_stack(["task_a", + "test_throw_stack_do_not_capture_the_wrong_task"], + ex); + do_check_true(!ex.stack.includes("task_b")); + run_next_test(); + }); + Task.spawn(function* task_b() { + for (let i = 0; i < iter_b; ++i) { + yield Promise.resolve(); + } + }); + } + } +}); + +// Put things together +add_test(function test_throw_complex_stack() +{ + // Setup the following stack: + // inner_method() + // task_3() + // task_2() + // task_1() + // function_3() + // function_2() + // function_1() + // test_throw_complex_stack() + (function function_1() { + return (function function_2() { + return (function function_3() { + return Task.spawn(function* task_1() { + yield Promise.resolve(); + try { + yield Task.spawn(function* task_2() { + yield Promise.resolve(); + yield Task.spawn(function* task_3() { + yield Promise.resolve(); + let inner_object = { + inner_method: Task.async(function*() { + throw new Error("BOOM"); + }) + }; + yield Promise.resolve(); + yield inner_object.inner_method(); + }); + }); + } catch (ex) { + yield Promise.resolve(); + throw ex; + } + }); + })(); + })(); + })().then( + () => do_throw("Shouldn't have succeeded"), + (ex) => { + let expect = ["inner_method", + "task_3", + "task_2", + "task_1", + "function_3", + "function_2", + "function_1", + "test_throw_complex_stack"]; + do_check_rewritten_stack(expect, ex); + + run_next_test(); + }); +}); + +add_test(function test_without_maintainStack() { + do_print("Calling generateReadableStack without a Task"); + Task.Debugging.generateReadableStack(new Error("Not a real error")); + + Task.Debugging.maintainStack = false; + + do_print("Calling generateReadableStack with neither a Task nor maintainStack"); + Task.Debugging.generateReadableStack(new Error("Not a real error")); + + do_print("Calling generateReadableStack without maintainStack"); + Task.spawn(function*() { + Task.Debugging.generateReadableStack(new Error("Not a real error")); + run_next_test(); + }); +}); + +add_test(function exit_stack_tests() { + Task.Debugging.maintainStack = maintainStack; + run_next_test(); +}); + |