Bug 1093931 - Update Loop mocha unit test framework to v2.0.1, which supports Promises, r=Standard8
authorDan Mosedale <dmose@meer.net>
Fri, 07 Nov 2014 13:59:09 -0800
changeset 214676 1b52d3719b7f25ca1448cd44ccc2f7b3306e7c36
parent 214675 7f0e90fc4932de7e942d93f492f9818590a15719
child 214677 3335d6e68892ceddc6ead115bec9e0af8ed77cf2
push id27791
push userkwierso@gmail.com
push dateSat, 08 Nov 2014 01:43:47 +0000
treeherdermozilla-central@b7f2bf6856a2 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersStandard8
bugs1093931
milestone36.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1093931 - Update Loop mocha unit test framework to v2.0.1, which supports Promises, r=Standard8
browser/components/loop/test/desktop-local/index.html
browser/components/loop/test/shared/index.html
browser/components/loop/test/shared/vendor/mocha-1.17.1.css
browser/components/loop/test/shared/vendor/mocha-1.17.1.js
browser/components/loop/test/shared/vendor/mocha-2.0.1.css
browser/components/loop/test/shared/vendor/mocha-2.0.1.js
browser/components/loop/test/standalone/index.html
browser/components/loop/test/standalone/multiplexGum_test.js
--- a/browser/components/loop/test/desktop-local/index.html
+++ b/browser/components/loop/test/desktop-local/index.html
@@ -1,33 +1,33 @@
 <!DOCTYPE html>
 <!-- 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/.  -->
 <html>
 <head>
   <meta charset="utf-8">
   <title>Loop desktop-local mocha tests</title>
-  <link rel="stylesheet" media="all" href="../shared/vendor/mocha-1.17.1.css">
+  <link rel="stylesheet" media="all" href="../shared/vendor/mocha-2.0.1.css">
 </head>
 <body>
   <div id="mocha">
     <p><a href="../">Index</a></p>
   </div>
   <div id="messages"></div>
   <div id="fixtures"></div>
   <!-- libs -->
   <script src="../../content/libs/l10n.js"></script>
   <script src="../../content/shared/libs/react-0.11.2.js"></script>
   <script src="../../content/shared/libs/jquery-2.1.0.js"></script>
   <script src="../../content/shared/libs/lodash-2.4.1.js"></script>
   <script src="../../content/shared/libs/backbone-1.1.2.js"></script>
 
   <!-- test dependencies -->
-  <script src="../shared/vendor/mocha-1.17.1.js"></script>
+  <script src="../shared/vendor/mocha-2.0.1.js"></script>
   <script src="../shared/vendor/chai-1.9.0.js"></script>
   <script src="../shared/vendor/sinon-1.9.0.js"></script>
   <script>
     /*global chai,mocha */
     chai.Assertion.includeStack = true;
     mocha.setup('bdd');
   </script>
 
--- a/browser/components/loop/test/shared/index.html
+++ b/browser/components/loop/test/shared/index.html
@@ -1,34 +1,34 @@
 <!DOCTYPE html>
 <!-- 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/.  -->
 <html>
 <head>
   <meta charset="utf-8">
   <title>Loop shared mocha tests</title>
-  <link rel="stylesheet" media="all" href="vendor/mocha-1.17.1.css">
+  <link rel="stylesheet" media="all" href="vendor/mocha-2.0.1.css">
 </head>
 <body>
   <div id="mocha">
     <p><a href="../">Index</a></p>
   </div>
   <div id="messages"></div>
   <div id="fixtures"></div>
 
   <!-- libs -->
   <script src="../../content/shared/libs/react-0.11.2.js"></script>
   <script src="../../content/shared/libs/jquery-2.1.0.js"></script>
   <script src="../../content/shared/libs/lodash-2.4.1.js"></script>
   <script src="../../content/shared/libs/backbone-1.1.2.js"></script>
   <script src="../../standalone/content/libs/l10n-gaia-02ca67948fe8.js"></script>
 
   <!-- test dependencies -->
-  <script src="vendor/mocha-1.17.1.js"></script>
+  <script src="vendor/mocha-2.0.1.js"></script>
   <script src="vendor/chai-1.9.0.js"></script>
   <script src="vendor/sinon-1.9.0.js"></script>
   <script>
     /*global chai, mocha */
     chai.Assertion.includeStack = true;
     mocha.setup('bdd');
   </script>
 
rename from browser/components/loop/test/shared/vendor/mocha-1.17.1.css
rename to browser/components/loop/test/shared/vendor/mocha-2.0.1.css
rename from browser/components/loop/test/shared/vendor/mocha-1.17.1.js
rename to browser/components/loop/test/shared/vendor/mocha-2.0.1.js
--- a/browser/components/loop/test/shared/vendor/mocha-1.17.1.js
+++ b/browser/components/loop/test/shared/vendor/mocha-2.0.1.js
@@ -43,17 +43,16 @@ require.relative = function (parent) {
       }
 
       return require(path.join('/'));
     };
   };
 
 
 require.register("browser/debug.js", function(module, exports, require){
-
 module.exports = function(type){
   return function(){
   }
 };
 
 }); // module: browser/debug.js
 
 require.register("browser/diff.js", function(module, exports, require){
@@ -409,18 +408,32 @@ var JsDiff = (function() {
 })();
 
 if (typeof module !== 'undefined') {
     module.exports = JsDiff;
 }
 
 }); // module: browser/diff.js
 
