Bug 1242505 - Part 2 - Update PromiseTestUtils for use in mochitests. r=Mossop
☠☠ backed out by 79db5648dbd2 ☠ ☠
authorPaolo Amadini <paolo.mozmail@amadzone.org>
Thu, 25 May 2017 15:00:29 +0100
changeset 409155 22e7144f857ce6a49764375f1e5ddf3e1ead7a96
parent 409154 b59573695517b2a464f22e00bd3792c50dcf371a
child 409156 8d53be05afc59519c5ce8cfae96d284a972fda71
push id7391
push usermtabara@mozilla.com
push dateMon, 12 Jun 2017 13:08:53 +0000
treeherdermozilla-beta@2191d7f87e2e [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersMossop
bugs1242505
milestone55.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 1242505 - Part 2 - Update PromiseTestUtils for use in mochitests. r=Mossop This adds a new coarse-grained whitelisting function, whose usage should be kept to a minimum but is necessary because many mochitests have cleanup issues on shutdown. The module now handles cases that only happen in mochitests, where rejections can occur in contexts that are unloaded and more than one test file can be executed sequentially in the same process. MozReview-Commit-ID: 8xejMxoSBzf
toolkit/modules/tests/modules/PromiseTestUtils.jsm
--- a/toolkit/modules/tests/modules/PromiseTestUtils.jsm
+++ b/toolkit/modules/tests/modules/PromiseTestUtils.jsm
@@ -48,16 +48,23 @@ this.PromiseTestUtils = {
    * When an uncaught rejection is detected, it is ignored if one of the
    * functions in this array returns true when called with the rejection details
    * as its only argument. When a function matches an expected rejection, it is
    * then removed from the array.
    */
   _rejectionIgnoreFns: [],
 
   /**
+   * If any of the functions in this array returns true when called with the
+   * rejection details as its only argument, the rejection is ignored. This
+   * happens after the "_rejectionIgnoreFns" array is processed.
+   */
+  _globalRejectionIgnoreFns: [],
+
+  /**
    * Called only by the test infrastructure, registers the rejection observers.
    *
    * This should be called only once, and a matching "uninit" call must be made
    * or the tests will crash on shutdown.
    */
   init() {
     if (this._initialized) {
       Cu.reportError("This object was already initialized.");
@@ -145,23 +152,31 @@ this.PromiseTestUtils = {
       let reason = PromiseDebugging.getState(promise).reason;
       if (reason === this._ensureDOMPromiseRejectionsProcessedReason) {
         // Ignore the special promise for ensureDOMPromiseRejectionsProcessed.
         return;
       }
       message = reason.message || ("" + reason);
     } catch (ex) {}
 
+    // We should convert the rejection stack to a string immediately. This is
+    // because the object might not be available when we report the rejection
+    // later, if the error occurred in a context that has been unloaded.
+    let stack = "(Unable to convert rejection stack to string.)";
+    try {
+      stack = "" + PromiseDebugging.getRejectionStack(promise);
+    } catch (ex) {}
+
     // It's important that we don't store any reference to the provided Promise
     // object or its value after this function returns in order to avoid leaks.
     this._rejections.push({
       id: PromiseDebugging.getPromiseID(promise),
       message,
       date: new Date(),
-      stack: PromiseDebugging.getRejectionStack(promise),
+      stack,
     });
   },
 
   // UncaughtRejectionObserver
   onConsumed(promise) {
     // We don't expect that many unhandled rejections will appear at the same
     // time, so the algorithm doesn't need to be optimized for that case.
     let id = PromiseDebugging.getPromiseID(promise);
@@ -190,16 +205,29 @@ this.PromiseTestUtils = {
    */
   expectUncaughtRejection(regExpOrCheckFn) {
     let checkFn = !("test" in regExpOrCheckFn) ? regExpOrCheckFn :
                   rejection => regExpOrCheckFn.test(rejection.message);
     this._rejectionIgnoreFns.push(checkFn);
   },
 
   /**
+   * Whitelists an entire class of Promise rejections. Usage of this function
+   * should be kept to a minimum because it has a broad scope and doesn't
+   * prevent new unhandled rejections of this class from being added.
+   *
+   * @param regExp
+   *        This should match the error message of the rejection.
+   */
+  whitelistRejectionsGlobally(regExp) {
+    this._globalRejectionIgnoreFns.push(
+      rejection => regExp.test(rejection.message));
+  },
+
+  /**
    * Fails the test if there are any uncaught rejections at this time that have
    * not been whitelisted using expectUncaughtRejection.
    *
    * Depending on the configuration of the test suite, this function might only
    * report the details of the first uncaught rejection that was generated.
    *
    * This is called by the test suite at the end of each test function.
    */
@@ -214,16 +242,21 @@ this.PromiseTestUtils = {
       // If one of the ignore functions matches, ignore the rejection, then
       // remove the function so that each function only matches one rejection.
       let index = this._rejectionIgnoreFns.findIndex(f => f(rejection));
       if (index != -1) {
         this._rejectionIgnoreFns.splice(index, 1);
         continue;
       }
 
+      // Check the global whitelisting functions.
+      if (this._globalRejectionIgnoreFns.some(fn => fn(rejection))) {
+        continue;
+      }
+
       // Report the error. This operation can throw an exception, depending on
       // the configuration of the test suite that handles the assertion.
       Assert.ok(false,
                 `A promise chain failed to handle a rejection:` +
                 ` ${rejection.message} - rejection date: ${rejection.date}` +
                 ` - stack: ${rejection.stack}`);
     }
   },
@@ -235,10 +268,12 @@ this.PromiseTestUtils = {
    * This is called by the test suite at the end of each test file.
    */
   assertNoMoreExpectedRejections() {
     // Only log this condition is there is a failure.
     if (this._rejectionIgnoreFns.length > 0) {
       Assert.equal(this._rejectionIgnoreFns.length, 0,
              "Unable to find a rejection expected by expectUncaughtRejection.");
     }
+    // Reset the list of expected rejections in case the test suite continues.
+    this._rejectionIgnoreFns = [];
   },
 };