tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

test_monitor_uncaught.js (9203B)


      1 /* This Source Code Form is subject to the terms of the Mozilla Public
      2 * License, v. 2.0. If a copy of the MPL was not distributed with this
      3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
      4 
      5 "use strict";
      6 
      7 const { setTimeout } = ChromeUtils.importESModule(
      8  "resource://gre/modules/Timer.sys.mjs"
      9 );
     10 const { PromiseTestUtils } = ChromeUtils.importESModule(
     11  "resource://testing-common/PromiseTestUtils.sys.mjs"
     12 );
     13 
     14 // Prevent test failures due to the unhandled rejections in this test file.
     15 PromiseTestUtils.disableUncaughtRejectionObserverForSelfTest();
     16 
     17 add_task(async function test_globals() {
     18  Assert.notEqual(
     19    PromiseDebugging,
     20    undefined,
     21    "PromiseDebugging is available."
     22  );
     23 });
     24 
     25 add_task(async function test_promiseID() {
     26  let p1 = new Promise(() => {});
     27  let p2 = new Promise(() => {});
     28  let p3 = p2.catch(null);
     29  let promise = [p1, p2, p3];
     30 
     31  let identifiers = promise.map(PromiseDebugging.getPromiseID);
     32  info("Identifiers: " + JSON.stringify(identifiers));
     33  let idSet = new Set(identifiers);
     34  Assert.equal(
     35    idSet.size,
     36    identifiers.length,
     37    "PromiseDebugging.getPromiseID returns a distinct id per promise"
     38  );
     39 
     40  let identifiers2 = promise.map(PromiseDebugging.getPromiseID);
     41  Assert.equal(
     42    JSON.stringify(identifiers),
     43    JSON.stringify(identifiers2),
     44    "Successive calls to PromiseDebugging.getPromiseID return the same id for the same promise"
     45  );
     46 });
     47 
     48 add_task(async function test_observe_uncaught() {
     49  // The names of Promise instances
     50  let names = new Map();
     51 
     52  // The results for UncaughtPromiseObserver callbacks.
     53  let CallbackResults = function (name) {
     54    this.name = name;
     55    this.expected = new Set();
     56    this.observed = new Set();
     57    this.blocker = new Promise(resolve => (this.resolve = resolve));
     58  };
     59  CallbackResults.prototype = {
     60    observe(promise) {
     61      info(this.name + " observing Promise " + names.get(promise));
     62      Assert.equal(
     63        PromiseDebugging.getState(promise).state,
     64        "rejected",
     65        this.name + " observed a rejected Promise"
     66      );
     67      if (!this.expected.has(promise)) {
     68        Assert.ok(
     69          false,
     70          this.name +
     71            " observed a Promise that it expected to observe, " +
     72            names.get(promise) +
     73            " (" +
     74            PromiseDebugging.getPromiseID(promise) +
     75            ", " +
     76            PromiseDebugging.getAllocationStack(promise) +
     77            ")"
     78        );
     79      }
     80      Assert.ok(
     81        this.expected.delete(promise),
     82        this.name +
     83          " observed a Promise that it expected to observe, " +
     84          names.get(promise) +
     85          " (" +
     86          PromiseDebugging.getPromiseID(promise) +
     87          ")"
     88      );
     89      Assert.ok(
     90        !this.observed.has(promise),
     91        this.name + " observed a Promise that it has not observed yet"
     92      );
     93      this.observed.add(promise);
     94      if (this.expected.size == 0) {
     95        this.resolve();
     96      } else {
     97        info(
     98          this.name +
     99            " is still waiting for " +
    100            this.expected.size +
    101            " observations:"
    102        );
    103        info(
    104          JSON.stringify(Array.from(this.expected.values(), x => names.get(x)))
    105        );
    106      }
    107    },
    108  };
    109 
    110  let onLeftUncaught = new CallbackResults("onLeftUncaught");
    111  let onConsumed = new CallbackResults("onConsumed");
    112 
    113  let observer = {
    114    onLeftUncaught(promise) {
    115      onLeftUncaught.observe(promise);
    116    },
    117    onConsumed(promise) {
    118      onConsumed.observe(promise);
    119    },
    120  };
    121 
    122  let resolveLater = function (delay = 20) {
    123    // eslint-disable-next-line mozilla/no-arbitrary-setTimeout
    124    return new Promise(resolve => setTimeout(resolve, delay));
    125  };
    126  let rejectLater = function (delay = 20) {
    127    // eslint-disable-next-line mozilla/no-arbitrary-setTimeout
    128    return new Promise((resolve, reject) => setTimeout(reject, delay));
    129  };
    130  let makeSamples = function* () {
    131    yield {
    132      promise: Promise.resolve(0),
    133      name: "Promise.resolve",
    134    };
    135    yield {
    136      promise: Promise.resolve(resolve => resolve(0)),
    137      name: "Resolution callback",
    138    };
    139    yield {
    140      promise: Promise.resolve(0).catch(null),
    141      name: "`catch(null)`",
    142    };
    143    yield {
    144      promise: Promise.reject(0).catch(() => {}),
    145      name: "Reject and catch immediately",
    146    };
    147    yield {
    148      promise: resolveLater(),
    149      name: "Resolve later",
    150    };
    151    yield {
    152      promise: Promise.reject("Simple rejection"),
    153      leftUncaught: true,
    154      consumed: false,
    155      name: "Promise.reject",
    156    };
    157 
    158    // Reject a promise now, consume it later.
    159    let p = Promise.reject("Reject now, consume later");
    160 
    161    // eslint-disable-next-line mozilla/no-arbitrary-setTimeout
    162    setTimeout(
    163      () =>
    164        p.catch(() => {
    165          info("Consumed promise");
    166        }),
    167      200
    168    );
    169    yield {
    170      promise: p,
    171      leftUncaught: true,
    172      consumed: true,
    173      name: "Reject now, consume later",
    174    };
    175 
    176    yield {
    177      promise: Promise.all([Promise.resolve("Promise.all"), rejectLater()]),
    178      leftUncaught: true,
    179      name: "Rejecting through Promise.all",
    180    };
    181    yield {
    182      promise: Promise.race([resolveLater(500), Promise.reject()]),
    183      leftUncaught: true, // The rejection wins the race.
    184      name: "Rejecting through Promise.race",
    185    };
    186    yield {
    187      promise: Promise.race([Promise.resolve(), rejectLater(500)]),
    188      leftUncaught: false, // The resolution wins the race.
    189      name: "Resolving through Promise.race",
    190    };
    191 
    192    let boom = new Error("`throw` in the constructor");
    193    yield {
    194      promise: new Promise(() => {
    195        throw boom;
    196      }),
    197      leftUncaught: true,
    198      name: "Throwing in the constructor",
    199    };
    200 
    201    let rejection = Promise.reject("`reject` during resolution");
    202    yield {
    203      promise: rejection,
    204      leftUncaught: false,
    205      consumed: false, // `rejection` is consumed immediately (see below)
    206      name: "Promise.reject, again",
    207    };
    208 
    209    yield {
    210      promise: new Promise(resolve => resolve(rejection)),
    211      leftUncaught: true,
    212      consumed: false,
    213      name: "Resolving with a rejected promise",
    214    };
    215 
    216    yield {
    217      promise: Promise.resolve(0).then(() => rejection),
    218      leftUncaught: true,
    219      consumed: false,
    220      name: "Returning a rejected promise from success handler",
    221    };
    222 
    223    yield {
    224      promise: Promise.resolve(0).then(() => {
    225        throw new Error();
    226      }),
    227      leftUncaught: true,
    228      consumed: false,
    229      name: "Throwing during the call to the success callback",
    230    };
    231  };
    232  let samples = [];
    233  for (let s of makeSamples()) {
    234    samples.push(s);
    235    info(
    236      "Promise '" +
    237        s.name +
    238        "' has id " +
    239        PromiseDebugging.getPromiseID(s.promise)
    240    );
    241  }
    242 
    243  PromiseDebugging.addUncaughtRejectionObserver(observer);
    244 
    245  for (let s of samples) {
    246    names.set(s.promise, s.name);
    247    if (s.leftUncaught || false) {
    248      onLeftUncaught.expected.add(s.promise);
    249    }
    250    if (s.consumed || false) {
    251      onConsumed.expected.add(s.promise);
    252    }
    253  }
    254 
    255  info("Test setup, waiting for callbacks.");
    256  await onLeftUncaught.blocker;
    257 
    258  info("All calls to onLeftUncaught are complete.");
    259  if (onConsumed.expected.size != 0) {
    260    info("onConsumed is still waiting for the following Promise:");
    261    info(
    262      JSON.stringify(
    263        Array.from(onConsumed.expected.values(), x => names.get(x))
    264      )
    265    );
    266    await onConsumed.blocker;
    267  }
    268 
    269  info("All calls to onConsumed are complete.");
    270  let removed = PromiseDebugging.removeUncaughtRejectionObserver(observer);
    271  Assert.ok(removed, "removeUncaughtRejectionObserver succeeded");
    272  removed = PromiseDebugging.removeUncaughtRejectionObserver(observer);
    273  Assert.ok(
    274    !removed,
    275    "second call to removeUncaughtRejectionObserver didn't remove anything"
    276  );
    277 });
    278 
    279 add_task(async function test_uninstall_observer() {
    280  let Observer = function () {
    281    this.blocker = new Promise(resolve => (this.resolve = resolve));
    282    this.active = true;
    283  };
    284  Observer.prototype = {
    285    set active(x) {
    286      this._active = x;
    287      if (x) {
    288        PromiseDebugging.addUncaughtRejectionObserver(this);
    289      } else {
    290        PromiseDebugging.removeUncaughtRejectionObserver(this);
    291      }
    292    },
    293    onLeftUncaught() {
    294      Assert.ok(this._active, "This observer is active.");
    295      this.resolve();
    296    },
    297    onConsumed() {
    298      Assert.ok(false, "We should not consume any Promise.");
    299    },
    300  };
    301 
    302  info("Adding an observer.");
    303  let deactivate = new Observer();
    304  Promise.reject("I am an uncaught rejection.");
    305  await deactivate.blocker;
    306  Assert.ok(true, "The observer has observed an uncaught Promise.");
    307  deactivate.active = false;
    308  info(
    309    "Removing the observer, it should not observe any further uncaught Promise."
    310  );
    311 
    312  info(
    313    "Rejecting a Promise and waiting a little to give a chance to observers."
    314  );
    315  let wait = new Observer();
    316  Promise.reject("I am another uncaught rejection.");
    317  await wait.blocker;
    318  // eslint-disable-next-line mozilla/no-arbitrary-setTimeout
    319  await new Promise(resolve => setTimeout(resolve, 100));
    320  // Normally, `deactivate` should not be notified of the uncaught rejection.
    321  wait.active = false;
    322 });