commit 6bc7fec8c4381a2ed4112ab9c43a6b243c6a0dd5 Author: Arthur Edelstein arthuredelstein@gmail.com Date: Fri Jul 17 12:37:24 2015 -0400
Bug 1078657 - Add SpawnTask.js for async tasks in mochitests. r=jmaher --- testing/mochitest/chrome/chrome.ini | 1 + testing/mochitest/chrome/test_sanitySpawnTask.xul | 70 ++++++ testing/mochitest/jar.mn | 1 + .../mochitest/tests/Harness_sanity/mochitest.ini | 2 +- .../tests/Harness_sanity/test_spawn_task.html | 73 ++++++ .../mochitest/tests/SimpleTest/LICENSE_SpawnTask | 24 ++ testing/mochitest/tests/SimpleTest/SpawnTask.js | 244 ++++++++++++++++++++ testing/mochitest/tests/SimpleTest/moz.build | 1 + 8 files changed, 415 insertions(+), 1 deletion(-)
diff --git a/testing/mochitest/chrome/chrome.ini b/testing/mochitest/chrome/chrome.ini index ecd3911..a5ed449 100644 --- a/testing/mochitest/chrome/chrome.ini +++ b/testing/mochitest/chrome/chrome.ini @@ -12,4 +12,5 @@ skip-if = buildapp == 'mulet' fail-if = true [test_sanityManifest_pf.xul] fail-if = true +[test_sanitySpawnTask.xul] [test_chromeGetTestFile.xul] diff --git a/testing/mochitest/chrome/test_sanitySpawnTask.xul b/testing/mochitest/chrome/test_sanitySpawnTask.xul new file mode 100644 index 0000000..c26b761 --- /dev/null +++ b/testing/mochitest/chrome/test_sanitySpawnTask.xul @@ -0,0 +1,70 @@ +<?xml version="1.0"?> +<!-- This Source Code Form is subject to the terms of the Mozilla Public + - License, v. 2.0. If a copy of the MPL was not distributed with this + - file, You can obtain one at http://mozilla.org/MPL/2.0/. --> +<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" + type="text/css"?> +<window title="Test spawnTawk function" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul%22%3E + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/> + <script type="application/javascript" + src="chrome://mochikit/content/tests/SimpleTest/SpawnTask.js"/> + <script type="application/javascript"> + <![CDATA[ + SimpleTest.waitForExplicitFinish(); + + var externalGeneratorFunction = function* () { + return 8; + }; + + var nestedFunction = function* () { + return yield function* () { + return yield function* () { + return yield function* () { + return yield Promise.resolve(9); + }(); + }(); + }(); + } + + var variousTests = function* () { + var val1 = yield [Promise.resolve(1), Promise.resolve(2), Promise.resolve(3)]; + is(val1.join(""), "123", "Array of promises -> Promise.all"); + var val2 = yield Promise.resolve(2); + is(val2, 2, "Resolved promise yields value."); + var val3 = yield function* () { return 3; }; + is(val3, 3, "Generator functions are spawned."); + //var val4 = yield function () { return 4; }; + //is(val4, 4, "Plain functions run and return."); + var val5 = yield (function* () { return 5; }()); + is(val5, 5, "Generators are spawned."); + try { + var val6 = yield Promise.reject(Error("error6")); + ok(false, "Shouldn't reach this line."); + } catch (error) { + is(error.message, "error6", "Rejected promise throws error."); + } + try { + var val7 = yield function* () { throw Error("error7"); }; + ok(false, "Shouldn't reach this line."); + } catch (error) { + is(error.message, "error7", "Thrown error propagates."); + } + var val8 = yield externalGeneratorFunction(); + is(val8, 8, "External generator also spawned."); + var val9 = yield nestedFunction(); + is(val9, 9, "Nested generator functions work."); + return 10; + }; + + spawnTask(variousTests).then(function(result) { + is(result, 10, "spawnTask(...) returns promise"); + SimpleTest.finish(); + }); + ]]> + </script> + + <body xmlns="http://www.w3.org/1999/xhtml" > + </body> +</window> diff --git a/testing/mochitest/jar.mn b/testing/mochitest/jar.mn index c678812..75f535d 100644 --- a/testing/mochitest/jar.mn +++ b/testing/mochitest/jar.mn @@ -20,6 +20,7 @@ mochikit.jar: content/static/harness.css (static/harness.css) content/tests/SimpleTest/ChromePowers.js (tests/SimpleTest/ChromePowers.js) content/tests/SimpleTest/EventUtils.js (tests/SimpleTest/EventUtils.js) + content/tests/SimpleTest/SpawnTask.js (tests/SimpleTest/SpawnTask.js) content/tests/SimpleTest/ChromeUtils.js (tests/SimpleTest/ChromeUtils.js) content/tests/SimpleTest/LogController.js (tests/SimpleTest/LogController.js) content/tests/SimpleTest/MemoryStats.js (tests/SimpleTest/MemoryStats.js) diff --git a/testing/mochitest/tests/Harness_sanity/mochitest.ini b/testing/mochitest/tests/Harness_sanity/mochitest.ini index 0eefc3f..935acc3 100644 --- a/testing/mochitest/tests/Harness_sanity/mochitest.ini +++ b/testing/mochitest/tests/Harness_sanity/mochitest.ini @@ -31,4 +31,4 @@ fail-if = true [test_sanity_manifest_pf.html] skip-if = toolkit == 'android' # we use the old manifest style on android fail-if = true - +[test_spawn_task.html] diff --git a/testing/mochitest/tests/Harness_sanity/test_spawn_task.html b/testing/mochitest/tests/Harness_sanity/test_spawn_task.html new file mode 100644 index 0000000..425b7fd --- /dev/null +++ b/testing/mochitest/tests/Harness_sanity/test_spawn_task.html @@ -0,0 +1,73 @@ +<!DOCTYPE HTML> +<html> +<head> + <title>Test for mochitest SpawnTask.js sanity</title> + <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <script type="text/javascript" src="/tests/SimpleTest/SpawnTask.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=">Mozilla Bug 1078657</a> +<p id="display"></p> +<div id="content" style="display: none"></div> +<pre id="test"> +<script class="testbody" type="text/javascript"> + +/** Test for sanity **/ +SimpleTest.waitForExplicitFinish(); + +var externalGeneratorFunction = function* () { + return 8; +}; + +var nestedFunction = function* () { + return yield function* () { + return yield function* () { + return yield function* () { + return yield Promise.resolve(9); + }(); + }(); + }(); +} + +var variousTests = function* () { + var val1 = yield [Promise.resolve(1), Promise.resolve(2), Promise.resolve(3)]; + is(val1.join(""), "123", "Array of promises -> Promise.all"); + var val2 = yield Promise.resolve(2); + is(val2, 2, "Resolved promise yields value."); + var val3 = yield function* () { return 3; }; + is(val3, 3, "Generator functions are spawned."); + //var val4 = yield function () { return 4; }; + //is(val4, 4, "Plain functions run and return."); + var val5 = yield (function* () { return 5; }()); + is(val5, 5, "Generators are spawned."); + try { + var val6 = yield Promise.reject(Error("error6")); + ok(false, "Shouldn't reach this line."); + } catch (error) { + is(error.message, "error6", "Rejected promise throws error."); + } + try { + var val7 = yield function* () { throw Error("error7"); }; + ok(false, "Shouldn't reach this line."); + } catch (error) { + is(error.message, "error7", "Thrown error propagates."); + } + var val8 = yield externalGeneratorFunction(); + is(val8, 8, "External generator also spawned."); + var val9 = yield nestedFunction(); + is(val9, 9, "Nested generator functions work."); + return 10; +}; + +spawnTask(variousTests).then(function(result) { + is(result, 10, "spawnTask(...) returns promise"); + SimpleTest.finish(); +}); + + +</script> +</pre> +</body> +</html> + diff --git a/testing/mochitest/tests/SimpleTest/LICENSE_SpawnTask b/testing/mochitest/tests/SimpleTest/LICENSE_SpawnTask new file mode 100644 index 0000000..088c54c --- /dev/null +++ b/testing/mochitest/tests/SimpleTest/LICENSE_SpawnTask @@ -0,0 +1,24 @@ +LICENSE for SpawnTask.js (the co library): + +(The MIT License) + +Copyright (c) 2014 TJ Holowaychuk <tj@vision-media.ca> + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +'Software'), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/testing/mochitest/tests/SimpleTest/SpawnTask.js b/testing/mochitest/tests/SimpleTest/SpawnTask.js new file mode 100644 index 0000000..954dcc4 --- /dev/null +++ b/testing/mochitest/tests/SimpleTest/SpawnTask.js @@ -0,0 +1,244 @@ +// # SpawnTask.js +// Directly copied from the "co" library by TJ Holowaychuk. +// See https://github.com/tj/co/tree/4.6.0 +// For use with mochitest-plain and mochitest-chrome. + +// __spawnTask(generatorFunction)__. +// Expose only the `co` function, which is very similar to Task.spawn in Task.jsm. +// We call this function spawnTask to make its purpose more plain, and to +// reduce the chance of name collisions. +var spawnTask = (function () { + +/** + * slice() reference. + */ + +var slice = Array.prototype.slice; + +/** + * Wrap the given generator `fn` into a + * function that returns a promise. + * This is a separate function so that + * every `co()` call doesn't create a new, + * unnecessary closure. + * + * @param {GeneratorFunction} fn + * @return {Function} + * @api public + */ + +co.wrap = function (fn) { + createPromise.__generatorFunction__ = fn; + return createPromise; + function createPromise() { + return co.call(this, fn.apply(this, arguments)); + } +}; + +/** + * Execute the generator function or a generator + * and return a promise. + * + * @param {Function} fn + * @return {Promise} + * @api public + */ + +function co(gen) { + var ctx = this; + var args = slice.call(arguments, 1) + + // we wrap everything in a promise to avoid promise chaining, + // which leads to memory leak errors. + // see https://github.com/tj/co/issues/180 + return new Promise(function(resolve, reject) { + if (typeof gen === 'function') gen = gen.apply(ctx, args); + if (!gen || typeof gen.next !== 'function') return resolve(gen); + + onFulfilled(); + + /** + * @param {Mixed} res + * @return {Promise} + * @api private + */ + + function onFulfilled(res) { + var ret; + try { + ret = gen.next(res); + } catch (e) { + return reject(e); + } + next(ret); + } + + /** + * @param {Error} err + * @return {Promise} + * @api private + */ + + function onRejected(err) { + var ret; + try { + ret = gen.throw(err); + } catch (e) { + return reject(e); + } + next(ret); + } + + /** + * Get the next value in the generator, + * return a promise. + * + * @param {Object} ret + * @return {Promise} + * @api private + */ + + function next(ret) { + if (ret.done) return resolve(ret.value); + var value = toPromise.call(ctx, ret.value); + if (value && isPromise(value)) return value.then(onFulfilled, onRejected); + return onRejected(new TypeError('You may only yield a function, promise, generator, array, or object, ' + + 'but the following object was passed: "' + String(ret.value) + '"')); + } + }); +} + +/** + * Convert a `yield`ed value into a promise. + * + * @param {Mixed} obj + * @return {Promise} + * @api private + */ + +function toPromise(obj) { + if (!obj) return obj; + if (isPromise(obj)) return obj; + if (isGeneratorFunction(obj) || isGenerator(obj)) return co.call(this, obj); + if ('function' == typeof obj) return thunkToPromise.call(this, obj); + if (Array.isArray(obj)) return arrayToPromise.call(this, obj); + if (isObject(obj)) return objectToPromise.call(this, obj); + return obj; +} + +/** + * Convert a thunk to a promise. + * + * @param {Function} + * @return {Promise} + * @api private + */ + +function thunkToPromise(fn) { + var ctx = this; + return new Promise(function (resolve, reject) { + fn.call(ctx, function (err, res) { + if (err) return reject(err); + if (arguments.length > 2) res = slice.call(arguments, 1); + resolve(res); + }); + }); +} + +/** + * Convert an array of "yieldables" to a promise. + * Uses `Promise.all()` internally. + * + * @param {Array} obj + * @return {Promise} + * @api private + */ + +function arrayToPromise(obj) { + return Promise.all(obj.map(toPromise, this)); +} + +/** + * Convert an object of "yieldables" to a promise. + * Uses `Promise.all()` internally. + * + * @param {Object} obj + * @return {Promise} + * @api private + */ + +function objectToPromise(obj){ + var results = new obj.constructor(); + var keys = Object.keys(obj); + var promises = []; + for (var i = 0; i < keys.length; i++) { + var key = keys[i]; + var promise = toPromise.call(this, obj[key]); + if (promise && isPromise(promise)) defer(promise, key); + else results[key] = obj[key]; + } + return Promise.all(promises).then(function () { + return results; + }); + + function defer(promise, key) { + // predefine the key in the result + results[key] = undefined; + promises.push(promise.then(function (res) { + results[key] = res; + })); + } +} + +/** + * Check if `obj` is a promise. + * + * @param {Object} obj + * @return {Boolean} + * @api private + */ + +function isPromise(obj) { + return 'function' == typeof obj.then; +} + +/** + * Check if `obj` is a generator. + * + * @param {Mixed} obj + * @return {Boolean} + * @api private + */ + +function isGenerator(obj) { + return 'function' == typeof obj.next && 'function' == typeof obj.throw; +} + +/** + * Check if `obj` is a generator function. + * + * @param {Mixed} obj + * @return {Boolean} + * @api private + */ +function isGeneratorFunction(obj) { + var constructor = obj.constructor; + if (!constructor) return false; + if ('GeneratorFunction' === constructor.name || 'GeneratorFunction' === constructor.displayName) return true; + return isGenerator(constructor.prototype); +} + +/** + * Check for plain object. + * + * @param {Mixed} val + * @return {Boolean} + * @api private + */ + +function isObject(val) { + return Object == val.constructor; +} + +return co; +})(); diff --git a/testing/mochitest/tests/SimpleTest/moz.build b/testing/mochitest/tests/SimpleTest/moz.build index 4cf31c7..e07b652 100644 --- a/testing/mochitest/tests/SimpleTest/moz.build +++ b/testing/mochitest/tests/SimpleTest/moz.build @@ -17,6 +17,7 @@ TEST_HARNESS_FILES.testing.mochitest.tests.SimpleTest += [ 'paint_listener.js', 'setup.js', 'SimpleTest.js', + 'SpawnTask.js', 'test.css', 'TestRunner.js', 'WindowSnapshot.js',
tor-commits@lists.torproject.org