fune/toolkit/modules/tests/xpcshell/test_task.js
Kris Maglione e930b89c34 Bug 1514594: Part 3 - Change ChromeUtils.import API.
***
Bug 1514594: Part 3a - Change ChromeUtils.import to return an exports object; not pollute global. r=mccr8

This changes the behavior of ChromeUtils.import() to return an exports object,
rather than a module global, in all cases except when `null` is passed as a
second argument, and changes the default behavior not to pollute the global
scope with the module's exports. Thus, the following code written for the old
model:

  ChromeUtils.import("resource://gre/modules/Services.jsm");

is approximately the same as the following, in the new model:

  var {Services} = ChromeUtils.import("resource://gre/modules/Services.jsm");

Since the two behaviors are mutually incompatible, this patch will land with a
scripted rewrite to update all existing callers to use the new model rather
than the old.
***
Bug 1514594: Part 3b - Mass rewrite all JS code to use the new ChromeUtils.import API. rs=Gijs

This was done using the followng script:

https://bitbucket.org/kmaglione/m-c-rewrites/src/tip/processors/cu-import-exports.jsm
***
Bug 1514594: Part 3c - Update ESLint plugin for ChromeUtils.import API changes. r=Standard8

Differential Revision: https://phabricator.services.mozilla.com/D16747
***
Bug 1514594: Part 3d - Remove/fix hundreds of duplicate imports from sync tests. r=Gijs

Differential Revision: https://phabricator.services.mozilla.com/D16748
***
Bug 1514594: Part 3e - Remove no-op ChromeUtils.import() calls. r=Gijs

Differential Revision: https://phabricator.services.mozilla.com/D16749
***
Bug 1514594: Part 3f.1 - Cleanup various test corner cases after mass rewrite. r=Gijs
***
Bug 1514594: Part 3f.2 - Cleanup various non-test corner cases after mass rewrite. r=Gijs

Differential Revision: https://phabricator.services.mozilla.com/D16750

--HG--
extra : rebase_source : 359574ee3064c90f33bf36c2ebe3159a24cc8895
extra : histedit_source : b93c8f42808b1599f9122d7842d2c0b3e656a594%2C64a3a4e3359dc889e2ab2b49461bab9e27fc10a7
2019-01-17 10:18:31 -08:00

579 lines
16 KiB
JavaScript

/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
/**
* This file tests the Task.jsm module.
*/
////////////////////////////////////////////////////////////////////////////////
/// Globals
ChromeUtils.defineModuleGetter(this, "Task",
"resource://testing-common/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) {
return new Promise(resolve => {
Services.tm.dispatchToMainThread(() => resolve(aValue));
});
}
////////////////////////////////////////////////////////////////////////////////
/// Tests
function run_test()
{
run_next_test();
}
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) {
Assert.equal(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) {
Assert.equal("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.
Assert.equal(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.
Assert.equal(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) {
Assert.equal("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) {
Assert.equal("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) {
Assert.equal("Task result", result);
run_next_test();
}, function (ex) {
do_throw("Unexpected error: " + ex);
});
});
add_test(function test_yielded_undefined()
{
Task.spawn(function* () {
yield;
return "We continued correctly.";
}).then(function (result) {
Assert.equal("We continued correctly.", result);
run_next_test();
}, function (ex) {
do_throw("Unexpected error: " + ex);
});
});
add_test(function test_yielded_primitive()
{
Task.spawn(function* () {
return "Primitive " + (yield "value.");
}).then(function (result) {
Assert.equal("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) {
Assert.equal("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.
Assert.equal("Rejection result by promise.", ex);
}
throw new Error("Exception uncaught by task.");
}).then(function (result) {
do_throw("Unexpected success!");
}, function (ex) {
Assert.equal("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) {
Assert.equal(8, result);
run_next_test();
}, function (ex) {
do_throw("Unexpected error: " + ex);
});
});
add_test(function test_nested_star()
{
Task.spawn(function* () {
return yield (function* () {
return yield 5;
})();
}).then(function (result) {
Assert.equal(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) {
Assert.equal(this, object);
return param;
})
};
// Ensure the async function returns a promise that resolves as expected.
Assert.equal((yield object.asyncFunction(1)), 1);
// Ensure a second call to the async function also returns such a promise.
Assert.equal((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) {
Assert.equal(this, object);
return param;
})
};
// Ensure the async function returns a promise that resolves as expected.
Assert.equal((yield object.asyncFunction(5)), 5);
// Ensure a second call to the async function also returns such a promise.
Assert.equal((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) {
Assert.equal(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;
});
Assert.equal((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) {
info("Checking that the expected frames appear in the right order");
info(frames.join(", "));
let stack = ex.stack;
info(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];
info("Searching for " + frame + " in line " + line);
if (line.includes(frame)) {
info("Found " + frame);
++framesFound;
} else {
info("Didn't find " + frame);
}
}
if (framesFound >= frames.length) {
return;
}
do_throw("Did not find: " + frames.slice(framesFound).join(", ") +
" in " + stack.substr(reLine.lastIndex));
info("Ensuring that we have removed Task.jsm, Promise.jsm");
Assert.ok(!stack.includes("Task.jsm"));
Assert.ok(!stack.includes("Promise.jsm"));
Assert.ok(!stack.includes("Promise-backend.js"));
}
// 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);
Assert.ok(!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() {
info("Calling generateReadableStack without a Task");
Task.Debugging.generateReadableStack(new Error("Not a real error"));
Task.Debugging.maintainStack = false;
info("Calling generateReadableStack with neither a Task nor maintainStack");
Task.Debugging.generateReadableStack(new Error("Not a real error"));
info("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();
});