[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