diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 00:47:55 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 00:47:55 +0000 |
commit | 26a029d407be480d791972afb5975cf62c9360a6 (patch) | |
tree | f435a8308119effd964b339f76abb83a57c29483 /dom/promise/tests/unit/test_monitor_uncaught.js | |
parent | Initial commit. (diff) | |
download | firefox-26a029d407be480d791972afb5975cf62c9360a6.tar.xz firefox-26a029d407be480d791972afb5975cf62c9360a6.zip |
Adding upstream version 124.0.1.upstream/124.0.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'dom/promise/tests/unit/test_monitor_uncaught.js')
-rw-r--r-- | dom/promise/tests/unit/test_monitor_uncaught.js | 322 |
1 files changed, 322 insertions, 0 deletions
diff --git a/dom/promise/tests/unit/test_monitor_uncaught.js b/dom/promise/tests/unit/test_monitor_uncaught.js new file mode 100644 index 0000000000..afa328cb97 --- /dev/null +++ b/dom/promise/tests/unit/test_monitor_uncaught.js @@ -0,0 +1,322 @@ +/* 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/. */ + +"use strict"; + +const { setTimeout } = ChromeUtils.importESModule( + "resource://gre/modules/Timer.sys.mjs" +); +const { PromiseTestUtils } = ChromeUtils.importESModule( + "resource://testing-common/PromiseTestUtils.sys.mjs" +); + +// Prevent test failures due to the unhandled rejections in this test file. +PromiseTestUtils.disableUncaughtRejectionObserverForSelfTest(); + +add_task(async function test_globals() { + Assert.notEqual( + PromiseDebugging, + undefined, + "PromiseDebugging is available." + ); +}); + +add_task(async function test_promiseID() { + let p1 = new Promise(resolve => {}); + let p2 = new Promise(resolve => {}); + let p3 = p2.catch(null); + let promise = [p1, p2, p3]; + + let identifiers = promise.map(PromiseDebugging.getPromiseID); + info("Identifiers: " + JSON.stringify(identifiers)); + let idSet = new Set(identifiers); + Assert.equal( + idSet.size, + identifiers.length, + "PromiseDebugging.getPromiseID returns a distinct id per promise" + ); + + let identifiers2 = promise.map(PromiseDebugging.getPromiseID); + Assert.equal( + JSON.stringify(identifiers), + JSON.stringify(identifiers2), + "Successive calls to PromiseDebugging.getPromiseID return the same id for the same promise" + ); +}); + +add_task(async function test_observe_uncaught() { + // The names of Promise instances + let names = new Map(); + + // The results for UncaughtPromiseObserver callbacks. + let CallbackResults = function (name) { + this.name = name; + this.expected = new Set(); + this.observed = new Set(); + this.blocker = new Promise(resolve => (this.resolve = resolve)); + }; + CallbackResults.prototype = { + observe(promise) { + info(this.name + " observing Promise " + names.get(promise)); + Assert.equal( + PromiseDebugging.getState(promise).state, + "rejected", + this.name + " observed a rejected Promise" + ); + if (!this.expected.has(promise)) { + Assert.ok( + false, + this.name + + " observed a Promise that it expected to observe, " + + names.get(promise) + + " (" + + PromiseDebugging.getPromiseID(promise) + + ", " + + PromiseDebugging.getAllocationStack(promise) + + ")" + ); + } + Assert.ok( + this.expected.delete(promise), + this.name + + " observed a Promise that it expected to observe, " + + names.get(promise) + + " (" + + PromiseDebugging.getPromiseID(promise) + + ")" + ); + Assert.ok( + !this.observed.has(promise), + this.name + " observed a Promise that it has not observed yet" + ); + this.observed.add(promise); + if (this.expected.size == 0) { + this.resolve(); + } else { + info( + this.name + + " is still waiting for " + + this.expected.size + + " observations:" + ); + info( + JSON.stringify(Array.from(this.expected.values(), x => names.get(x))) + ); + } + }, + }; + + let onLeftUncaught = new CallbackResults("onLeftUncaught"); + let onConsumed = new CallbackResults("onConsumed"); + + let observer = { + onLeftUncaught(promise, data) { + onLeftUncaught.observe(promise); + }, + onConsumed(promise) { + onConsumed.observe(promise); + }, + }; + + let resolveLater = function (delay = 20) { + // eslint-disable-next-line mozilla/no-arbitrary-setTimeout + return new Promise((resolve, reject) => setTimeout(resolve, delay)); + }; + let rejectLater = function (delay = 20) { + // eslint-disable-next-line mozilla/no-arbitrary-setTimeout + return new Promise((resolve, reject) => setTimeout(reject, delay)); + }; + let makeSamples = function* () { + yield { + promise: Promise.resolve(0), + name: "Promise.resolve", + }; + yield { + promise: Promise.resolve(resolve => resolve(0)), + name: "Resolution callback", + }; + yield { + promise: Promise.resolve(0).catch(null), + name: "`catch(null)`", + }; + yield { + promise: Promise.reject(0).catch(() => {}), + name: "Reject and catch immediately", + }; + yield { + promise: resolveLater(), + name: "Resolve later", + }; + yield { + promise: Promise.reject("Simple rejection"), + leftUncaught: true, + consumed: false, + name: "Promise.reject", + }; + + // Reject a promise now, consume it later. + let p = Promise.reject("Reject now, consume later"); + + // eslint-disable-next-line mozilla/no-arbitrary-setTimeout + setTimeout( + () => + p.catch(() => { + info("Consumed promise"); + }), + 200 + ); + yield { + promise: p, + leftUncaught: true, + consumed: true, + name: "Reject now, consume later", + }; + + yield { + promise: Promise.all([Promise.resolve("Promise.all"), rejectLater()]), + leftUncaught: true, + name: "Rejecting through Promise.all", + }; + yield { + promise: Promise.race([resolveLater(500), Promise.reject()]), + leftUncaught: true, // The rejection wins the race. + name: "Rejecting through Promise.race", + }; + yield { + promise: Promise.race([Promise.resolve(), rejectLater(500)]), + leftUncaught: false, // The resolution wins the race. + name: "Resolving through Promise.race", + }; + + let boom = new Error("`throw` in the constructor"); + yield { + promise: new Promise(() => { + throw boom; + }), + leftUncaught: true, + name: "Throwing in the constructor", + }; + + let rejection = Promise.reject("`reject` during resolution"); + yield { + promise: rejection, + leftUncaught: false, + consumed: false, // `rejection` is consumed immediately (see below) + name: "Promise.reject, again", + }; + + yield { + promise: new Promise(resolve => resolve(rejection)), + leftUncaught: true, + consumed: false, + name: "Resolving with a rejected promise", + }; + + yield { + promise: Promise.resolve(0).then(() => rejection), + leftUncaught: true, + consumed: false, + name: "Returning a rejected promise from success handler", + }; + + yield { + promise: Promise.resolve(0).then(() => { + throw new Error(); + }), + leftUncaught: true, + consumed: false, + name: "Throwing during the call to the success callback", + }; + }; + let samples = []; + for (let s of makeSamples()) { + samples.push(s); + info( + "Promise '" + + s.name + + "' has id " + + PromiseDebugging.getPromiseID(s.promise) + ); + } + + PromiseDebugging.addUncaughtRejectionObserver(observer); + + for (let s of samples) { + names.set(s.promise, s.name); + if (s.leftUncaught || false) { + onLeftUncaught.expected.add(s.promise); + } + if (s.consumed || false) { + onConsumed.expected.add(s.promise); + } + } + + info("Test setup, waiting for callbacks."); + await onLeftUncaught.blocker; + + info("All calls to onLeftUncaught are complete."); + if (onConsumed.expected.size != 0) { + info("onConsumed is still waiting for the following Promise:"); + info( + JSON.stringify( + Array.from(onConsumed.expected.values(), x => names.get(x)) + ) + ); + await onConsumed.blocker; + } + + info("All calls to onConsumed are complete."); + let removed = PromiseDebugging.removeUncaughtRejectionObserver(observer); + Assert.ok(removed, "removeUncaughtRejectionObserver succeeded"); + removed = PromiseDebugging.removeUncaughtRejectionObserver(observer); + Assert.ok( + !removed, + "second call to removeUncaughtRejectionObserver didn't remove anything" + ); +}); + +add_task(async function test_uninstall_observer() { + let Observer = function () { + this.blocker = new Promise(resolve => (this.resolve = resolve)); + this.active = true; + }; + Observer.prototype = { + set active(x) { + this._active = x; + if (x) { + PromiseDebugging.addUncaughtRejectionObserver(this); + } else { + PromiseDebugging.removeUncaughtRejectionObserver(this); + } + }, + onLeftUncaught() { + Assert.ok(this._active, "This observer is active."); + this.resolve(); + }, + onConsumed() { + Assert.ok(false, "We should not consume any Promise."); + }, + }; + + info("Adding an observer."); + let deactivate = new Observer(); + Promise.reject("I am an uncaught rejection."); + await deactivate.blocker; + Assert.ok(true, "The observer has observed an uncaught Promise."); + deactivate.active = false; + info( + "Removing the observer, it should not observe any further uncaught Promise." + ); + + info( + "Rejecting a Promise and waiting a little to give a chance to observers." + ); + let wait = new Observer(); + Promise.reject("I am another uncaught rejection."); + await wait.blocker; + // eslint-disable-next-line mozilla/no-arbitrary-setTimeout + await new Promise(resolve => setTimeout(resolve, 100)); + // Normally, `deactivate` should not be notified of the uncaught rejection. + wait.active = false; +}); |