From 60ef7a0e23fd320a11281f67c64a39ff95612ce9 Mon Sep 17 00:00:00 2001 From: Petka Antonov Date: Wed, 29 May 2019 13:59:16 +0300 Subject: [PATCH] Fixes #1468, stabilize unhandled rejection tests --- src/debuggability.js | 129 ++++++++++++++++++++-- test/browser/mocha.js | 5 +- test/mocha/bluebird-multiple-instances.js | 4 + test/mocha/helpers/util.js | 19 ++-- test/mocha/unhandled_rejections.js | 29 +++-- tools/build.js | 2 +- tools/mocha_runner.js | 4 +- 7 files changed, 157 insertions(+), 35 deletions(-) diff --git a/src/debuggability.js b/src/debuggability.js index b939fa66f..d702346a4 100644 --- a/src/debuggability.js +++ b/src/debuggability.js @@ -31,6 +31,77 @@ var longStackTraces = !!(util.env("BLUEBIRD_LONG_STACK_TRACES") != 0 && var wForgottenReturn = util.env("BLUEBIRD_W_FORGOTTEN_RETURN") != 0 && (warnings || !!util.env("BLUEBIRD_W_FORGOTTEN_RETURN")); +var deferUnhandledRejectionCheck; +(function() { + var promises = []; + + function unhandledRejectionCheck() { + for (var i = 0; i < promises.length; ++i) { + promises[i]._notifyUnhandledRejection(); + } + unhandledRejectionClear(); + } + + function unhandledRejectionClear() { + promises.length = 0; + } + + if (util.isNode) { + deferUnhandledRejectionCheck = (function() { + var timers = require("timers"); + var timerSetTimeout = timers.setTimeout; + var timer = timerSetTimeout(unhandledRejectionCheck, 1); + timer.unref(); + + return function(promise) { + promises.push(promise); + if (typeof timer.refresh === "function") { + timer.refresh(); + } else { + timerSetTimeout(unhandledRejectionCheck, 1).unref(); + } + }; + })(); + } else if (typeof document === "object" && document.createElement) { + deferUnhandledRejectionCheck = (function() { + var iframeSetTimeout; + + function checkIframe() { + if (document.body) { + var iframe = document.createElement("iframe"); + document.body.appendChild(iframe); + if (iframe.contentWindow && + iframe.contentWindow.setTimeout) { + iframeSetTimeout = iframe.contentWindow.setTimeout; + } + document.body.removeChild(iframe); + } + } + checkIframe(); + return function(promise) { + promises.push(promise); + if (iframeSetTimeout) { + iframeSetTimeout(unhandledRejectionCheck, 1); + } else { + checkIframe(); + } + }; + })(); + } else { + deferUnhandledRejectionCheck = function(promise) { + promises.push(promise); + setTimeout(unhandledRejectionCheck, 1); + }; + } + + es5.defineProperty(Promise, "_unhandledRejectionCheck", { + value: unhandledRejectionCheck + }); + es5.defineProperty(Promise, "_unhandledRejectionClear", { + value: unhandledRejectionClear + }); +})(); + Promise.prototype.suppressUnhandledRejections = function() { var target = this._target(); target._bitField = ((target._bitField & (~IS_REJECTION_UNHANDLED)) | @@ -40,10 +111,7 @@ Promise.prototype.suppressUnhandledRejections = function() { Promise.prototype._ensurePossibleRejectionHandled = function () { if ((this._bitField & IS_REJECTION_IGNORED) !== 0) return; this._setRejectionIsUnhandled(); - var self = this; - setTimeout(function() { - self._notifyUnhandledRejection(); - }, 1); + deferUnhandledRejectionCheck(this); }; Promise.prototype._notifyUnhandledRejectionIsHandled = function () { @@ -144,22 +212,61 @@ Promise.hasLongStackTraces = function () { return config.longStackTraces && longStackTracesIsSupported(); }; + +var legacyHandlers = { + unhandledrejection: { + before: function() { + var ret = util.global.onunhandledrejection; + util.global.onunhandledrejection = null; + return ret; + }, + after: function(fn) { + util.global.onunhandledrejection = fn; + } + }, + rejectionhandled: { + before: function() { + var ret = util.global.onrejectionhandled; + util.global.onrejectionhandled = null; + return ret; + }, + after: function(fn) { + util.global.onrejectionhandled = fn; + } + } +}; + var fireDomEvent = (function() { + var dispatch = function(legacy, e) { + if (legacy) { + var fn; + try { + fn = legacy.before(); + return !util.global.dispatchEvent(e); + } finally { + legacy.after(fn); + } + } else { + return !util.global.dispatchEvent(e); + } + }; try { if (typeof CustomEvent === "function") { var event = new CustomEvent("CustomEvent"); util.global.dispatchEvent(event); return function(name, event) { + name = name.toLowerCase(); var eventData = { detail: event, cancelable: true }; - var domEvent = new CustomEvent(name.toLowerCase(), eventData); + var domEvent = new CustomEvent(name, eventData); es5.defineProperty( domEvent, "promise", {value: event.promise}); es5.defineProperty( domEvent, "reason", {value: event.reason}); - return !util.global.dispatchEvent(domEvent); + + return dispatch(legacyHandlers[name], domEvent); }; // In Firefox < 48 CustomEvent is not available in workers but // Event is. @@ -167,23 +274,25 @@ var fireDomEvent = (function() { var event = new Event("CustomEvent"); util.global.dispatchEvent(event); return function(name, event) { - var domEvent = new Event(name.toLowerCase(), { + name = name.toLowerCase(); + var domEvent = new Event(name, { cancelable: true }); domEvent.detail = event; es5.defineProperty(domEvent, "promise", {value: event.promise}); es5.defineProperty(domEvent, "reason", {value: event.reason}); - return !util.global.dispatchEvent(domEvent); + return dispatch(legacyHandlers[name], domEvent); }; } else { var event = document.createEvent("CustomEvent"); event.initCustomEvent("testingtheevent", false, true, {}); util.global.dispatchEvent(event); return function(name, event) { + name = name.toLowerCase(); var domEvent = document.createEvent("CustomEvent"); - domEvent.initCustomEvent(name.toLowerCase(), false, true, + domEvent.initCustomEvent(name, false, true, event); - return !util.global.dispatchEvent(domEvent); + return dispatch(legacyHandlers[name], domEvent); }; } } catch (e) {} diff --git a/test/browser/mocha.js b/test/browser/mocha.js index 564a4f318..cf8f333fe 100644 --- a/test/browser/mocha.js +++ b/test/browser/mocha.js @@ -4215,7 +4215,7 @@ function Runnable(title, fn) { this.fn = fn; this.async = fn && fn.length; this.sync = ! this.async; - this._timeout = 2000; + this._timeout = 1000 * 60 * 5; this._slow = 75; this._enableTimeouts = true; this.timedOut = false; @@ -4331,6 +4331,7 @@ Runnable.prototype.resetTimeout = function(){ if (!this._enableTimeouts) return; this.clearTimeout(); + this.timer = setTimeout(function(){ if (!self._enableTimeouts) return; self.callback(new Error('timeout of ' + ms + 'ms exceeded')); @@ -5185,7 +5186,7 @@ function Suite(title, parentContext) { this._afterEach = []; this._afterAll = []; this.root = !title; - this._timeout = 2000; + this._timeout = 1000 * 60 * 5; this._enableTimeouts = true; this._slow = 75; this._bail = false; diff --git a/test/mocha/bluebird-multiple-instances.js b/test/mocha/bluebird-multiple-instances.js index b18823f04..50337bf6b 100644 --- a/test/mocha/bluebird-multiple-instances.js +++ b/test/mocha/bluebird-multiple-instances.js @@ -57,6 +57,10 @@ if (isNodeJS) { setTimeout(function(){ d1.fulfill(); d2.fulfill(); + setTimeout(function() { + Promise1._unhandledRejectionCheck(); + Promise2._unhandledRejectionCheck(); + }, 100); }, 1); return Promise.all([spy1.promise, spy2.promise]); }); diff --git a/test/mocha/helpers/util.js b/test/mocha/helpers/util.js index 067708334..22ece67c0 100644 --- a/test/mocha/helpers/util.js +++ b/test/mocha/helpers/util.js @@ -99,7 +99,7 @@ module.exports = { var promise = new Promise(function() { resolve = arguments[0]; reject = arguments[1]; - }).timeout(500); + }); var ret = function(fn) { ret.callback = fn; return ret.node; @@ -122,7 +122,7 @@ module.exports = { var promise = new Promise(function() { resolve = arguments[0]; reject = arguments[1]; - }).timeout(500); + }); domain = require('domain').create(); domain.on("error", function(e) { try { @@ -136,18 +136,18 @@ module.exports = { return promise; }, - //Since there is only a single handler possible at a time, older - //tests that are run just before this file could affect the results - //that's why there is 500ms limit in grunt file between each test - //beacuse the unhandled rejection handler will run within 100ms right now onUnhandledFail: function(testFunction) { + Promise._unhandledRejectionClear(); return new Promise(function(resolve, reject) { var err = new Error("Reporting handled rejection as unhandled from: " + testFunction); Promise.onPossiblyUnhandledRejection(function() { reject(err); }); - Promise.delay(25).then(resolve); + Promise.delay(150).then(function() { + Promise._unhandledRejectionCheck(); + resolve(); + }); }).lastly(function() { Promise.onPossiblyUnhandledRejection(null); }); @@ -183,7 +183,10 @@ module.exports = { resolve(e); } }); - Promise.delay(200).then(function() { + Promise.delay(50).then(function() { + Promise._unhandledRejectionCheck(); + return Promise.delay(1); + }).then(function() { var message = "Expected onPossiblyUnhandledRejection to be called " + total + " times but it was only called " + cur + " times"; reject(new Error(message)); diff --git a/test/mocha/unhandled_rejections.js b/test/mocha/unhandled_rejections.js index 672b7d009..0745b5b4f 100644 --- a/test/mocha/unhandled_rejections.js +++ b/test/mocha/unhandled_rejections.js @@ -517,9 +517,11 @@ describe("Promise.onUnhandledRejectionHandled", function() { var a = new Promise(function(){ throw reason; }); - setTimeout(function(){ + + setTimeout(function() { + Promise._unhandledRejectionCheck(); a.then(assert.fail, function(){}); - }, 200); + }, 1); return Promise.all([spy1.promise, spy2.promise]); }); @@ -564,9 +566,10 @@ describe("global events", function() { var promise = new Promise(function() {throw err;}); setTimeout(function() { + Promise._unhandledRejectionCheck(); promise.then(assert.fail, function(){}); - }, 150); - }).timeout(500); + }, 1); + }); }); specify("are fired with local events", function() { @@ -603,9 +606,10 @@ describe("global events", function() { var promise = new Promise(function() {throw err;}); setTimeout(function() { + Promise._unhandledRejectionCheck(); promise.then(assert.fail, function(){}); - }, 150); - }).timeout(500); + }, 1); + }); }); }); @@ -661,27 +665,28 @@ if (windowDomEventSupported) { assert.strictEqual(e.detail.promise, promise); assert.strictEqual(e.detail.reason, undefined); assert.strictEqual(e.promise, promise); - assert.strictEqual(e.reason, err); + assert.strictEqual(e.reason, undefined); order.push(3); }); attachEvent("rejectionhandled", function(e) { assert.strictEqual(e.detail.promise, promise); assert.strictEqual(e.detail.reason, undefined); assert.strictEqual(e.promise, promise); - assert.strictEqual(e.reason, err); + assert.strictEqual(e.reason, undefined); assert.strictEqual(e.defaultPrevented, true); order.push(4); resolve(); }); setTimeout(function() { + Promise._unhandledRejectionCheck(); promise.then(assert.fail, function(r) { order.push(5); assert.strictEqual(r, err); assert.deepEqual(order, [1,2,3,4,5]); }); - }, 150); - }).timeout(500); + }, 1); + }); }) }); @@ -722,7 +727,7 @@ if (windowDomEventSupported) { worker.postMessage("reject"); }).then(function () { assert.deepEqual(order, [1, 2]); - }).timeout(500); + }); }); }); } @@ -796,7 +801,7 @@ describe("issues", function () { specify("GH-1501-2", function testFunction() { var ret = onUnhandledFail(testFunction); - Promise.reduce([Promise.delay(100), Promise.reject(new Error("reason"))], + Promise.reduce([Promise.delay(1), Promise.reject(new Error("reason"))], function() {}, {}).caught(function() {}); return ret; diff --git a/tools/build.js b/tools/build.js index 3cef5e266..721f513b2 100644 --- a/tools/build.js +++ b/tools/build.js @@ -212,7 +212,7 @@ function buildBrowser(sources, dir, tmpDir, depsRequireCode, minify, npmPackage, entries: entries, detectGlobals: false, standalone: "Promise" - }).exclude('async_hooks'); + }).exclude('async_hooks').exclude("timers"); return Promise.promisify(b.bundle, b)().then(function(src) { var alias = "\ ;if (typeof window !== 'undefined' && window !== null) { \ diff --git a/tools/mocha_runner.js b/tools/mocha_runner.js index 9686b9709..96ce3ca6a 100644 --- a/tools/mocha_runner.js +++ b/tools/mocha_runner.js @@ -94,8 +94,8 @@ module.exports = function mochaRun(progress) { return Promise.each(testGroup, function(test, index, length) { var mocha = new Mocha({ reporter: "spec", - timeout: 50000, //200 caused non-deterministic test failures - //when a test uses timeouts just barely under 200 ms + timeout: "300s", + enableTimeouts: true, slow: Infinity, bail: true });