+require.register("browser/escape-string-regexp.js", function(module, exports, require){
+'use strict';
+
+var matchOperatorsRe = /[|\\{}()[\]^$+*?.]/g;
+
+module.exports = function (str) {
+  if (typeof str !== 'string') {
+    throw new TypeError('Expected a string');
+  }
+
+  return str.replace(matchOperatorsRe,  '\\$&');
+};
+
+}); // module: browser/escape-string-regexp.js
+
 require.register("browser/events.js", function(module, exports, require){
-
 /**
  * Module exports.
  */
 
 exports.EventEmitter = EventEmitter;
 
 /**
  * Check if `obj` is an array.
@@ -588,22 +601,27 @@ EventEmitter.prototype.emit = function (
       listeners[i].apply(this, args);
     }
   } else {
     return false;
   }
 
   return true;
 };
+
 }); // module: browser/events.js
 
 require.register("browser/fs.js", function(module, exports, require){
 
 }); // module: browser/fs.js
 
+require.register("browser/glob.js", function(module, exports, require){
+
+}); // module: browser/glob.js
+
 require.register("browser/path.js", function(module, exports, require){
 
 }); // module: browser/path.js
 
 require.register("browser/progress.js", function(module, exports, require){
 /**
  * Expose `Progress`.
  */
@@ -695,67 +713,65 @@ Progress.prototype.draw = function(ctx){
   try {
     var percent = Math.min(this.percent, 100)
       , size = this._size
       , half = size / 2
       , x = half
       , y = half
       , rad = half - 1
       , fontSize = this._fontSize;
-  
+
     ctx.font = fontSize + 'px ' + this._font;
-  
+
     var angle = Math.PI * 2 * (percent / 100);
     ctx.clearRect(0, 0, size, size);
-  
+
     // outer circle
     ctx.strokeStyle = '#9f9f9f';
     ctx.beginPath();
     ctx.arc(x, y, rad, 0, angle, false);
     ctx.stroke();
-  
+
     // inner circle
     ctx.strokeStyle = '#eee';
     ctx.beginPath();
     ctx.arc(x, y, rad - 1, 0, angle, true);
     ctx.stroke();
-  
+
     // text
     var text = this._text || (percent | 0) + '%'
       , w = ctx.measureText(text).width;
-  
+
     ctx.fillText(
         text
       , x - w / 2 + 1
       , y + fontSize / 2 - 1);
   } catch (ex) {} //don't fail if we can't render progress
   return this;
 };
 
 }); // module: browser/progress.js
 
 require.register("browser/tty.js", function(module, exports, require){
-
 exports.isatty = function(){
   return true;
 };
 
 exports.getWindowSize = function(){
   if ('innerHeight' in global) {
     return [global.innerHeight, global.innerWidth];
   } else {
     // In a Web Worker, the DOM Window is not available.
     return [640, 480];
   }
 };
 
 }); // module: browser/tty.js
 
 require.register("context.js", function(module, exports, require){
-
 /**
  * Expose `Context`.
  */
 
 module.exports = Context;
 
 /**
  * Initialize a new `Context`.
@@ -783,21 +799,36 @@ Context.prototype.runnable = function(ru
  * Set test timeout `ms`.
  *
  * @param {Number} ms
  * @return {Context} self
  * @api private
  */
 
 Context.prototype.timeout = function(ms){
+  if (arguments.length === 0) return this.runnable().timeout();
   this.runnable().timeout(ms);
   return this;
 };
 
 /**
+ * Set test timeout `enabled`.
+ *
+ * @param {Boolean} enabled
+ * @return {Context} self
+ * @api private
+ */
+
+Context.prototype.enableTimeouts = function (enabled) {
+  this.runnable().enableTimeouts(enabled);
+  return this;
+};
+
+
+/**
  * Set test slowness threshold `ms`.
  *
  * @param {Number} ms
  * @return {Context} self
  * @api private
  */
 
 Context.prototype.slow = function(ms){
@@ -818,17 +849,16 @@ Context.prototype.inspect = function(){
     if ('test' == key) return;
     return val;
   }, 2);
 };
 
 }); // module: context.js
 
 require.register("hook.js", function(module, exports, require){
-
 /**
  * Module dependencies.
  */
 
 var Runnable = require('./runnable');
 
 /**
  * Expose `Hook`.
@@ -875,24 +905,24 @@ Hook.prototype.error = function(err){
   }
 
   this._error = err;
 };
 
 }); // module: hook.js
 
 require.register("interfaces/bdd.js", function(module, exports, require){
-
 /**
  * Module dependencies.
  */
 
 var Suite = require('../suite')
   , Test = require('../test')
-  , utils = require('../utils');
+  , utils = require('../utils')
+  , escapeRe = require('browser/escape-string-regexp');
 
 /**
  * BDD-style interface:
  *
  *      describe('Array', function(){
  *        describe('#indexOf()', function(){
  *          it('should return -1 when not present', function(){
  *
@@ -910,52 +940,53 @@ module.exports = function(suite){
   var suites = [suite];
 
   suite.on('pre-require', function(context, file, mocha){
 
     /**
      * Execute before running tests.
      */
 
-    context.before = function(fn){
-      suites[0].beforeAll(fn);
+    context.before = function(name, fn){
+      suites[0].beforeAll(name, fn);
     };
 
     /**
      * Execute after running tests.
      */
 
-    context.after = function(fn){
-      suites[0].afterAll(fn);
+    context.after = function(name, fn){
+      suites[0].afterAll(name, fn);
     };
 
     /**
      * Execute before each test case.
      */
 
-    context.beforeEach = function(fn){
-      suites[0].beforeEach(fn);
+    context.beforeEach = function(name, fn){
+      suites[0].beforeEach(name, fn);
     };
 
     /**
      * Execute after each test case.
      */
 
-    context.afterEach = function(fn){
-      suites[0].afterEach(fn);
+    context.afterEach = function(name, fn){
+      suites[0].afterEach(name, fn);
     };
 
     /**
      * Describe a "suite" with the given `title`
      * and callback `fn` containing nested suites
      * and/or tests.
      */
 
     context.describe = context.context = function(title, fn){
       var suite = Suite.create(suites[0], title);
+      suite.file = file;
       suites.unshift(suite);
       fn.call(suite);
       suites.shift();
       return suite;
     };
 
     /**
      * Pending describe.
@@ -984,29 +1015,30 @@ module.exports = function(suite){
     /**
      * Describe a specification or test-case
      * with the given `title` and callback `fn`
      * acting as a thunk.
      */
 
     context.it = context.specify = function(title, fn){
       var suite = suites[0];
-      if (suite.pending) var fn = null;
+      if (suite.pending) fn = null;
       var test = new Test(title, fn);
+      test.file = file;
       suite.addTest(test);
       return test;
     };
 
     /**
      * Exclusive test-case.
      */
 
     context.it.only = function(title, fn){
       var test = context.it(title, fn);
-      var reString = '^' + utils.escapeRegexp(test.fullTitle()) + '$';
+      var reString = '^' + escapeRe(test.fullTitle()) + '$';
       mocha.grep(new RegExp(reString));
       return test;
     };
 
     /**
      * Pending test case.
      */
 
@@ -1016,17 +1048,16 @@ module.exports = function(suite){
       context.it(title);
     };
   });
 };
 
 }); // module: interfaces/bdd.js
 
 require.register("interfaces/exports.js", function(module, exports, require){
-
 /**
  * Module dependencies.
  */
 
 var Suite = require('../suite')
   , Test = require('../test');
 
 /**
@@ -1046,17 +1077,17 @@ var Suite = require('../suite')
  *
  */
 
 module.exports = function(suite){
   var suites = [suite];
 
   suite.on('require', visit);
 
-  function visit(obj) {
+  function visit(obj, file) {
     var suite;
     for (var key in obj) {
       if ('function' == typeof obj[key]) {
         var fn = obj[key];
         switch (key) {
           case 'before':
             suites[0].beforeAll(fn);
             break;
@@ -1065,47 +1096,48 @@ module.exports = function(suite){
             break;
           case 'beforeEach':
             suites[0].beforeEach(fn);
             break;
           case 'afterEach':
             suites[0].afterEach(fn);
             break;
           default:
-            suites[0].addTest(new Test(key, fn));
+            var test = new Test(key, fn);
+            test.file = file;
+            suites[0].addTest(test);
         }
       } else {
-        var suite = Suite.create(suites[0], key);
+        suite = Suite.create(suites[0], key);
         suites.unshift(suite);
         visit(obj[key]);
         suites.shift();
       }
     }
   }
 };
 
 }); // module: interfaces/exports.js
 
 require.register("interfaces/index.js", function(module, exports, require){
-
 exports.bdd = require('./bdd');
 exports.tdd = require('./tdd');
 exports.qunit = require('./qunit');
 exports.exports = require('./exports');
 
 }); // module: interfaces/index.js
 
 require.register("interfaces/qunit.js", function(module, exports, require){
-
 /**
  * Module dependencies.
  */
 
 var Suite = require('../suite')
   , Test = require('../test')
+  , escapeRe = require('browser/escape-string-regexp')
   , utils = require('../utils');
 
 /**
  * QUnit-style interface:
  *
  *     suite('Array');
  *
  *     test('#length', function(){
@@ -1132,51 +1164,52 @@ module.exports = function(suite){
   var suites = [suite];
 
   suite.on('pre-require', function(context, file, mocha){
 
     /**
      * Execute before running tests.
      */
 
-    context.before = function(fn){
-      suites[0].beforeAll(fn);
+    context.before = function(name, fn){
+      suites[0].beforeAll(name, fn);
     };
 
     /**
      * Execute after running tests.
      */
 
-    context.after = function(fn){
-      suites[0].afterAll(fn);
+    context.after = function(name, fn){
+      suites[0].afterAll(name, fn);
     };
 
     /**
      * Execute before each test case.
      */
 
-    context.beforeEach = function(fn){
-      suites[0].beforeEach(fn);
+    context.beforeEach = function(name, fn){
+      suites[0].beforeEach(name, fn);
     };
 
     /**
      * Execute after each test case.
      */
 
-    context.afterEach = function(fn){
-      suites[0].afterEach(fn);
+    context.afterEach = function(name, fn){
+      suites[0].afterEach(name, fn);
     };
 
     /**
      * Describe a "suite" with the given `title`.
      */
 
     context.suite = function(title){
       if (suites.length > 1) suites.shift();
       var suite = Suite.create(suites[0], title);
+      suite.file = file;
       suites.unshift(suite);
       return suite;
     };
 
     /**
      * Exclusive test-case.
      */
 
@@ -1188,51 +1221,52 @@ module.exports = function(suite){
     /**
      * Describe a specification or test-case
      * with the given `title` and callback `fn`
      * acting as a thunk.
      */
 
     context.test = function(title, fn){
       var test = new Test(title, fn);
+      test.file = file;
       suites[0].addTest(test);
       return test;
     };
 
     /**
      * Exclusive test-case.
      */
 
     context.test.only = function(title, fn){
       var test = context.test(title, fn);
-      var reString = '^' + utils.escapeRegexp(test.fullTitle()) + '$';
+      var reString = '^' + escapeRe(test.fullTitle()) + '$';
       mocha.grep(new RegExp(reString));
     };
 
     /**
      * Pending test case.
      */
 
     context.test.skip = function(title){
       context.test(title);
     };
   });
 };
 
 }); // module: interfaces/qunit.js
 
 require.register("interfaces/tdd.js", function(module, exports, require){
-
 /**
  * Module dependencies.
  */
 
 var Suite = require('../suite')
   , Test = require('../test')
-  , utils = require('../utils');;
+  , escapeRe = require('browser/escape-string-regexp')
+  , utils = require('../utils');
 
 /**
  * TDD-style interface:
  *
  *      suite('Array', function(){
  *        suite('#indexOf()', function(){
  *          suiteSetup(function(){
  *
@@ -1258,52 +1292,53 @@ module.exports = function(suite){
   var suites = [suite];
 
   suite.on('pre-require', function(context, file, mocha){
 
     /**
      * Execute before each test case.
      */
 
-    context.setup = function(fn){
-      suites[0].beforeEach(fn);
+    context.setup = function(name, fn){
+      suites[0].beforeEach(name, fn);
     };
 
     /**
      * Execute after each test case.
      */
 
-    context.teardown = function(fn){
-      suites[0].afterEach(fn);
+    context.teardown = function(name, fn){
+      suites[0].afterEach(name, fn);
     };
 
     /**
      * Execute before the suite.
      */
 
-    context.suiteSetup = function(fn){
-      suites[0].beforeAll(fn);
+    context.suiteSetup = function(name, fn){
+      suites[0].beforeAll(name, fn);
     };
 
     /**
      * Execute after the suite.
      */
 
-    context.suiteTeardown = function(fn){
-      suites[0].afterAll(fn);
+    context.suiteTeardown = function(name, fn){
+      suites[0].afterAll(name, fn);
     };
 
     /**
      * Describe a "suite" with the given `title`
      * and callback `fn` containing nested suites
      * and/or tests.
      */
 
     context.suite = function(title, fn){
       var suite = Suite.create(suites[0], title);
+      suite.file = file;
       suites.unshift(suite);
       fn.call(suite);
       suites.shift();
       return suite;
     };
 
     /**
      * Pending suite.
@@ -1328,29 +1363,30 @@ module.exports = function(suite){
     /**
      * Describe a specification or test-case
      * with the given `title` and callback `fn`
      * acting as a thunk.
      */
 
     context.test = function(title, fn){
       var suite = suites[0];
-      if (suite.pending) var fn = null;
+      if (suite.pending) fn = null;
       var test = new Test(title, fn);
+      test.file = file;
       suite.addTest(test);
       return test;
     };
 
     /**
      * Exclusive test-case.
      */
 
     context.test.only = function(title, fn){
       var test = context.test(title, fn);
-      var reString = '^' + utils.escapeRegexp(test.fullTitle()) + '$';
+      var reString = '^' + escapeRe(test.fullTitle()) + '$';
       mocha.grep(new RegExp(reString));
     };
 
     /**
      * Pending test case.
      */
 
     context.test.skip = function(title){
@@ -1368,25 +1404,36 @@ require.register("mocha.js", function(mo
  * MIT Licensed
  */
 
 /**
  * Module dependencies.
  */
 
 var path = require('browser/path')
+  , escapeRe = require('browser/escape-string-regexp')
   , utils = require('./utils');
 
 /**
  * Expose `Mocha`.
  */
 
 exports = module.exports = Mocha;
 
 /**
+ * To require local UIs and reporters when running in node.
+ */
+
+if (typeof process !== 'undefined' && typeof process.cwd === 'function') {
+  var join = path.join
+    , cwd = process.cwd();
+  module.paths.push(cwd, join(cwd, 'node_modules'));
+}
+
+/**
  * Expose internals.
  */
 
 exports.utils = utils;
 exports.interfaces = require('./interfaces');
 exports.reporters = require('./reporters');
 exports.Runnable = require('./runnable');
 exports.Context = require('./context');
@@ -1408,17 +1455,17 @@ function image(name) {
 }
 
 /**
  * Setup mocha with `options`.
  *
  * Options:
  *
  *   - `ui` name "bdd", "tdd", "exports" etc
- *   - `reporter` reporter instance, defaults to `mocha.reporters.Dot`
+ *   - `reporter` reporter instance, defaults to `mocha.reporters.spec`
  *   - `globals` array of accepted globals
  *   - `timeout` timeout in milliseconds
  *   - `bail` bail on the first test failure
  *   - `slow` milliseconds to wait before considering a test slow
  *   - `ignoreLeaks` ignore global leaks
  *   - `grep` string or regexp to filter tests with
  *
  * @param {Object} options
@@ -1431,16 +1478,17 @@ function Mocha(options) {
   this.options = options;
   this.grep(options.grep);
   this.suite = new exports.Suite('', new exports.Context);
   this.ui(options.ui);
   this.bail(options.bail);
   this.reporter(options.reporter);
   if (null != options.timeout) this.timeout(options.timeout);
   this.useColors(options.useColors)
+  if (options.enableTimeouts !== null) this.enableTimeouts(options.enableTimeouts);
   if (options.slow) this.slow(options.slow);
 
   this.suite.on('pre-require', function (context) {
     exports.afterEach = context.afterEach || context.teardown;
     exports.after = context.after || context.suiteTeardown;
     exports.beforeEach = context.beforeEach || context.setup;
     exports.before = context.before || context.suiteSetup;
     exports.describe = context.describe || context.suite;
@@ -1475,27 +1523,27 @@ Mocha.prototype.bail = function(bail){
  */
 
 Mocha.prototype.addFile = function(file){
   this.files.push(file);
   return this;
 };
 
 /**
- * Set reporter to `reporter`, defaults to "dot".
+ * Set reporter to `reporter`, defaults to "spec".
  *
  * @param {String|Function} reporter name or constructor
  * @api public
  */
 
 Mocha.prototype.reporter = function(reporter){
   if ('function' == typeof reporter) {
     this._reporter = reporter;
   } else {
-    reporter = reporter || 'dot';
+    reporter = reporter || 'spec';
     var _reporter;
     try { _reporter = require('./reporters/' + reporter); } catch (err) {};
     if (!_reporter) try { _reporter = require(reporter); } catch (err) {};
     if (!_reporter && reporter === 'teamcity')
       console.warn('The Teamcity reporter was moved to a package named ' +
         'mocha-teamcity-reporter ' +
         '(https://npmjs.org/package/mocha-teamcity-reporter).');
     if (!_reporter) throw new Error('invalid reporter "' + reporter + '"');
@@ -1568,17 +1616,17 @@ Mocha.prototype._growl = function(runner
  *
  * @param {RegExp|String} re
  * @return {Mocha}
  * @api public
  */
 
 Mocha.prototype.grep = function(re){
   this.options.grep = 'string' == typeof re
-    ? new RegExp(utils.escapeRegexp(re))
+    ? new RegExp(escapeRe(re))
     : re;
   return this;
 };
 
 /**
  * Invert `.grep()` matches.
  *
  * @return {Mocha}
@@ -1692,41 +1740,67 @@ Mocha.prototype.timeout = function(timeo
  */
 
 Mocha.prototype.slow = function(slow){
   this.suite.slow(slow);
   return this;
 };
 
 /**
+ * Enable timeouts.
+ *
+ * @param {Boolean} enabled
+ * @return {Mocha}
+ * @api public
+ */
+
+Mocha.prototype.enableTimeouts = function(enabled) {
+  this.suite.enableTimeouts(arguments.length && enabled !== undefined
+    ? enabled
+    : true);
+  return this
+};
+
+/**
  * Makes all tests async (accepting a callback)
  *
  * @return {Mocha}
  * @api public
  */
 
 Mocha.prototype.asyncOnly = function(){
   this.options.asyncOnly = true;
   return this;
 };
 
 /**
+ * Disable syntax highlighting (in browser).
+ * @returns {Mocha}
+ * @api public
+ */
+Mocha.prototype.noHighlighting = function() {
+  this.options.noHighlighting = true;
+  return this;
+};
+
+/**
  * Run tests and invoke `fn()` when complete.
  *
  * @param {Function} fn
  * @return {Runner}
  * @api public
  */
 
 Mocha.prototype.run = function(fn){
   if (this.files.length) this.loadFiles();
   var suite = this.suite;
   var options = this.options;
+  options.files = this.files;
   var runner = new exports.Runner(suite);
-  var reporter = new this._reporter(runner);
+  var reporter = new this._reporter(runner, options);
   runner.ignoreLeaks = false !== options.ignoreLeaks;
   runner.asyncOnly = options.asyncOnly;
   if (options.grep) runner.grep(options.grep, options.invert);
   if (options.globals) runner.globals(options.globals);
   if (options.growl) this._growl(runner, reporter);
   exports.reporters.Base.useColors = options.useColors;
   exports.reporters.Base.inlineDiffs = options.useInlineDiffs;
   return runner.run(fn);
@@ -1756,17 +1830,17 @@ var y = d * 365.25;
  * @param {Object} options
  * @return {String|Number}
  * @api public
  */
 
 module.exports = function(val, options){
   options = options || {};
   if ('string' == typeof val) return parse(val);
-  return options.long ? longFormat(val) : shortFormat(val);
+  return options['long'] ? longFormat(val) : shortFormat(val);
 };
 
 /**
  * Parse the given `str` and return milliseconds.
  *
  * @param {String} str
  * @return {Number}
  * @api private
@@ -1843,17 +1917,16 @@ function plural(ms, n, name) {
   if (ms < n) return;
   if (ms < n * 1.5) return Math.floor(ms / n) + ' ' + name;
   return Math.ceil(ms / n) + ' ' + name + 's';
 }
 
 }); // module: ms.js
 
 require.register("reporters/base.js", function(module, exports, require){
-
 /**
  * Module dependencies.
  */
 
 var tty = require('browser/tty')
   , diff = require('browser/diff')
   , ms = require('../ms')
   , utils = require('../utils');
@@ -2025,22 +2098,22 @@ exports.list = function(failures){
     // uncaught
     if (err.uncaught) {
       msg = 'Uncaught ' + msg;
     }
 
     // explicitly show diff
     if (err.showDiff && sameType(actual, expected)) {
       escape = false;
-      err.actual = actual = stringify(canonicalize(actual));
-      err.expected = expected = stringify(canonicalize(expected));
+      err.actual = actual = utils.stringify(actual);
+      err.expected = expected = utils.stringify(expected);
     }
 
     // actual / expected diff
-    if ('string' == typeof actual && 'string' == typeof expected) {
+    if (err.showDiff && 'string' == typeof actual && 'string' == typeof expected) {
       fmt = color('error title', '  %s) %s:\n%s') + color('error stack', '\n%s\n');
       var match = message.match(/^([^:]+): expected/);
       msg = '\n      ' + color('error message', match ? match[1] : msg);
 
       if (exports.inlineDiffs) {
         msg += inlineDiff(err, escape);
       } else {
         msg += unifiedDiff(err, escape);
@@ -2289,82 +2362,33 @@ function escapeInvisibles(line) {
 
 function colorLines(name, str) {
   return str.split('\n').map(function(str){
     return color(name, str);
   }).join('\n');
 }
 
 /**
- * Stringify `obj`.
- *
- * @param {Object} obj
- * @return {String}
- * @api private
- */
-
-function stringify(obj) {
-  if (obj instanceof RegExp) return obj.toString();
-  return JSON.stringify(obj, null, 2);
-}
-
-/**
- * Return a new object that has the keys in sorted order.
- * @param {Object} obj
- * @return {Object}
- * @api private
- */
-
- function canonicalize(obj, stack) {
-   stack = stack || [];
-
-   if (utils.indexOf(stack, obj) !== -1) return obj;
-
-   var canonicalizedObj;
-
-   if ('[object Array]' == {}.toString.call(obj)) {
-     stack.push(obj);
-     canonicalizedObj = utils.map(obj, function(item) {
-       return canonicalize(item, stack);
-     });
-     stack.pop();
-   } else if (typeof obj === 'object' && obj !== null) {
-     stack.push(obj);
-     canonicalizedObj = {};
-     utils.forEach(utils.keys(obj).sort(), function(key) {
-       canonicalizedObj[key] = canonicalize(obj[key], stack);
-     });
-     stack.pop();
-   } else {
-     canonicalizedObj = obj;
-   }
-
-   return canonicalizedObj;
- }
-
-/**
  * Check that a / b have the same type.
  *
  * @param {Object} a
  * @param {Object} b
  * @return {Boolean}
  * @api private
  */
 
 function sameType(a, b) {
   a = Object.prototype.toString.call(a);
   b = Object.prototype.toString.call(b);
   return a == b;
 }
 
-
 }); // module: reporters/base.js
 
 require.register("reporters/doc.js", function(module, exports, require){
-
 /**
  * Module dependencies.
  */
 
 var Base = require('./base')
   , utils = require('../utils');
 
 /**
@@ -2409,22 +2433,28 @@ function Doc(runner) {
     --indents;
   });
 
   runner.on('pass', function(test){
     console.log('%s  <dt>%s</dt>', indent(), utils.escape(test.title));
     var code = utils.escape(utils.clean(test.fn.toString()));
     console.log('%s  <dd><pre><code>%s</code></pre></dd>', indent(), code);
   });
+
+  runner.on('fail', function(test, err){
+    console.log('%s  <dt class="error">%s</dt>', indent(), utils.escape(test.title));
+    var code = utils.escape(utils.clean(test.fn.toString()));
+    console.log('%s  <dd class="error"><pre><code>%s</code></pre></dd>', indent(), code);
+    console.log('%s  <dd class="error">%s</dd>', indent(), utils.escape(err));
+  });
 }
 
 }); // module: reporters/doc.js
 
 require.register("reporters/dot.js", function(module, exports, require){
-
 /**
  * Module dependencies.
  */
 
 var Base = require('./base')
   , color = Base.color;
 
 /**
@@ -2441,23 +2471,24 @@ exports = module.exports = Dot;
  */
 
 function Dot(runner) {
   Base.call(this, runner);
 
   var self = this
     , stats = this.stats
     , width = Base.window.width * .75 | 0
-    , n = 0;
+    , n = -1;
 
   runner.on('start', function(){
     process.stdout.write('\n  ');
   });
 
   runner.on('pending', function(test){
+    if (++n % width == 0) process.stdout.write('\n  ');
     process.stdout.write(color('pending', Base.symbols.dot));
   });
 
   runner.on('pass', function(test){
     if (++n % width == 0) process.stdout.write('\n  ');
     if ('slow' == test.speed) {
       process.stdout.write(color('bright yellow', Base.symbols.dot));
     } else {
@@ -2480,20 +2511,20 @@ function Dot(runner) {
  * Inherit from `Base.prototype`.
  */
 
 function F(){};
 F.prototype = Base.prototype;
 Dot.prototype = new F;
 Dot.prototype.constructor = Dot;
 
+
 }); // module: reporters/dot.js
 
 require.register("reporters/html-cov.js", function(module, exports, require){
-
 /**
  * Module dependencies.
  */
 
 var JSONCov = require('./json-cov')
   , fs = require('browser/fs');
 
 /**
@@ -2534,20 +2565,20 @@ function HTMLCov(runner) {
  */
 
 function coverageClass(n) {
   if (n >= 75) return 'high';
   if (n >= 50) return 'medium';
   if (n >= 25) return 'low';
   return 'terrible';
 }
+
 }); // module: reporters/html-cov.js
 
 require.register("reporters/html.js", function(module, exports, require){
-
 /**
  * Module dependencies.
  */
 
 var Base = require('./base')
   , utils = require('../utils')
   , Progress = require('../browser/progress')
   , escape = utils.escape;
@@ -2581,17 +2612,17 @@ var statsTemplate = '<ul id="mocha-stats
 
 /**
  * Initialize a new `HTML` reporter.
  *
  * @param {Runner} runner
  * @api public
  */
 
-function HTML(runner, root) {
+function HTML(runner) {
   Base.call(this, runner);
 
   var self = this
     , stats = this.stats
     , total = runner.total
     , stat = fragment(statsTemplate)
     , items = stat.getElementsByTagName('li')
     , passes = items[1].getElementsByTagName('em')[0]
@@ -2599,18 +2630,17 @@ function HTML(runner, root) {
     , failures = items[2].getElementsByTagName('em')[0]
     , failuresLink = items[2].getElementsByTagName('a')[0]
     , duration = items[3].getElementsByTagName('em')[0]
     , canvas = stat.getElementsByTagName('canvas')[0]
     , report = fragment('<ul id="mocha-report"></ul>')
     , stack = [report]
     , progress
     , ctx
-
-  root = root || document.getElementById('mocha');
+    , root = document.getElementById('mocha');
 
   if (canvas.getContext) {
     var ratio = window.devicePixelRatio || 1;
     canvas.style.width = canvas.width;
     canvas.style.height = canvas.height;
     canvas.width *= ratio;
     canvas.height *= ratio;
     ctx = canvas.getContext('2d');
@@ -2718,33 +2748,42 @@ function HTML(runner, root) {
     }
 
     // Don't call .appendChild if #mocha-report was already .shift()'ed off the stack.
     if (stack[0]) stack[0].appendChild(el);
   });
 }
 
 /**
+ * Makes a URL, preserving querystring ("search") parameters.
+ * @param {string} s
+ * @returns {string} your new URL
+ */
+var makeUrl = function makeUrl(s) {
+  var search = window.location.search;
+  return (search ? search + '&' : '?' ) + 'grep=' + encodeURIComponent(s);
+};
+
+/**
  * Provide suite URL
  *
  * @param {Object} [suite]
  */
-
 HTML.prototype.suiteURL = function(suite){
-  return '?grep=' + encodeURIComponent(suite.fullTitle());
+  return makeUrl(suite.fullTitle());
 };
 
 /**
  * Provide test URL
  *
  * @param {Object} [test]
  */
 
 HTML.prototype.testURL = function(test){
-  return '?grep=' + encodeURIComponent(test.fullTitle());
+  return makeUrl(test.fullTitle());
 };
 
 /**
  * Display error `msg`.
  */
 
 function error(msg) {
   document.body.appendChild(fragment('<div id="mocha-error">%s</div>', msg));
@@ -2815,17 +2854,16 @@ function on(el, event, fn) {
   } else {
     el.attachEvent('on' + event, fn);
   }
 }
 
 }); // module: reporters/html.js
 
 require.register("reporters/index.js", function(module, exports, require){
-
 exports.Base = require('./base');
 exports.Dot = require('./dot');
 exports.Doc = require('./doc');
 exports.TAP = require('./tap');
 exports.JSON = require('./json');
 exports.HTML = require('./html');
 exports.List = require('./list');
 exports.Min = require('./min');
@@ -2837,17 +2875,16 @@ exports.Progress = require('./progress')
 exports.Landing = require('./landing');
 exports.JSONCov = require('./json-cov');
 exports.HTMLCov = require('./html-cov');
 exports.JSONStream = require('./json-stream');
 
 }); // module: reporters/index.js
 
 require.register("reporters/json-cov.js", function(module, exports, require){
-
 /**
  * Module dependencies.
  */
 
 var Base = require('./base');
 
 /**
  * Expose `JSONCov`.
@@ -2928,17 +2965,17 @@ function map(cov) {
     return a.filename.localeCompare(b.filename);
   });
 
   if (ret.sloc > 0) {
     ret.coverage = (ret.hits / ret.sloc) * 100;
   }
 
   return ret;
-};
+}
 
 /**
  * Map jscoverage data for a single source file
  * to a JSON structure suitable for reporting.
  *
  * @param {String} filename name of the source file
  * @param {Object} data jscoverage coverage data
  * @return {Object}
@@ -2994,17 +3031,16 @@ function clean(test) {
     , fullTitle: test.fullTitle()
     , duration: test.duration
   }
 }
 
 }); // module: reporters/json-cov.js
 
 require.register("reporters/json-stream.js", function(module, exports, require){
-
 /**
  * Module dependencies.
  */
 
 var Base = require('./base')
   , color = Base.color;
 
 /**
@@ -3031,17 +3067,19 @@ function List(runner) {
     console.log(JSON.stringify(['start', { total: total }]));
   });
 
   runner.on('pass', function(test){
     console.log(JSON.stringify(['pass', clean(test)]));
   });
 
   runner.on('fail', function(test, err){
-    console.log(JSON.stringify(['fail', clean(test)]));
+    test = clean(test);
+    test.err = err.message;
+    console.log(JSON.stringify(['fail', test]));
   });
 
   runner.on('end', function(){
     process.stdout.write(JSON.stringify(['end', self.stats]));
   });
 }
 
 /**
@@ -3055,20 +3093,20 @@ function List(runner) {
 
 function clean(test) {
   return {
       title: test.title
     , fullTitle: test.fullTitle()
     , duration: test.duration
   }
 }
+
 }); // module: reporters/json-stream.js
 
 require.register("reporters/json.js", function(module, exports, require){
-
 /**
  * Module dependencies.
  */
 
 var Base = require('./base')
   , cursor = Base.cursor
   , color = Base.color;
 
@@ -3085,63 +3123,86 @@ exports = module.exports = JSONReporter;
  * @api public
  */
 
 function JSONReporter(runner) {
   var self = this;
   Base.call(this, runner);
 
   var tests = []
+    , pending = []
     , failures = []
     , passes = [];
 
   runner.on('test end', function(test){
     tests.push(test);
   });
 
   runner.on('pass', function(test){
     passes.push(test);
   });
 
   runner.on('fail', function(test){
     failures.push(test);
   });
 
+  runner.on('pending', function(test){
+    pending.push(test);
+  });
+
   runner.on('end', function(){
     var obj = {
-        stats: self.stats
-      , tests: tests.map(clean)
-      , failures: failures.map(clean)
-      , passes: passes.map(clean)
+      stats: self.stats,
+      tests: tests.map(clean),
+      pending: pending.map(clean),
+      failures: failures.map(clean),
+      passes: passes.map(clean)
     };
 
+    runner.testResults = obj;
+
     process.stdout.write(JSON.stringify(obj, null, 2));
   });
 }
 
 /**
  * Return a plain-object representation of `test`
  * free of cyclic properties etc.
  *
  * @param {Object} test
  * @return {Object}
  * @api private
  */
 
 function clean(test) {
   return {
-      title: test.title
-    , fullTitle: test.fullTitle()
-    , duration: test.duration
+    title: test.title,
+    fullTitle: test.fullTitle(),
+    duration: test.duration,
+    err: errorJSON(test.err || {})
   }
 }
+
+/**
+ * Transform `error` into a JSON object.
+ * @param {Error} err
+ * @return {Object}
+ */
+
+function errorJSON(err) {
+  var res = {};
+  Object.getOwnPropertyNames(err).forEach(function(key) {
+    res[key] = err[key];
+  }, err);
+  return res;
+}
+
 }); // module: reporters/json.js
 
 require.register("reporters/landing.js", function(module, exports, require){
-
 /**
  * Module dependencies.
  */
 
 var Base = require('./base')
   , cursor = Base.cursor
   , color = Base.color;
 
@@ -3189,34 +3250,34 @@ function Landing(runner) {
     , n = 0;
 
   function runway() {
     var buf = Array(width).join('-');
     return '  ' + color('runway', buf);
   }
 
   runner.on('start', function(){
-    stream.write('\n  ');
+    stream.write('\n\n\n  ');
     cursor.hide();
   });
 
   runner.on('test end', function(test){
     // check if the plane crashed
     var col = -1 == crashed
       ? width * ++n / total | 0
       : crashed;
 
     // show the crash
     if ('failed' == test.state) {
       plane = color('plane crash', '✈');
       crashed = col;
     }
 
     // render landing strip
-    stream.write('\u001b[4F\n\n');
+    stream.write('\u001b['+(width+1)+'D\u001b[2A');
     stream.write(runway());
     stream.write('\n  ');
     stream.write(color('runway', Array(col).join('⋅')));
     stream.write(plane)
     stream.write(color('runway', Array(width - col).join('⋅') + '\n'));
     stream.write(runway());
     stream.write('\u001b[0m');
   });
@@ -3232,20 +3293,20 @@ function Landing(runner) {
  * Inherit from `Base.prototype`.
  */
 
 function F(){};
 F.prototype = Base.prototype;
 Landing.prototype = new F;
 Landing.prototype.constructor = Landing;
 
+
 }); // module: reporters/landing.js
 
 require.register("reporters/list.js", function(module, exports, require){
-
 /**
  * Module dependencies.
  */
 
 var Base = require('./base')
   , cursor = Base.cursor
   , color = Base.color;
 
@@ -3398,20 +3459,20 @@ function Markdown(runner) {
   });
 
   runner.on('end', function(){
     process.stdout.write('# TOC\n');
     process.stdout.write(generateTOC(runner.suite));
     process.stdout.write(buf);
   });
 }
+
 }); // module: reporters/markdown.js
 
 require.register("reporters/min.js", function(module, exports, require){
-
 /**
  * Module dependencies.
  */
 
 var Base = require('./base');
 
 /**
  * Expose `Min`.
@@ -3634,17 +3695,17 @@ NyanCat.prototype.face = function() {
     return '( x .x)';
   } else if (stats.pending) {
     return '( o .o)';
   } else if(stats.passes) {
     return '( ^ .^)';
   } else {
     return '( - .-)';
   }
-}
+};
 
 /**
  * Move cursor up `n`.
  *
  * @param {Number} n
  * @api private
  */
 
@@ -3715,17 +3776,16 @@ function F(){};
 F.prototype = Base.prototype;
 NyanCat.prototype = new F;
 NyanCat.prototype.constructor = NyanCat;
 
 
 }); // module: reporters/nyan.js
 
 require.register("reporters/progress.js", function(module, exports, require){
-
 /**
  * Module dependencies.
  */
 
 var Base = require('./base')
   , cursor = Base.cursor
   , color = Base.color;
 
@@ -3753,17 +3813,18 @@ function Progress(runner, options) {
   Base.call(this, runner);
 
   var self = this
     , options = options || {}
     , stats = this.stats
     , width = Base.window.width * .50 | 0
     , total = runner.total
     , complete = 0
-    , max = Math.max;
+    , max = Math.max
+    , lastN = -1;
 
   // default chars
   options.open = options.open || '[';
   options.complete = options.complete || '▬';
   options.incomplete = options.incomplete || Base.symbols.dot;
   options.close = options.close || ']';
   options.verbose = false;
 
@@ -3776,16 +3837,22 @@ function Progress(runner, options) {
   // tests complete
   runner.on('test end', function(){
     complete++;
     var incomplete = total - complete
       , percent = complete / total
       , n = width * percent | 0
       , i = width - n;
 
+    if (lastN === n && !options.verbose) {
+      // Don't re-render the line if it hasn't changed
+      return;
+    }
+    lastN = n;
+
     cursor.CR();
     process.stdout.write('\u001b[J');
     process.stdout.write(color('progress', '  ' + options.open));
     process.stdout.write(Array(n).join(options.complete));
     process.stdout.write(Array(i).join(options.incomplete));
     process.stdout.write(color('progress', options.close));
     if (options.verbose) {
       process.stdout.write(color('progress', ' ' + complete + ' of ' + total));
@@ -3809,17 +3876,16 @@ function F(){};
 F.prototype = Base.prototype;
 Progress.prototype = new F;
 Progress.prototype.constructor = Progress;
 
 
 }); // module: reporters/progress.js
 
 require.register("reporters/spec.js", function(module, exports, require){
-
 /**
  * Module dependencies.
  */
 
 var Base = require('./base')
   , cursor = Base.cursor
   , color = Base.color;
 
@@ -3900,17 +3966,16 @@ function F(){};
 F.prototype = Base.prototype;
 Spec.prototype = new F;
 Spec.prototype.constructor = Spec;
 
 
 }); // module: reporters/spec.js
 
 require.register("reporters/tap.js", function(module, exports, require){
-
 /**
  * Module dependencies.
  */
 
 var Base = require('./base')
   , cursor = Base.cursor
   , color = Base.color;
 
@@ -3977,17 +4042,16 @@ function TAP(runner) {
 
 function title(test) {
   return test.fullTitle().replace(/#/g, '');
 }
 
 }); // module: reporters/tap.js
 
 require.register("reporters/xunit.js", function(module, exports, require){
-
 /**
  * Module dependencies.
  */
 
 var Base = require('./base')
   , utils = require('../utils')
   , escape = utils.escape;
 
@@ -4066,18 +4130,17 @@ function test(test) {
   var attrs = {
       classname: test.parent.fullTitle()
     , name: test.title
     , time: (test.duration / 1000) || 0
   };
 
   if ('failed' == test.state) {
     var err = test.err;
-    attrs.message = escape(err.message);
-    console.log(tag('testcase', attrs, false, tag('failure', attrs, false, cdata(err.stack))));
+    console.log(tag('testcase', attrs, false, tag('failure', {}, false, cdata(escape(err.message) + "\n" + err.stack))));
   } else if (test.pending) {
     console.log(tag('testcase', attrs, false, tag('skipped', {}, true)));
   } else {
     console.log(tag('testcase', attrs, true) );
   }
 }
 
 /**
@@ -4104,17 +4167,16 @@ function tag(name, attrs, close, content
 
 function cdata(str) {
   return '<![CDATA[' + escape(str) + ']]>';
 }
 
 }); // module: reporters/xunit.js
 
 require.register("runnable.js", function(module, exports, require){
-
 /**
  * Module dependencies.
  */
 
 var EventEmitter = require('browser/events').EventEmitter
   , debug = require('browser/debug')('mocha:runnable')
   , milliseconds = require('./ms');
 
@@ -4150,17 +4212,19 @@ module.exports = Runnable;
 
 function Runnable(title, fn) {
   this.title = title;
   this.fn = fn;
   this.async = fn && fn.length;
   this.sync = ! this.async;
   this._timeout = 2000;
   this._slow = 75;
+  this._enableTimeouts = true;
   this.timedOut = false;
+  this._trace = new Error('done() called multiple times')
 }
 
 /**
  * Inherit from `EventEmitter.prototype`.
  */
 
 function F(){};
 F.prototype = EventEmitter.prototype;
@@ -4173,16 +4237,17 @@ Runnable.prototype.constructor = Runnabl
  *
  * @param {Number|String} ms
  * @return {Runnable|Number} ms or self
  * @api private
  */
 
 Runnable.prototype.timeout = function(ms){
   if (0 == arguments.length) return this._timeout;
+  if (ms === 0) this._enableTimeouts = false;
   if ('string' == typeof ms) ms = milliseconds(ms);
   debug('timeout %d', ms);
   this._timeout = ms;
   if (this.timer) this.resetTimeout();
   return this;
 };
 
 /**
@@ -4197,16 +4262,31 @@ Runnable.prototype.slow = function(ms){
   if (0 === arguments.length) return this._slow;
   if ('string' == typeof ms) ms = milliseconds(ms);
   debug('timeout %d', ms);
   this._slow = ms;
   return this;
 };
 
 /**
+ * Set and & get timeout `enabled`.
+ *
+ * @param {Boolean} enabled
+ * @return {Runnable|Boolean} enabled or self
+ * @api private
+ */
+
+Runnable.prototype.enableTimeouts = function(enabled){
+  if (arguments.length === 0) return this._enableTimeouts;
+  debug('enableTimeouts %s', enabled);
+  this._enableTimeouts = enabled;
+  return this;
+};
+
+/**
  * Return the full title generated by recursively
  * concatenating the parent's full title.
  *
  * @return {String}
  * @api public
  */
 
 Runnable.prototype.fullTitle = function(){
@@ -4244,18 +4324,20 @@ Runnable.prototype.inspect = function(){
  *
  * @api private
  */
 
 Runnable.prototype.resetTimeout = function(){
   var self = this;
   var ms = this.timeout() || 1e9;
 
+  if (!this._enableTimeouts) return;
   this.clearTimeout();
   this.timer = setTimeout(function(){
+    if (!self._enableTimeouts) return;
     self.callback(new Error('timeout of ' + ms + 'ms exceeded'));
     self.timedOut = true;
   }, ms);
 };
 
 /**
  * Whitelist these globals for this test run
  *
@@ -4270,79 +4352,97 @@ Runnable.prototype.globals = function(ar
  * Run the test and invoke `fn(err)`.
  *
  * @param {Function} fn
  * @api private
  */
 
 Runnable.prototype.run = function(fn){
   var self = this
-    , ms = this.timeout()
     , start = new Date
     , ctx = this.ctx
     , finished
     , emitted;
 
-  if (ctx) ctx.runnable(this);
-
-  // timeout
-  if (this.async) {
-    if (ms) {
-      this.timer = setTimeout(function(){
-        done(new Error('timeout of ' + ms + 'ms exceeded'));
-        self.timedOut = true;
-      }, ms);
-    }
-  }
+  // Some times the ctx exists but it is not runnable
+  if (ctx && ctx.runnable) ctx.runnable(this);
 
   // called multiple times
   function multiple(err) {
     if (emitted) return;
     emitted = true;
-    self.emit('error', err || new Error('done() called multiple times'));
+    self.emit('error', err || new Error('done() called multiple times; stacktrace may be inaccurate'));
   }
 
   // finished
   function done(err) {
+    var ms = self.timeout();
     if (self.timedOut) return;
-    if (finished) return multiple(err);
+    if (finished) return multiple(err || self._trace);
     self.clearTimeout();
     self.duration = new Date - start;
     finished = true;
+    if (!err && self.duration > ms && self._enableTimeouts) err = new Error('timeout of ' + ms + 'ms exceeded');
     fn(err);
   }
 
   // for .resetTimeout()
   this.callback = done;
 
-  // async
+  // explicit async with `done` argument
   if (this.async) {
+    this.resetTimeout();
+
     try {
       this.fn.call(ctx, function(err){
         if (err instanceof Error || toString.call(err) === "[object Error]") return done(err);
-        if (null != err) return done(new Error('done() invoked with non-Error: ' + err));
+        if (null != err) {
+          if (Object.prototype.toString.call(err) === '[object Object]') {
+            return done(new Error('done() invoked with non-Error: ' + JSON.stringify(err)));
+          } else {
+            return done(new Error('done() invoked with non-Error: ' + err));
+          }
+        }
         done();
       });
     } catch (err) {
       done(err);
     }
     return;
   }
 
   if (this.asyncOnly) {
     return done(new Error('--async-only option in use without declaring `done()`'));
   }
 
-  // sync
+  // sync or promise-returning
   try {
-    if (!this.pending) this.fn.call(ctx);
-    this.duration = new Date - start;
-    fn();
+    if (this.pending) {
+      done();
+    } else {
+      callFn(this.fn);
+    }
   } catch (err) {
-    fn(err);
+    done(err);
+  }
+
+  function callFn(fn) {
+    var result = fn.call(ctx);
+    if (result && typeof result.then === 'function') {
+      self.resetTimeout();
+      result
+        .then(function() {
+          done()
+        },
+        function(reason) {
+          done(reason || new Error('Promise rejected with no or falsy reason'))
+        });
+    } else {
+      done();
+    }
   }
 };
 
 }); // module: runnable.js
 
 require.register("runner.js", function(module, exports, require){
 /**
  * Module dependencies.
@@ -4506,17 +4606,16 @@ Runner.prototype.globals = function(arr)
  * @api private
  */
 
 Runner.prototype.checkGlobals = function(test){
   if (this.ignoreLeaks) return;
   var ok = this._globals;
 
   var globals = this.globalProps();
-  var isNode = process.kill;
   var leaks;
 
   if (test) {
     ok = ok.concat(test._allowedGlobals || []);
   }
 
   if(this.prevGlobalsLength == globals.length) return;
   this.prevGlobalsLength = globals.length;
@@ -4878,22 +4977,35 @@ Runner.prototype.runSuite = function(sui
 /**
  * Handle uncaught exceptions.
  *
  * @param {Error} err
  * @api private
  */
 
 Runner.prototype.uncaught = function(err){
-  debug('uncaught exception %s', err.message);
+  if (err) {
+    debug('uncaught exception %s', err !== function () {
+      return this;
+    }.call(err) ? err : ( err.message || err ));
+  } else {
+    debug('uncaught undefined exception');
+    err = new Error('Caught undefined error, did you throw without specifying what?');
+  }
+  err.uncaught = true;
+
   var runnable = this.currentRunnable;
-  if (!runnable || 'failed' == runnable.state) return;
+  if (!runnable) return;
+
+  var wasAlreadyDone = runnable.state;
+  this.fail(runnable, err);
+
   runnable.clearTimeout();
-  err.uncaught = true;
-  this.fail(runnable, err);
+
+  if (wasAlreadyDone) return;
 
   // recover from test
   if ('test' == runnable.type) {
     this.emit('test end', runnable);
     this.hookUp('afterEach', this.next);
     return;
   }
 
@@ -4944,17 +5056,17 @@ Runner.prototype.run = function(fn){
  * Cleanly abort execution
  *
  * @return {Runner} for chaining
  * @api public
  */
 Runner.prototype.abort = function(){
   debug('aborting');
   this._abort = true;
-}
+};
 
 /**
  * Filter leaks with the given globals flagged as `ok`.
  *
  * @param {Array} ok
  * @param {Array} globals
  * @return {Array}
  * @api private
@@ -5008,17 +5120,16 @@ function filterLeaks(ok, globals) {
   }
 
   return [];
  }
 
 }); // module: runner.js
 
 require.register("suite.js", function(module, exports, require){
-
 /**
  * Module dependencies.
  */
 
 var EventEmitter = require('browser/events').EventEmitter
   , debug = require('browser/debug')('mocha:suite')
   , milliseconds = require('./ms')
   , utils = require('./utils')
@@ -5056,28 +5167,31 @@ exports.create = function(parent, title)
  * Initialize a new `Suite` with the given
  * `title` and `ctx`.
  *
  * @param {String} title
  * @param {Context} ctx
  * @api private
  */
 
-function Suite(title, ctx) {
+function Suite(title, parentContext) {
   this.title = title;
-  this.ctx = ctx;
+  var context = function() {};
+  context.prototype = parentContext;
+  this.ctx = new context();
   this.suites = [];
   this.tests = [];
   this.pending = false;
   this._beforeEach = [];
   this._beforeAll = [];
   this._afterEach = [];
   this._afterAll = [];
   this.root = !title;
   this._timeout = 2000;
+  this._enableTimeouts = true;
   this._slow = 75;
   this._bail = false;
 }
 
 /**
  * Inherit from `EventEmitter.prototype`.
  */
 
@@ -5094,38 +5208,55 @@ Suite.prototype.constructor = Suite;
  * @api private
  */
 
 Suite.prototype.clone = function(){
   var suite = new Suite(this.title);
   debug('clone');
   suite.ctx = this.ctx;
   suite.timeout(this.timeout());
+  suite.enableTimeouts(this.enableTimeouts());
   suite.slow(this.slow());
   suite.bail(this.bail());
   return suite;
 };
 
 /**
  * Set timeout `ms` or short-hand such as "2s".
  *
  * @param {Number|String} ms
  * @return {Suite|Number} for chaining
  * @api private
  */
 
 Suite.prototype.timeout = function(ms){
   if (0 == arguments.length) return this._timeout;
+  if (ms === 0) this._enableTimeouts = false;
   if ('string' == typeof ms) ms = milliseconds(ms);
   debug('timeout %d', ms);
   this._timeout = parseInt(ms, 10);
   return this;
 };
 
 /**
+  * Set timeout `enabled`.
+  *
+  * @param {Boolean} enabled
+  * @return {Suite|Boolean} self or enabled
+  * @api private
+  */
+
+Suite.prototype.enableTimeouts = function(enabled){
+  if (arguments.length === 0) return this._enableTimeouts;
+  debug('enableTimeouts %s', enabled);
+  this._enableTimeouts = enabled;
+  return this;
+};
+
+/**
  * Set slow `ms` or short-hand such as "2s".
  *
  * @param {Number|String} ms
  * @return {Suite|Number} for chaining
  * @api private
  */
 
 Suite.prototype.slow = function(ms){
@@ -5154,81 +5285,109 @@ Suite.prototype.bail = function(bail){
 /**
  * Run `fn(test[, done])` before running tests.
  *
  * @param {Function} fn
  * @return {Suite} for chaining
  * @api private
  */
 
-Suite.prototype.beforeAll = function(fn){
+Suite.prototype.beforeAll = function(title, fn){
   if (this.pending) return this;
-  var hook = new Hook('"before all" hook', fn);
+  if ('function' === typeof title) {
+    fn = title;
+    title = fn.name;
+  }
+  title = '"before all" hook' + (title ? ': ' + title : '');
+
+  var hook = new Hook(title, fn);
   hook.parent = this;
   hook.timeout(this.timeout());
+  hook.enableTimeouts(this.enableTimeouts());
   hook.slow(this.slow());
   hook.ctx = this.ctx;
   this._beforeAll.push(hook);
   this.emit('beforeAll', hook);
   return this;
 };
 
 /**
  * Run `fn(test[, done])` after running tests.
  *
  * @param {Function} fn
  * @return {Suite} for chaining
  * @api private
  */
 
-Suite.prototype.afterAll = function(fn){
+Suite.prototype.afterAll = function(title, fn){
   if (this.pending) return this;
-  var hook = new Hook('"after all" hook', fn);
+  if ('function' === typeof title) {
+    fn = title;
+    title = fn.name;
+  }
+  title = '"after all" hook' + (title ? ': ' + title : '');
+
+  var hook = new Hook(title, fn);
   hook.parent = this;
   hook.timeout(this.timeout());
+  hook.enableTimeouts(this.enableTimeouts());
   hook.slow(this.slow());
   hook.ctx = this.ctx;
   this._afterAll.push(hook);
   this.emit('afterAll', hook);
   return this;
 };
 
 /**
  * Run `fn(test[, done])` before each test case.
  *
  * @param {Function} fn
  * @return {Suite} for chaining
  * @api private
  */
 
-Suite.prototype.beforeEach = function(fn){
+Suite.prototype.beforeEach = function(title, fn){
   if (this.pending) return this;
-  var hook = new Hook('"before each" hook', fn);
+  if ('function' === typeof title) {
+    fn = title;
+    title = fn.name;
+  }
+  title = '"before each" hook' + (title ? ': ' + title : '');
+
+  var hook = new Hook(title, fn);
   hook.parent = this;
   hook.timeout(this.timeout());
+  hook.enableTimeouts(this.enableTimeouts());
   hook.slow(this.slow());
   hook.ctx = this.ctx;
   this._beforeEach.push(hook);
   this.emit('beforeEach', hook);
   return this;
 };
 
 /**
  * Run `fn(test[, done])` after each test case.
  *
  * @param {Function} fn
  * @return {Suite} for chaining
  * @api private
  */
 
-Suite.prototype.afterEach = function(fn){
+Suite.prototype.afterEach = function(title, fn){
   if (this.pending) return this;
-  var hook = new Hook('"after each" hook', fn);
+  if ('function' === typeof title) {
+    fn = title;
+    title = fn.name;
+  }
+  title = '"after each" hook' + (title ? ': ' + title : '');
+
+  var hook = new Hook(title, fn);
   hook.parent = this;
   hook.timeout(this.timeout());
+  hook.enableTimeouts(this.enableTimeouts());
   hook.slow(this.slow());
   hook.ctx = this.ctx;
   this._afterEach.push(hook);
   this.emit('afterEach', hook);
   return this;
 };
 
 /**
@@ -5237,16 +5396,17 @@ Suite.prototype.afterEach = function(fn)
  * @param {Suite} suite
  * @return {Suite} for chaining
  * @api private
  */
 
 Suite.prototype.addSuite = function(suite){
   suite.parent = this;
   suite.timeout(this.timeout());
+  suite.enableTimeouts(this.enableTimeouts());
   suite.slow(this.slow());
   suite.bail(this.bail());
   this.suites.push(suite);
   this.emit('suite', suite);
   return this;
 };
 
 /**
@@ -5255,16 +5415,17 @@ Suite.prototype.addSuite = function(suit
  * @param {Test} test
  * @return {Suite} for chaining
  * @api private
  */
 
 Suite.prototype.addTest = function(test){
   test.parent = this;
   test.timeout(this.timeout());
+  test.enableTimeouts(this.enableTimeouts());
   test.slow(this.slow());
   test.ctx = this.ctx;
   this.tests.push(test);
   this.emit('test', test);
   return this;
 };
 
 /**
@@ -5312,17 +5473,16 @@ Suite.prototype.eachTest = function(fn){
     suite.eachTest(fn);
   });
   return this;
 };
 
 }); // module: suite.js
 
 require.register("test.js", function(module, exports, require){
-
 /**
  * Module dependencies.
  */
 
 var Runnable = require('./runnable');
 
 /**
  * Expose `Test`.
@@ -5358,16 +5518,19 @@ Test.prototype.constructor = Test;
 
 require.register("utils.js", function(module, exports, require){
 /**
  * Module dependencies.
  */
 
 var fs = require('browser/fs')
   , path = require('browser/path')
+  , basename = path.basename
+  , exists = fs.existsSync || path.existsSync
+  , glob = require('browser/glob')
   , join = path.join
   , debug = require('browser/debug')('mocha:watch');
 
 /**
  * Ignored directories.
  */
 
 var ignore = ['node_modules', '.git'];
@@ -5523,26 +5686,29 @@ function ignored(path){
 
 /**
  * Lookup files in the given `dir`.
  *
  * @return {Array}
  * @api private
  */
 
-exports.files = function(dir, ret){
+exports.files = function(dir, ext, ret){
   ret = ret || [];
+  ext = ext || ['js'];
+
+  var re = new RegExp('\\.(' + ext.join('|') + ')$');
 
   fs.readdirSync(dir)
   .filter(ignored)
   .forEach(function(path){
     path = join(dir, path);
     if (fs.statSync(path).isDirectory()) {
-      exports.files(path, ret);
-    } else if (path.match(/\.(js|coffee|litcoffee|coffee.md)$/)) {
+      exports.files(path, ext, ret);
+    } else if (path.match(re)) {
       ret.push(path);
     }
   });
 
   return ret;
 };
 
 /**
@@ -5563,41 +5729,29 @@ exports.slug = function(str){
 /**
  * Strip the function definition from `str`,
  * and re-indent for pre whitespace.
  */
 
 exports.clean = function(str) {
   str = str
     .replace(/\r\n?|[\n\u2028\u2029]/g, "\n").replace(/^\uFEFF/, '')
-    .replace(/^function *\(.*\) *{/, '')
+    .replace(/^function *\(.*\) *{|\(.*\) *=> *{?/, '')
     .replace(/\s+\}$/, '');
 
   var spaces = str.match(/^\n?( *)/)[1].length
     , tabs = str.match(/^\n?(\t*)/)[1].length
     , re = new RegExp('^\n?' + (tabs ? '\t' : ' ') + '{' + (tabs ? tabs : spaces) + '}', 'gm');
 
   str = str.replace(re, '');
 
   return exports.trim(str);
 };
 
 /**
- * Escape regular expression characters in `str`.
- *
- * @param {String} str
- * @return {String}
- * @api private
- */
-
-exports.escapeRegexp = function(str){
-  return str.replace(/[-\\^$*+?.()|[\]{}]/g, "\\$&");
-};
-
-/**
  * Trim the given `str`.
  *
  * @param {String} str
  * @return {String}
  * @api private
  */
 
 exports.trim = function(str){
@@ -5634,37 +5788,132 @@ exports.parseQuery = function(qs){
 function highlight(js) {
   return js
     .replace(/</g, '&lt;')
     .replace(/>/g, '&gt;')
     .replace(/\/\/(.*)/gm, '<span class="comment">//$1</span>')
     .replace(/('.*?')/gm, '<span class="string">$1</span>')
     .replace(/(\d+\.\d+)/gm, '<span class="number">$1</span>')
     .replace(/(\d+)/gm, '<span class="number">$1</span>')
-    .replace(/\bnew *(\w+)/gm, '<span class="keyword">new</span> <span class="init">$1</span>')
+    .replace(/\bnew[ \t]+(\w+)/gm, '<span class="keyword">new</span> <span class="init">$1</span>')
     .replace(/\b(function|new|throw|return|var|if|else)\b/gm, '<span class="keyword">$1</span>')
 }
 
 /**
  * Highlight the contents of tag `name`.
  *
  * @param {String} name
  * @api private
  */
 
 exports.highlightTags = function(name) {
-  var code = document.getElementsByTagName(name);
+  var code = document.getElementById('mocha').getElementsByTagName(name);
   for (var i = 0, len = code.length; i < len; ++i) {
     code[i].innerHTML = highlight(code[i].innerHTML);
   }
 };
 
+
+/**
+ * Stringify `obj`.
+ *
+ * @param {Object} obj
+ * @return {String}
+ * @api private
+ */
+
+exports.stringify = function(obj) {
+  if (obj instanceof RegExp) return obj.toString();
+  return JSON.stringify(exports.canonicalize(obj), null, 2).replace(/,(\n|$)/g, '$1');
+};
+
+/**
+ * Return a new object that has the keys in sorted order.
+ * @param {Object} obj
+ * @param {Array} [stack]
+ * @return {Object}
+ * @api private
+ */
+
+exports.canonicalize = function(obj, stack) {
+  stack = stack || [];
+
+  if (exports.indexOf(stack, obj) !== -1) return '[Circular]';
+
+  var canonicalizedObj;
+
+  if ({}.toString.call(obj) === '[object Array]') {
+    stack.push(obj);
+    canonicalizedObj = exports.map(obj, function (item) {
+      return exports.canonicalize(item, stack);
+    });
+    stack.pop();
+  } else if (typeof obj === 'object' && obj !== null) {
+    stack.push(obj);
+    canonicalizedObj = {};
+    exports.forEach(exports.keys(obj).sort(), function (key) {
+      canonicalizedObj[key] = exports.canonicalize(obj[key], stack);
+    });
+    stack.pop();
+  } else {
+    canonicalizedObj = obj;
+  }
+
+  return canonicalizedObj;
+ };
+
+/**
+ * Lookup file names at the given `path`.
+ */
+exports.lookupFiles = function lookupFiles(path, extensions, recursive) {
+  var files = [];
+  var re = new RegExp('\\.(' + extensions.join('|') + ')$');
+
+  if (!exists(path)) {
+    if (exists(path + '.js')) {
+      path += '.js';
+    } else {
+      files = glob.sync(path);
+      if (!files.length) throw new Error("cannot resolve path (or pattern) '" + path + "'");
+      return files;
+    }
+  }
+
+  try {
+    var stat = fs.statSync(path);
+    if (stat.isFile()) return path;
+  }
+  catch (ignored) {
+    return;
+  }
+
+  fs.readdirSync(path).forEach(function(file){
+    file = join(path, file);
+    try {
+      var stat = fs.statSync(file);
+      if (stat.isDirectory()) {
+        if (recursive) {
+          files = files.concat(lookupFiles(file, extensions, recursive));
+        }
+        return;
+      }
+    }
+    catch (ignored) {
+      return;
+    }
+    if (!stat.isFile() || !re.test(file) || basename(file)[0] === '.') return;
+    files.push(file);
+  });
+
+  return files;
+};
+
 }); // module: utils.js
 // The global object is "self" in Web Workers.
-global = (function() { return this; })();
+var global = (function() { return this; })();
 
 /**
  * Save timer references to avoid Sinon interfering (see GH-237).
  */
 
 var Date = global.Date;
 var setTimeout = global.setTimeout;
 var setInterval = global.setInterval;
@@ -5681,23 +5930,30 @@ var clearInterval = global.clearInterval
  */
 
 var process = {};
 process.exit = function(status){};
 process.stdout = {};
 
 var uncaughtExceptionHandlers = [];
 
+var originalOnerrorHandler = global.onerror;
+
 /**
  * Remove uncaughtException listener.
+ * Revert to original onerror handler if previously defined.
  */
 
 process.removeListener = function(e, fn){
   if ('uncaughtException' == e) {
-    global.onerror = function() {};
+    if (originalOnerrorHandler) {
+      global.onerror = originalOnerrorHandler;
+    } else {
+      global.onerror = function() {};
+    }
     var i = Mocha.utils.indexOf(uncaughtExceptionHandlers, fn);
     if (i != -1) { uncaughtExceptionHandlers.splice(i, 1); }
   }
 };
 
 /**
  * Implements uncaughtException listener.
  */
@@ -5790,23 +6046,24 @@ mocha.setup = function(opts){
 mocha.run = function(fn){
   var options = mocha.options;
   mocha.globals('location');
 
   var query = Mocha.utils.parseQuery(global.location.search || '');
   if (query.grep) mocha.grep(query.grep);
   if (query.invert) mocha.invert();
 
-  return Mocha.prototype.run.call(mocha, function(){
+  return Mocha.prototype.run.call(mocha, function(err){
     // The DOM Document is not available in Web Workers.
-    if (global.document) {
+    var document = global.document;
+    if (document && document.getElementById('mocha') && options.noHighlighting !== true) {
       Mocha.utils.highlightTags('code');
     }
-    if (fn) fn();
+    if (fn) fn(err);
   });
 };
 
 /**
  * Expose the process shim.
  */
 
 Mocha.process = process;
-})();
\ No newline at end of file
+})();
--- a/browser/components/loop/test/standalone/index.html
+++ b/browser/components/loop/test/standalone/index.html
@@ -1,33 +1,33 @@
 <!DOCTYPE html>
 <!-- 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/.  -->
 <html>
 <head>
   <meta charset="utf-8">
   <title>Loop mocha tests</title>
-  <link rel="stylesheet" media="all" href="../shared/vendor/mocha-1.17.1.css">
+  <link rel="stylesheet" media="all" href="../shared/vendor/mocha-2.0.1.css">
 </head>
 <body>
   <div id="mocha">
     <p><a href="../">Index</a></p>
     <p><a href="../shared/">Shared Tests</a></p>
  </div>
   <div id="messages"></div>
   <div id="fixtures"></div>
   <!-- libs -->
   <script src="../../content/shared/libs/react-0.11.2.js"></script>
   <script src="../../content/shared/libs/jquery-2.1.0.js"></script>
   <script src="../../content/shared/libs/lodash-2.4.1.js"></script>
   <script src="../../content/shared/libs/backbone-1.1.2.js"></script>
   <script src="../../standalone/content/libs/l10n-gaia-02ca67948fe8.js"></script>
   <!-- test dependencies -->
-  <script src="../shared/vendor/mocha-1.17.1.js"></script>
+  <script src="../shared/vendor/mocha-2.0.1.js"></script>
   <script src="../shared/vendor/chai-1.9.0.js"></script>
   <script src="../shared/vendor/sinon-1.9.0.js"></script>
   <script src="../shared/sdk_mock.js"></script>
   <script>
     chai.Assertion.includeStack = true;
     mocha.setup('bdd');
   </script>
   <!-- App scripts -->
--- a/browser/components/loop/test/standalone/multiplexGum_test.js
+++ b/browser/components/loop/test/standalone/multiplexGum_test.js
@@ -194,17 +194,17 @@ describe("loop.standaloneMedia._Multiple
           expect(error).to.eql(fakeError);
           expect(calls).to.eql(11);
           done();
         });
       });
 
     it("should not call a getPermsAndCacheMedia success callback at the time" +
        " of gUM success callback fires",
-      function(done) {
+      function() {
         var fakeLocalStream = {};
         multiplexGum.userMedia.localStream = fakeLocalStream;
         navigator.originalGum.callsArgWith(1, fakeLocalStream);
         var calledOnce = false;
         var promiseCalledOnce = new Promise(function(resolve, reject) {
 
           multiplexGum.getPermsAndCacheMedia(null,
             function gPACMSuccess(localStream) {
@@ -214,59 +214,55 @@ describe("loop.standaloneMedia._Multiple
               if (calledOnce) {
                 sinon.assert.fail("original callback was called twice");
               }
               calledOnce = true;
               resolve();
             }, function() {
               sinon.assert.fail("error callback should not have fired");
               reject();
-              done();
             });
         });
 
-        promiseCalledOnce.then(function() {
+        return promiseCalledOnce.then(function() {
           defaultGum(null, function gUMSuccess(localStream2) {
             expect(localStream2).to.eql(fakeLocalStream);
             expect(multiplexGum.userMedia).to.have.property('pending', false);
             expect(multiplexGum.userMedia.successCallbacks.length).to.equal(0);
-            done();
           });
         });
       });
 
     it("should not call a getPermsAndCacheMedia error callback when the " +
       " gUM error callback fires",
-      function(done) {
+      function() {
         var fakeError = "monkeys ate the stream";
         multiplexGum.userMedia.error = fakeError;
         navigator.originalGum.callsArgWith(2, fakeError);
         var calledOnce = false;
         var promiseCalledOnce = new Promise(function(resolve, reject) {
           multiplexGum.getPermsAndCacheMedia(null, function() {
             sinon.assert.fail("success callback should not have fired");
             reject();
-            done();
           }, function gPACMError(errString) {
             expect(errString).to.eql(fakeError);
             expect(multiplexGum.userMedia).to.have.property('pending', false);
             if (calledOnce) {
               sinon.assert.fail("original error callback was called twice");
             }
             calledOnce = true;
             resolve();
           });
         });
 
-        promiseCalledOnce.then(function() {
+        return promiseCalledOnce.then(function() {
           defaultGum(null, function() {},
             function gUMError(errString) {
               expect(errString).to.eql(fakeError);
               expect(multiplexGum.userMedia).to.have.property('pending', false);
-              done();
             });
         });
       });
 
     it("should call the success callback with a new stream, " +
        " when a new stream is available",
       function(done) {
         var endedStream = {ended: true};