summaryrefslogtreecommitdiffstats
path: root/toolkit/modules/tests/xpcshell/test_task.js
diff options
context:
space:
mode:
Diffstat (limited to 'toolkit/modules/tests/xpcshell/test_task.js')
-rw-r--r--toolkit/modules/tests/xpcshell/test_task.js642
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();
+});
+