diff options
author | Petka Antonov <petka_antonov@hotmail.com> | 2015-02-22 14:44:12 +0200 |
---|---|---|
committer | Rod Vagg <rod@vagg.org> | 2015-02-24 18:27:14 -0600 |
commit | 872702d9b712fc60d4e6b06a1e1526e0506abda0 (patch) | |
tree | 8880d64e4816b2b32666de5a530313276fced713 /test/parallel/test-promises-unhandled-rejections.js | |
parent | 8a1e22af3a44d83dbaacbe9f5299a62bb8e279e0 (diff) | |
download | android-node-v8-872702d9b712fc60d4e6b06a1e1526e0506abda0.tar.gz android-node-v8-872702d9b712fc60d4e6b06a1e1526e0506abda0.tar.bz2 android-node-v8-872702d9b712fc60d4e6b06a1e1526e0506abda0.zip |
node: implement unhandled rejection tracking
Implement unhandled rejection tracking for promises as
specified in https://gist.github.com/benjamingr/0237932cee84712951a2
Fixes #256
PR-URL: https://github.com/iojs/io.js/pull/758
Reviewed-By: Trevor Norris <trev.norris@gmail.com>
Reviewed-By: Ben Noordhuis <info@bnoordhuis.nl>
Reviewed-By: Domenic Denicola <domenic@domenicdenicola.com>
Diffstat (limited to 'test/parallel/test-promises-unhandled-rejections.js')
-rw-r--r-- | test/parallel/test-promises-unhandled-rejections.js | 606 |
1 files changed, 606 insertions, 0 deletions
diff --git a/test/parallel/test-promises-unhandled-rejections.js b/test/parallel/test-promises-unhandled-rejections.js new file mode 100644 index 0000000000..997d147a6d --- /dev/null +++ b/test/parallel/test-promises-unhandled-rejections.js @@ -0,0 +1,606 @@ +var common = require('../common'); +var assert = require('assert'); +var domain = require('domain'); + +var asyncTest = (function() { + var asyncTestsEnabled = false; + var asyncTestLastCheck; + var asyncTestQueue = []; + var asyncTestHandle; + var currentTest = null; + + function fail(error) { + var stack = currentTest + ? error.stack + '\nFrom previous event:\n' + currentTest.stack + : error.stack; + + if (currentTest) + process.stderr.write('\'' + currentTest.description + '\' failed\n\n'); + + process.stderr.write(stack); + process.exit(2); + } + + function nextAsyncTest() { + var called = false; + function done(err) { + if (called) return fail(new Error('done called twice')); + called = true; + asyncTestLastCheck = Date.now(); + if (arguments.length > 0) return fail(err); + setTimeout(nextAsyncTest, 10); + } + + if (asyncTestQueue.length) { + var test = asyncTestQueue.shift(); + currentTest = test; + test.action(done); + } else { + clearInterval(asyncTestHandle); + } + } + + return function asyncTest(description, fn) { + var stack = new Error().stack.split('\n').slice(1).join('\n'); + asyncTestQueue.push({ + action: fn, + stack: stack, + description: description + }); + if (!asyncTestsEnabled) { + asyncTestsEnabled = true; + asyncTestLastCheck = Date.now(); + process.on('uncaughtException', fail); + asyncTestHandle = setInterval(function() { + var now = Date.now(); + if (now - asyncTestLastCheck > 10000) { + return fail(new Error('Async test timeout exceeded')); + } + }, 10); + setTimeout(nextAsyncTest, 10); + } + }; + +})(); + +function setupException(fn) { + var listeners = process.listeners('uncaughtException'); + process.removeAllListeners('uncaughtException'); + process.on('uncaughtException', fn); + return function clean() { + process.removeListener('uncaughtException', fn); + listeners.forEach(function(listener) { + process.on('uncaughtException', listener); + }); + }; +} + +function clean() { + process.removeAllListeners('unhandledRejection'); + process.removeAllListeners('rejectionHandled'); +} + +function onUnhandledSucceed(done, predicate) { + clean(); + process.on('unhandledRejection', function(reason, promise) { + try { + predicate(reason, promise); + } catch (e) { + return done(e); + } + done(); + }); +} + +function onUnhandledFail(done) { + clean(); + process.on('unhandledRejection', function(reason, promise) { + done(new Error('unhandledRejection not supposed to be triggered')); + }); + process.on('rejectionHandled', function() { + done(new Error('rejectionHandled not supposed to be triggered')); + }); + setTimeout(function() { + done(); + }, 10); +} + +asyncTest('synchronously rejected promise should trigger unhandledRejection', function(done) { + var e = new Error(); + onUnhandledSucceed(done, function(reason, promise) { + assert.strictEqual(e, reason); + }); + Promise.reject(e); +}); + +asyncTest('synchronously rejected promise should trigger unhandledRejection', function(done) { + var e = new Error(); + onUnhandledSucceed(done, function(reason, promise) { + assert.strictEqual(e, reason); + }); + new Promise(function(_, reject) { + reject(e); + }); +}); + +asyncTest('Promise rejected after setImmediate should trigger unhandledRejection', function(done) { + var e = new Error(); + onUnhandledSucceed(done, function(reason, promise) { + assert.strictEqual(e, reason); + }); + new Promise(function(_, reject) { + setImmediate(function() { + reject(e); + }); + }); +}); + +asyncTest('Promise rejected after setTimeout(,1) should trigger unhandled rejection', function(done) { + var e = new Error(); + onUnhandledSucceed(done, function(reason, promise) { + assert.strictEqual(e, reason); + }); + new Promise(function(_, reject) { + setTimeout(function() { + reject(e); + }, 1); + }); +}); + +asyncTest('Catching a promise rejection after setImmediate is not soon enough to stop unhandledRejection', function(done) { + var e = new Error(); + onUnhandledSucceed(done, function(reason, promise) { + assert.strictEqual(e, reason); + }); + var _reject; + var promise = new Promise(function(_, reject) { + _reject = reject; + }) + _reject(e); + setImmediate(function() { + promise.then(assert.fail, function(){}); + }); +}); + +asyncTest('When re-throwing new errors in a promise catch, only the re-thrown error should hit unhandledRejection', function(done) { + var e = new Error(); + var e2 = new Error(); + onUnhandledSucceed(done, function(reason, promise) { + assert.strictEqual(e2, reason); + assert.strictEqual(promise2, promise); + }); + var promise2 = Promise.reject(e).then(assert.fail, function(reason) { + assert.strictEqual(e, reason); + throw e2; + }); +}); + +asyncTest('Test params of unhandledRejection for a synchronously-rejected promise', function(done) { + var e = new Error(); + var e2 = new Error(); + onUnhandledSucceed(done, function(reason, promise) { + assert.strictEqual(e, reason); + assert.strictEqual(promise, promise); + }); + var promise = Promise.reject(e); +}); + +asyncTest('When re-throwing new errors in a promise catch, only the re-thrown error should hit unhandledRejection: original promise rejected async with setTimeout(,1)', function(done) { + var e = new Error(); + var e2 = new Error(); + onUnhandledSucceed(done, function(reason, promise) { + assert.strictEqual(e2, reason); + assert.strictEqual(promise2, promise); + }); + var promise2 = new Promise(function(_, reject) { + setTimeout(function() { + reject(e); + }, 1); + }).then(assert.fail, function(reason) { + assert.strictEqual(e, reason); + throw e2; + }); +}); + +asyncTest('When re-throwing new errors in a promise catch, only the re-thrown error should hit unhandledRejection: promise catch attached a process.nextTick after rejection', function(done) { + var e = new Error(); + var e2 = new Error(); + onUnhandledSucceed(done, function(reason, promise) { + assert.strictEqual(e2, reason); + assert.strictEqual(promise2, promise); + }); + var promise = new Promise(function(_, reject) { + setTimeout(function() { + reject(e); + process.nextTick(function() { + promise2 = promise.then(assert.fail, function(reason) { + assert.strictEqual(e, reason); + throw e2; + }); + }); + }, 1); + }); + var promise2; +}); + +asyncTest('unhandledRejection should not be triggered if a promise catch is attached synchronously upon the promise\'s creation', function(done) { + var e = new Error(); + onUnhandledFail(done); + Promise.reject(e).then(assert.fail, function(){}); +}); + +asyncTest('unhandledRejection should not be triggered if a promise catch is attached synchronously upon the promise\'s creation', function(done) { + var e = new Error(); + onUnhandledFail(done); + new Promise(function(_, reject) { + reject(e); + }).then(assert.fail, function(){}); +}); + +asyncTest('Attaching a promise catch in a process.nextTick is soon enough to prevent unhandledRejection', function(done) { + var e = new Error(); + onUnhandledFail(done); + var promise = Promise.reject(e); + process.nextTick(function() { + promise.then(assert.fail, function(){}); + }); +}); + +asyncTest('Attaching a promise catch in a process.nextTick is soon enough to prevent unhandledRejection', function(done) { + var e = new Error(); + onUnhandledFail(done); + var promise = new Promise(function(_, reject) { + reject(e); + }); + process.nextTick(function() { + promise.then(assert.fail, function(){}); + }); +}); + +// State adapation tests +asyncTest('catching a promise which is asynchronously rejected (via resolution to an asynchronously-rejected promise) prevents unhandledRejection', function(done) { + var e = new Error(); + onUnhandledFail(done); + Promise.resolve().then(function() { + return new Promise(function(_, reject) { + setTimeout(function() { + reject(e); + }, 1); + }); + }).then(assert.fail, function(reason) { + assert.strictEqual(e, reason); + }); +}); + +asyncTest('Catching a rejected promise derived from throwing in a fulfillment handler prevents unhandledRejection', function(done) { + var e = new Error(); + onUnhandledFail(done); + Promise.resolve().then(function() { + throw e; + }).then(assert.fail, function(reason) { + assert.strictEqual(e, reason); + }); +}); + +asyncTest('Catching a rejected promise derived from returning a synchronously-rejected promise in a fulfillment handler prevents unhandledRejection', function(done) { + var e = new Error(); + onUnhandledFail(done); + Promise.resolve().then(function() { + return Promise.reject(e); + }).then(assert.fail, function(reason) { + assert.strictEqual(e, reason); + }); +}); + +asyncTest('A rejected promise derived from returning an asynchronously-rejected promise in a fulfillment handler does trigger unhandledRejection', function(done) { + var e = new Error(); + var _promise; + onUnhandledSucceed(done, function(reason, promise) { + assert.strictEqual(e, reason); + assert.strictEqual(_promise, promise); + }); + _promise = Promise.resolve().then(function() { + return new Promise(function(_, reject) { + setTimeout(function() { + reject(e); + }, 1); + }); + }); +}); + +asyncTest('A rejected promise derived from throwing in a fulfillment handler does trigger unhandledRejection', function(done) { + var e = new Error(); + var _promise; + onUnhandledSucceed(done, function(reason, promise) { + assert.strictEqual(e, reason); + assert.strictEqual(_promise, promise); + }); + _promise = Promise.resolve().then(function() { + throw e; + }); +}); + +asyncTest('A rejected promise derived from returning a synchronously-rejected promise in a fulfillment handler does trigger unhandledRejection', function(done) { + var e = new Error(); + var _promise; + onUnhandledSucceed(done, function(reason, promise) { + assert.strictEqual(e, reason); + assert.strictEqual(_promise, promise); + }); + _promise = Promise.resolve().then(function() { + return Promise.reject(e); + }); +}); + +// Combinations with Promise.all +asyncTest('Catching the Promise.all() of a collection that includes a rejected promise prevents unhandledRejection', function(done) { + var e = new Error(); + onUnhandledFail(done); + Promise.all([Promise.reject(e)]).then(assert.fail, function() {}); +}); + +asyncTest('Catching the Promise.all() of a collection that includes a nextTick-async rejected promise prevents unhandledRejection', function(done) { + var e = new Error(); + onUnhandledFail(done); + var p = new Promise(function(_, reject) { + process.nextTick(function() { + reject(e); + }); + }); + p = Promise.all([p]); + process.nextTick(function() { + p.then(assert.fail, function() {}); + }); +}); + +asyncTest('Failing to catch the Promise.all() of a collection that includes a rejected promise triggers unhandledRejection for the returned promise, not the passed promise', function(done) { + var e = new Error(); + onUnhandledSucceed(done, function(reason, promise) { + assert.strictEqual(e, reason); + assert.strictEqual(p, promise); + }); + var p = Promise.all([Promise.reject(e)]); +}); + +asyncTest('Waiting setTimeout(, 10) to catch a promise causes an unhandledRejection + rejectionHandled pair', function(done) { + clean(); + var unhandledPromises = []; + var e = new Error(); + process.on('unhandledRejection', function(reason, promise) { + assert.strictEqual(e, reason); + unhandledPromises.push(promise); + }); + process.on('rejectionHandled', function(promise) { + assert.strictEqual(1, unhandledPromises.length); + assert.strictEqual(unhandledPromises[0], promise); + assert.strictEqual(thePromise, promise); + done(); + }); + + var thePromise = new Promise(function() { + throw e; + }); + setTimeout(function() { + thePromise.then(assert.fail, function(reason) { + assert.strictEqual(e, reason); + }); + }, 10); +}); + +asyncTest('Waiting for some combination of process.nextTick + promise microtasks to attach a catch handler is still soon enough to prevent unhandledRejection', function(done) { + var e = new Error(); + onUnhandledFail(done); + + + var a = Promise.reject(e); + process.nextTick(function() { + Promise.resolve().then(function() { + process.nextTick(function() { + Promise.resolve().then(function() { + a.catch(function() {}); + }); + }); + }); + }); +}); + +asyncTest('Waiting for some combination of process.nextTick + promise microtasks to attach a catch handler is still soon enough to prevent unhandledRejection: inside setImmediate', function(done) { + var e = new Error(); + onUnhandledFail(done); + + setImmediate(function() { + var a = Promise.reject(e); + process.nextTick(function() { + Promise.resolve().then(function() { + process.nextTick(function() { + Promise.resolve().then(function() { + a.catch(function() {}); + }); + }); + }); + }); + }); +}); + +asyncTest('Waiting for some combination of process.nextTick + promise microtasks to attach a catch handler is still soon enough to prevent unhandledRejection: inside setTimeout', function(done) { + var e = new Error(); + onUnhandledFail(done); + + setTimeout(function() { + var a = Promise.reject(e); + process.nextTick(function() { + Promise.resolve().then(function() { + process.nextTick(function() { + Promise.resolve().then(function() { + a.catch(function() {}); + }); + }); + }); + }); + }, 0); +}); + +asyncTest('Waiting for some combination of promise microtasks + process.nextTick to attach a catch handler is still soon enough to prevent unhandledRejection', function(done) { + var e = new Error(); + onUnhandledFail(done); + + + var a = Promise.reject(e); + Promise.resolve().then(function() { + process.nextTick(function() { + Promise.resolve().then(function() { + process.nextTick(function() { + a.catch(function() {}); + }); + }); + }); + }); +}); + +asyncTest('Waiting for some combination of promise microtasks + process.nextTick to attach a catch handler is still soon enough to prevent unhandledRejection: inside setImmediate', function(done) { + var e = new Error(); + onUnhandledFail(done); + + setImmediate(function() { + var a = Promise.reject(e); + Promise.resolve().then(function() { + process.nextTick(function() { + Promise.resolve().then(function() { + process.nextTick(function() { + a.catch(function() {}); + }); + }); + }); + }); + }); +}); + +asyncTest('Waiting for some combination of promise microtasks + process.nextTick to attach a catch handler is still soon enough to prevent unhandledRejection: inside setTimeout', function(done) { + var e = new Error(); + onUnhandledFail(done); + + setTimeout(function() { + var a = Promise.reject(e); + Promise.resolve().then(function() { + process.nextTick(function() { + Promise.resolve().then(function() { + process.nextTick(function() { + a.catch(function() {}); + }); + }); + }); + }); + }, 0); +}); + +asyncTest('setImmediate + promise microtasks is too late to attach a catch handler; unhandledRejection will be triggered in that case. (setImmediate before promise creation/rejection)', function(done) { + var e = new Error(); + onUnhandledSucceed(done, function(reason, promise) { + assert.strictEqual(e, reason); + assert.strictEqual(p, promise); + }); + var p = Promise.reject(e); + setImmediate(function() { + Promise.resolve().then(function () { + p.catch(function(){}); + }); + }); +}); + +asyncTest('setImmediate + promise microtasks is too late to attach a catch handler; unhandledRejection will be triggered in that case (setImmediate before promise creation/rejection)', function(done) { + onUnhandledSucceed(done, function(reason, promise) { + assert.strictEqual(undefined, reason); + assert.strictEqual(p, promise); + }); + setImmediate(function() { + Promise.resolve().then(function () { + Promise.resolve().then(function () { + Promise.resolve().then(function () { + Promise.resolve().then(function () { + p.catch(function(){}); + }); + }); + }); + }); + }); + var p = Promise.reject(); +}); + +asyncTest('setImmediate + promise microtasks is too late to attach a catch handler; unhandledRejection will be triggered in that case (setImmediate after promise creation/rejection)', function(done) { + onUnhandledSucceed(done, function(reason, promise) { + assert.strictEqual(undefined, reason); + assert.strictEqual(p, promise); + }); + var p = Promise.reject(); + setImmediate(function() { + Promise.resolve().then(function () { + Promise.resolve().then(function () { + Promise.resolve().then(function () { + Promise.resolve().then(function () { + p.catch(function(){}); + }); + }); + }); + }); + }); +}); + +asyncTest('Promise unhandledRejection handler does not interfere with domain error handlers being given exceptions thrown from nextTick.', function(done) { + var d = domain.create(); + var domainReceivedError; + d.on('error', function(e) { + domainReceivedError = e; + }); + d.run(function() { + var e = new Error('error'); + var domainError = new Error('domain error'); + onUnhandledSucceed(done, function(reason, promise) { + assert.strictEqual(reason, e); + assert.strictEqual(domainReceivedError, domainError); + d.dispose(); + }); + var a = Promise.reject(e); + process.nextTick(function() { + throw domainError; + }); + }); +}); + +asyncTest('nextTick is immediately scheduled when called inside an event handler', function(done) { + clean(); + var e = new Error('error'); + process.on('unhandledRejection', function(reason, promise) { + var order = []; + process.nextTick(function() { + order.push(1); + }); + setTimeout(function() { + order.push(2); + assert.deepEqual([1,2], order); + done(); + }, 1); + }); + Promise.reject(e); +}); + +asyncTest('Throwing an error inside a rejectionHandled handler goes to unhandledException, and does not cause .catch() to throw an exception', function(done) { + clean(); + var e = new Error(); + var e2 = new Error(); + var tearDownException = setupException(function(err) { + assert.equal(e2, err); + tearDownException(); + done(); + }); + process.on('rejectionHandled', function() { + throw e2; + }); + var p = Promise.reject(e); + setTimeout(function() { + try { + p.catch(function(){}); + } catch (e) { + done(new Error('fail')); + } + }, 1); +}); |