diff options
Diffstat (limited to 'dom/promise/tests/unit/test_monitor_uncaught.js')
-rw-r--r-- | dom/promise/tests/unit/test_monitor_uncaught.js | 274 |
1 files changed, 274 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 000000000..7dd80d212 --- /dev/null +++ b/dom/promise/tests/unit/test_monitor_uncaught.js @@ -0,0 +1,274 @@ +/* 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"; + +var { utils: Cu } = Components; + +Cu.import("resource://gre/modules/Timer.jsm", this); +Cu.import("resource://testing-common/PromiseTestUtils.jsm", this); + +// Prevent test failures due to the unhandled rejections in this test file. +PromiseTestUtils.disableUncaughtRejectionObserverForSelfTest(); + +add_task(function* test_globals() { + Assert.equal(Promise.defer || undefined, undefined, "We are testing DOM Promise."); + Assert.notEqual(PromiseDebugging, undefined, "PromiseDebugging is available."); +}); + +add_task(function* test_promiseID() { + let p1 = new Promise(resolve => {}); + let p2 = new Promise(resolve => {}); + let p3 = p2.then(null, null); + let promise = [p1, p2, p3]; + + let identifiers = promise.map(PromiseDebugging.getPromiseID); + do_print("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(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: function(promise) { + do_print(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 { + do_print(this.name + " is still waiting for " + this.expected.size + " observations:"); + do_print(JSON.stringify(Array.from(this.expected.values(), (x) => names.get(x)))); + } + }, + }; + + let onLeftUncaught = new CallbackResults("onLeftUncaught"); + let onConsumed = new CallbackResults("onConsumed"); + + let observer = { + onLeftUncaught: function(promise, data) { + onLeftUncaught.observe(promise); + }, + onConsumed: function(promise) { + onConsumed.observe(promise); + }, + }; + + let resolveLater = function(delay = 20) { + return new Promise((resolve, reject) => setTimeout(resolve, delay)); + }; + let rejectLater = function(delay = 20) { + 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).then(null, null), + name: "`then(null, null)`" + }; + yield { + promise: Promise.reject(0).then(null, () => {}), + 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"); + setTimeout(() => p.then(null, () => { + do_print("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); + do_print("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); + } + } + + do_print("Test setup, waiting for callbacks."); + yield onLeftUncaught.blocker; + + do_print("All calls to onLeftUncaught are complete."); + if (onConsumed.expected.size != 0) { + do_print("onConsumed is still waiting for the following Promise:"); + do_print(JSON.stringify(Array.from(onConsumed.expected.values(), (x) => names.get(x)))); + yield onConsumed.blocker; + } + + do_print("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(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: function() { + Assert.ok(this._active, "This observer is active."); + this.resolve(); + }, + onConsumed: function() { + Assert.ok(false, "We should not consume any Promise."); + }, + }; + + do_print("Adding an observer."); + let deactivate = new Observer(); + Promise.reject("I am an uncaught rejection."); + yield deactivate.blocker; + Assert.ok(true, "The observer has observed an uncaught Promise."); + deactivate.active = false; + do_print("Removing the observer, it should not observe any further uncaught Promise."); + + do_print("Rejecting a Promise and waiting a little to give a chance to observers."); + let wait = new Observer(); + Promise.reject("I am another uncaught rejection."); + yield wait.blocker; + yield new Promise(resolve => setTimeout(resolve, 100)); + // Normally, `deactivate` should not be notified of the uncaught rejection. + wait.active = false; +}); + +function run_test() { + run_next_test(); +} |