From da9f0435f7a6a1ad21557028540a1c837b95435b Mon Sep 17 00:00:00 2001 From: Bob Evans Date: Thu, 3 Oct 2024 17:10:31 -0400 Subject: [PATCH 1/2] test: Migrated bluebird versioned tests to `node:test` --- test/lib/agent_helper.js | 12 +- test/lib/promises/fixtures.js | 115 + test/lib/promises/helpers.js | 47 + test/lib/promises/transaction-state.js | 195 +- test/versioned/bluebird/fixtures.js | 568 +++++ test/versioned/bluebird/helpers.js | 163 ++ test/versioned/bluebird/methods.js | 2245 ----------------- test/versioned/bluebird/methods.tap.js | 22 - test/versioned/bluebird/methods.test.js | 1835 ++++++++++++++ test/versioned/bluebird/package.json | 6 +- ...regressions.tap.js => regressions.test.js} | 11 +- .../bluebird/transaction-state.tap.js | 31 - .../bluebird/transaction-state.test.js | 21 + test/versioned/when/package.json | 1 + test/versioned/when/when.tap.js | 30 +- test/versioned/when/when.test.js | 44 + 16 files changed, 2840 insertions(+), 2506 deletions(-) create mode 100644 test/lib/promises/fixtures.js create mode 100644 test/lib/promises/helpers.js create mode 100644 test/versioned/bluebird/fixtures.js create mode 100644 test/versioned/bluebird/helpers.js delete mode 100644 test/versioned/bluebird/methods.js delete mode 100644 test/versioned/bluebird/methods.tap.js create mode 100644 test/versioned/bluebird/methods.test.js rename test/versioned/bluebird/{regressions.tap.js => regressions.test.js} (80%) delete mode 100644 test/versioned/bluebird/transaction-state.tap.js create mode 100644 test/versioned/bluebird/transaction-state.test.js create mode 100644 test/versioned/when/when.test.js diff --git a/test/lib/agent_helper.js b/test/lib/agent_helper.js index fbd81f11a1..1a6183ea72 100644 --- a/test/lib/agent_helper.js +++ b/test/lib/agent_helper.js @@ -250,9 +250,15 @@ helper.unloadAgent = (agent, shimmer = require('../../lib/shimmer')) => { helper.loadTestAgent = (t, conf, setState = true) => { const agent = helper.instrumentMockedAgent(conf, setState) - t.teardown(() => { - helper.unloadAgent(agent) - }) + if (t.after) { + t.after(() => { + helper.unloadAgent(agent) + }) + } else { + t.teardown(() => { + helper.unloadAgent(agent) + }) + } return agent } diff --git a/test/lib/promises/fixtures.js b/test/lib/promises/fixtures.js new file mode 100644 index 0000000000..ebe7c91794 --- /dev/null +++ b/test/lib/promises/fixtures.js @@ -0,0 +1,115 @@ +/* + * Copyright 2024 New Relic Corporation. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +'use strict' +const { tspl } = require('@matteo.collina/tspl') +const helper = require('../agent_helper') +const COUNT = 2 +const { checkTransaction, end, runMultiple } = require('./helpers') + +module.exports = function init({ t, agent, Promise }) { + return async function performTests(name, resolve, reject) { + const inTx = doPerformTests({ t, agent, Promise, name, resolve, reject, inTx: true }) + const notInTx = doPerformTests({ t, agent, Promise, name, resolve, reject, inTx: false }) + return Promise.all([inTx, notInTx]) + } +} + +async function doPerformTests({ t, agent, Promise, name, resolve, reject, inTx }) { + name += ' ' + (inTx ? 'with' : 'without') + ' transaction' + + await t.test(name + ': does not cause JSON to crash', async function (t) { + const plan = tspl(t, { plan: 1 * COUNT + 1 }) + + runMultiple( + COUNT, + function (i, cb) { + if (inTx) { + helper.runInTransaction(agent, test) + } else { + test(null) + } + + function test(transaction) { + const p = resolve(Promise).then(end(transaction, cb), end(transaction, cb)) + const d = p.domain + delete p.domain + plan.doesNotThrow(function () { + JSON.stringify(p) + }, 'should not cause stringification to crash') + p.domain = d + } + }, + function (err) { + plan.ok(!err, 'should not error') + } + ) + await plan.completed + }) + + await t.test(name + ': preserves transaction in resolve callback', async function (t) { + const plan = tspl(t, { plan: 4 * COUNT + 1 }) + + runMultiple( + COUNT, + function (i, cb) { + if (inTx) { + helper.runInTransaction(agent, test) + } else { + test(null) + } + + function test(transaction) { + resolve(Promise) + .then(function step() { + plan.ok(1, 'should not change execution profile') + return i + }) + .then(function finalHandler(res) { + plan.equal(res, i, 'should be the correct value') + checkTransaction(plan, agent, transaction) + }) + .then(end(transaction, cb), end(transaction, cb)) + } + }, + function (err) { + plan.ok(!err, 'should not error') + } + ) + await plan.completed + }) + + await t.test(name + ': preserves transaction in reject callback', async function (t) { + const plan = tspl(t, { plan: 3 * COUNT + 1 }) + + runMultiple( + COUNT, + function (i, cb) { + if (inTx) { + helper.runInTransaction(agent, test) + } else { + test(null) + } + + function test(transaction) { + const err = new Error('some error ' + i) + reject(Promise, err) + .then(function unusedStep() { + plan.ok(0, 'should not change execution profile') + }) + .catch(function catchHandler(reason) { + plan.equal(reason, err, 'should be the same error') + checkTransaction(plan, agent, transaction) + }) + .then(end(transaction, cb), end(transaction, cb)) + } + }, + function (err) { + plan.ok(!err, 'should not error') + } + ) + await plan.completed + }) +} diff --git a/test/lib/promises/helpers.js b/test/lib/promises/helpers.js new file mode 100644 index 0000000000..19eca073ae --- /dev/null +++ b/test/lib/promises/helpers.js @@ -0,0 +1,47 @@ +/* + * Copyright 2024 New Relic Corporation. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +'use strict' + +function runMultiple(count, fn, cb) { + let finished = 0 + for (let i = 0; i < count; ++i) { + fn(i, function runMultipleCallback() { + if (++finished >= count) { + cb() + } + }) + } +} + +function checkTransaction(plan, agent, transaction) { + const currentTransaction = agent.getTransaction() + + if (transaction) { + plan.ok(currentTransaction, 'should be in a transaction') + if (!currentTransaction) { + return + } + plan.equal(currentTransaction.id, transaction.id, 'should be the same transaction') + } else { + plan.ok(!currentTransaction, 'should not be in a transaction') + plan.ok(1) // Make test count match for both branches. + } +} + +function end(tx, cb) { + return function () { + if (tx) { + tx.end() + } + cb() + } +} + +module.exports = { + checkTransaction, + end, + runMultiple +} diff --git a/test/lib/promises/transaction-state.js b/test/lib/promises/transaction-state.js index 901a0cd48f..68e9676e55 100644 --- a/test/lib/promises/transaction-state.js +++ b/test/lib/promises/transaction-state.js @@ -6,16 +6,15 @@ 'use strict' const helper = require('../agent_helper') +const { tspl } = require('@matteo.collina/tspl') +const { checkTransaction } = require('./helpers') +const initFixtures = require('./fixtures') -const COUNT = 2 - -module.exports = runTests -runTests.runMultiple = runMultiple - -function runTests(t, agent, Promise, library) { +module.exports = async function runTests({ t, agent, Promise, library }) { + const performTests = initFixtures({ t, agent, Promise }) /* eslint-disable no-shadow, brace-style */ if (library) { - performTests( + await performTests( 'Library Fullfillment Factories', function (Promise, val) { return library.resolve(val) @@ -26,7 +25,7 @@ function runTests(t, agent, Promise, library) { ) } - performTests( + await performTests( 'Promise Fullfillment Factories', function (Promise, val) { return Promise.resolve(val) @@ -36,7 +35,7 @@ function runTests(t, agent, Promise, library) { } ) - performTests( + await performTests( 'New Synchronous', function (Promise, val) { return new Promise(function (res) { @@ -50,7 +49,7 @@ function runTests(t, agent, Promise, library) { } ) - performTests( + await performTests( 'New Asynchronous', function (Promise, val) { return new Promise(function (res) { @@ -69,7 +68,7 @@ function runTests(t, agent, Promise, library) { ) if (Promise.method) { - performTests( + await performTests( 'Promise.method', function (Promise, val) { return Promise.method(function () { @@ -85,7 +84,7 @@ function runTests(t, agent, Promise, library) { } if (Promise.try) { - performTests( + await performTests( 'Promise.try', function (Promise, val) { return Promise.try(function () { @@ -99,112 +98,9 @@ function runTests(t, agent, Promise, library) { } ) } - /* eslint-enable no-shadow, brace-style */ - - function performTests(name, resolve, reject) { - doPerformTests(name, resolve, reject, true) - doPerformTests(name, resolve, reject, false) - } - - function doPerformTests(name, resolve, reject, inTx) { - name += ' ' + (inTx ? 'with' : 'without') + ' transaction' - - t.test(name + ': does not cause JSON to crash', function (t) { - t.plan(1 * COUNT + 1) - - runMultiple( - COUNT, - function (i, cb) { - if (inTx) { - helper.runInTransaction(agent, test) - } else { - test(null) - } - - function test(transaction) { - const p = resolve(Promise).then(end(transaction, cb), end(transaction, cb)) - const d = p.domain - delete p.domain - t.doesNotThrow(function () { - JSON.stringify(p) - }, 'should not cause stringification to crash') - p.domain = d - } - }, - function (err) { - t.error(err, 'should not error') - t.end() - } - ) - }) - - t.test(name + ': preserves transaction in resolve callback', function (t) { - t.plan(4 * COUNT + 1) - - runMultiple( - COUNT, - function (i, cb) { - if (inTx) { - helper.runInTransaction(agent, test) - } else { - test(null) - } - - function test(transaction) { - resolve(Promise) - .then(function step() { - t.pass('should not change execution profile') - return i - }) - .then(function finalHandler(res) { - t.equal(res, i, 'should be the correct value') - checkTransaction(t, agent, transaction) - }) - .then(end(transaction, cb), end(transaction, cb)) - } - }, - function (err) { - t.error(err, 'should not error') - t.end() - } - ) - }) - - t.test(name + ': preserves transaction in reject callback', function (t) { - t.plan(3 * COUNT + 1) - - runMultiple( - COUNT, - function (i, cb) { - if (inTx) { - helper.runInTransaction(agent, test) - } else { - test(null) - } - - function test(transaction) { - const err = new Error('some error ' + i) - reject(Promise, err) - .then(function unusedStep() { - t.fail('should not change execution profile') - }) - .catch(function catchHandler(reason) { - t.equal(reason, err, 'should be the same error') - checkTransaction(t, agent, transaction) - }) - .then(end(transaction, cb), end(transaction, cb)) - } - }, - function (err) { - t.error(err, 'should not error') - t.end() - } - ) - }) - } - t.test('preserves transaction with resolved chained promises', function (t) { - t.plan(4) + await t.test('preserves transaction with resolved chained promises', async function (t) { + const plan = tspl(t, { plan: 4 }) helper.runInTransaction(agent, function transactionWrapper(transaction) { Promise.resolve(0) @@ -215,25 +111,24 @@ function runTests(t, agent, Promise, library) { return 2 }) .then(function finalHandler(res) { - t.equal(res, 2, 'should be the correct result') - checkTransaction(t, agent, transaction) + plan.equal(res, 2, 'should be the correct result') + checkTransaction(plan, agent, transaction) transaction.end() }) .then( function () { - t.pass('should resolve cleanly') - t.end() + plan.ok(1, 'should resolve cleanly') }, - function (err) { - t.fail(err) - t.end() + function () { + plan.ok(0) } ) }) + await plan.completed }) - t.test('preserves transaction with rejected chained promises', function (t) { - t.plan(4) + await t.test('preserves transaction with rejected chained promises', async function (t) { + const plan = tspl(t, { plan: 4 }) helper.runInTransaction(agent, function transactionWrapper(transaction) { const err = new Error('some error') @@ -245,58 +140,22 @@ function runTests(t, agent, Promise, library) { throw err }) .then(function unusedStep() { - t.fail('should not change execution profile') + plan.ok(0, 'should not change execution profile') }) .catch(function catchHandler(reason) { - t.equal(reason, err, 'should be the same error') - checkTransaction(t, agent, transaction) + plan.equal(reason, err, 'should be the same error') + checkTransaction(plan, agent, transaction) transaction.end() }) .then( function finallyHandler() { - t.pass('should resolve cleanly') - t.end() + plan.ok(1, 'should resolve cleanly') }, function (err) { - t.fail(err) - t.end() + plan.ok(!err) } ) }) + await plan.completed }) } - -function runMultiple(count, fn, cb) { - let finished = 0 - for (let i = 0; i < count; ++i) { - fn(i, function runMultipleCallback() { - if (++finished >= count) { - cb() - } - }) - } -} - -function checkTransaction(t, agent, transaction) { - const currentTransaction = agent.getTransaction() - - if (transaction) { - t.ok(currentTransaction, 'should be in a transaction') - if (!currentTransaction) { - return - } - t.equal(currentTransaction.id, transaction.id, 'should be the same transaction') - } else { - t.notOk(currentTransaction, 'should not be in a transaction') - t.pass('') // Make test count match for both branches. - } -} - -function end(tx, cb) { - return function () { - if (tx) { - tx.end() - } - cb() - } -} diff --git a/test/versioned/bluebird/fixtures.js b/test/versioned/bluebird/fixtures.js new file mode 100644 index 0000000000..1c5dcfc38a --- /dev/null +++ b/test/versioned/bluebird/fixtures.js @@ -0,0 +1,568 @@ +/* + * Copyright 2024 New Relic Corporation. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +'use strict' +const test = require('node:test') +const helper = require('../../lib/agent_helper') +const { tspl } = require('@matteo.collina/tspl') +const { + addTask, + afterEach, + beforeEach, + id, + testPromiseClassMethod, + testPromiseInstanceMethod +} = require('./helpers') + +async function testPromiseContext({ t, factory }) { + await t.test('context switch', async function (t) { + const { agent, Promise } = t.nr + factory = factory.bind(null, Promise) + const plan = tspl(t, { plan: 2 }) + + const ctxA = helper.runInTransaction(agent, function (tx) { + return { + transaction: tx, + promise: factory('[tx a] ') + } + }) + + helper.runInTransaction(agent, function (txB) { + t.after(function () { + ctxA.transaction.end() + txB.end() + }) + plan.notEqual(id(ctxA.transaction), id(txB), 'should not be in transaction a') + + ctxA.promise + .catch(function () {}) + .then(function () { + const tx = agent.tracer.getTransaction() + plan.equal(id(tx), id(ctxA.transaction), 'should be in expected context') + }) + }) + await plan.completed + }) + + // Create in tx a, continue outside of tx + await t.test('context loss', async function (t) { + const plan = tspl(t, { plan: 2 }) + const { agent, Promise } = t.nr + factory = factory.bind(null, Promise) + + const ctxA = helper.runInTransaction(agent, function (tx) { + t.after(function () { + tx.end() + }) + + return { + transaction: tx, + promise: factory('[tx a] ') + } + }) + + plan.ok(!agent.tracer.getTransaction(), 'should not be in transaction') + ctxA.promise + .catch(function () {}) + .then(function () { + const tx = agent.tracer.getTransaction() + plan.equal(id(tx), id(ctxA.transaction), 'should be in expected context') + }) + await plan.completed + }) + + // Create outside tx, continue in tx a + await t.test('context gain', async function (t) { + const plan = tspl(t, { plan: 2 }) + const { agent, Promise } = t.nr + factory = factory.bind(null, Promise) + + const promise = factory('[no tx] ') + + plan.ok(!agent.tracer.getTransaction(), 'should not be in transaction') + helper.runInTransaction(agent, function (tx) { + promise + .catch(function () {}) + .then(function () { + const tx2 = agent.tracer.getTransaction() + plan.equal(id(tx2), id(tx), 'should be in expected context') + }) + }) + await plan.completed + }) + + // Create test in tx a, end tx a, continue in tx b + await t.test('context expiration', async function (t) { + const plan = tspl(t, { plan: 2 }) + const { agent, Promise } = t.nr + factory = factory.bind(null, Promise) + + const ctxA = helper.runInTransaction(agent, function (tx) { + return { + transaction: tx, + promise: factory('[tx a] ') + } + }) + + ctxA.transaction.end() + helper.runInTransaction(agent, function (txB) { + t.after(function () { + ctxA.transaction.end() + txB.end() + }) + plan.notEqual(id(ctxA.transaction), id(txB), 'should not be in transaction a') + + ctxA.promise + .catch(function () {}) + .then(function () { + const tx = agent.tracer.getTransaction() + plan.equal(id(tx), id(txB), 'should be in expected context') + }) + }) + await plan.completed + }) +} + +function testTryBehavior(method) { + test('Promise.' + method, async function (t) { + t.beforeEach(beforeEach) + t.afterEach(afterEach) + + await testPromiseContext({ + t, + factory: function (Promise, name) { + return Promise[method](function () { + return name + }) + } + }) + + await t.test('usage', function (t, end) { + const { Promise } = t.nr + testPromiseClassMethod({ + t, + end, + count: 3, + testFunc: function tryTest({ plan, name }) { + return Promise[method](function () { + throw new Error('Promise.' + method + ' test error') + }) + .then( + function () { + plan.ok(0, name + 'should not go into resolve after throwing') + }, + function (err) { + plan.ok(err, name + 'should have error') + plan.equal( + err.message, + 'Promise.' + method + ' test error', + name + 'should be correct error' + ) + } + ) + .then(function () { + const foo = { what: 'Promise.' + method + ' test object' } + return Promise[method](function () { + return foo + }).then(function (obj) { + plan.equal(obj, foo, name + 'should also work on success') + }) + }) + } + }) + }) + }) +} + +async function testThrowBehavior(methodName) { + test('Promise#' + methodName, async function (t) { + t.beforeEach(beforeEach) + t.afterEach(afterEach) + + await testPromiseContext({ + t, + factory: function (Promise, name) { + return Promise.resolve()[methodName](new Error(name)) + } + }) + + await t.test('usage', function (t, end) { + testPromiseInstanceMethod({ + t, + end, + count: 1, + testFunc: function throwTest({ plan, name, promise }) { + const foo = { what: 'throw test object' } + return promise[methodName](foo) + .then(function () { + plan.ok(0, name + 'should not go into resolve handler after throw') + }) + .catch(function (err) { + plan.equal(err, foo, name + 'should pass throught the correct object') + }) + } + }) + }) + + await testPromiseInstanceCastMethod({ + t, + count: 1, + testFunc: function ({ plan, promise, value }) { + return promise.thenThrow(value).catch(function (err) { + plan.equal(err, value, 'should have expected error') + }) + } + }) + }) +} + +function testPromiseClassCastMethod({ t, count, testFunc }) { + return testAllCastTypes({ t, count, factory: testFunc }) +} + +function testPromiseInstanceCastMethod({ t, count, testFunc }) { + return testAllCastTypes({ + t, + count, + factory: function ({ Promise, name, value, plan }) { + return testFunc({ Promise, promise: Promise.resolve(name), name, value, plan }) + } + }) +} + +async function testAllCastTypes({ t, count, factory }) { + const values = [42, 'foobar', {}, [], function () {}] + + await t.test('in context', function (t, end) { + const { agent } = t.nr + const plan = tspl(t, { plan: count * values.length + 1 }) + + helper.runInTransaction(agent, function (tx) { + _test({ plan, t, name: '[no-tx]', i: 0 }) + .then(function () { + const txB = agent.tracer.getTransaction() + plan.equal(id(tx), id(txB), 'should maintain transaction state') + }) + .then(end) + }) + }) + + await t.test('out of context', function (t, end) { + const plan = tspl(t, { plan: count * values.length }) + _test({ plan, t, name: '[no-tx]', i: 0 }) + .catch(function (err) { + plan.ok(!err) + }) + .then(end) + }) + + function _test({ plan, t, name, i }) { + const { Promise } = t.nr + const value = values[i] + return factory({ Promise, name, value, plan }).then(function () { + if (++i < values.length) { + return _test({ plan, t, name, i }) + } + }) + } +} + +function testResolveBehavior(method) { + test('Promise.' + method, async function (t) { + t.beforeEach(beforeEach) + t.afterEach(afterEach) + + await testPromiseContext({ + t, + factory: function (Promise, name) { + return Promise[method](name) + } + }) + + await t.test('usage', function (t, end) { + const { Promise } = t.nr + testPromiseClassMethod({ + t, + end, + count: 1, + testFunc: function tryTest({ plan, name }) { + return Promise[method](name + ' ' + method + ' value').then(function (res) { + plan.equal(res, name + ' ' + method + ' value', name + 'should pass the value') + }) + } + }) + }) + + await testPromiseClassCastMethod({ + t, + count: 1, + testFunc: function ({ plan, Promise, value }) { + return Promise[method](value).then(function (val) { + plan.deepEqual(val, value, 'should have expected value') + }) + } + }) + }) +} + +function testFromCallbackBehavior(methodName) { + test('Promise.' + methodName, async function (t) { + t.beforeEach(beforeEach) + t.afterEach(afterEach) + + await testPromiseContext({ + t, + factory: function (Promise, name) { + return Promise[methodName](function (cb) { + addTask(t.nr, cb, null, name) + }) + } + }) + + await t.test('usage', function (t, end) { + const { Promise } = t.nr + testPromiseClassMethod({ + t, + end, + count: 3, + testFunc: function tryTest({ plan, name }) { + return Promise[methodName](function (cb) { + addTask(t.nr, cb, null, 'foobar ' + name) + }) + .then(function (res) { + plan.equal(res, 'foobar ' + name, name + 'should pass result through') + + return Promise[methodName](function (cb) { + addTask(t.nr, cb, new Error('Promise.' + methodName + ' test error')) + }) + }) + .then( + function () { + plan.ok(0, name + 'should not resolve after rejecting') + }, + function (err) { + plan.ok(err, name + 'should have an error') + plan.equal( + err.message, + 'Promise.' + methodName + ' test error', + name + 'should have correct error' + ) + } + ) + } + }) + }) + }) +} + +function testFinallyBehavior(methodName) { + test('Promise#' + methodName, async function (t) { + t.beforeEach(beforeEach) + t.afterEach(afterEach) + + await testPromiseContext({ + t, + factory: function (Promise, name) { + return Promise.resolve(name)[methodName](function () {}) + } + }) + + await t.test('usage', function (t, end) { + testPromiseInstanceMethod({ + t, + end, + count: 6, + testFunc: function throwTest({ plan, name, promise }) { + return promise[methodName](function () { + plan.equal(arguments.length, 0, name + 'should not receive any parameters') + }) + .then(function (res) { + plan.deepEqual( + res, + [1, 2, 3, name], + name + 'should pass values beyond ' + methodName + ' handler' + ) + throw new Error('Promise#' + methodName + ' test error') + }) + [methodName](function () { + plan.equal(arguments.length, 0, name + 'should not receive any parameters') + plan.ok(1, name + 'should go into ' + methodName + ' handler from rejected promise') + }) + .catch(function (err) { + plan.ok(err, name + 'should pass error beyond ' + methodName + ' handler') + plan.equal( + err.message, + 'Promise#' + methodName + ' test error', + name + 'should be correct error' + ) + }) + } + }) + }) + }) +} + +function testRejectBehavior(method) { + test('Promise.' + method, async function (t) { + t.beforeEach(beforeEach) + t.afterEach(afterEach) + + await testPromiseContext({ + t, + factory: function (Promise, name) { + return Promise[method](name) + } + }) + + await t.test('usage', function (t, end) { + const { Promise } = t.nr + testPromiseClassMethod({ + t, + end, + count: 1, + testFunc: function rejectTest({ plan, name }) { + return Promise[method](name + ' ' + method + ' value').then( + function () { + plan.ok(0, name + 'should not resolve after a rejection') + }, + function (err) { + plan.equal(err, name + ' ' + method + ' value', name + 'should reject with the err') + } + ) + } + }) + }) + + await testPromiseClassCastMethod({ + t, + count: 1, + testFunc: function ({ plan, Promise, name, value }) { + return Promise[method](value).then( + function () { + plan.ok(0, name + 'should not resolve after a rejection') + }, + function (err) { + plan.equal(err, value, name + 'should reject with correct error') + } + ) + } + }) + }) +} + +function testAsCallbackBehavior(methodName) { + test('Promise#' + methodName, async function (t) { + t.beforeEach(beforeEach) + t.afterEach(afterEach) + + await testPromiseContext({ + t, + factory: function (Promise, name) { + return Promise.resolve(name)[methodName](function () {}) + } + }) + + await t.test('usage', function (t, end) { + const { agent } = t.nr + testPromiseInstanceMethod({ + t, + end, + count: 8, + testFunc: function asCallbackTest({ plan, name, promise }) { + const startTransaction = agent.getTransaction() + return promise[methodName](function (err, result) { + const inCallbackTransaction = agent.getTransaction() + plan.equal( + id(startTransaction), + id(inCallbackTransaction), + name + 'should have the same transaction inside the success callback' + ) + plan.ok(!err, name + 'should not have an error') + plan.deepEqual(result, [1, 2, 3, name], name + 'should have the correct result value') + }) + .then(function () { + throw new Error('Promise#' + methodName + ' test error') + }) + .then(function () { + plan.ok(0, name + 'should have skipped then after rejection') + }) + [methodName](function (err, result) { + const inCallbackTransaction = agent.getTransaction() + plan.equal( + id(startTransaction), + id(inCallbackTransaction), + name + 'should have the same transaction inside the error callback' + ) + plan.ok(err, name + 'should have error in ' + methodName) + plan.ok(!result, name + 'should not have a result') + plan.equal( + err.message, + 'Promise#' + methodName + ' test error', + name + 'should be the correct error' + ) + }) + .catch(function (err) { + plan.ok(err, name + 'should have error in catch too') + // Swallowing error that doesn't get caught in the asCallback/nodeify. + }) + } + }) + }) + }) +} + +function testCatchBehavior(methodName) { + test('Promise#' + methodName, async function (t) { + t.beforeEach(beforeEach) + t.afterEach(afterEach) + + await testPromiseContext({ + t, + factory: function (Promise, name) { + return Promise.reject(new Error(name))[methodName](function (err) { + return err + }) + } + }) + + await t.test('usage', function (t, end) { + testPromiseInstanceMethod({ + t, + end, + count: 2, + testFunc: function asCallbackTest({ plan, name, promise }) { + return promise[methodName](function (err) { + plan.ok(!err, name + 'should not go into ' + methodName + ' from a resolved promise') + }) + .then(function () { + throw new Error('Promise#' + methodName + ' test error') + }) + [methodName](function (err) { + plan.ok(err, name + 'should pass error into rejection handler') + plan.equal( + err.message, + 'Promise#' + methodName + ' test error', + name + 'should be correct error' + ) + }) + } + }) + }) + }) +} + +module.exports = { + testAsCallbackBehavior, + testCatchBehavior, + testFinallyBehavior, + testPromiseClassCastMethod, + testPromiseInstanceCastMethod, + testPromiseContext, + testRejectBehavior, + testResolveBehavior, + testFromCallbackBehavior, + testTryBehavior, + testThrowBehavior +} diff --git a/test/versioned/bluebird/helpers.js b/test/versioned/bluebird/helpers.js new file mode 100644 index 0000000000..6009ea1857 --- /dev/null +++ b/test/versioned/bluebird/helpers.js @@ -0,0 +1,163 @@ +/* + * Copyright 2024 New Relic Corporation. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +'use strict' +const assert = require('node:assert') +const { runMultiple } = require('../../lib/promises/helpers') +const { tspl } = require('@matteo.collina/tspl') +const symbols = require('../../../lib/symbols') +const helper = require('../../lib/agent_helper') +const util = require('util') +const setImmediatePromisified = util.promisify(setImmediate) + +async function beforeEach(ctx) { + ctx.nr = {} + ctx.nr.agent = helper.instrumentMockedAgent() + ctx.nr.Promise = require('bluebird') + ctx.nr.tasks = [] + ctx.nr.interval = setInterval(function () { + if (ctx.nr.tasks.length) { + ctx.nr.tasks.pop()() + } + }, 25) + + await setImmediatePromisified() +} + +async function afterEach(ctx) { + helper.unloadAgent(ctx.nr.agent) + clearInterval(ctx.nr.interval) + + await setImmediatePromisified() +} + +function id(tx) { + return tx && tx.id +} + +function addTask() { + const args = [].slice.apply(arguments) + const { tasks } = args.shift() // Pop test context + const fn = args.shift() // Pop function from args + tasks.push(function () { + fn.apply(null, args) + }) +} + +function testPromiseClassMethod({ t, count, testFunc, end }) { + testPromiseMethod({ t, count, factory: testFunc, end }) +} + +function testPromiseInstanceMethod({ t, count, testFunc, end }) { + const { Promise } = t.nr + testPromiseMethod({ + t, + end, + count, + factory: function ({ plan, name }) { + const promise = Promise.resolve([1, 2, 3, name]) + return testFunc({ plan, name, promise }) + } + }) +} + +function testPromiseMethod({ t, count, factory, end }) { + const { agent } = t.nr + const COUNT = 2 + const plan = tspl(t, { plan: count * 3 + (COUNT + 1) * 3 }) + + plan.doesNotThrow(function outTXPromiseThrowTest() { + const name = '[no tx] ' + let isAsync = false + factory({ plan, name }) + .finally(function () { + plan.ok(isAsync, name + 'should have executed asynchronously') + }) + .then( + function () { + plan.ok(!agent.getTransaction(), name + 'has no transaction') + testInTransaction() + }, + function (err) { + if (err) { + /* eslint-disable no-console */ + console.log(err.stack) + /* eslint-enable no-console */ + } + plan.ok(!err, name + 'should not result in error') + end() + } + ) + isAsync = true + }, '[no tx] should not throw out of a transaction') + + function testInTransaction() { + runMultiple( + COUNT, + function (i, cb) { + helper.runInTransaction(agent, function transactionWrapper(transaction) { + const name = '[tx ' + i + '] ' + plan.doesNotThrow(function inTXPromiseThrowTest() { + let isAsync = false + factory({ plan, name }) + .finally(function () { + plan.ok(isAsync, name + 'should have executed asynchronously') + }) + .then( + function () { + plan.equal( + id(agent.getTransaction()), + id(transaction), + name + 'has the right transaction' + ) + }, + function (err) { + if (err) { + /* eslint-disable no-console */ + console.log(err) + console.log(err.stack) + /* eslint-enable no-console */ + } + plan.ok(!err, name + 'should not result in error') + } + ) + .finally(cb) + isAsync = true + }, name + 'should not throw in a transaction') + }) + }, + function () { + end() + } + ) + } +} + +function areMethodsWrapped(source) { + const methods = Object.keys(source).sort() + methods.forEach((method) => { + const wrapped = source[method] + const original = wrapped[symbols.original] + + // Skip this property if it is internal (starts or ends with underscore), is + // a class (starts with a capital letter), or is not a function. + if (/(?:^[_A-Z]|_$)/.test(method) || typeof original !== 'function') { + return + } + + assert.ok(original, `${method} original exists`) + assert.notEqual(wrapped, original, `${method} wrapped is not diff from original`) + }) +} + +module.exports = { + addTask, + afterEach, + areMethodsWrapped, + beforeEach, + id, + testPromiseClassMethod, + testPromiseInstanceMethod +} diff --git a/test/versioned/bluebird/methods.js b/test/versioned/bluebird/methods.js deleted file mode 100644 index 25aead2343..0000000000 --- a/test/versioned/bluebird/methods.js +++ /dev/null @@ -1,2245 +0,0 @@ -/* - * Copyright 2020 New Relic Corporation. All rights reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -'use strict' - -const helper = require('../../lib/agent_helper') -const testTransactionState = require('../../lib/promises/transaction-state') -const util = require('util') -const symbols = require('../../../lib/symbols') - -const runMultiple = testTransactionState.runMultiple -const tasks = [] -let interval = null - -const setImmediatePromisified = util.promisify(setImmediate) - -module.exports = function (t, library, loadLibrary) { - loadLibrary = - loadLibrary || - function () { - return require(library) - } - const ptap = new PromiseTap(t, loadLibrary()) - - t.beforeEach(async () => { - if (interval) { - clearInterval(interval) - } - interval = setInterval(function () { - if (tasks.length) { - tasks.pop()() - } - }, 25) - - await setImmediatePromisified() - }) - - t.afterEach(async () => { - clearInterval(interval) - interval = null - - await setImmediatePromisified() - }) - - ptap.test('new Promise() throw', function (t) { - testPromiseClassMethod(t, 2, function throwTest(Promise, name) { - try { - return new Promise(function () { - throw new Error(name + ' test error') - }).then( - function () { - t.fail(name + ' Error should have been caught.') - }, - function (err) { - t.ok(err, name + ' Error should go to the reject handler') - t.equal(err.message, name + ' test error', name + ' Error should be as expected') - } - ) - } catch (e) { - t.error(e) - t.fail(name + ' Should have gone to reject handler') - } - }) - }) - - ptap.test('new Promise() resolve then throw', function (t) { - testPromiseClassMethod(t, 1, function resolveThrowTest(Promise, name) { - try { - return new Promise(function (resolve) { - resolve(name + ' foo') - throw new Error(name + ' test error') - }).then( - function (res) { - t.equal(res, name + ' foo', name + ' promise should be resolved.') - }, - function () { - t.fail(name + ' Error should have been swallowed by promise.') - } - ) - } catch (e) { - t.fail(name + ' Error should have passed to `reject`.') - } - }) - }) - - ptap.test('new Promise -> resolve', function (t) { - t.plan(2) - - t.test('context', function (t) { - testPromiseContext(t, function (Promise, name) { - return new Promise(function (resolve) { - resolve(name) - }) - }) - }) - - t.test('usage', function (t) { - testPromiseClassMethod(t, 3, function resolveTest(Promise, name) { - const contextManager = helper.getContextManager() - const inTx = !!contextManager.getContext() - - return new Promise(function (resolve) { - addTask(function () { - t.notOk(contextManager.getContext(), name + 'should lose tx') - resolve('foobar ' + name) - }) - }).then(function (res) { - if (inTx) { - t.ok(contextManager.getContext(), name + 'should return tx') - } else { - t.notOk(contextManager.getContext(), name + 'should not create tx') - } - t.equal(res, 'foobar ' + name, name + 'should resolve with correct value') - }) - }) - }) - }) - - // ------------------------------------------------------------------------ // - // ------------------------------------------------------------------------ // - // ------------------------------------------------------------------------ // - - ptap.test('Promise.all', function (t) { - t.plan(2) - - t.test('context', function (t) { - testPromiseContext(t, function (Promise, name) { - return Promise.all([name]) - }) - }) - - t.test('usage', function (t) { - testPromiseClassMethod(t, 1, function (Promise, name) { - const p1 = Promise.resolve(name + '1') - const p2 = Promise.resolve(name + '2') - - return Promise.all([p1, p2]).then(function (result) { - t.deepEqual(result, [name + '1', name + '2'], name + 'should not change result') - }) - }) - }) - }) - - ptap.test('Promise.allSettled', function (t) { - t.plan(2) - - t.test('context', function (t) { - testPromiseContext(t, function (Promise, name) { - return Promise.allSettled([Promise.resolve(name), Promise.reject(name)]) - }) - }) - - t.test('usage', function (t) { - testPromiseClassMethod(t, 1, function (Promise, name) { - const p1 = Promise.resolve(name + '1') - const p2 = Promise.reject(name + '2') - - return Promise.allSettled([p1, p2]).then(function (inspections) { - const result = inspections.map(function (i) { - return i.isFulfilled() ? { value: i.value() } : { reason: i.reason() } - }) - t.deepEqual( - result, - [{ value: name + '1' }, { reason: name + '2' }], - name + 'should not change result' - ) - }) - }) - }) - }) - - ptap.test('Promise.any', function (t) { - t.plan(2) - - t.test('context', function (t) { - testPromiseContext(t, function (Promise, name) { - return Promise.any([name]) - }) - }) - - t.test('usage', function (t) { - testPromiseClassMethod(t, 1, function (Promise, name) { - return Promise.any([ - Promise.reject(name + 'rejection!'), - Promise.resolve(name + 'resolved'), - Promise.delay(15, name + 'delayed') - ]).then(function (result) { - t.equal(result, name + 'resolved', 'should not change the result') - }) - }) - }) - }) - - testTryBehavior('attempt') - - ptap.test('Promise.bind', function (t) { - t.plan(3) - - t.test('context', function (t) { - testPromiseContext(t, function (Promise, name) { - return Promise.bind(name) - }) - }) - - t.test('usage', function (t) { - testPromiseClassMethod(t, 2, function (Promise, name) { - const ctx = {} - return Promise.bind(ctx, name).then(function (value) { - t.equal(this, ctx, 'should have expected `this` value') - t.equal(value, name, 'should not change passed value') - }) - }) - }) - - t.test('casting', function (t) { - testPromiseClassCastMethod(t, 4, function (t, Promise, name, ctx) { - return Promise.bind(ctx, name).then(function (value) { - t.equal(this, ctx, 'should have expected `this` value') - t.equal(value, name, 'should not change passed value') - - // Try with this context type in both positions. - return Promise.bind(name, ctx).then(function (val2) { - t.equal(this, name, 'should have expected `this` value') - t.equal(val2, ctx, 'should not change passed value') - }) - }) - }) - }) - }) - - testResolveBehavior('cast') - ptap.skip('Promise.config') - - ptap.test('Promise.coroutine', function (t) { - t.plan(2) - - t.test('context', function (t) { - testPromiseContext(t, function (Promise, name) { - return Promise.coroutine(function* (_name) { - for (let i = 0; i < 10; ++i) { - yield Promise.delay(5) - } - return _name - })(name) - }) - }) - - t.test('usage', function (t) { - testPromiseClassMethod(t, 4, function (Promise, name) { - let count = 0 - - t.doesNotThrow(function () { - Promise.coroutine.addYieldHandler(function (value) { - if (value === name) { - t.pass('should call yield handler') - return Promise.resolve(value + ' yielded') - } - }) - }, 'should be able to add yield handler') - - return Promise.coroutine(function* (_name) { - for (let i = 0; i < 10; ++i) { - yield Promise.delay(5) - ++count - } - return yield _name - })(name).then(function (result) { - t.equal(count, 10, 'should step through whole coroutine') - t.equal(result, name + ' yielded', 'should pass through resolve value') - }) - }) - }) - }) - - ptap.skip('Promise.defer') - - ptap.test('Promise.delay', function (t) { - t.plan(3) - - t.test('context', function (t) { - testPromiseContext(t, function (Promise, name) { - return Promise.delay(5, name) - }) - }) - - t.test('usage', function (t) { - testPromiseClassMethod(t, 3, function (Promise, name) { - const DELAY = 500 - const MARGIN = 100 - const start = Date.now() - return Promise.delay(DELAY, name).then(function (result) { - const duration = Date.now() - start - t.ok(duration < DELAY + MARGIN, 'should not take more than expected time') - t.ok(duration > DELAY - MARGIN, 'should not take less than expected time') - t.equal(result, name, 'should pass through resolve value') - }) - }) - }) - - t.test('casting', function (t) { - testPromiseClassCastMethod(t, 1, function (t, Promise, name, value) { - return Promise.delay(5, value).then(function (val) { - t.equal(val, value, 'should have expected value') - }) - }) - }) - }) - - ptap.test('Promise.each', function (t) { - t.plan(2) - - t.test('context', function (t) { - testPromiseContext(t, function (Promise, name) { - return Promise.each([name], function () {}) - }) - }) - - t.test('usage', function (t) { - testPromiseClassMethod(t, 5, function (Promise, name) { - return Promise.each( - [ - Promise.resolve(name + '1'), - Promise.resolve(name + '2'), - Promise.resolve(name + '3'), - Promise.resolve(name + '4') - ], - function (value, i) { - t.equal(value, name + (i + 1), 'should not change input to iterator') - } - ).then(function (result) { - t.deepEqual(result, [name + '1', name + '2', name + '3', name + '4']) - }) - }) - }) - }) - - ptap.test('Promise.filter', function (t) { - t.plan(2) - - t.test('context', function (t) { - testPromiseContext(t, function (Promise, name) { - return Promise.filter([name], function () { - return true - }) - }) - }) - - t.test('usage', function (t) { - testPromiseClassMethod(t, 1, function (Promise, name) { - return Promise.filter( - [ - Promise.resolve(name + '1'), - Promise.resolve(name + '2'), - Promise.resolve(name + '3'), - Promise.resolve(name + '4') - ], - function (value) { - return Promise.resolve(/[24]$/.test(value)) - } - ).then(function (result) { - t.deepEqual(result, [name + '2', name + '4'], 'should not change the result') - }) - }) - }) - }) - - testResolveBehavior('fulfilled') - testFromCallbackBehavior('fromCallback') - testFromCallbackBehavior('fromNode') - - ptap.test('Promise.getNewLibraryCopy', function (t) { - helper.loadTestAgent(t) - const Promise = loadLibrary() - const Promise2 = Promise.getNewLibraryCopy() - - t.ok(Promise2.resolve[symbols.original], 'should have wrapped class methods') - t.ok(Promise2.prototype.then[symbols.original], 'should have wrapped instance methods') - t.end() - }) - - ptap.skip('Promise.hasLongStackTraces') - - ptap.test('Promise.is', function (t) { - helper.loadTestAgent(t) - const Promise = loadLibrary() - - let p = new Promise(function (resolve) { - setImmediate(resolve) - }) - t.ok(Promise.is(p), 'should not break promise identification (new)') - - p = p.then(function () {}) - t.ok(Promise.is(p), 'should not break promise identification (then)') - - t.end() - }) - - ptap.test('Promise.join', function (t) { - t.plan(3) - - t.test('context', function (t) { - testPromiseContext(t, function (Promise, name) { - return Promise.join(name) - }) - }) - - t.test('usage', function (t) { - testPromiseClassMethod(t, 1, function joinTest(Promise, name) { - return Promise.join( - Promise.resolve(1), - Promise.resolve(2), - Promise.resolve(3), - Promise.resolve(name) - ).then(function (res) { - t.same(res, [1, 2, 3, name], name + 'should have all the values') - }) - }) - }) - - t.test('casting', function (t) { - testPromiseClassCastMethod(t, 1, function (t, Promise, name, value) { - return Promise.join(value, name).then(function (values) { - t.deepEqual(values, [value, name], 'should have expected values') - }) - }) - }) - }) - - ptap.skip('Promise.longStackTraces') - - ptap.test('Promise.map', function (t) { - t.plan(2) - - t.test('context', function (t) { - testPromiseContext(t, function (Promise, name) { - return Promise.map([name], function (v) { - return v.toUpperCase() - }) - }) - }) - - t.test('usage', function (t) { - testPromiseClassMethod(t, 1, function (Promise, name) { - return Promise.map([Promise.resolve('1'), Promise.resolve('2')], function (item) { - return Promise.resolve(name + item) - }).then(function (result) { - t.deepEqual(result, [name + '1', name + '2'], 'should not change the result') - }) - }) - }) - }) - - ptap.test('Promise.mapSeries', function (t) { - t.plan(2) - - t.test('context', function (t) { - testPromiseContext(t, function (Promise, name) { - return Promise.mapSeries([name], function (v) { - return v.toUpperCase() - }) - }) - }) - - t.test('usage', function (t) { - testPromiseClassMethod(t, 1, function (Promise, name) { - return Promise.mapSeries([Promise.resolve('1'), Promise.resolve('2')], function (item) { - return Promise.resolve(name + item) - }).then(function (result) { - t.deepEqual(result, [name + '1', name + '2'], 'should not change the result') - }) - }) - }) - }) - - ptap.test('Promise.method', function (t) { - t.plan(2) - - t.test('context', function (t) { - testPromiseContext(t, function (Promise, name) { - return Promise.method(function () { - return name - })() - }) - }) - - t.test('usage', function (t) { - testPromiseClassMethod(t, 3, function methodTest(Promise, name) { - const fn = Promise.method(function () { - throw new Error('Promise.method test error') - }) - - return fn() - .then( - function () { - t.fail(name + 'should not go into resolve after throwing') - }, - function (err) { - t.ok(err, name + 'should have error') - if (err) { - t.equal(err.message, 'Promise.method test error', name + 'should be correct error') - } - } - ) - .then(function () { - const foo = { what: 'Promise.method test object' } - const fn2 = Promise.method(function () { - return foo - }) - - return fn2().then(function (obj) { - t.equal(obj, foo, name + 'should also work on success') - }) - }) - }) - }) - }) - - ptap.test('Promise.noConflict', function (t) { - helper.loadTestAgent(t) - const Promise = loadLibrary() - const Promise2 = Promise.noConflict() - - t.ok(Promise2.resolve[symbols.original], 'should have wrapped class methods') - t.ok(Promise2.prototype.then[symbols.original], 'should have wrapped instance methods') - t.end() - }) - - ptap.skip('Promise.onPossiblyUnhandledRejection') - ptap.skip('Promise.onUnhandledRejectionHandled') - - ptap.test('Promise.promisify', function (t) { - t.plan(2) - - t.test('context', function (t) { - testPromiseContext(t, function (Promise, name) { - return Promise.promisify(function (cb) { - cb(null, name) - })() - }) - }) - - t.test('usage', function (t) { - testPromiseClassMethod(t, 4, function methodTest(Promise, name) { - const fn = Promise.promisify(function (cb) { - cb(new Error('Promise.promisify test error')) - }) - - // Test error handling. - return fn() - .then( - function () { - t.fail(name + 'should not go into resolve after throwing') - }, - function (err) { - t.ok(err, name + 'should have error') - if (err) { - t.equal( - err.message, - 'Promise.promisify test error', - name + 'should be correct error' - ) - } - } - ) - .then(function () { - // Test success handling. - const foo = { what: 'Promise.promisify test object' } - const fn2 = Promise.promisify(function (cb) { - cb(null, foo) - }) - - return fn2().then(function (obj) { - t.equal(obj, foo, name + 'should also work on success') - }) - }) - .then(() => { - // Test property copying. - const unwrapped = (cb) => cb() - const property = { name } - unwrapped.property = property - - const wrapped = Promise.promisify(unwrapped) - t.equal(wrapped.property, property, 'should have copied properties') - }) - }) - }) - }) - - // XXX: Promise.promisifyAll _does_ cause state loss due to the construction - // of an internal promise that doesn't use the normal executor. However, - // instrumenting this method is treacherous as we will have to mimic the - // library's own property-finding techniques. In bluebird's case this - // involves walking the prototype chain and collecting the name of every - // property on every prototype. - ptap.skip('Promise.promisifyAll') - - ptap.test('Promise.props', function (t) { - t.plan(2) - - t.test('context', function (t) { - testPromiseContext(t, function (Promise, name) { - return Promise.props({ name: name }) - }) - }) - - t.test('usage', function (t) { - testPromiseClassMethod(t, 1, function (Promise, name) { - return Promise.props({ - first: Promise.resolve(name + '1'), - second: Promise.resolve(name + '2') - }).then(function (result) { - t.deepEqual( - result, - { first: name + '1', second: name + '2' }, - 'should not change results' - ) - }) - }) - }) - }) - - ptap.test('Promise.race', function (t) { - t.plan(2) - - t.test('context', function (t) { - testPromiseContext(t, function (Promise, name) { - return Promise.race([name]) - }) - }) - - t.test('usage', function (t) { - testPromiseClassMethod(t, 1, function (Promise, name) { - return Promise.race([ - Promise.resolve(name + 'resolved'), - Promise.reject(name + 'rejection!'), - Promise.delay(15, name + 'delayed') - ]).then(function (result) { - t.equal(result, name + 'resolved', 'should not change the result') - }) - }) - }) - }) - - ptap.test('Promise.reduce', function (t) { - t.plan(2) - - t.test('context', function (t) { - testPromiseContext(t, function (Promise, name) { - return Promise.reduce([name, name], function (a, b) { - return a + b - }) - }) - }) - - t.test('usage', function (t) { - testPromiseClassMethod(t, 1, function (Promise, name) { - return Promise.reduce( - [Promise.resolve('1'), Promise.resolve('2'), Promise.resolve('3'), Promise.resolve('4')], - function (a, b) { - return Promise.resolve(name + a + b) - } - ).then(function (result) { - t.equal(result, name + name + name + '1234', 'should not change the result') - }) - }) - }) - }) - - ptap.skip('Promise.pending') - testRejectBehavior('reject') - testRejectBehavior('rejected') - testResolveBehavior('resolve') - - // Settle was deprecated in Bluebird v3. - ptap.skip('Promise.settle') - ptap.skip('Promise.setScheduler') - - ptap.test('Promise.some', function (t) { - t.plan(2) - - t.test('context', function (t) { - testPromiseContext(t, function (Promise, name) { - return Promise.some([name], 1) - }) - }) - - t.test('usage', function (t) { - testPromiseClassMethod(t, 1, function (Promise, name) { - return Promise.some( - [ - Promise.resolve(name + 'resolved'), - Promise.reject(name + 'rejection!'), - Promise.delay(100, name + 'delayed more'), - Promise.delay(5, name + 'delayed') - ], - 2 - ).then(function (result) { - t.deepEqual(result, [name + 'resolved', name + 'delayed'], 'should not change the result') - }) - }) - }) - }) - - // Spawn was deprecated in Bluebird v3. - ptap.skip('Promise.spawn') - testTryBehavior('try') - ptap.skip('Promise.using') - - // ------------------------------------------------------------------------ // - // ------------------------------------------------------------------------ // - // ------------------------------------------------------------------------ // - - ptap.test('Promise#all', function (t) { - t.plan(2) - - t.test('context', function (t) { - testPromiseContext(t, function (Promise, name) { - return Promise.resolve([Promise.resolve(name + '1'), Promise.resolve(name + '2')]).all() - }) - }) - - t.test('usage', function (t) { - testPromiseInstanceMethod(t, 1, function (Promise, p, name) { - return p - .then(function () { - return [Promise.resolve(name + '1'), Promise.resolve(name + '2')] - }) - .all() - .then(function (result) { - t.deepEqual(result, [name + '1', name + '2'], name + 'should not change result') - }) - }) - }) - }) - - ptap.test('Promise#any', function (t) { - t.plan(2) - - t.test('context', function (t) { - testPromiseContext(t, function (Promise, name) { - return Promise.resolve([ - Promise.reject(name + 'rejection!'), - Promise.resolve(name + 'resolved'), - Promise.delay(15, name + 'delayed') - ]).any() - }) - }) - - t.test('usage', function (t) { - testPromiseInstanceMethod(t, 1, function (Promise, p, name) { - return p - .then(function () { - return [ - Promise.reject(name + 'rejection!'), - Promise.resolve(name + 'resolved'), - Promise.delay(15, name + 'delayed') - ] - }) - .any() - .then(function (result) { - t.equal(result, name + 'resolved', 'should not change the result') - }) - }) - }) - }) - - testAsCallbackBehavior('asCallback') - - ptap.test('Promise#bind', function (t) { - t.plan(3) - - t.test('context', function (t) { - testPromiseContext(t, function (Promise, name) { - return Promise.resolve(name).bind({ name: name }) - }) - }) - - t.test('usage', function (t) { - testPromiseInstanceMethod(t, 4, function bindTest(Promise, p, name) { - const foo = { what: 'test object' } - const ctx2 = { what: 'a different test object' } - const err = new Error('oh dear') - return p - .bind(foo) - .then(function (res) { - t.equal(this, foo, name + 'should have correct this value') - t.same(res, [1, 2, 3, name], name + 'parameters should be correct') - - return Promise.reject(err) - }) - .bind(ctx2, name) - .catch(function (reason) { - t.equal(this, ctx2, 'should have expected `this` value') - t.equal(reason, err, 'should not change rejection reason') - }) - }) - }) - - t.test('casting', function (t) { - testPromiseInstanceCastMethod(t, 2, function (t, Promise, p, name, value) { - return p.bind(value).then(function (val) { - t.equal(this, value, 'should have correct context') - t.equal(val, name, 'should have expected value') - }) - }) - }) - }) - - ptap.skip('Promise#break') - - ptap.test('Promise#call', function (t) { - t.plan(2) - - t.test('context', function (t) { - testPromiseContext(t, function (Promise, name) { - return Promise.resolve({ - foo: function () { - return Promise.resolve(name) - } - }).call('foo') - }) - }) - - t.test('usage', function (t) { - testPromiseInstanceMethod(t, 3, function callTest(Promise, p, name) { - const foo = { - test: function () { - t.equal(this, foo, name + 'should have correct this value') - t.pass(name + 'should call the test method of foo') - return 'foobar' - } - } - return p - .then(function () { - return foo - }) - .call('test') - .then(function (res) { - t.same(res, 'foobar', name + 'parameters should be correct') - }) - }) - }) - }) - - ptap.skip('Promise#cancel') - - testCatchBehavior('catch') - - ptap.test('Promise#catchReturn', function (t) { - t.plan(3) - - t.test('context', function (t) { - testPromiseContext(t, function (Promise, name) { - return Promise.reject(new Error()).catchReturn(name) - }) - }) - - t.test('usage', function (t) { - testPromiseInstanceMethod(t, 1, function catchReturnTest(Promise, p, name) { - const foo = { what: 'catchReturn test object' } - return p - .throw(new Error('catchReturn test error')) - .catchReturn(foo) - .then(function (res) { - t.equal(res, foo, name + 'should pass throught the correct object') - }) - }) - }) - - t.test('casting', function (t) { - testPromiseInstanceCastMethod(t, 1, function (t, Promise, p, name, value) { - return p - .then(function () { - throw new Error('woops') - }) - .catchReturn(value) - .then(function (val) { - t.equal(val, value, 'should have expected value') - }) - }) - }) - }) - - ptap.test('Promise#catchThrow', function (t) { - t.plan(3) - - t.test('context', function (t) { - testPromiseContext(t, function (Promise, name) { - return Promise.reject(new Error()).catchThrow(new Error(name)) - }) - }) - - t.test('usage', function (t) { - testPromiseInstanceMethod(t, 1, function catchThrowTest(Promise, p, name) { - const foo = { what: 'catchThrow test object' } - return p - .throw(new Error('catchThrow test error')) - .catchThrow(foo) - .catch(function (err) { - t.equal(err, foo, name + 'should pass throught the correct object') - }) - }) - }) - - t.test('casting', function (t) { - testPromiseInstanceCastMethod(t, 1, function (t, Promise, p, name, value) { - return p - .then(function () { - throw new Error('woops') - }) - .catchThrow(value) - .catch(function (err) { - t.equal(err, value, 'should have expected error') - }) - }) - }) - }) - - testCatchBehavior('caught') - - ptap.test('Promise#delay', function (t) { - t.plan(2) - - t.test('context', function (t) { - testPromiseContext(t, function (Promise, name) { - return Promise.resolve(name).delay(10) - }) - }) - - t.test('usage', function (t) { - testPromiseInstanceMethod(t, 3, function delayTest(Promise, p, name) { - const DELAY = 500 - const MARGIN = 100 - const start = Date.now() - return p - .return(name) - .delay(DELAY) - .then(function (result) { - const duration = Date.now() - start - t.ok(duration < DELAY + MARGIN, 'should not take more than expected time') - t.ok(duration > DELAY - MARGIN, 'should not take less than expected time') - t.equal(result, name, 'should pass through resolve value') - }) - }) - }) - }) - - ptap.skip('Promise#disposer') - ptap.skip('Promise#done') - - ptap.test('Promise#each', function (t) { - t.plan(2) - - t.test('context', function (t) { - testPromiseContext(t, function (Promise, name) { - return Promise.resolve([ - Promise.delay(Math.random() * 10, name + '1'), - Promise.delay(Math.random() * 10, name + '2'), - Promise.delay(Math.random() * 10, name + '3'), - Promise.delay(Math.random() * 10, name + '4') - ]).each(function (value, i) { - return Promise.delay(i, value) - }) - }) - }) - - t.test('usage', function (t) { - testPromiseInstanceMethod(t, 5, function (Promise, p, name) { - return p - .then(function () { - return [ - Promise.delay(Math.random() * 10, name + '1'), - Promise.delay(Math.random() * 10, name + '2'), - Promise.delay(Math.random() * 10, name + '3'), - Promise.delay(Math.random() * 10, name + '4') - ] - }) - .each(function (value, i) { - t.equal(value, name + (i + 1), 'should not change input to iterator') - }) - .then(function (result) { - t.deepEqual(result, [name + '1', name + '2', name + '3', name + '4']) - }) - }) - }) - }) - - ptap.test('Promise#error', function (t) { - t.plan(2) - - function OperationalError(message) { - this.message = message - this.isOperational = true - } - - t.test('context', function (t) { - testPromiseContext(t, function (Promise, name) { - return Promise.reject(new OperationalError(name)).error(function (err) { - return err - }) - }) - }) - - t.test('usage', function (t) { - testPromiseInstanceMethod(t, 2, function catchTest(Promise, p, name) { - return p - .error(function (err) { - t.error(err, name + 'should not go into error from a resolved promise') - }) - .then(function () { - throw new OperationalError('Promise#error test error') - }) - .error(function (err) { - t.ok(err, name + 'should pass error into rejection handler') - t.equal( - err && err.message, - 'Promise#error test error', - name + 'should be correct error' - ) - }) - }) - }) - }) - - ptap.test('Promise#filter', function (t) { - t.plan(2) - - t.test('context', function (t) { - testPromiseContext(t, function (Promise, name) { - return Promise.resolve([ - Promise.resolve(name + '1'), - Promise.resolve(name + '2'), - Promise.resolve(name + '3'), - Promise.resolve(name + '4') - ]).filter(function (value, i) { - return Promise.delay(i, /[24]$/.test(value)) - }) - }) - }) - - t.test('usage', function (t) { - testPromiseInstanceMethod(t, 1, function (Promise, p, name) { - return p - .then(function () { - return [ - Promise.resolve(name + '1'), - Promise.resolve(name + '2'), - Promise.resolve(name + '3'), - Promise.resolve(name + '4') - ] - }) - .filter(function (value) { - return Promise.resolve(/[24]$/.test(value)) - }) - .then(function (result) { - t.deepEqual(result, [name + '2', name + '4'], 'should not change the result') - }) - }) - }) - }) - - testFinallyBehavior('finally') - - ptap.test('Promise#get', function (t) { - t.plan(2) - - t.test('context', function (t) { - testPromiseContext(t, function (Promise, name) { - return Promise.resolve({ name: Promise.resolve(name) }).get('name') - }) - }) - - t.test('usage', function (t) { - testPromiseInstanceMethod(t, 1, function getTest(Promise, p, name) { - return p.get('length').then(function (res) { - t.equal(res, 4, name + 'should get the property specified') - }) - }) - }) - }) - - ptap.skip('Promise#isCancellable') - ptap.skip('Promise#isCancelled') - ptap.skip('Promise#isFulfilled') - ptap.skip('Promise#isPending') - ptap.skip('Promise#isRejected') - ptap.skip('Promise#isResolved') - - testFinallyBehavior('lastly') - - ptap.test('Promise#map', function (t) { - t.plan(2) - - t.test('context', function (t) { - testPromiseContext(t, function (Promise, name) { - return Promise.resolve([Promise.resolve('1'), Promise.resolve('2')]).map(function (item) { - return Promise.resolve(name + item) - }) - }) - }) - - t.test('usage', function (t) { - testPromiseInstanceMethod(t, 1, function (Promise, p, name) { - return p - .then(function () { - return [Promise.resolve('1'), Promise.resolve('2')] - }) - .map(function (item) { - return Promise.resolve(name + item) - }) - .then(function (result) { - t.deepEqual(result, [name + '1', name + '2'], 'should not change the result') - }) - }) - }) - }) - - ptap.test('Promise#mapSeries', function (t) { - t.plan(2) - - t.test('context', function (t) { - testPromiseContext(t, function (Promise, name) { - return Promise.resolve([Promise.resolve('1'), Promise.resolve('2')]).mapSeries(function ( - item - ) { - return Promise.resolve(name + item) - }) - }) - }) - - t.test('usage', function (t) { - testPromiseInstanceMethod(t, 1, function (Promise, p, name) { - return p - .then(function () { - return [Promise.resolve('1'), Promise.resolve('2')] - }) - .mapSeries(function (item) { - return Promise.resolve(name + item) - }) - .then(function (result) { - t.deepEqual(result, [name + '1', name + '2'], 'should not change the result') - }) - }) - }) - }) - - testAsCallbackBehavior('nodeify') - - ptap.test('Promise#props', function (t) { - t.plan(2) - - t.test('context', function (t) { - testPromiseContext(t, function (Promise, name) { - return Promise.resolve({ - first: Promise.delay(5, name + '1'), - second: Promise.delay(5, name + '2') - }).props() - }) - }) - - t.test('usage', function (t) { - testPromiseInstanceMethod(t, 1, function (Promise, p, name) { - return p - .then(function () { - return { - first: Promise.resolve(name + '1'), - second: Promise.resolve(name + '2') - } - }) - .props() - .then(function (result) { - t.deepEqual( - result, - { first: name + '1', second: name + '2' }, - 'should not change results' - ) - }) - }) - }) - }) - - ptap.test('Promise#race', function (t) { - t.plan(2) - - t.test('context', function (t) { - testPromiseContext(t, function (Promise, name) { - return Promise.resolve([ - Promise.resolve(name + 'resolved'), - Promise.delay(15, name + 'delayed') - ]).race() - }) - }) - - t.test('usage', function (t) { - testPromiseInstanceMethod(t, 1, function (Promise, p, name) { - return p - .then(function () { - return [Promise.resolve(name + 'resolved'), Promise.delay(15, name + 'delayed')] - }) - .race() - .then(function (result) { - t.equal(result, name + 'resolved', 'should not change the result') - }) - }) - }) - }) - - ptap.skip('Promise#reason') - - ptap.test('Promise#reduce', function (t) { - t.plan(2) - - t.test('context', function (t) { - testPromiseContext(t, function (Promise, name) { - return Promise.resolve([ - Promise.resolve('1'), - Promise.resolve('2'), - Promise.resolve('3'), - Promise.resolve('4') - ]).reduce(function (a, b) { - return Promise.resolve(name + a + b) - }) - }) - }) - - t.test('usage', function (t) { - testPromiseInstanceMethod(t, 1, function (Promise, p, name) { - return p - .then(function () { - return [ - Promise.resolve('1'), - Promise.resolve('2'), - Promise.resolve('3'), - Promise.resolve('4') - ] - }) - .reduce(function (a, b) { - return Promise.resolve(name + a + b) - }) - .then(function (result) { - t.equal(result, name + name + name + '1234', 'should not change the result') - }) - }) - }) - }) - - ptap.test('Promise#reflect', function (t) { - t.plan(2) - - t.test('context', function (t) { - testPromiseContext(t, function (Promise, name) { - return Promise.resolve(name).reflect() - }) - }) - - t.test('usage', function (t) { - testPromiseInstanceMethod(t, 12, function (Promise, p, name) { - return p - .reflect() - .then(function (inspection) { - // Inspection of a resolved promise. - t.comment('resolved promise inspection') - t.notOk(inspection.isPending(), name + 'should not be pending') - t.notOk(inspection.isRejected(), name + 'should not be rejected') - t.ok(inspection.isFulfilled(), name + 'should be fulfilled') - t.notOk(inspection.isCancelled(), name + 'should not be cancelled') - t.throws(function () { - inspection.reason() - }, name + 'should throw when accessing reason') - t.ok(inspection.value(), name + 'should have the value') - }) - .throw(new Error(name + 'test error')) - .reflect() - .then(function (inspection) { - // Inspection of a rejected promise. - t.comment('rejected promise inspection') - t.notOk(inspection.isPending(), name + 'should not be pending') - t.ok(inspection.isRejected(), name + 'should be rejected') - t.notOk(inspection.isFulfilled(), name + 'should not be fulfilled') - t.notOk(inspection.isCancelled(), name + 'should not be cancelled') - t.ok(inspection.reason(), name + 'should have the reason for rejection') - t.throws(function () { - inspection.value() - }, 'should throw accessing the value') - }) - }) - }) - }) - - ptap.test('Promise#return', function (t) { - t.plan(3) - - t.test('context', function (t) { - testPromiseContext(t, function (Promise, name) { - return Promise.resolve().return(name) - }) - }) - - t.test('usage', function (t) { - testPromiseInstanceMethod(t, 1, function returnTest(Promise, p, name) { - const foo = { what: 'return test object' } - return p.return(foo).then(function (res) { - t.equal(res, foo, name + 'should pass throught the correct object') - }) - }) - }) - - t.test('casting', function (t) { - testPromiseInstanceCastMethod(t, 1, function (t, Promise, p, name, value) { - return p.return(value).then(function (val) { - t.equal(val, value, 'should have expected value') - }) - }) - }) - }) - - ptap.skip('Promise#settle') - - ptap.test('Promise#some', function (t) { - t.plan(2) - - t.test('context', function (t) { - testPromiseContext(t, function (Promise, name) { - return Promise.resolve([ - Promise.resolve(name + 'resolved'), - Promise.reject(name + 'rejection!'), - Promise.delay(100, name + 'delayed more'), - Promise.delay(5, name + 'delayed') - ]).some(2) - }) - }) - - t.test('usage', function (t) { - testPromiseInstanceMethod(t, 1, function (Promise, p, name) { - return p - .then(function () { - return [ - Promise.resolve(name + 'resolved'), - Promise.reject(name + 'rejection!'), - Promise.delay(100, name + 'delayed more'), - Promise.delay(5, name + 'delayed') - ] - }) - .some(2) - .then(function (result) { - t.deepEqual( - result, - [name + 'resolved', name + 'delayed'], - 'should not change the result' - ) - }) - }) - }) - }) - - ptap.test('Promise#spread', function (t) { - t.plan(2) - - t.test('context', function (t) { - testPromiseContext(t, function (Promise, name) { - return Promise.resolve([name, 1, 2, 3, 4]).spread(function () {}) - }) - }) - - t.test('usage', function (t) { - testPromiseInstanceMethod(t, 1, function spreadTest(Promise, p, name) { - return p.spread(function (a, b, c, d) { - t.same([a, b, c, d], [1, 2, 3, name], name + 'parameters should be correct') - }) - }) - }) - }) - - ptap.skip('Promise#suppressUnhandledRejections') - - ptap.test('Promise#tap', function (t) { - t.plan(2) - - t.test('context', function (t) { - testPromiseContext(t, function (Promise, name) { - return Promise.resolve(name).tap(function () {}) - }) - }) - - t.test('usage', function (t) { - testPromiseInstanceMethod(t, 4, function tapTest(Promise, p, name) { - return p - .tap(function (res) { - t.same(res, [1, 2, 3, name], name + 'should pass values into tap handler') - }) - .then(function (res) { - t.same(res, [1, 2, 3, name], name + 'should pass values beyond tap handler') - throw new Error('Promise#tap test error') - }) - .tap(function () { - t.fail(name + 'should not call tap after rejected promises') - }) - .catch(function (err) { - t.ok(err, name + 'should pass error beyond tap handler') - t.equal(err && err.message, 'Promise#tap test error', name + 'should be correct error') - }) - }) - }) - }) - - ptap.test('Promise#tapCatch', function (t) { - t.plan(2) - - t.test('context', function (t) { - testPromiseContext(t, function (Promise, name) { - return Promise.reject(new Error(name)).tapCatch(function () {}) - }) - }) - - t.test('usage', function (t) { - testPromiseInstanceMethod(t, 3, function tapTest(Promise, p, name) { - return p - .throw(new Error(name)) - .tapCatch(function (err) { - t.equal(err && err.message, name, name + 'should pass values into tapCatch handler') - }) - .then(function () { - t.fail('should not enter following resolve handler') - }) - .catch(function (err) { - t.equal(err && err.message, name, name + 'should pass values beyond tapCatch handler') - return name + 'resolve test' - }) - .tapCatch(function () { - t.fail(name + 'should not call tapCatch after resolved promises') - }) - .then(function (value) { - t.equal(value, name + 'resolve test', name + 'should pass error beyond tap handler') - }) - }) - }) - }) - - ptap.test('Promise#then', function (t) { - t.plan(2) - - t.test('context', function (t) { - testPromiseContext(t, function (Promise, name) { - return Promise.resolve().then(function () { - return name - }) - }) - }) - - t.test('usage', function (t) { - testPromiseInstanceMethod(t, 3, function thenTest(Promise, p, name) { - return p - .then(function (res) { - t.same(res, [1, 2, 3, name], name + 'should have the correct result value') - throw new Error('Promise#then test error') - }) - .then( - function () { - t.fail(name + 'should not go into resolve handler from rejected promise') - }, - function (err) { - t.ok(err, name + 'should pass error into thenned rejection handler') - t.equal( - err && err.message, - 'Promise#then test error', - name + 'should be correct error' - ) - } - ) - }) - }) - }) - - ptap.test('Promise#thenReturn', function (t) { - t.plan(3) - - t.test('context', function (t) { - testPromiseContext(t, function (Promise, name) { - return Promise.resolve().thenReturn(name) - }) - }) - - t.test('usage', function (t) { - testPromiseInstanceMethod(t, 3, function thenTest(Promise, p, name) { - return p - .thenReturn(name) - .then(function (res) { - t.same(res, name, name + 'should have the correct result value') - throw new Error('Promise#then test error') - }) - .thenReturn('oops!') - .then( - function () { - t.fail(name + 'should not go into resolve handler from rejected promise') - }, - function (err) { - t.ok(err, name + 'should pass error into thenned rejection handler') - t.equal( - err && err.message, - 'Promise#then test error', - name + 'should be correct error' - ) - } - ) - }) - }) - - t.test('casting', function (t) { - testPromiseInstanceCastMethod(t, 1, function (t, Promise, p, name, value) { - return p.thenReturn(value).then(function (val) { - t.equal(val, value, 'should have expected value') - }) - }) - }) - }) - - testThrowBehavior('thenThrow') - - ptap.test('Promise#timeout', function (t) { - t.plan(2) - - t.test('context', function (t) { - testPromiseContext(t, function (Promise, name) { - return Promise.resolve(name).timeout(10) - }) - }) - - t.test('usage', function (t) { - testPromiseInstanceMethod(t, 4, function (Promise, p, name) { - let start = null - return p - .timeout(1000) - .then( - function (res) { - t.same(res, [1, 2, 3, name], name + 'should pass values into tap handler') - start = Date.now() - }, - function (err) { - t.error(err, name + 'should not have timed out') - } - ) - .delay(1000, 'never see me') - .timeout(500, name + 'timed out') - .then( - function () { - t.fail(name + 'should have timed out long delay') - }, - function (err) { - const duration = Date.now() - start - t.ok(duration < 600, name + 'should not timeout slower than expected') - t.ok(duration > 400, name + 'should not timeout faster than expected') - t.equal(err.message, name + 'timed out', name + 'should have expected error') - } - ) - }) - }) - }) - - testThrowBehavior('throw') - - ptap.skip('Promise#toJSON') - ptap.skip('Promise#toString') - ptap.skip('Promise#value') - - // Check the tests against the library's own static and instance methods. - ptap.check(loadLibrary) - - // ------------------------------------------------------------------------ // - // ------------------------------------------------------------------------ // - // ------------------------------------------------------------------------ // - - function testAsCallbackBehavior(methodName) { - ptap.test('Promise#' + methodName, function (t) { - t.plan(2) - - t.test('context', function (t) { - testPromiseContext(t, function (Promise, name) { - return Promise.resolve(name)[methodName](function () {}) - }) - }) - - t.test('usage', function (t) { - testPromiseInstanceMethod(t, 8, function asCallbackTest(Promise, p, name, agent) { - const startTransaction = agent.getTransaction() - return p[methodName](function (err, result) { - const inCallbackTransaction = agent.getTransaction() - t.equal( - id(startTransaction), - id(inCallbackTransaction), - name + 'should have the same transaction inside the success callback' - ) - t.notOk(err, name + 'should not have an error') - t.same(result, [1, 2, 3, name], name + 'should have the correct result value') - }) - .then(function () { - throw new Error('Promise#' + methodName + ' test error') - }) - .then(function () { - t.fail(name + 'should have skipped then after rejection') - }) - [methodName](function (err, result) { - const inCallbackTransaction = agent.getTransaction() - t.equal( - id(startTransaction), - id(inCallbackTransaction), - name + 'should have the same transaction inside the error callback' - ) - t.ok(err, name + 'should have error in ' + methodName) - t.notOk(result, name + 'should not have a result') - if (err) { - t.equal( - err.message, - 'Promise#' + methodName + ' test error', - name + 'should be the correct error' - ) - } - }) - .catch(function (err) { - t.ok(err, name + 'should have error in catch too') - // Swallowing error that doesn't get caught in the asCallback/nodeify. - }) - }) - }) - }) - } - - function testCatchBehavior(methodName) { - ptap.test('Promise#' + methodName, function (t) { - t.plan(2) - - t.test('context', function (t) { - testPromiseContext(t, function (Promise, name) { - return Promise.reject(new Error(name))[methodName](function (err) { - return err - }) - }) - }) - - t.test('usage', function (t) { - testPromiseInstanceMethod(t, 2, function catchTest(Promise, p, name) { - return p[methodName](function (err) { - t.error(err, name + 'should not go into ' + methodName + ' from a resolved promise') - }) - .then(function () { - throw new Error('Promise#' + methodName + ' test error') - }) - [methodName](function (err) { - t.ok(err, name + 'should pass error into rejection handler') - t.equal( - err && err.message, - 'Promise#' + methodName + ' test error', - name + 'should be correct error' - ) - }) - }) - }) - }) - } - - function testFinallyBehavior(methodName) { - ptap.test('Promise#' + methodName, function (t) { - t.plan(2) - - t.test('context', function (t) { - testPromiseContext(t, function (Promise, name) { - return Promise.resolve(name)[methodName](function () {}) - }) - }) - - t.test('usage', function (t) { - testPromiseInstanceMethod(t, 6, function finallyTest(Promise, p, name) { - return p[methodName](function () { - t.equal(arguments.length, 0, name + 'should not receive any parameters') - }) - .then(function (res) { - t.same( - res, - [1, 2, 3, name], - name + 'should pass values beyond ' + methodName + ' handler' - ) - throw new Error('Promise#' + methodName + ' test error') - }) - [methodName](function () { - t.equal(arguments.length, 0, name + 'should not receive any parameters') - t.pass(name + 'should go into ' + methodName + ' handler from rejected promise') - }) - .catch(function (err) { - t.ok(err, name + 'should pass error beyond ' + methodName + ' handler') - if (err) { - t.equal( - err.message, - 'Promise#' + methodName + ' test error', - name + 'should be correct error' - ) - } - }) - }) - }) - }) - } - - function testFromCallbackBehavior(methodName) { - ptap.test('Promise.' + methodName, function (t) { - t.plan(2) - - t.test('context', function (t) { - testPromiseContext(t, function (Promise, name) { - return Promise[methodName](function (cb) { - addTask(cb, null, name) - }) - }) - }) - - t.test('usage', function (t) { - testPromiseClassMethod(t, 3, function fromCallbackTest(Promise, name) { - return Promise[methodName](function (cb) { - addTask(cb, null, 'foobar ' + name) - }) - .then(function (res) { - t.equal(res, 'foobar ' + name, name + 'should pass result through') - - return Promise[methodName](function (cb) { - addTask(cb, new Error('Promise.' + methodName + ' test error')) - }) - }) - .then( - function () { - t.fail(name + 'should not resolve after rejecting') - }, - function (err) { - t.ok(err, name + 'should have an error') - if (err) { - t.equal( - err.message, - 'Promise.' + methodName + ' test error', - name + 'should have correct error' - ) - } - } - ) - }) - }) - }) - } - - function testRejectBehavior(method) { - ptap.test('Promise.' + method, function (t) { - t.plan(3) - - t.test('context', function (t) { - testPromiseContext(t, function (Promise, name) { - return Promise[method](name) - }) - }) - - t.test('usage', function (t) { - testPromiseClassMethod(t, 1, function rejectTest(Promise, name) { - return Promise[method](name + ' ' + method + ' value').then( - function () { - t.fail(name + 'should not resolve after a rejection') - }, - function (err) { - t.equal(err, name + ' ' + method + ' value', name + 'should reject with the err') - } - ) - }) - }) - - t.test('casting', function (t) { - testPromiseClassCastMethod(t, 1, function (t, Promise, name, value) { - return Promise[method](value).then( - function () { - t.fail(name + 'should not resolve after a rejection') - }, - function (err) { - t.equal(err, value, name + 'should reject with correct error') - } - ) - }) - }) - }) - } - - function testResolveBehavior(method) { - ptap.test('Promise.' + method, function (t) { - t.plan(3) - - t.test('context', function (t) { - testPromiseContext(t, function (Promise, name) { - return Promise[method](name) - }) - }) - - t.test('usage', function (t) { - testPromiseClassMethod(t, 1, function resolveTest(Promise, name) { - return Promise[method](name + ' ' + method + ' value').then(function (res) { - t.equal(res, name + ' ' + method + ' value', name + 'should pass the value') - }) - }) - }) - - t.test('casting', function (t) { - testPromiseClassCastMethod(t, 1, function (t, Promise, name, value) { - return Promise[method](value).then(function (val) { - t.deepEqual(val, value, 'should have expected value') - }) - }) - }) - }) - } - - function testThrowBehavior(methodName) { - ptap.test('Promise#' + methodName, function (t) { - t.plan(3) - - t.test('context', function (t) { - testPromiseContext(t, function (Promise, name) { - return Promise.resolve()[methodName](new Error(name)) - }) - }) - - t.test('usage', function (t) { - testPromiseInstanceMethod(t, 1, function throwTest(Promise, p, name) { - const foo = { what: 'throw test object' } - return p[methodName](foo) - .then(function () { - t.fail(name + 'should not go into resolve handler after throw') - }) - .catch(function (err) { - t.equal(err, foo, name + 'should pass throught the correct object') - }) - }) - }) - - t.test('casting', function (t) { - testPromiseInstanceCastMethod(t, 1, function (t, Promise, p, name, value) { - return p.thenThrow(value).catch(function (err) { - t.equal(err, value, 'should have expected error') - }) - }) - }) - }) - } - - function testTryBehavior(method) { - ptap.test('Promise.' + method, function (t) { - t.plan(2) - - t.test('context', function (t) { - testPromiseContext(t, function (Promise, name) { - return Promise[method](function () { - return name - }) - }) - }) - - t.test('usage', function (t) { - testPromiseClassMethod(t, 3, function tryTest(Promise, name) { - return Promise[method](function () { - throw new Error('Promise.' + method + ' test error') - }) - .then( - function () { - t.fail(name + 'should not go into resolve after throwing') - }, - function (err) { - t.ok(err, name + 'should have error') - if (err) { - t.equal( - err.message, - 'Promise.' + method + ' test error', - name + 'should be correct error' - ) - } - } - ) - .then(function () { - const foo = { what: 'Promise.' + method + ' test object' } - return Promise[method](function () { - return foo - }).then(function (obj) { - t.equal(obj, foo, name + 'should also work on success') - }) - }) - }) - }) - }) - } - - // ------------------------------------------------------------------------ // - // ------------------------------------------------------------------------ // - // ------------------------------------------------------------------------ // - - function testPromiseInstanceMethod(t, plan, testFunc) { - const agent = helper.loadTestAgent(t) - const Promise = loadLibrary() - - _testPromiseMethod(t, plan, agent, function (name) { - const p = Promise.resolve([1, 2, 3, name]) - return testFunc(Promise, p, name, agent) - }) - } - - function testPromiseInstanceCastMethod(t, plan, testFunc) { - const agent = helper.loadTestAgent(t) - const Promise = loadLibrary() - - _testAllCastTypes(t, plan, agent, function (t, name, value) { - return testFunc(t, Promise, Promise.resolve(name), name, value) - }) - } - - function testPromiseClassMethod(t, plan, testFunc) { - const agent = helper.loadTestAgent(t) - const Promise = loadLibrary() - - _testPromiseMethod(t, plan, agent, function (name) { - return testFunc(Promise, name) - }) - } - - function testPromiseClassCastMethod(t, plan, testFunc) { - const agent = helper.loadTestAgent(t) - const Promise = loadLibrary() - - _testAllCastTypes(t, plan, agent, function (t, name, value) { - return testFunc(t, Promise, name, value) - }) - } - - function testPromiseContext(t, factory) { - const agent = helper.loadTestAgent(t) - const Promise = loadLibrary() - - _testPromiseContext(t, agent, factory.bind(null, Promise)) - } -} - -function addTask() { - const args = [].slice.apply(arguments) - const fn = args.shift() // Pop front. - tasks.push(function () { - fn.apply(null, args) - }) -} - -function _testPromiseMethod(t, plan, agent, testFunc) { - const COUNT = 2 - t.plan(plan * 3 + (COUNT + 1) * 3) - - t.doesNotThrow(function outTXPromiseThrowTest() { - const name = '[no tx] ' - let isAsync = false - testFunc(name) - .finally(function () { - t.ok(isAsync, name + 'should have executed asynchronously') - }) - .then( - function () { - t.notOk(agent.getTransaction(), name + 'has no transaction') - testInTransaction() - }, - function (err) { - if (err) { - /* eslint-disable no-console */ - console.log(err.stack) - /* eslint-enable no-console */ - } - t.notOk(err, name + 'should not result in error') - t.end() - } - ) - isAsync = true - }, '[no tx] should not throw out of a transaction') - - function testInTransaction() { - runMultiple( - COUNT, - function (i, cb) { - helper.runInTransaction(agent, function transactionWrapper(transaction) { - const name = '[tx ' + i + '] ' - t.doesNotThrow(function inTXPromiseThrowTest() { - let isAsync = false - testFunc(name) - .finally(function () { - t.ok(isAsync, name + 'should have executed asynchronously') - }) - .then( - function () { - t.equal( - id(agent.getTransaction()), - id(transaction), - name + 'has the right transaction' - ) - }, - function (err) { - if (err) { - /* eslint-disable no-console */ - console.log(err) - console.log(err.stack) - /* eslint-enable no-console */ - } - t.notOk(err, name + 'should not result in error') - } - ) - .finally(cb) - isAsync = true - }, name + 'should not throw in a transaction') - }) - }, - function () { - t.end() - } - ) - } -} - -function _testPromiseContext(t, agent, factory) { - t.plan(4) - - // Create in tx a, continue in tx b - t.test('context switch', function (t) { - t.plan(2) - - const ctxA = helper.runInTransaction(agent, function (tx) { - return { - transaction: tx, - promise: factory('[tx a] ') - } - }) - - helper.runInTransaction(agent, function (txB) { - t.teardown(function () { - ctxA.transaction.end() - txB.end() - }) - t.notEqual(id(ctxA.transaction), id(txB), 'should not be in transaction a') - - ctxA.promise - .catch(function () {}) - .then(function () { - const tx = agent.tracer.getTransaction() - t.comment('A: ' + id(ctxA.transaction) + ' | B: ' + id(txB)) - t.equal(id(tx), id(ctxA.transaction), 'should be in expected context') - }) - }) - }) - - // Create in tx a, continue outside of tx - t.test('context loss', function (t) { - t.plan(2) - - const ctxA = helper.runInTransaction(agent, function (tx) { - t.teardown(function () { - tx.end() - }) - - return { - transaction: tx, - promise: factory('[tx a] ') - } - }) - - t.notOk(agent.tracer.getTransaction(), 'should not be in transaction') - ctxA.promise - .catch(function () {}) - .then(function () { - const tx = agent.tracer.getTransaction() - t.equal(id(tx), id(ctxA.transaction), 'should be in expected context') - }) - }) - - // Create outside tx, continue in tx a - t.test('context gain', function (t) { - t.plan(2) - - const promise = factory('[no tx] ') - - t.notOk(agent.tracer.getTransaction(), 'should not be in transaction') - helper.runInTransaction(agent, function (tx) { - promise - .catch(function () {}) - .then(function () { - const tx2 = agent.tracer.getTransaction() - t.equal(id(tx2), id(tx), 'should be in expected context') - }) - }) - }) - - // Create test in tx a, end tx a, continue in tx b - t.test('context expiration', function (t) { - t.plan(2) - - const ctxA = helper.runInTransaction(agent, function (tx) { - return { - transaction: tx, - promise: factory('[tx a] ') - } - }) - - ctxA.transaction.end() - helper.runInTransaction(agent, function (txB) { - t.teardown(function () { - ctxA.transaction.end() - txB.end() - }) - t.notEqual(id(ctxA.transaction), id(txB), 'should not be in transaction a') - - ctxA.promise - .catch(function () {}) - .then(function () { - const tx = agent.tracer.getTransaction() - t.comment('A: ' + id(ctxA.transaction) + ' | B: ' + id(txB)) - t.equal(id(tx), id(txB), 'should be in expected context') - }) - }) - }) -} - -function _testAllCastTypes(t, plan, agent, testFunc) { - const values = [42, 'foobar', {}, [], function () {}] - - t.plan(2) - t.test('in context', function (t) { - t.plan(plan * values.length + 1) - - helper.runInTransaction(agent, function (tx) { - _test(t, '[no-tx]', 0) - .then(function () { - const txB = agent.tracer.getTransaction() - t.equal(id(tx), id(txB), 'should maintain transaction state') - }) - .catch(function (err) { - t.error(err) - }) - .then(t.end) - }) - }) - - t.test('out of context', function (t) { - t.plan(plan * values.length) - _test(t, '[no-tx]', 0) - .catch(function (err) { - t.error(err) - }) - .then(t.end) - }) - - function _test(t, name, i) { - const val = values[i] - t.comment(typeof val === 'function' ? val.toString() : JSON.stringify(val)) - return testFunc(t, name, val).then(function () { - if (++i < values.length) { - return _test(t, name, i) - } - }) - } -} - -function PromiseTap(t, Promise) { - this.t = t - this.testedClassMethods = [] - this.testedInstanceMethods = [] - this.Promise = Promise -} - -PromiseTap.prototype.test = function (name, test) { - const match = name.match(/^Promise([#.])(.+)$/) - if (match) { - const location = match[1] - const methodName = match[2] - let exists = false - - if (location === '.') { - exists = typeof this.Promise[methodName] === 'function' - this.testedClassMethods.push(methodName) - } else if (location === '#') { - exists = typeof this.Promise.prototype[methodName] === 'function' - this.testedInstanceMethods.push(methodName) - } - - this.t.test(name, function (t) { - if (exists) { - test(t) - } else { - t.pass(name + ' not supported by library') - t.end() - } - }) - } else { - this.t.test(name, test) - } -} - -PromiseTap.prototype.skip = function (name) { - this.test(name, function (t) { - t.pass('Skipping ' + name) - t.end() - }) -} - -PromiseTap.prototype.check = function (loadLibrary) { - const self = this - this.t.test('check', function (t) { - helper.loadTestAgent(t) - const Promise = loadLibrary() - - const classMethods = Object.keys(self.Promise).sort() - self._check(t, Promise, classMethods, self.testedClassMethods, '.') - - const instanceMethods = Object.keys(self.Promise.prototype).sort() - self._check(t, Promise.prototype, instanceMethods, self.testedInstanceMethods, '#') - - t.end() - }) -} - -PromiseTap.prototype._check = function (t, source, methods, tested, type) { - const prefix = 'Promise' + type - const originalSource = type === '.' ? this.Promise : this.Promise.prototype - - methods.forEach(function (method) { - const wrapped = source[method] - const original = wrapped[symbols.original] || originalSource[method] - - // Skip this property if it is internal (starts or ends with underscore), is - // a class (starts with a capital letter), or is not a function. - if (/(?:^[_A-Z]|_$)/.test(method) || typeof original !== 'function') { - return - } - - const longName = prefix + method - t.ok(tested.indexOf(method) > -1, 'should test ' + prefix + method) - - Object.keys(original).forEach(function (key) { - t.ok(wrapped[key] != null, 'should copy ' + longName + '.' + key) - }) - }, this) -} - -function id(tx) { - return tx && tx.id -} diff --git a/test/versioned/bluebird/methods.tap.js b/test/versioned/bluebird/methods.tap.js deleted file mode 100644 index 1091f7cc67..0000000000 --- a/test/versioned/bluebird/methods.tap.js +++ /dev/null @@ -1,22 +0,0 @@ -/* - * Copyright 2020 New Relic Corporation. All rights reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -'use strict' - -const tap = require('tap') -const testMethods = require('./methods') - -tap.test('bluebird', function (t) { - t.autoend() - - t.test('methods', function (t) { - t.autoend() - testMethods(t, 'bluebird', loadBluebird) - }) -}) - -function loadBluebird() { - return require('bluebird') // Load relative to this file. -} diff --git a/test/versioned/bluebird/methods.test.js b/test/versioned/bluebird/methods.test.js new file mode 100644 index 0000000000..abc480da4f --- /dev/null +++ b/test/versioned/bluebird/methods.test.js @@ -0,0 +1,1835 @@ +/* + * Copyright 2024 New Relic Corporation. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +'use strict' +const assert = require('node:assert') +const test = require('node:test') +const semver = require('semver') +const symbols = require('../../../lib/symbols') +const helper = require('../../lib/agent_helper') +const { + testAsCallbackBehavior, + testCatchBehavior, + testFinallyBehavior, + testFromCallbackBehavior, + testPromiseContext, + testRejectBehavior, + testResolveBehavior, + testThrowBehavior, + testTryBehavior, + testPromiseClassCastMethod, + testPromiseInstanceCastMethod +} = require('./fixtures') +const { + addTask, + afterEach, + areMethodsWrapped, + beforeEach, + testPromiseClassMethod, + testPromiseInstanceMethod +} = require('./helpers') +const { version: pkgVersion } = require('bluebird/package') + +testTryBehavior('try') +testTryBehavior('attempt') +testResolveBehavior('cast') +testResolveBehavior('fulfilled') +testResolveBehavior('resolve') +testThrowBehavior('thenThrow') +testThrowBehavior('throw') +testFromCallbackBehavior('fromCallback') +testFromCallbackBehavior('fromNode') +testFinallyBehavior('finally') +testFinallyBehavior('lastly') +testRejectBehavior('reject') +testRejectBehavior('rejected') +testAsCallbackBehavior('asCallback') +testAsCallbackBehavior('nodeify') +testCatchBehavior('catch') +testCatchBehavior('caught') + +test('new Promise()', async function (t) { + t.beforeEach(beforeEach) + t.afterEach(afterEach) + + await t.test('throw', function (t, end) { + const { Promise } = t.nr + testPromiseClassMethod({ + t, + end, + count: 2, + testFunc: function throwTest({ name, plan }) { + try { + return new Promise(function () { + throw new Error(name + ' test error') + }).then( + function () { + plan.ok(0, `${name} Error should have been caught`) + }, + function (err) { + plan.ok(err, name + ' Error should go to the reject handler') + plan.equal(err.message, name + ' test error', name + ' Error should be as expected') + } + ) + } catch (e) { + plan.ok(!e) + } + } + }) + }) + + await t.test('resolve then throw', function (t, end) { + const { Promise } = t.nr + testPromiseClassMethod({ + t, + end, + count: 1, + testFunc: function resolveThrowTest({ name, plan }) { + try { + return new Promise(function (resolve) { + resolve(name + ' foo') + throw new Error(name + ' test error') + }).then( + function (res) { + plan.equal(res, name + ' foo', name + ' promise should be resolved.') + }, + function () { + plan.ok(0, `${name} Error should have been caught`) + } + ) + } catch (e) { + plan.ok(!e) + } + } + }) + }) + + await t.test('resolve usage', function (t, end) { + const { Promise } = t.nr + testPromiseClassMethod({ + t, + end, + count: 3, + testFunc: function resolveTest({ name, plan }) { + const contextManager = helper.getContextManager() + const inTx = !!contextManager.getContext() + + return new Promise(function (resolve) { + addTask(t.nr, function () { + plan.ok(!contextManager.getContext(), name + 'should lose tx') + resolve('foobar ' + name) + }) + }).then(function (res) { + if (inTx) { + plan.ok(contextManager.getContext(), name + 'should return tx') + } else { + plan.ok(!contextManager.getContext(), name + 'should not create tx') + } + plan.equal(res, 'foobar ' + name, name + 'should resolve with correct value') + }) + } + }) + }) + + await testPromiseContext({ + t, + factory: function (Promise, name) { + return new Promise((resolve) => resolve(name)) + } + }) +}) + +test('Promise.all', async function (t) { + t.beforeEach(beforeEach) + t.afterEach(afterEach) + + await t.test('usage', function (t, end) { + const { Promise } = t.nr + testPromiseClassMethod({ + t, + count: 1, + end, + testFunc: function ({ name, plan }) { + const p1 = Promise.resolve(name + '1') + const p2 = Promise.resolve(name + '2') + + return Promise.all([p1, p2]).then(function (result) { + plan.deepEqual(result, [name + '1', name + '2'], name + 'should not change result') + }) + } + }) + }) + + await testPromiseContext({ + t, + factory: function (Promise, name) { + return Promise.all([name]) + } + }) +}) + +test('Promise.allSettled', { skip: semver.lt(pkgVersion, '3.7.0') }, async function (t) { + t.beforeEach(beforeEach) + t.afterEach(afterEach) + + await testPromiseContext({ + t, + factory: function (Promise, name) { + return Promise.allSettled([Promise.resolve(name), Promise.reject(name)]) + } + }) + + await t.test('usage', function (t, end) { + const { Promise } = t.nr + testPromiseClassMethod({ + t, + count: 1, + end, + testFunc: function ({ name, plan }) { + const p1 = Promise.resolve(name + '1') + const p2 = Promise.reject(name + '2') + + return Promise.allSettled([p1, p2]).then(function (inspections) { + const result = inspections.map(function (i) { + return i.isFulfilled() ? { value: i.value() } : { reason: i.reason() } + }) + plan.deepEqual( + result, + [{ value: name + '1' }, { reason: name + '2' }], + name + 'should not change result' + ) + }) + } + }) + }) +}) + +test('Promise.any', async function (t) { + t.beforeEach(beforeEach) + t.afterEach(afterEach) + + await testPromiseContext({ + t, + factory: function (Promise, name) { + return Promise.any([name]) + } + }) + + await t.test('usage', function (t, end) { + const { Promise } = t.nr + testPromiseClassMethod({ + t, + end, + count: 1, + testFunc: function ({ plan, name }) { + return Promise.any([ + Promise.reject(name + 'rejection!'), + Promise.resolve(name + 'resolved'), + Promise.delay(15, name + 'delayed') + ]).then(function (result) { + plan.equal(result, name + 'resolved', 'should not change the result') + }) + } + }) + }) +}) + +test('Promise.bind', async function (t) { + t.beforeEach(beforeEach) + t.afterEach(afterEach) + + await testPromiseContext({ + t, + factory: function (Promise, name) { + return Promise.bind(name) + } + }) + + await t.test('usage', function (t, end) { + const { Promise } = t.nr + testPromiseClassMethod({ + t, + end, + count: 2, + testFunc: function ({ plan, name }) { + const ctx = {} + return Promise.bind(ctx, name).then(function (value) { + plan.equal(this, ctx, 'should have expected `this` value') + plan.equal(value, name, 'should not change passed value') + }) + } + }) + }) + + await testPromiseClassCastMethod({ + t, + count: 4, + testFunc: function ({ plan, Promise, name, value }) { + return Promise.bind(value, name).then(function (ctx) { + plan.equal(this, value, 'should have expected `this` value') + plan.equal(ctx, name, 'should not change passed value') + + // Try with this context type in both positions. + return Promise.bind(name, value).then(function (val2) { + plan.equal(this, name, 'should have expected `this` value') + plan.equal(val2, value, 'should not change passed value') + }) + }) + } + }) +}) + +test('Promise.coroutine', async function (t) { + t.beforeEach(beforeEach) + t.afterEach(afterEach) + + await testPromiseContext({ + t, + factory: function (Promise, name) { + return Promise.coroutine(function* (_name) { + for (let i = 0; i < 10; ++i) { + yield Promise.delay(5) + } + return _name + })(name) + } + }) + + await t.test('usage', function (t, end) { + const { Promise } = t.nr + testPromiseClassMethod({ + t, + end, + count: 4, + testFunc: function ({ plan, name }) { + let count = 0 + + plan.doesNotThrow(function () { + Promise.coroutine.addYieldHandler(function (value) { + if (value === name) { + plan.ok(1, 'should call yield handler') + return Promise.resolve(value + ' yielded') + } + }) + }, 'should be able to add yield handler') + + return Promise.coroutine(function* (_name) { + for (let i = 0; i < 10; ++i) { + yield Promise.delay(5) + ++count + } + return yield _name + })(name).then(function (result) { + plan.equal(count, 10, 'should step through whole coroutine') + plan.equal(result, name + ' yielded', 'should pass through resolve value') + }) + } + }) + }) +}) + +test('Promise.delay', async function (t) { + t.beforeEach(beforeEach) + t.afterEach(afterEach) + + await testPromiseContext({ + t, + factory: function (Promise, name) { + return Promise.delay(5, name) + } + }) + + await t.test('usage', function (t, end) { + const { Promise } = t.nr + testPromiseClassMethod({ + t, + end, + count: 3, + testFunc: function ({ plan, name }) { + const DELAY = 500 + const MARGIN = 100 + const start = Date.now() + return Promise.delay(DELAY, name).then(function (result) { + const duration = Date.now() - start + plan.ok(duration < DELAY + MARGIN, 'should not take more than expected time') + plan.ok(duration > DELAY - MARGIN, 'should not take less than expected time') + plan.equal(result, name, 'should pass through resolve value') + }) + } + }) + }) + + await testPromiseClassCastMethod({ + t, + count: 1, + testFunc: function ({ plan, Promise, value }) { + return Promise.delay(5, value).then(function (val) { + plan.equal(val, value, 'should have expected value') + }) + } + }) +}) + +test('Promise.each', async function (t) { + t.beforeEach(beforeEach) + t.afterEach(afterEach) + + await testPromiseContext({ + t, + factory: function (Promise, name) { + return Promise.each([name], function () {}) + } + }) + + await t.test('usage', function (t, end) { + const { Promise } = t.nr + testPromiseClassMethod({ + t, + end, + count: 5, + testFunc: function ({ plan, name }) { + return Promise.each( + [ + Promise.resolve(name + '1'), + Promise.resolve(name + '2'), + Promise.resolve(name + '3'), + Promise.resolve(name + '4') + ], + function (value, i) { + plan.equal(value, name + (i + 1), 'should not change input to iterator') + } + ).then(function (result) { + plan.deepEqual(result, [name + '1', name + '2', name + '3', name + '4']) + }) + } + }) + }) +}) + +test('Promise.filter', async function (t) { + t.beforeEach(beforeEach) + t.afterEach(afterEach) + + await testPromiseContext({ + t, + factory: function (Promise, name) { + return Promise.filter([name], function () { + return true + }) + } + }) + + await t.test('usage', function (t, end) { + const { Promise } = t.nr + testPromiseClassMethod({ + t, + end, + count: 1, + testFunc: function ({ plan, name }) { + return Promise.filter( + [ + Promise.resolve(name + '1'), + Promise.resolve(name + '2'), + Promise.resolve(name + '3'), + Promise.resolve(name + '4') + ], + function (value) { + return Promise.resolve(/[24]$/.test(value)) + } + ).then(function (result) { + plan.deepEqual(result, [name + '2', name + '4'], 'should not change the result') + }) + } + }) + }) +}) + +test('Promise.getNewLibraryCopy', { skip: semver.lt(pkgVersion, '3.4.1') }, function (t) { + helper.loadTestAgent(t) + const Promise = require('bluebird') + const Promise2 = Promise.getNewLibraryCopy() + + assert.ok(Promise2.resolve[symbols.original], 'should have wrapped class methods') + assert.ok(Promise2.prototype.then[symbols.original], 'should have wrapped instance methods') +}) + +test('Promise.is', function (t) { + helper.loadTestAgent(t) + const Promise = require('bluebird') + + let p = new Promise(function (resolve) { + setImmediate(resolve) + }) + assert.ok(Promise.is(p), 'should not break promise identification (new)') + + p = p.then(function () {}) + assert.ok(Promise.is(p), 'should not break promise identification (then)') +}) + +test('Promise.join', async function (t) { + t.beforeEach(beforeEach) + t.afterEach(afterEach) + + await testPromiseContext({ + t, + factory: function (Promise, name) { + return Promise.join(name) + } + }) + + await t.test('usage', function (t, end) { + const { Promise } = t.nr + testPromiseClassMethod({ + t, + end, + count: 1, + testFunc: function ({ plan, name }) { + return Promise.join( + Promise.resolve(1), + Promise.resolve(2), + Promise.resolve(3), + Promise.resolve(name) + ).then(function (res) { + plan.deepEqual(res, [1, 2, 3, name], name + 'should have all the values') + }) + } + }) + }) + + await testPromiseClassCastMethod({ + t, + count: 1, + testFunc: function ({ plan, Promise, name, value }) { + return Promise.join(value, name).then(function (values) { + plan.deepEqual(values, [value, name], 'should have expected values') + }) + } + }) +}) + +test('Promise.map', async function (t) { + t.beforeEach(beforeEach) + t.afterEach(afterEach) + + await testPromiseContext({ + t, + factory: function (Promise, name) { + return Promise.map([name], function (v) { + return v.toUpperCase() + }) + } + }) + + await t.test('usage', function (t, end) { + const { Promise } = t.nr + testPromiseClassMethod({ + t, + end, + count: 1, + testFunc: function ({ plan, name }) { + return Promise.map([Promise.resolve('1'), Promise.resolve('2')], function (item) { + return Promise.resolve(name + item) + }).then(function (result) { + plan.deepEqual(result, [name + '1', name + '2'], 'should not change the result') + }) + } + }) + }) +}) + +test('Promise.mapSeries', async function (t) { + t.beforeEach(beforeEach) + t.afterEach(afterEach) + + await testPromiseContext({ + t, + factory: function (Promise, name) { + return Promise.mapSeries([name], function (v) { + return v.toUpperCase() + }) + } + }) + + await t.test('usage', function (t, end) { + const { Promise } = t.nr + testPromiseClassMethod({ + t, + end, + count: 1, + testFunc: function ({ plan, name }) { + return Promise.mapSeries([Promise.resolve('1'), Promise.resolve('2')], function (item) { + return Promise.resolve(name + item) + }).then(function (result) { + plan.deepEqual(result, [name + '1', name + '2'], 'should not change the result') + }) + } + }) + }) +}) + +test('Promise.method', async function (t) { + t.beforeEach(beforeEach) + t.afterEach(afterEach) + + await testPromiseContext({ + t, + factory: function (Promise, name) { + return Promise.method(function () { + return name + })() + } + }) + + await t.test('usage', function (t, end) { + const { Promise } = t.nr + testPromiseClassMethod({ + t, + end, + count: 3, + testFunc: function ({ plan, name }) { + const fn = Promise.method(function () { + throw new Error('Promise.method test error') + }) + + return fn() + .then( + function () { + plan.ok(0, name + 'should not go into resolve after throwing') + }, + function (err) { + plan.ok(err, name + 'should have error') + plan.equal(err.message, 'Promise.method test error', name + 'should be correct error') + } + ) + .then(function () { + const foo = { what: 'Promise.method test object' } + const fn2 = Promise.method(function () { + return foo + }) + + return fn2().then(function (obj) { + plan.equal(obj, foo, name + 'should also work on success') + }) + }) + } + }) + }) +}) + +test('Promise.noConflict', function (t) { + helper.loadTestAgent(t) + const Promise = require('bluebird') + const Promise2 = Promise.noConflict() + + assert.ok(Promise2.resolve[symbols.original], 'should have wrapped class methods') + assert.ok(Promise2.prototype.then[symbols.original], 'should have wrapped instance methods') +}) + +test('Promise.promisify', async function (t) { + t.beforeEach(beforeEach) + t.afterEach(afterEach) + + await testPromiseContext({ + t, + factory: function (Promise, name) { + return Promise.promisify(function (cb) { + cb(null, name) + })() + } + }) + + await t.test('usage', function (t, end) { + const { Promise } = t.nr + testPromiseClassMethod({ + t, + end, + count: 4, + testFunc: function ({ plan, name }) { + const fn = Promise.promisify(function (cb) { + cb(new Error('Promise.promisify test error')) + }) + + // Test error handling. + return fn() + .then( + function () { + plan.ok(0, name + 'should not go into resolve after throwing') + }, + function (err) { + plan.ok(err, name + 'should have error') + plan.equal( + err.message, + 'Promise.promisify test error', + name + 'should be correct error' + ) + } + ) + .then(function () { + // Test success handling. + const foo = { what: 'Promise.promisify test object' } + const fn2 = Promise.promisify(function (cb) { + cb(null, foo) + }) + + return fn2().then(function (obj) { + plan.equal(obj, foo, name + 'should also work on success') + }) + }) + .then(() => { + // Test property copying. + const unwrapped = (cb) => cb() + const property = { name } + unwrapped.property = property + + const wrapped = Promise.promisify(unwrapped) + plan.equal(wrapped.property, property, 'should have copied properties') + }) + } + }) + }) +}) + +test('Promise.props', async function (t) { + t.beforeEach(beforeEach) + t.afterEach(afterEach) + + await testPromiseContext({ + t, + factory: function (Promise, name) { + return Promise.props({ name }) + } + }) + + await t.test('usage', function (t, end) { + const { Promise } = t.nr + testPromiseClassMethod({ + t, + end, + count: 1, + testFunc: function ({ plan, name }) { + return Promise.props({ + first: Promise.resolve(name + '1'), + second: Promise.resolve(name + '2') + }).then(function (result) { + plan.deepEqual( + result, + { first: name + '1', second: name + '2' }, + 'should not change results' + ) + }) + } + }) + }) +}) + +test('Promise.race', async function (t) { + t.beforeEach(beforeEach) + t.afterEach(afterEach) + + await testPromiseContext({ + t, + factory: function (Promise, name) { + return Promise.race([name]) + } + }) + + await t.test('usage', function (t, end) { + const { Promise } = t.nr + testPromiseClassMethod({ + t, + end, + count: 1, + testFunc: function ({ plan, name }) { + return Promise.race([ + Promise.resolve(name + 'resolved'), + Promise.reject(name + 'rejection!'), + Promise.delay(15, name + 'delayed') + ]).then(function (result) { + plan.equal(result, name + 'resolved', 'should not change the result') + }) + } + }) + }) +}) + +test('Promise.reduce', async function (t) { + t.beforeEach(beforeEach) + t.afterEach(afterEach) + + await testPromiseContext({ + t, + factory: function (Promise, name) { + return Promise.reduce([name, name], function (a, b) { + return a + b + }) + } + }) + + await t.test('usage', function (t, end) { + const { Promise } = t.nr + testPromiseClassMethod({ + t, + end, + count: 1, + testFunc: function ({ plan, name }) { + return Promise.reduce( + [Promise.resolve('1'), Promise.resolve('2'), Promise.resolve('3'), Promise.resolve('4')], + function (a, b) { + return Promise.resolve(name + a + b) + } + ).then(function (result) { + plan.equal(result, name + name + name + '1234', 'should not change the result') + }) + } + }) + }) +}) + +test('Promise.some', async function (t) { + t.beforeEach(beforeEach) + t.afterEach(afterEach) + + await testPromiseContext({ + t, + factory: function (Promise, name) { + return Promise.some([name], 1) + } + }) + + await t.test('usage', function (t, end) { + const { Promise } = t.nr + testPromiseClassMethod({ + t, + end, + count: 1, + testFunc: function ({ plan, name }) { + return Promise.some( + [ + Promise.resolve(name + 'resolved'), + Promise.reject(name + 'rejection!'), + Promise.delay(100, name + 'delayed more'), + Promise.delay(5, name + 'delayed') + ], + 2 + ).then(function (result) { + plan.deepEqual( + result, + [name + 'resolved', name + 'delayed'], + 'should not change the result' + ) + }) + } + }) + }) +}) + +test('Promise#all', async function (t) { + t.beforeEach(beforeEach) + t.afterEach(afterEach) + + await testPromiseContext({ + t, + factory: function (Promise, name) { + return Promise.resolve([Promise.resolve(name + '1'), Promise.resolve(name + '2')]).all() + } + }) + + await t.test('usage', function (t, end) { + const { Promise } = t.nr + testPromiseInstanceMethod({ + t, + end, + count: 1, + testFunc: function ({ plan, promise, name }) { + return promise + .then(function () { + return [Promise.resolve(name + '1'), Promise.resolve(name + '2')] + }) + .all() + .then(function (result) { + plan.deepEqual(result, [name + '1', name + '2'], name + 'should not change result') + }) + } + }) + }) +}) + +test('Promise#any', async function (t) { + t.beforeEach(beforeEach) + t.afterEach(afterEach) + + await testPromiseContext({ + t, + factory: function (Promise, name) { + return Promise.resolve([ + Promise.reject(name + 'rejection!'), + Promise.resolve(name + 'resolved'), + Promise.delay(15, name + 'delayed') + ]).any() + } + }) + + await t.test('usage', function (t, end) { + const { Promise } = t.nr + testPromiseInstanceMethod({ + t, + end, + count: 1, + testFunc: function ({ plan, promise, name }) { + return promise + .then(function () { + return [ + Promise.reject(name + 'rejection!'), + Promise.resolve(name + 'resolved'), + Promise.delay(15, name + 'delayed') + ] + }) + .any() + .then(function (result) { + plan.equal(result, name + 'resolved', 'should not change the result') + }) + } + }) + }) +}) + +test('Promise#bind', async function (t) { + t.beforeEach(beforeEach) + t.afterEach(afterEach) + + await testPromiseContext({ + t, + factory: function (Promise, name) { + return Promise.resolve(name).bind({ name: name }) + } + }) + + await t.test('usage', function (t, end) { + const { Promise } = t.nr + testPromiseInstanceMethod({ + t, + end, + count: 4, + testFunc: function ({ plan, promise, name }) { + const foo = { what: 'test object' } + const ctx2 = { what: 'a different test object' } + const err = new Error('oh dear') + return promise + .bind(foo) + .then(function (res) { + plan.equal(this, foo, name + 'should have correct this value') + plan.deepEqual(res, [1, 2, 3, name], name + 'parameters should be correct') + + return Promise.reject(err) + }) + .bind(ctx2, name) + .catch(function (reason) { + plan.equal(this, ctx2, 'should have expected `this` value') + plan.equal(reason, err, 'should not change rejection reason') + }) + } + }) + }) + + await testPromiseInstanceCastMethod({ + t, + count: 2, + testFunc: function ({ plan, promise, name, value }) { + return promise.bind(value).then(function (val) { + plan.equal(this, value, 'should have correct context') + plan.equal(val, name, 'should have expected value') + }) + } + }) +}) + +test('Promise#call', async function (t) { + t.beforeEach(beforeEach) + t.afterEach(afterEach) + + await testPromiseContext({ + t, + factory: function (Promise, name) { + return Promise.resolve({ + foo: function () { + return Promise.resolve(name) + } + }).call('foo') + } + }) + + await t.test('usage', function (t, end) { + testPromiseInstanceMethod({ + t, + end, + count: 3, + testFunc: function ({ plan, promise, name }) { + const foo = { + test: function () { + plan.equal(this, foo, name + 'should have correct this value') + plan.ok(1, name + 'should call the test method of foo') + return 'foobar' + } + } + return promise + .then(function () { + return foo + }) + .call('test') + .then(function (res) { + plan.deepEqual(res, 'foobar', name + 'parameters should be correct') + }) + } + }) + }) +}) + +test('Promise#catchReturn', async function (t) { + t.beforeEach(beforeEach) + t.afterEach(afterEach) + + await testPromiseContext({ + t, + factory: function (Promise, name) { + return Promise.reject(new Error()).catchReturn(name) + } + }) + + await t.test('usage', function (t, end) { + testPromiseInstanceMethod({ + t, + end, + count: 1, + testFunc: function ({ plan, promise, name }) { + const foo = { what: 'catchReturn test object' } + return promise + .throw(new Error('catchReturn test error')) + .catchReturn(foo) + .then(function (res) { + plan.equal(res, foo, name + 'should pass throught the correct object') + }) + } + }) + }) + + await testPromiseInstanceCastMethod({ + t, + count: 1, + testFunc: function ({ plan, promise, value }) { + return promise + .then(function () { + throw new Error('woops') + }) + .catchReturn(value) + .then(function (val) { + plan.equal(val, value, 'should have expected value') + }) + } + }) +}) + +test('Promise#catchThrow', async function (t) { + t.beforeEach(beforeEach) + t.afterEach(afterEach) + + await testPromiseContext({ + t, + factory: function (Promise, name) { + return Promise.reject(new Error()).catchThrow(new Error(name)) + } + }) + + await t.test('usage', function (t, end) { + testPromiseInstanceMethod({ + t, + end, + count: 1, + testFunc: function ({ plan, promise, name }) { + const foo = { what: 'catchThrow test object' } + return promise + .throw(new Error('catchThrow test error')) + .catchThrow(foo) + .catch(function (err) { + plan.equal(err, foo, name + 'should pass throught the correct object') + }) + } + }) + }) + + await testPromiseInstanceCastMethod({ + t, + count: 1, + testFunc: function ({ plan, promise, value }) { + return promise + .then(function () { + throw new Error('woops') + }) + .catchThrow(value) + .catch(function (err) { + plan.equal(err, value, 'should have expected error') + }) + } + }) +}) + +test('Promise#delay', async function (t) { + t.beforeEach(beforeEach) + t.afterEach(afterEach) + + await testPromiseContext({ + t, + factory: function (Promise, name) { + return Promise.resolve(name).delay(10) + } + }) + + await t.test('usage', function (t, end) { + testPromiseInstanceMethod({ + t, + end, + count: 3, + testFunc: function ({ plan, promise, name }) { + const DELAY = 500 + const MARGIN = 100 + const start = Date.now() + return promise + .return(name) + .delay(DELAY) + .then(function (result) { + const duration = Date.now() - start + plan.ok(duration < DELAY + MARGIN, 'should not take more than expected time') + plan.ok(duration > DELAY - MARGIN, 'should not take less than expected time') + plan.equal(result, name, 'should pass through resolve value') + }) + } + }) + }) +}) + +test('Promise#each', async function (t) { + t.beforeEach(beforeEach) + t.afterEach(afterEach) + + await testPromiseContext({ + t, + factory: function (Promise, name) { + return Promise.resolve([ + Promise.delay(Math.random() * 10, name + '1'), + Promise.delay(Math.random() * 10, name + '2'), + Promise.delay(Math.random() * 10, name + '3'), + Promise.delay(Math.random() * 10, name + '4') + ]).each(function (value, i) { + return Promise.delay(i, value) + }) + } + }) + + await t.test('usage', function (t, end) { + const { Promise } = t.nr + testPromiseInstanceMethod({ + t, + end, + count: 5, + testFunc: function ({ plan, promise, name }) { + return promise + .then(function () { + return [ + Promise.delay(Math.random() * 10, name + '1'), + Promise.delay(Math.random() * 10, name + '2'), + Promise.delay(Math.random() * 10, name + '3'), + Promise.delay(Math.random() * 10, name + '4') + ] + }) + .each(function (value, i) { + plan.equal(value, name + (i + 1), 'should not change input to iterator') + }) + .then(function (result) { + plan.deepEqual(result, [name + '1', name + '2', name + '3', name + '4']) + }) + } + }) + }) +}) + +test('Promise#error', async function (t) { + t.beforeEach(beforeEach) + t.afterEach(afterEach) + + function OperationalError(message) { + this.message = message + this.isOperational = true + } + + await testPromiseContext({ + t, + factory: function (Promise, name) { + return Promise.reject(new OperationalError(name)).error(function (err) { + return err + }) + } + }) + + await t.test('usage', function (t, end) { + testPromiseInstanceMethod({ + t, + end, + count: 2, + testFunc: function ({ plan, promise, name }) { + return promise + .error(function (err) { + plan.ok(!err, name + 'should not go into error from a resolved promise') + }) + .then(function () { + throw new OperationalError('Promise#error test error') + }) + .error(function (err) { + plan.ok(err, name + 'should pass error into rejection handler') + plan.equal(err.message, 'Promise#error test error', name + 'should be correct error') + }) + } + }) + }) +}) + +test('Promise#filter', async function (t) { + t.beforeEach(beforeEach) + t.afterEach(afterEach) + + await testPromiseContext({ + t, + factory: function (Promise, name) { + return Promise.resolve([ + Promise.resolve(name + '1'), + Promise.resolve(name + '2'), + Promise.resolve(name + '3'), + Promise.resolve(name + '4') + ]).filter(function (value, i) { + return Promise.delay(i, /[24]$/.test(value)) + }) + } + }) + + await t.test('usage', function (t, end) { + const { Promise } = t.nr + testPromiseInstanceMethod({ + t, + end, + count: 1, + testFunc: function ({ plan, promise, name }) { + return promise + .then(function () { + return [ + Promise.resolve(name + '1'), + Promise.resolve(name + '2'), + Promise.resolve(name + '3'), + Promise.resolve(name + '4') + ] + }) + .filter(function (value) { + return Promise.resolve(/[24]$/.test(value)) + }) + .then(function (result) { + plan.deepEqual(result, [name + '2', name + '4'], 'should not change the result') + }) + } + }) + }) +}) + +test('Promise#get', async function (t) { + t.beforeEach(beforeEach) + t.afterEach(afterEach) + + await testPromiseContext({ + t, + factory: function (Promise, name) { + return Promise.resolve({ name: Promise.resolve(name) }).get('name') + } + }) + + await t.test('usage', function (t, end) { + testPromiseInstanceMethod({ + t, + end, + count: 1, + testFunc: function ({ plan, promise, name }) { + return promise.get('length').then(function (res) { + plan.equal(res, 4, name + 'should get the property specified') + }) + } + }) + }) +}) + +test('Promise#map', async function (t) { + t.beforeEach(beforeEach) + t.afterEach(afterEach) + + await testPromiseContext({ + t, + factory: function (Promise, name) { + return Promise.resolve([Promise.resolve('1'), Promise.resolve('2')]).map(function (item) { + return Promise.resolve(name + item) + }) + } + }) + + await t.test('usage', function (t, end) { + const { Promise } = t.nr + testPromiseInstanceMethod({ + t, + end, + count: 1, + testFunc: function ({ plan, promise, name }) { + return promise + .then(function () { + return [Promise.resolve('1'), Promise.resolve('2')] + }) + .map(function (item) { + return Promise.resolve(name + item) + }) + .then(function (result) { + plan.deepEqual(result, [name + '1', name + '2'], 'should not change the result') + }) + } + }) + }) +}) + +test('Promise#mapSeries', async function (t) { + t.beforeEach(beforeEach) + t.afterEach(afterEach) + + await testPromiseContext({ + t, + factory: function (Promise, name) { + return Promise.resolve([Promise.resolve('1'), Promise.resolve('2')]).mapSeries(function ( + item + ) { + return Promise.resolve(name + item) + }) + } + }) + + await t.test('usage', function (t, end) { + const { Promise } = t.nr + testPromiseInstanceMethod({ + t, + end, + count: 1, + testFunc: function ({ plan, promise, name }) { + return promise + .then(function () { + return [Promise.resolve('1'), Promise.resolve('2')] + }) + .mapSeries(function (item) { + return Promise.resolve(name + item) + }) + .then(function (result) { + plan.deepEqual(result, [name + '1', name + '2'], 'should not change the result') + }) + } + }) + }) +}) + +test('Promise#props', async function (t) { + t.beforeEach(beforeEach) + t.afterEach(afterEach) + + await testPromiseContext({ + t, + factory: function (Promise, name) { + return Promise.resolve({ + first: Promise.delay(5, name + '1'), + second: Promise.delay(5, name + '2') + }).props() + } + }) + + await t.test('usage', function (t, end) { + const { Promise } = t.nr + testPromiseInstanceMethod({ + t, + end, + count: 1, + testFunc: function ({ plan, promise, name }) { + return promise + .then(function () { + return { + first: Promise.resolve(name + '1'), + second: Promise.resolve(name + '2') + } + }) + .props() + .then(function (result) { + plan.deepEqual( + result, + { first: name + '1', second: name + '2' }, + 'should not change results' + ) + }) + } + }) + }) +}) + +test('Promise#race', async function (t) { + t.beforeEach(beforeEach) + t.afterEach(afterEach) + + await testPromiseContext({ + t, + factory: function (Promise, name) { + return Promise.resolve([ + Promise.resolve(name + 'resolved'), + Promise.delay(15, name + 'delayed') + ]).race() + } + }) + + await t.test('usage', function (t, end) { + const { Promise } = t.nr + testPromiseInstanceMethod({ + t, + end, + count: 1, + testFunc: function ({ plan, promise, name }) { + return promise + .then(function () { + return [Promise.resolve(name + 'resolved'), Promise.delay(15, name + 'delayed')] + }) + .race() + .then(function (result) { + plan.equal(result, name + 'resolved', 'should not change the result') + }) + } + }) + }) +}) + +test('Promise#reduce', async function (t) { + t.beforeEach(beforeEach) + t.afterEach(afterEach) + + await testPromiseContext({ + t, + factory: function (Promise, name) { + return Promise.resolve([ + Promise.resolve('1'), + Promise.resolve('2'), + Promise.resolve('3'), + Promise.resolve('4') + ]).reduce(function (a, b) { + return Promise.resolve(name + a + b) + }) + } + }) + + await t.test('usage', function (t, end) { + const { Promise } = t.nr + testPromiseInstanceMethod({ + t, + end, + count: 1, + testFunc: function ({ plan, promise, name }) { + return promise + .then(function () { + return [ + Promise.resolve('1'), + Promise.resolve('2'), + Promise.resolve('3'), + Promise.resolve('4') + ] + }) + .reduce(function (a, b) { + return Promise.resolve(name + a + b) + }) + .then(function (result) { + plan.equal(result, name + name + name + '1234', 'should not change the result') + }) + } + }) + }) +}) + +test('Promise#reflect', async function (t) { + t.beforeEach(beforeEach) + t.afterEach(afterEach) + + await testPromiseContext({ + t, + factory: function (Promise, name) { + return Promise.resolve(name).reflect() + } + }) + + await t.test('usage', function (t, end) { + testPromiseInstanceMethod({ + t, + end, + count: 12, + testFunc: function ({ plan, promise, name }) { + return promise + .reflect() + .then(function (inspection) { + // Inspection of a resolved promise. + plan.ok(!inspection.isPending(), name + 'should not be pending') + plan.ok(!inspection.isRejected(), name + 'should not be rejected') + plan.ok(inspection.isFulfilled(), name + 'should be fulfilled') + plan.ok(!inspection.isCancelled(), name + 'should not be cancelled') + plan.throws(function () { + inspection.reason() + }, name + 'should throw when accessing reason') + plan.ok(inspection.value(), name + 'should have the value') + }) + .throw(new Error(name + 'test error')) + .reflect() + .then(function (inspection) { + plan.ok(!inspection.isPending(), name + 'should not be pending') + plan.ok(inspection.isRejected(), name + 'should be rejected') + plan.ok(!inspection.isFulfilled(), name + 'should not be fulfilled') + plan.ok(!inspection.isCancelled(), name + 'should not be cancelled') + plan.ok(inspection.reason(), name + 'should have the reason for rejection') + plan.throws(function () { + inspection.value() + }, 'should throw accessing the value') + }) + } + }) + }) +}) + +test('Promise#return', async function (t) { + t.beforeEach(beforeEach) + t.afterEach(afterEach) + + await testPromiseContext({ + t, + factory: function (Promise, name) { + return Promise.resolve().return(name) + } + }) + + await t.test('usage', function (t, end) { + testPromiseInstanceMethod({ + t, + end, + count: 1, + testFunc: function ({ plan, promise, name }) { + const foo = { what: 'return test object' } + return promise.return(foo).then(function (res) { + plan.equal(res, foo, name + 'should pass throught the correct object') + }) + } + }) + }) + + await testPromiseInstanceCastMethod({ + t, + count: 1, + testFunc: function ({ plan, promise, value }) { + return promise.return(value).then(function (val) { + plan.equal(val, value, 'should have expected value') + }) + } + }) +}) + +test('Promise#some', async function (t) { + t.beforeEach(beforeEach) + t.afterEach(afterEach) + + await testPromiseContext({ + t, + factory: function (Promise, name) { + return Promise.resolve([ + Promise.resolve(name + 'resolved'), + Promise.reject(name + 'rejection!'), + Promise.delay(100, name + 'delayed more'), + Promise.delay(5, name + 'delayed') + ]).some(2) + } + }) + + await t.test('usage', function (t, end) { + const { Promise } = t.nr + testPromiseInstanceMethod({ + t, + end, + count: 1, + testFunc: function ({ plan, promise, name }) { + return promise + .then(function () { + return [ + Promise.resolve(name + 'resolved'), + Promise.reject(name + 'rejection!'), + Promise.delay(100, name + 'delayed more'), + Promise.delay(5, name + 'delayed') + ] + }) + .some(2) + .then(function (result) { + plan.deepEqual( + result, + [name + 'resolved', name + 'delayed'], + 'should not change the result' + ) + }) + } + }) + }) +}) + +test('Promise#spread', async function (t) { + t.beforeEach(beforeEach) + t.afterEach(afterEach) + + await testPromiseContext({ + t, + factory: function (Promise, name) { + return Promise.resolve([name, 1, 2, 3, 4]).spread(function () {}) + } + }) + + await t.test('usage', function (t, end) { + testPromiseInstanceMethod({ + t, + end, + count: 1, + testFunc: function ({ plan, promise, name }) { + return promise.spread(function (a, b, c, d) { + plan.deepEqual([a, b, c, d], [1, 2, 3, name], name + 'parameters should be correct') + }) + } + }) + }) +}) + +test('Promise#tap', async function (t) { + t.beforeEach(beforeEach) + t.afterEach(afterEach) + + await testPromiseContext({ + t, + factory: function (Promise, name) { + return Promise.resolve(name).tap(function () {}) + } + }) + + await t.test('usage', function (t, end) { + testPromiseInstanceMethod({ + t, + end, + count: 4, + testFunc: function ({ plan, promise, name }) { + return promise + .tap(function (res) { + plan.deepEqual(res, [1, 2, 3, name], name + 'should pass values into tap handler') + }) + .then(function (res) { + plan.deepEqual(res, [1, 2, 3, name], name + 'should pass values beyond tap handler') + throw new Error('Promise#tap test error') + }) + .tap(function () { + plan.ok(0, name + 'should not call tap after rejected promises') + }) + .catch(function (err) { + plan.ok(err, name + 'should pass error beyond tap handler') + plan.equal( + err && err.message, + 'Promise#tap test error', + name + 'should be correct error' + ) + }) + } + }) + }) +}) + +test('Promise#tapCatch', { skip: semver.lt(pkgVersion, '3.5.0') }, async function (t) { + t.beforeEach(beforeEach) + t.afterEach(afterEach) + + await testPromiseContext({ + t, + factory: function (Promise, name) { + return Promise.reject(new Error(name)).tapCatch(function () {}) + } + }) + + await t.test('usage', function (t, end) { + testPromiseInstanceMethod({ + t, + end, + count: 3, + testFunc: function ({ plan, promise, name }) { + return promise + .throw(new Error(name)) + .tapCatch(function (err) { + plan.equal(err && err.message, name, name + 'should pass values into tapCatch handler') + }) + .then(function () { + plan.ok(0, 'should not enter following resolve handler') + }) + .catch(function (err) { + plan.equal( + err && err.message, + name, + name + 'should pass values beyond tapCatch handler' + ) + return name + 'resolve test' + }) + .tapCatch(function () { + plan.ok(0, name + 'should not call tapCatch after resolved promises') + }) + .then(function (value) { + plan.equal(value, name + 'resolve test', name + 'should pass error beyond tap handler') + }) + } + }) + }) +}) + +test('Promise#then', async function (t) { + t.beforeEach(beforeEach) + t.afterEach(afterEach) + + await testPromiseContext({ + t, + factory: function (Promise, name) { + return Promise.resolve().then(function () { + return name + }) + } + }) + + await t.test('usage', function (t, end) { + testPromiseInstanceMethod({ + t, + end, + count: 3, + testFunc: function ({ plan, promise, name }) { + return promise + .then(function (res) { + plan.deepEqual(res, [1, 2, 3, name], name + 'should have the correct result value') + throw new Error('Promise#then test error') + }) + .then( + function () { + plan.ok(0, name + 'should not go into resolve handler from rejected promise') + }, + function (err) { + plan.ok(err, name + 'should pass error into thenned rejection handler') + plan.equal(err.message, 'Promise#then test error', name + 'should be correct error') + } + ) + } + }) + }) +}) + +test('Promise#thenReturn', async function (t) { + t.beforeEach(beforeEach) + t.afterEach(afterEach) + + await testPromiseContext({ + t, + factory: function (Promise, name) { + return Promise.resolve().thenReturn(name) + } + }) + + await t.test('usage', function (t, end) { + testPromiseInstanceMethod({ + t, + end, + count: 3, + testFunc: function ({ plan, promise, name }) { + return promise + .thenReturn(name) + .then(function (res) { + plan.deepEqual(res, name, name + 'should have the correct result value') + throw new Error('Promise#then test error') + }) + .thenReturn('oops!') + .then( + function () { + plan.ok(0, name + 'should not go into resolve handler from rejected promise') + }, + function (err) { + plan.ok(err, name + 'should pass error into thenned rejection handler') + plan.equal(err.message, 'Promise#then test error', name + 'should be correct error') + } + ) + } + }) + }) + + await testPromiseInstanceCastMethod({ + t, + count: 1, + testFunc: function ({ plan, promise, value }) { + return promise.thenReturn(value).then(function (val) { + plan.equal(val, value, 'should have expected value') + }) + } + }) +}) + +test('Promise#timeout', async function (t) { + t.beforeEach(beforeEach) + t.afterEach(afterEach) + + await testPromiseContext({ + t, + factory: function (Promise, name) { + return Promise.resolve(name).timeout(10) + } + }) + + await t.test('usage', function (t, end) { + testPromiseInstanceMethod({ + t, + end, + count: 4, + testFunc: function ({ plan, promise, name }) { + let start = null + return promise + .timeout(1000) + .then( + function (res) { + plan.deepEqual(res, [1, 2, 3, name], name + 'should pass values into tap handler') + start = Date.now() + }, + function (err) { + plan.ok(!err, name + 'should not have timed out') + } + ) + .delay(1000, 'never see me') + .timeout(500, name + 'timed out') + .then( + function () { + plan.ok(0, name + 'should have timed out long delay') + }, + function (err) { + const duration = Date.now() - start + plan.ok(duration < 600, name + 'should not timeout slower than expected') + plan.ok(duration > 400, name + 'should not timeout faster than expected') + plan.equal(err.message, name + 'timed out', name + 'should have expected error') + } + ) + } + }) + }) +}) + +test('bluebird static and instance methods check', function (t) { + helper.loadTestAgent(t) + const Promise = require('bluebird') + + areMethodsWrapped(Promise) + areMethodsWrapped(Promise.prototype) +}) diff --git a/test/versioned/bluebird/package.json b/test/versioned/bluebird/package.json index 7eaf36bd2e..01ecb0f007 100644 --- a/test/versioned/bluebird/package.json +++ b/test/versioned/bluebird/package.json @@ -12,8 +12,8 @@ "bluebird": ">=2.0.0" }, "files": [ - "regressions.tap.js", - "transaction-state.tap.js" + "regressions.test.js", + "transaction-state.test.js" ] }, { @@ -24,7 +24,7 @@ "bluebird": ">=3.0.0" }, "files": [ - "methods.tap.js" + "methods.test.js" ] } ], diff --git a/test/versioned/bluebird/regressions.tap.js b/test/versioned/bluebird/regressions.test.js similarity index 80% rename from test/versioned/bluebird/regressions.tap.js rename to test/versioned/bluebird/regressions.test.js index f5aabdf3a6..b38497c500 100644 --- a/test/versioned/bluebird/regressions.tap.js +++ b/test/versioned/bluebird/regressions.test.js @@ -4,17 +4,16 @@ */ 'use strict' -const tap = require('tap') +const test = require('node:test') const helper = require('../../lib/agent_helper') -tap.test('bluebird', function (t) { - t.autoend() - t.test('NODE-1649 Stack overflow on recursive promise', function (t) { +test('bluebird', async function (t) { + await t.test('NODE-1649 Stack overflow on recursive promise', async function (t) { // This was resolved in 2.6.0 as a side-effect of completely refactoring the // promise instrumentation. const agent = helper.loadMockedAgent() - t.teardown(function () { + t.after(function () { helper.unloadAgent(agent) }) const Promise = require('bluebird') @@ -41,7 +40,7 @@ tap.test('bluebird', function (t) { } } - return helper.runInTransaction(agent, function () { + await helper.runInTransaction(agent, function () { return getData(new Provider(10000)) }) }) diff --git a/test/versioned/bluebird/transaction-state.tap.js b/test/versioned/bluebird/transaction-state.tap.js deleted file mode 100644 index 1e2c10d337..0000000000 --- a/test/versioned/bluebird/transaction-state.tap.js +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Copyright 2020 New Relic Corporation. All rights reserved. - * SPDX-License-Identifier: Apache-2.0 - */ - -'use strict' - -const helper = require('../../lib/agent_helper') -const tap = require('tap') -const testTransactionState = require('../../lib/promises/transaction-state') - -tap.test('bluebird', function (t) { - t.autoend() - - t.test('transaction state', function (t) { - const agent = setupAgent(t) - const Promise = require('bluebird') - testTransactionState(t, agent, Promise) - t.autoend() - }) -}) - -function setupAgent(t, enableSegments) { - const agent = helper.instrumentMockedAgent({ - feature_flag: { promise_segments: enableSegments } - }) - t.teardown(function tearDown() { - helper.unloadAgent(agent) - }) - return agent -} diff --git a/test/versioned/bluebird/transaction-state.test.js b/test/versioned/bluebird/transaction-state.test.js new file mode 100644 index 0000000000..fdba44813f --- /dev/null +++ b/test/versioned/bluebird/transaction-state.test.js @@ -0,0 +1,21 @@ +/* + * Copyright 2020 New Relic Corporation. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +'use strict' + +const helper = require('../../lib/agent_helper') +const test = require('node:test') +const testTransactionState = require('../../lib/promises/transaction-state') + +test('bluebird', async function (t) { + const agent = helper.instrumentMockedAgent() + const Promise = require('bluebird') + + t.after(() => { + helper.unloadAgent(agent) + }) + + await testTransactionState({ t, agent, Promise }) +}) diff --git a/test/versioned/when/package.json b/test/versioned/when/package.json index 4cdaa7b838..5fd9ab2559 100644 --- a/test/versioned/when/package.json +++ b/test/versioned/when/package.json @@ -12,6 +12,7 @@ "when": ">=3.7.0" }, "files": [ + "when.test.js", "when.tap.js" ] } diff --git a/test/versioned/when/when.tap.js b/test/versioned/when/when.tap.js index 0d2bfb2c1a..7c55645e5e 100644 --- a/test/versioned/when/when.tap.js +++ b/test/versioned/when/when.tap.js @@ -6,8 +6,8 @@ 'use strict' const helper = require('../../lib/agent_helper') -const testPromiseSegments = require(`./legacy-promise-segments`) -const testTransactionState = require(`../../lib/promises/transaction-state`) +const testPromiseSegments = require('./legacy-promise-segments') +const { runMultiple } = require('../../lib/promises/helpers') // grab process emit before tap / async-hooks-domain can mess with it const originalEmit = process.emit @@ -15,32 +15,6 @@ const originalEmit = process.emit const tap = require('tap') const test = tap.test -const runMultiple = testTransactionState.runMultiple - -test('Promise constructor retains all properties', function (t) { - let Promise = require('when').Promise - const originalKeys = Object.keys(Promise) - - setupAgent(t) - Promise = require('when').Promise - const wrappedKeys = Object.keys(Promise) - - originalKeys.forEach(function (key) { - if (wrappedKeys.indexOf(key) === -1) { - t.fail('Property ' + key + ' is not present on wrapped Promise') - } - }) - - t.end() -}) - -test('transaction state', function (t) { - const agent = setupAgent(t) - const when = require('when') - testTransactionState(t, agent, when.Promise, when) - t.autoend() -}) - test('segments', function (t) { const agent = setupAgent(t) const when = require('when') diff --git a/test/versioned/when/when.test.js b/test/versioned/when/when.test.js new file mode 100644 index 0000000000..2ce0d34d43 --- /dev/null +++ b/test/versioned/when/when.test.js @@ -0,0 +1,44 @@ +/* + * Copyright 2024 New Relic Corporation. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 + */ + +'use strict' +const assert = require('node:assert') +const test = require('node:test') +const helper = require('../../lib/agent_helper') +const testTransactionState = require(`../../lib/promises/transaction-state`) + +function setupAgent(t) { + const agent = helper.instrumentMockedAgent() + t.after(() => { + helper.unloadAgent(agent) + }) +} + +test('Promise constructor retains all properties', function (t) { + let Promise = require('when').Promise + const originalKeys = Object.keys(Promise) + + setupAgent(t) + Promise = require('when').Promise + const wrappedKeys = Object.keys(Promise) + + originalKeys.forEach(function (key) { + if (wrappedKeys.indexOf(key) === -1) { + assert.ok(0, 'Property ' + key + ' is not present on wrapped Promise') + } + }) +}) + +test('transaction state', async function (t) { + const agent = helper.instrumentMockedAgent() + const when = require('when') + const Promise = when.Promise + + t.after(() => { + helper.unloadAgent(agent) + }) + + await testTransactionState({ t, agent, Promise, library: when }) +}) From 3053a227a3e5ed1e0b799ac3d69970da8ee3737a Mon Sep 17 00:00:00 2001 From: Bob Evans Date: Fri, 4 Oct 2024 14:51:04 -0400 Subject: [PATCH 2/2] chore: addressed PR feedback --- .../promises/{fixtures.js => common-tests.js} | 0 test/lib/promises/transaction-state.js | 4 ++-- .../bluebird/{fixtures.js => common-tests.js} | 4 ++-- test/versioned/bluebird/helpers.js | 22 +++++-------------- test/versioned/bluebird/methods.test.js | 6 ++--- 5 files changed, 12 insertions(+), 24 deletions(-) rename test/lib/promises/{fixtures.js => common-tests.js} (100%) rename test/versioned/bluebird/{fixtures.js => common-tests.js} (99%) diff --git a/test/lib/promises/fixtures.js b/test/lib/promises/common-tests.js similarity index 100% rename from test/lib/promises/fixtures.js rename to test/lib/promises/common-tests.js diff --git a/test/lib/promises/transaction-state.js b/test/lib/promises/transaction-state.js index 68e9676e55..b4d13737f4 100644 --- a/test/lib/promises/transaction-state.js +++ b/test/lib/promises/transaction-state.js @@ -8,10 +8,10 @@ const helper = require('../agent_helper') const { tspl } = require('@matteo.collina/tspl') const { checkTransaction } = require('./helpers') -const initFixtures = require('./fixtures') +const initSharedTests = require('./common-tests') module.exports = async function runTests({ t, agent, Promise, library }) { - const performTests = initFixtures({ t, agent, Promise }) + const performTests = initSharedTests({ t, agent, Promise }) /* eslint-disable no-shadow, brace-style */ if (library) { await performTests( diff --git a/test/versioned/bluebird/fixtures.js b/test/versioned/bluebird/common-tests.js similarity index 99% rename from test/versioned/bluebird/fixtures.js rename to test/versioned/bluebird/common-tests.js index 1c5dcfc38a..b9dcc5178b 100644 --- a/test/versioned/bluebird/fixtures.js +++ b/test/versioned/bluebird/common-tests.js @@ -479,7 +479,7 @@ function testAsCallbackBehavior(methodName) { id(inCallbackTransaction), name + 'should have the same transaction inside the success callback' ) - plan.ok(!err, name + 'should not have an error') + plan.ok(!err) plan.deepEqual(result, [1, 2, 3, name], name + 'should have the correct result value') }) .then(function () { @@ -534,7 +534,7 @@ function testCatchBehavior(methodName) { count: 2, testFunc: function asCallbackTest({ plan, name, promise }) { return promise[methodName](function (err) { - plan.ok(!err, name + 'should not go into ' + methodName + ' from a resolved promise') + plan.ok(!err) }) .then(function () { throw new Error('Promise#' + methodName + ' test error') diff --git a/test/versioned/bluebird/helpers.js b/test/versioned/bluebird/helpers.js index 6009ea1857..07043921fd 100644 --- a/test/versioned/bluebird/helpers.js +++ b/test/versioned/bluebird/helpers.js @@ -9,8 +9,7 @@ const { runMultiple } = require('../../lib/promises/helpers') const { tspl } = require('@matteo.collina/tspl') const symbols = require('../../../lib/symbols') const helper = require('../../lib/agent_helper') -const util = require('util') -const setImmediatePromisified = util.promisify(setImmediate) +const { setImmediate } = require('timers/promises') async function beforeEach(ctx) { ctx.nr = {} @@ -23,14 +22,14 @@ async function beforeEach(ctx) { } }, 25) - await setImmediatePromisified() + await setImmediate() } async function afterEach(ctx) { helper.unloadAgent(ctx.nr.agent) clearInterval(ctx.nr.interval) - await setImmediatePromisified() + await setImmediate() } function id(tx) { @@ -81,12 +80,7 @@ function testPromiseMethod({ t, count, factory, end }) { testInTransaction() }, function (err) { - if (err) { - /* eslint-disable no-console */ - console.log(err.stack) - /* eslint-enable no-console */ - } - plan.ok(!err, name + 'should not result in error') + plan.ok(!err) end() } ) @@ -114,13 +108,7 @@ function testPromiseMethod({ t, count, factory, end }) { ) }, function (err) { - if (err) { - /* eslint-disable no-console */ - console.log(err) - console.log(err.stack) - /* eslint-enable no-console */ - } - plan.ok(!err, name + 'should not result in error') + plan.ok(!err) } ) .finally(cb) diff --git a/test/versioned/bluebird/methods.test.js b/test/versioned/bluebird/methods.test.js index abc480da4f..440fa447d9 100644 --- a/test/versioned/bluebird/methods.test.js +++ b/test/versioned/bluebird/methods.test.js @@ -21,7 +21,7 @@ const { testTryBehavior, testPromiseClassCastMethod, testPromiseInstanceCastMethod -} = require('./fixtures') +} = require('./common-tests') const { addTask, afterEach, @@ -1179,7 +1179,7 @@ test('Promise#error', async function (t) { testFunc: function ({ plan, promise, name }) { return promise .error(function (err) { - plan.ok(!err, name + 'should not go into error from a resolved promise') + plan.ok(!err) }) .then(function () { throw new OperationalError('Promise#error test error') @@ -1805,7 +1805,7 @@ test('Promise#timeout', async function (t) { start = Date.now() }, function (err) { - plan.ok(!err, name + 'should not have timed out') + plan.ok(!err) } ) .delay(1000, 'never see me')