node/test/parallel/test-util-callbackify.js
Ruben Bridgewater ac2fc0dd5f
errors: improve ERR_INVALID_ARG_TYPE
ERR_INVALID_ARG_TYPE is the most common error used throughout the
code base. This improves the error message by providing more details
to the user and by indicating more precisely which values are allowed
ones and which ones are not.

It adds the actual input to the error message in case it's a primitive.
If it's a class instance, it'll print the class name instead of
"object" and "falsy" or similar entries are not named "type" anymore.

PR-URL: https://github.com/nodejs/node/pull/29675
Reviewed-By: Rich Trott <rtrott@gmail.com>
2019-12-20 03:10:13 +01:00

290 lines
7.5 KiB
JavaScript

'use strict';
const common = require('../common');
// This test checks that the semantics of `util.callbackify` are as described in
// the API docs
const assert = require('assert');
const { callbackify } = require('util');
const { execFile } = require('child_process');
const fixtures = require('../common/fixtures');
const values = [
'hello world',
null,
undefined,
false,
0,
{},
{ key: 'value' },
Symbol('I am a symbol'),
function ok() {},
['array', 'with', 4, 'values'],
new Error('boo')
];
{
// Test that the resolution value is passed as second argument to callback
for (const value of values) {
// Test and `async function`
async function asyncFn() {
return value;
}
const cbAsyncFn = callbackify(asyncFn);
cbAsyncFn(common.mustCall((err, ret) => {
assert.ifError(err);
assert.strictEqual(ret, value);
}));
// Test Promise factory
function promiseFn() {
return Promise.resolve(value);
}
const cbPromiseFn = callbackify(promiseFn);
cbPromiseFn(common.mustCall((err, ret) => {
assert.ifError(err);
assert.strictEqual(ret, value);
}));
// Test Thenable
function thenableFn() {
return {
then(onRes, onRej) {
onRes(value);
}
};
}
const cbThenableFn = callbackify(thenableFn);
cbThenableFn(common.mustCall((err, ret) => {
assert.ifError(err);
assert.strictEqual(ret, value);
}));
}
}
{
// Test that rejection reason is passed as first argument to callback
for (const value of values) {
// Test an `async function`
async function asyncFn() {
return Promise.reject(value);
}
const cbAsyncFn = callbackify(asyncFn);
assert.strictEqual(cbAsyncFn.length, 1);
assert.strictEqual(cbAsyncFn.name, 'asyncFnCallbackified');
cbAsyncFn(common.mustCall((err, ret) => {
assert.strictEqual(ret, undefined);
if (err instanceof Error) {
if ('reason' in err) {
assert(!value);
assert.strictEqual(err.code, 'ERR_FALSY_VALUE_REJECTION');
assert.strictEqual(err.reason, value);
} else {
assert.strictEqual(String(value).endsWith(err.message), true);
}
} else {
assert.strictEqual(err, value);
}
}));
// Test a Promise factory
function promiseFn() {
return Promise.reject(value);
}
const obj = {};
Object.defineProperty(promiseFn, 'name', {
value: obj,
writable: false,
enumerable: false,
configurable: true
});
const cbPromiseFn = callbackify(promiseFn);
assert.strictEqual(promiseFn.name, obj);
cbPromiseFn(common.mustCall((err, ret) => {
assert.strictEqual(ret, undefined);
if (err instanceof Error) {
if ('reason' in err) {
assert(!value);
assert.strictEqual(err.code, 'ERR_FALSY_VALUE_REJECTION');
assert.strictEqual(err.reason, value);
} else {
assert.strictEqual(String(value).endsWith(err.message), true);
}
} else {
assert.strictEqual(err, value);
}
}));
// Test Thenable
function thenableFn() {
return {
then(onRes, onRej) {
onRej(value);
}
};
}
const cbThenableFn = callbackify(thenableFn);
cbThenableFn(common.mustCall((err, ret) => {
assert.strictEqual(ret, undefined);
if (err instanceof Error) {
if ('reason' in err) {
assert(!value);
assert.strictEqual(err.code, 'ERR_FALSY_VALUE_REJECTION');
assert.strictEqual(err.reason, value);
} else {
assert.strictEqual(String(value).endsWith(err.message), true);
}
} else {
assert.strictEqual(err, value);
}
}));
}
}
{
// Test that arguments passed to callbackified function are passed to original
for (const value of values) {
async function asyncFn(arg) {
assert.strictEqual(arg, value);
return arg;
}
const cbAsyncFn = callbackify(asyncFn);
assert.strictEqual(cbAsyncFn.length, 2);
assert.notStrictEqual(
Object.getPrototypeOf(cbAsyncFn),
Object.getPrototypeOf(asyncFn)
);
assert.strictEqual(Object.getPrototypeOf(cbAsyncFn), Function.prototype);
cbAsyncFn(value, common.mustCall((err, ret) => {
assert.ifError(err);
assert.strictEqual(ret, value);
}));
function promiseFn(arg) {
assert.strictEqual(arg, value);
return Promise.resolve(arg);
}
const obj = {};
Object.defineProperty(promiseFn, 'length', {
value: obj,
writable: false,
enumerable: false,
configurable: true
});
const cbPromiseFn = callbackify(promiseFn);
assert.strictEqual(promiseFn.length, obj);
cbPromiseFn(value, common.mustCall((err, ret) => {
assert.ifError(err);
assert.strictEqual(ret, value);
}));
}
}
{
// Test that `this` binding is the same for callbackified and original
for (const value of values) {
const iAmThis = {
fn(arg) {
assert.strictEqual(this, iAmThis);
return Promise.resolve(arg);
},
};
iAmThis.cbFn = callbackify(iAmThis.fn);
iAmThis.cbFn(value, common.mustCall(function(err, ret) {
assert.ifError(err);
assert.strictEqual(ret, value);
assert.strictEqual(this, iAmThis);
}));
const iAmThat = {
async fn(arg) {
assert.strictEqual(this, iAmThat);
return arg;
},
};
iAmThat.cbFn = callbackify(iAmThat.fn);
iAmThat.cbFn(value, common.mustCall(function(err, ret) {
assert.ifError(err);
assert.strictEqual(ret, value);
assert.strictEqual(this, iAmThat);
}));
}
}
{
// Test that callback that throws emits an `uncaughtException` event
const fixture = fixtures.path('uncaught-exceptions', 'callbackify1.js');
execFile(
process.execPath,
[fixture],
common.mustCall((err, stdout, stderr) => {
assert.strictEqual(err.code, 1);
assert.strictEqual(Object.getPrototypeOf(err).name, 'Error');
assert.strictEqual(stdout, '');
const errLines = stderr.trim().split(/[\r\n]+/);
const errLine = errLines.find((l) => /^Error/.exec(l));
assert.strictEqual(errLine, `Error: ${fixture}`);
})
);
}
{
// Test that handled `uncaughtException` works and passes rejection reason
const fixture = fixtures.path('uncaught-exceptions', 'callbackify2.js');
execFile(
process.execPath,
[fixture],
common.mustCall((err, stdout, stderr) => {
assert.ifError(err);
assert.strictEqual(
stdout.trim(),
`ifError got unwanted exception: ${fixture}`);
assert.strictEqual(stderr, '');
})
);
}
{
// Verify that non-function inputs throw.
['foo', null, undefined, false, 0, {}, Symbol(), []].forEach((value) => {
common.expectsError(() => {
callbackify(value);
}, {
code: 'ERR_INVALID_ARG_TYPE',
type: TypeError,
message: 'The "original" argument must be of type function.' +
common.invalidArgTypeHelper(value)
});
});
}
{
async function asyncFn() {
return 42;
}
const cb = callbackify(asyncFn);
const args = [];
// Verify that the last argument to the callbackified function is a function.
['foo', null, undefined, false, 0, {}, Symbol(), []].forEach((value) => {
args.push(value);
common.expectsError(() => {
cb(...args);
}, {
code: 'ERR_INVALID_ARG_TYPE',
type: TypeError,
message: 'The last argument must be of type function.' +
common.invalidArgTypeHelper(value)
});
});
}