[tbb-commits] [tor-browser/tor-browser-38.1.0esr-5.0-1] Bug 1078657 - Add SpawnTask.js for async tasks in mochitests. r=jmaher
mikeperry at torproject.org
mikeperry at torproject.org
Tue Jul 28 08:41:37 UTC 2015
commit 6bc7fec8c4381a2ed4112ab9c43a6b243c6a0dd5
Author: Arthur Edelstein <arthuredelstein at 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">
+ <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 at 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',
More information about the tbb-commits
mailing list