node/test/parallel/test-util-inspect.js
Jordan Harband e577618227 util: inspect: enumerable Symbols no longer have square brackets
Implements https://github.com/orgs/nodejs/discussions/41283#discussioncomment-11188239

PR-URL: https://github.com/nodejs/node/pull/55778
Reviewed-By: Ruben Bridgewater <ruben@bridgewater.de>
Reviewed-By: Moshe Atlow <moshe@atlow.co.il>
Reviewed-By: Chemi Atlow <chemi@atlow.co.il>
Reviewed-By: Juan José Arboleda <soyjuanarbol@gmail.com>
Reviewed-By: Rafael Gonzaga <rafael.nunu@hotmail.com>
2024-11-17 06:57:37 +00:00

3358 lines
105 KiB
JavaScript
Raw Permalink Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// Flags: --expose-internals
// Copyright Joyent, Inc. and other Node contributors.
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to permit
// persons to whom the Software is furnished to do so, subject to the
// following conditions:
//
// The above copyright notice and this permission notice shall be included
// in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
// USE OR OTHER DEALINGS IN THE SOFTWARE.
'use strict';
const common = require('../common');
const assert = require('assert');
const { internalBinding } = require('internal/test/binding');
const JSStream = internalBinding('js_stream').JSStream;
const util = require('util');
const vm = require('vm');
const v8 = require('v8');
const { previewEntries } = internalBinding('util');
const { inspect } = util;
const { MessageChannel } = require('worker_threads');
const url = require('url');
assert.strictEqual(util.inspect(1), '1');
assert.strictEqual(util.inspect(false), 'false');
assert.strictEqual(util.inspect(''), "''");
assert.strictEqual(util.inspect('hello'), "'hello'");
assert.strictEqual(util.inspect(function abc() {}), '[Function: abc]');
assert.strictEqual(util.inspect(() => {}), '[Function (anonymous)]');
assert.strictEqual(
util.inspect(async function() {}),
'[AsyncFunction (anonymous)]'
);
assert.strictEqual(util.inspect(async () => {}), '[AsyncFunction (anonymous)]');
// Special function inspection.
{
const fn = (() => function*() {})();
assert.strictEqual(
util.inspect(fn),
'[GeneratorFunction (anonymous)]'
);
assert.strictEqual(
util.inspect(async function* abc() {}),
'[AsyncGeneratorFunction: abc]'
);
Object.setPrototypeOf(fn, Object.getPrototypeOf(async () => {}));
assert.strictEqual(
util.inspect(fn),
'[GeneratorFunction (anonymous)] AsyncFunction'
);
Object.defineProperty(fn, 'name', { value: 5, configurable: true });
assert.strictEqual(
util.inspect(fn),
'[GeneratorFunction: 5] AsyncFunction'
);
Object.defineProperty(fn, Symbol.toStringTag, {
value: 'Foobar',
configurable: true
});
assert.strictEqual(
util.inspect({ ['5']: fn }),
"{ '5': [GeneratorFunction: 5] AsyncFunction [Foobar] }"
);
Object.defineProperty(fn, 'name', { value: '5', configurable: true });
Object.setPrototypeOf(fn, null);
assert.strictEqual(
util.inspect(fn),
'[GeneratorFunction (null prototype): 5] [Foobar]'
);
assert.strictEqual(
util.inspect({ ['5']: fn }),
"{ '5': [GeneratorFunction (null prototype): 5] [Foobar] }"
);
}
assert.strictEqual(util.inspect(undefined), 'undefined');
assert.strictEqual(util.inspect(null), 'null');
assert.strictEqual(util.inspect(/foo(bar\n)?/gi), '/foo(bar\\n)?/gi');
assert.strictEqual(
util.inspect(new Date('Sun, 14 Feb 2010 11:48:40 GMT')),
new Date('2010-02-14T12:48:40+01:00').toISOString()
);
assert.strictEqual(util.inspect(new Date('')), (new Date('')).toString());
assert.strictEqual(util.inspect('\n\x01'), "'\\n\\x01'");
assert.strictEqual(
util.inspect(`${Array(75).fill(1)}'\n\x1d\n\x03\x85\x7f\x7e\x9f\xa0`),
// eslint-disable-next-line no-irregular-whitespace
`"${Array(75).fill(1)}'\\n" +\n '\\x1D\\n' +\n '\\x03\\x85\\x7F~\\x9F '`
);
assert.strictEqual(util.inspect([]), '[]');
assert.strictEqual(util.inspect({ __proto__: [] }), 'Array {}');
assert.strictEqual(util.inspect([1, 2]), '[ 1, 2 ]');
assert.strictEqual(util.inspect([1, [2, 3]]), '[ 1, [ 2, 3 ] ]');
assert.strictEqual(util.inspect({}), '{}');
assert.strictEqual(util.inspect({ a: 1 }), '{ a: 1 }');
assert.strictEqual(util.inspect({ a: function() {} }), '{ a: [Function: a] }');
assert.strictEqual(util.inspect({ a: () => {} }), '{ a: [Function: a] }');
// eslint-disable-next-line func-name-matching
assert.strictEqual(util.inspect({ a: async function abc() {} }),
'{ a: [AsyncFunction: abc] }');
assert.strictEqual(util.inspect({ a: async () => {} }),
'{ a: [AsyncFunction: a] }');
assert.strictEqual(util.inspect({ a: function*() {} }),
'{ a: [GeneratorFunction: a] }');
assert.strictEqual(util.inspect({ a: 1, b: 2 }), '{ a: 1, b: 2 }');
assert.strictEqual(util.inspect({ 'a': {} }), '{ a: {} }');
assert.strictEqual(util.inspect({ 'a': { 'b': 2 } }), '{ a: { b: 2 } }');
assert.strictEqual(util.inspect({ 'a': { 'b': { 'c': { 'd': 2 } } } }),
'{ a: { b: { c: [Object] } } }');
assert.strictEqual(
util.inspect({ 'a': { 'b': { 'c': { 'd': 2 } } } }, false, null),
'{\n a: { b: { c: { d: 2 } } }\n}');
assert.strictEqual(util.inspect([1, 2, 3], true), '[ 1, 2, 3, [length]: 3 ]');
assert.strictEqual(util.inspect({ 'a': { 'b': { 'c': 2 } } }, false, 0),
'{ a: [Object] }');
assert.strictEqual(util.inspect({ 'a': { 'b': { 'c': 2 } } }, false, 1),
'{ a: { b: [Object] } }');
assert.strictEqual(util.inspect({ 'a': { 'b': ['c'] } }, false, 1),
'{ a: { b: [Array] } }');
assert.strictEqual(util.inspect(new Uint8Array(0)), 'Uint8Array(0) []');
assert(inspect(new Uint8Array(0), { showHidden: true }).includes('[buffer]'));
assert.strictEqual(
util.inspect(
Object.create(
{},
{ visible: { value: 1, enumerable: true }, hidden: { value: 2 } }
)
),
'{ visible: 1 }'
);
assert.strictEqual(
util.inspect(
Object.assign(new String('hello'), { [Symbol('foo')]: 123 }),
{ showHidden: true }
),
"[String: 'hello'] { [length]: 5, Symbol(foo): 123 }"
);
assert.match(util.inspect((new JSStream())._externalStream),
/^\[External: [0-9a-f]+\]$/);
{
const regexp = /regexp/;
regexp.aprop = 42;
assert.strictEqual(util.inspect({ a: regexp }, false, 0), '{ a: /regexp/ }');
}
assert.match(
util.inspect({ a: { a: { a: { a: {} } } } }, undefined, undefined, true),
/Object/
);
assert.doesNotMatch(
util.inspect({ a: { a: { a: { a: {} } } } }, undefined, null, true),
/Object/
);
{
const showHidden = true;
const ab = new Uint8Array([1, 2, 3, 4]).buffer;
const dv = new DataView(ab, 1, 2);
assert.strictEqual(
util.inspect(ab, showHidden),
'ArrayBuffer { [Uint8Contents]: <01 02 03 04>, byteLength: 4 }'
);
assert.strictEqual(util.inspect(new DataView(ab, 1, 2), showHidden),
'DataView {\n' +
' byteLength: 2,\n' +
' byteOffset: 1,\n' +
' buffer: ArrayBuffer {' +
' [Uint8Contents]: <01 02 03 04>, byteLength: 4 }\n}');
assert.strictEqual(
util.inspect(ab, showHidden),
'ArrayBuffer { [Uint8Contents]: <01 02 03 04>, byteLength: 4 }'
);
assert.strictEqual(util.inspect(dv, showHidden),
'DataView {\n' +
' byteLength: 2,\n' +
' byteOffset: 1,\n' +
' buffer: ArrayBuffer { [Uint8Contents]: ' +
'<01 02 03 04>, byteLength: 4 }\n}');
ab.x = 42;
dv.y = 1337;
assert.strictEqual(util.inspect(ab, showHidden),
'ArrayBuffer { [Uint8Contents]: <01 02 03 04>, ' +
'byteLength: 4, x: 42 }');
assert.strictEqual(util.inspect(dv, showHidden),
'DataView {\n' +
' byteLength: 2,\n' +
' byteOffset: 1,\n' +
' buffer: ArrayBuffer { [Uint8Contents]: <01 02 03 04>,' +
' byteLength: 4, x: 42 },\n' +
' y: 1337\n}');
}
{
const ab = new ArrayBuffer(42);
assert.strictEqual(ab.byteLength, 42);
new MessageChannel().port1.postMessage(ab, [ ab ]);
assert.strictEqual(ab.byteLength, 0);
assert.strictEqual(util.inspect(ab),
'ArrayBuffer { (detached), byteLength: 0 }');
}
// Truncate output for ArrayBuffers using plural or singular bytes
{
const ab = new ArrayBuffer(3);
assert.strictEqual(util.inspect(ab, { showHidden: true, maxArrayLength: 2 }),
'ArrayBuffer { [Uint8Contents]' +
': <00 00 ... 1 more byte>, byteLength: 3 }');
assert.strictEqual(util.inspect(ab, { showHidden: true, maxArrayLength: 1 }),
'ArrayBuffer { [Uint8Contents]' +
': <00 ... 2 more bytes>, byteLength: 3 }');
}
// Now do the same checks but from a different context.
{
const showHidden = false;
const ab = vm.runInNewContext('new ArrayBuffer(4)');
const dv = vm.runInNewContext('new DataView(ab, 1, 2)', { ab });
assert.strictEqual(
util.inspect(ab, showHidden),
'ArrayBuffer { [Uint8Contents]: <00 00 00 00>, byteLength: 4 }'
);
assert.strictEqual(util.inspect(new DataView(ab, 1, 2), showHidden),
'DataView {\n' +
' byteLength: 2,\n' +
' byteOffset: 1,\n' +
' buffer: ArrayBuffer { [Uint8Contents]: <00 00 00 00>,' +
' byteLength: 4 }\n}');
assert.strictEqual(
util.inspect(ab, showHidden),
'ArrayBuffer { [Uint8Contents]: <00 00 00 00>, byteLength: 4 }'
);
assert.strictEqual(util.inspect(dv, showHidden),
'DataView {\n' +
' byteLength: 2,\n' +
' byteOffset: 1,\n' +
' buffer: ArrayBuffer { [Uint8Contents]: <00 00 00 00>,' +
' byteLength: 4 }\n}');
ab.x = 42;
dv.y = 1337;
assert.strictEqual(util.inspect(ab, showHidden),
'ArrayBuffer { [Uint8Contents]: <00 00 00 00>, ' +
'byteLength: 4, x: 42 }');
assert.strictEqual(util.inspect(dv, showHidden),
'DataView {\n' +
' byteLength: 2,\n' +
' byteOffset: 1,\n' +
' buffer: ArrayBuffer { [Uint8Contents]: <00 00 00 00>,' +
' byteLength: 4, x: 42 },\n' +
' y: 1337\n}');
}
[ Float32Array,
Float64Array,
Int16Array,
Int32Array,
Int8Array,
Uint16Array,
Uint32Array,
Uint8Array,
Uint8ClampedArray ].forEach((constructor) => {
const length = 2;
const byteLength = length * constructor.BYTES_PER_ELEMENT;
const array = new constructor(new ArrayBuffer(byteLength), 0, length);
array[0] = 65;
array[1] = 97;
assert.strictEqual(
util.inspect(array, { showHidden: true }),
`${constructor.name}(${length}) [\n` +
' 65,\n' +
' 97,\n' +
` [BYTES_PER_ELEMENT]: ${constructor.BYTES_PER_ELEMENT},\n` +
` [length]: ${length},\n` +
` [byteLength]: ${byteLength},\n` +
' [byteOffset]: 0,\n' +
` [buffer]: ArrayBuffer { byteLength: ${byteLength} }\n]`);
assert.strictEqual(
util.inspect(array, false),
`${constructor.name}(${length}) [ 65, 97 ]`
);
});
// Now check that declaring a TypedArray in a different context works the same.
[ Float32Array,
Float64Array,
Int16Array,
Int32Array,
Int8Array,
Uint16Array,
Uint32Array,
Uint8Array,
Uint8ClampedArray ].forEach((constructor) => {
const length = 2;
const byteLength = length * constructor.BYTES_PER_ELEMENT;
const array = vm.runInNewContext(
'new constructor(new ArrayBuffer(byteLength), 0, length)',
{ constructor, byteLength, length }
);
array[0] = 65;
array[1] = 97;
assert.strictEqual(
util.inspect(array, true),
`${constructor.name}(${length}) [\n` +
' 65,\n' +
' 97,\n' +
` [BYTES_PER_ELEMENT]: ${constructor.BYTES_PER_ELEMENT},\n` +
` [length]: ${length},\n` +
` [byteLength]: ${byteLength},\n` +
' [byteOffset]: 0,\n' +
` [buffer]: ArrayBuffer { byteLength: ${byteLength} }\n]`);
assert.strictEqual(
util.inspect(array, false),
`${constructor.name}(${length}) [ 65, 97 ]`
);
});
{
const brokenLength = new Float32Array(2);
Object.defineProperty(brokenLength, 'length', { value: -1 });
assert.strictEqual(inspect(brokenLength), 'Float32Array(2) [ 0n, 0n ]');
}
assert.strictEqual(
util.inspect(Object.create({}, {
visible: { value: 1, enumerable: true },
hidden: { value: 2 }
}), { showHidden: true }),
'{ visible: 1, [hidden]: 2 }'
);
// Objects without prototype.
assert.strictEqual(
util.inspect(Object.create(null, {
name: { value: 'Tim', enumerable: true },
hidden: { value: 'secret' }
}), { showHidden: true }),
"[Object: null prototype] { name: 'Tim', [hidden]: 'secret' }"
);
assert.strictEqual(
util.inspect(Object.create(null, {
name: { value: 'Tim', enumerable: true },
hidden: { value: 'secret' }
})),
"[Object: null prototype] { name: 'Tim' }"
);
// Dynamic properties.
{
assert.strictEqual(
util.inspect({ get readonly() { return 1; } }),
'{ readonly: [Getter] }');
assert.strictEqual(
util.inspect({ get readwrite() { return 1; }, set readwrite(val) {} }),
'{ readwrite: [Getter/Setter] }');
assert.strictEqual(
// eslint-disable-next-line accessor-pairs
util.inspect({ set writeonly(val) {} }),
'{ writeonly: [Setter] }');
const value = {};
value.a = value;
assert.strictEqual(util.inspect(value), '<ref *1> { a: [Circular *1] }');
const getterFn = {
get one() {
return null;
}
};
assert.strictEqual(
util.inspect(getterFn, { getters: true }),
'{ one: [Getter: null] }'
);
}
// Array with dynamic properties.
{
const value = [1, 2, 3];
Object.defineProperty(
value,
'growingLength',
{
enumerable: true,
get: function() { this.push(true); return this.length; }
}
);
Object.defineProperty(
value,
'-1',
{
enumerable: true,
value: -1
}
);
assert.strictEqual(util.inspect(value),
"[ 1, 2, 3, growingLength: [Getter], '-1': -1 ]");
}
// Array with inherited number properties.
{
class CustomArray extends Array {}
CustomArray.prototype[5] = 'foo';
CustomArray.prototype[49] = 'bar';
CustomArray.prototype.foo = true;
const arr = new CustomArray(50);
arr[49] = 'I win';
assert.strictEqual(
util.inspect(arr),
"CustomArray(50) [ <49 empty items>, 'I win' ]"
);
assert.strictEqual(
util.inspect(arr, { showHidden: true }),
'CustomArray(50) [\n' +
' <49 empty items>,\n' +
" 'I win',\n" +
' [length]: 50,\n' +
" '5': 'foo',\n" +
' foo: true\n' +
']'
);
}
// Array with extra properties.
{
const arr = [1, 2, 3, , ]; // eslint-disable-line no-sparse-arrays
arr.foo = 'bar';
assert.strictEqual(util.inspect(arr),
"[ 1, 2, 3, <1 empty item>, foo: 'bar' ]");
const arr2 = [];
assert.strictEqual(util.inspect([], { showHidden: true }), '[ [length]: 0 ]');
arr2['00'] = 1;
assert.strictEqual(util.inspect(arr2), "[ '00': 1 ]");
assert.strictEqual(util.inspect(arr2, { showHidden: true }),
"[ [length]: 0, '00': 1 ]");
arr2[1] = 0;
assert.strictEqual(util.inspect(arr2), "[ <1 empty item>, 0, '00': 1 ]");
assert.strictEqual(util.inspect(arr2, { showHidden: true }),
"[ <1 empty item>, 0, [length]: 2, '00': 1 ]");
delete arr2[1];
assert.strictEqual(util.inspect(arr2), "[ <2 empty items>, '00': 1 ]");
assert.strictEqual(util.inspect(arr2, { showHidden: true }),
"[ <2 empty items>, [length]: 2, '00': 1 ]");
arr2['01'] = 2;
assert.strictEqual(util.inspect(arr2),
"[ <2 empty items>, '00': 1, '01': 2 ]");
assert.strictEqual(util.inspect(arr2, { showHidden: true }),
"[ <2 empty items>, [length]: 2, '00': 1, '01': 2 ]");
delete arr2['00'];
arr2[0] = 0;
assert.strictEqual(util.inspect(arr2),
"[ 0, <1 empty item>, '01': 2 ]");
assert.strictEqual(util.inspect(arr2, { showHidden: true }),
"[ 0, <1 empty item>, [length]: 2, '01': 2 ]");
delete arr2['01'];
arr2[2 ** 32 - 2] = 'max';
arr2[2 ** 32 - 1] = 'too far';
assert.strictEqual(
util.inspect(arr2),
"[ 0, <4294967293 empty items>, 'max', '4294967295': 'too far' ]"
);
const arr3 = [];
arr3[-1] = -1;
assert.strictEqual(util.inspect(arr3), "[ '-1': -1 ]");
}
// Indices out of bounds.
{
const arr = [];
arr[2 ** 32] = true; // Not a valid array index.
assert.strictEqual(util.inspect(arr), "[ '4294967296': true ]");
arr[0] = true;
arr[10] = true;
assert.strictEqual(util.inspect(arr),
"[ true, <9 empty items>, true, '4294967296': true ]");
arr[2 ** 32 - 2] = true;
arr[2 ** 32 - 1] = true;
arr[2 ** 32 + 1] = true;
delete arr[0];
delete arr[10];
assert.strictEqual(util.inspect(arr),
['[',
'<4294967294 empty items>,',
'true,',
"'4294967296': true,",
"'4294967295': true,",
"'4294967297': true\n]",
].join('\n '));
}
// Function with properties.
{
const value = () => {};
value.aprop = 42;
assert.strictEqual(util.inspect(value), '[Function: value] { aprop: 42 }');
}
// Anonymous function with properties.
{
const value = (() => function() {})();
value.aprop = 42;
assert.strictEqual(
util.inspect(value),
'[Function (anonymous)] { aprop: 42 }'
);
}
// Regular expressions with properties.
{
const value = /123/ig;
value.aprop = 42;
assert.strictEqual(util.inspect(value), '/123/gi { aprop: 42 }');
}
// Dates with properties.
{
const value = new Date('Sun, 14 Feb 2010 11:48:40 GMT');
value.aprop = 42;
assert.strictEqual(util.inspect(value),
'2010-02-14T11:48:40.000Z { aprop: 42 }');
}
// Test the internal isDate implementation.
{
const Date2 = vm.runInNewContext('Date');
const d = new Date2();
const orig = util.inspect(d);
Date2.prototype.foo = 'bar';
const after = util.inspect(d);
assert.strictEqual(orig, after);
}
// Test positive/negative zero.
assert.strictEqual(util.inspect(0), '0');
assert.strictEqual(util.inspect(-0), '-0');
// Edge case from check.
assert.strictEqual(util.inspect(-5e-324), '-5e-324');
// Test for sparse array.
{
const a = ['foo', 'bar', 'baz'];
assert.strictEqual(util.inspect(a), "[ 'foo', 'bar', 'baz' ]");
delete a[1];
assert.strictEqual(util.inspect(a), "[ 'foo', <1 empty item>, 'baz' ]");
assert.strictEqual(
util.inspect(a, true),
"[ 'foo', <1 empty item>, 'baz', [length]: 3 ]"
);
assert.strictEqual(util.inspect(new Array(5)), '[ <5 empty items> ]');
a[3] = 'bar';
a[100] = 'qux';
assert.strictEqual(
util.inspect(a, { breakLength: Infinity }),
"[ 'foo', <1 empty item>, 'baz', 'bar', <96 empty items>, 'qux' ]"
);
delete a[3];
assert.strictEqual(
util.inspect(a, { maxArrayLength: 4 }),
"[ 'foo', <1 empty item>, 'baz', <97 empty items>, ... 1 more item ]"
);
// test 4 special case
assert.strictEqual(util.inspect(a, {
maxArrayLength: 2
}), "[ 'foo', <1 empty item>, ... 99 more items ]");
}
// Test for Array constructor in different context.
{
const map = new Map();
map.set(1, 2);
// Passing only a single argument to indicate a set iterator.
const valsSetIterator = previewEntries(map.entries());
// Passing through true to indicate a map iterator.
const valsMapIterEntries = previewEntries(map.entries(), true);
const valsMapIterKeys = previewEntries(map.keys(), true);
assert.strictEqual(util.inspect(valsSetIterator), '[ 1, 2 ]');
assert.strictEqual(util.inspect(valsMapIterEntries), '[ [ 1, 2 ], true ]');
assert.strictEqual(util.inspect(valsMapIterKeys), '[ [ 1 ], false ]');
}
// Test for other constructors in different context.
{
let obj = vm.runInNewContext('(function(){return {}})()', {});
assert.strictEqual(util.inspect(obj), '{}');
obj = vm.runInNewContext('const m=new Map();m.set(1,2);m', {});
assert.strictEqual(util.inspect(obj), 'Map(1) { 1 => 2 }');
obj = vm.runInNewContext('const s=new Set();s.add(1);s.add(2);s', {});
assert.strictEqual(util.inspect(obj), 'Set(2) { 1, 2 }');
obj = vm.runInNewContext('fn=function(){};new Promise(fn,fn)', {});
assert.strictEqual(util.inspect(obj), 'Promise { <pending> }');
}
// Test for property descriptors.
{
const getter = Object.create(null, {
a: {
get: function() { return 'aaa'; }
}
});
const setter = Object.create(null, {
b: { // eslint-disable-line accessor-pairs
set: function() {}
}
});
const getterAndSetter = Object.create(null, {
c: {
get: function() { return 'ccc'; },
set: function() {}
}
});
assert.strictEqual(
util.inspect(getter, true),
'[Object: null prototype] { [a]: [Getter] }'
);
assert.strictEqual(
util.inspect(setter, true),
'[Object: null prototype] { [b]: [Setter] }'
);
assert.strictEqual(
util.inspect(getterAndSetter, true),
'[Object: null prototype] { [c]: [Getter/Setter] }'
);
}
// Exceptions should print the error message, not '{}'.
{
[
new Error(),
new Error('FAIL'),
new TypeError('FAIL'),
new SyntaxError('FAIL'),
].forEach((err) => {
assert.strictEqual(util.inspect(err), err.stack);
});
assert.throws(
() => undef(), // eslint-disable-line no-undef
(e) => {
assert.strictEqual(util.inspect(e), e.stack);
return true;
}
);
const ex = util.inspect(new Error('FAIL'), true);
assert(ex.includes('Error: FAIL'));
assert(ex.includes('[stack]'));
assert(ex.includes('[message]'));
}
{
const falsyCause1 = new Error('', { cause: false });
delete falsyCause1.stack;
const falsyCause2 = new Error(undefined, { cause: null });
falsyCause2.stack = '';
const undefinedCause = new Error('', { cause: undefined });
undefinedCause.stack = '';
assert.strictEqual(util.inspect(falsyCause1), '[Error] { [cause]: false }');
assert.strictEqual(util.inspect(falsyCause2), '[Error] { [cause]: null }');
assert.strictEqual(
util.inspect(undefinedCause),
'[Error] { [cause]: undefined }'
);
}
{
const tmp = Error.stackTraceLimit;
Error.stackTraceLimit = 0;
const err = new Error('foo');
const err2 = new Error('foo\nbar');
assert.strictEqual(util.inspect(err, { compact: true }), '[Error: foo]');
assert(err.stack);
delete err.stack;
assert(!err.stack);
assert.strictEqual(util.inspect(err, { compact: true }), '[Error: foo]');
assert.strictEqual(
util.inspect(err2, { compact: true }),
'[Error: foo\nbar]'
);
err.bar = true;
err2.bar = true;
assert.strictEqual(
util.inspect(err, { compact: true }),
'{ [Error: foo] bar: true }'
);
assert.strictEqual(
util.inspect(err2, { compact: true }),
'{ [Error: foo\nbar]\n bar: true }'
);
assert.strictEqual(
util.inspect(err, { compact: true, breakLength: 5 }),
'{ [Error: foo]\n bar: true }'
);
assert.strictEqual(
util.inspect(err, { compact: true, breakLength: 1 }),
'{ [Error: foo]\n bar:\n true }'
);
assert.strictEqual(
util.inspect(err2, { compact: true, breakLength: 5 }),
'{ [Error: foo\nbar]\n bar: true }'
);
assert.strictEqual(
util.inspect(err, { compact: false }),
'[Error: foo] {\n bar: true\n}'
);
assert.strictEqual(
util.inspect(err2, { compact: false }),
'[Error: foo\nbar] {\n bar: true\n}'
);
Error.stackTraceLimit = tmp;
}
// Prevent enumerable error properties from being printed.
{
let err = new Error();
err.message = 'foobar';
let out = util.inspect(err).split('\n');
assert.strictEqual(out[0], 'Error: foobar');
assert(out[out.length - 1].startsWith(' at '));
// Reset the error, the stack is otherwise not recreated.
err = new Error();
err.message = 'foobar';
err.name = 'Unique';
Object.defineProperty(err, 'stack', { value: err.stack, enumerable: true });
out = util.inspect(err).split('\n');
assert.strictEqual(out[0], 'Unique: foobar');
assert(out[out.length - 1].startsWith(' at '));
err.name = 'Baz';
out = util.inspect(err).split('\n');
assert.strictEqual(out[0], 'Unique: foobar');
assert.strictEqual(out[out.length - 2], " name: 'Baz'");
assert.strictEqual(out[out.length - 1], '}');
}
// Doesn't capture stack trace.
{
function BadCustomError(msg) {
Error.call(this);
Object.defineProperty(this, 'message',
{ value: msg, enumerable: false });
Object.defineProperty(this, 'name',
{ value: 'BadCustomError', enumerable: false });
}
Object.setPrototypeOf(BadCustomError.prototype, Error.prototype);
Object.setPrototypeOf(BadCustomError, Error);
assert.strictEqual(
util.inspect(new BadCustomError('foo')),
'[BadCustomError: foo]'
);
}
// Tampered error stack or name property (different type than string).
// Note: Symbols are not supported by `Error#toString()` which is called by
// accessing the `stack` property.
[
[404, '404: foo', '[404]'],
[0, '0: foo', '[RangeError: foo]'],
[0n, '0: foo', '[RangeError: foo]'],
[null, 'null: foo', '[RangeError: foo]'],
[undefined, 'RangeError: foo', '[RangeError: foo]'],
[false, 'false: foo', '[RangeError: foo]'],
['', 'foo', '[RangeError: foo]'],
[[1, 2, 3], '1,2,3: foo', '[1,2,3]'],
].forEach(([value, outputStart, stack]) => {
let err = new RangeError('foo');
err.name = value;
assert(
util.inspect(err).startsWith(outputStart),
util.format(
'The name set to %o did not result in the expected output "%s"',
value,
outputStart
)
);
err = new RangeError('foo');
err.stack = value;
assert.strictEqual(util.inspect(err), stack);
});
// https://github.com/nodejs/node-v0.x-archive/issues/1941
assert.strictEqual(util.inspect({ __proto__: Date.prototype }), 'Date {}');
// https://github.com/nodejs/node-v0.x-archive/issues/1944
{
const d = new Date();
d.toUTCString = null;
util.inspect(d);
}
// Should not throw.
{
const d = new Date();
d.toISOString = null;
util.inspect(d);
}
// Should not throw.
{
const r = /regexp/;
r.toString = null;
util.inspect(r);
}
// See https://github.com/nodejs/node-v0.x-archive/issues/2225
{
const x = { [util.inspect.custom]: util.inspect };
assert(util.inspect(x).includes(
'Symbol(nodejs.util.inspect.custom): [Function: inspect] {\n'));
}
// `util.inspect` should display the escaped value of a key.
{
const w = {
'\\': 1,
'\\\\': 2,
'\\\\\\': 3,
'\\\\\\\\': 4,
'\n': 5,
'\r': 6
};
const y = ['a', 'b', 'c'];
y['\\\\'] = 'd';
y['\n'] = 'e';
y['\r'] = 'f';
assert.strictEqual(
util.inspect(w),
"{ '\\\\': 1, '\\\\\\\\': 2, '\\\\\\\\\\\\': 3, " +
"'\\\\\\\\\\\\\\\\': 4, '\\n': 5, '\\r': 6 }"
);
assert.strictEqual(
util.inspect(y),
"[ 'a', 'b', 'c', '\\\\\\\\': 'd', " +
"'\\n': 'e', '\\r': 'f' ]"
);
}
// Escape unpaired surrogate pairs.
{
const edgeChar = String.fromCharCode(0xd799);
for (let charCode = 0xD800; charCode < 0xDFFF; charCode++) {
const surrogate = String.fromCharCode(charCode);
assert.strictEqual(
util.inspect(surrogate),
`'\\u${charCode.toString(16)}'`
);
assert.strictEqual(
util.inspect(`${'a'.repeat(200)}${surrogate}`),
`'${'a'.repeat(200)}\\u${charCode.toString(16)}'`
);
assert.strictEqual(
util.inspect(`${surrogate}${'a'.repeat(200)}`),
`'\\u${charCode.toString(16)}${'a'.repeat(200)}'`
);
if (charCode < 0xdc00) {
const highSurrogate = surrogate;
const lowSurrogate = String.fromCharCode(charCode + 1024);
assert(
!util.inspect(
`${edgeChar}${highSurrogate}${lowSurrogate}${edgeChar}`
).includes('\\u')
);
assert.strictEqual(
(util.inspect(
`${highSurrogate}${highSurrogate}${lowSurrogate}`
).match(/\\u/g) ?? []).length,
1
);
} else {
assert.strictEqual(
util.inspect(`${edgeChar}${surrogate}${edgeChar}`),
`'${edgeChar}\\u${charCode.toString(16)}${edgeChar}'`
);
}
}
}
// Test util.inspect.styles and util.inspect.colors.
{
function testColorStyle(style, input) {
const colorName = util.inspect.styles[style];
let color = ['', ''];
if (util.inspect.colors[colorName])
color = util.inspect.colors[colorName];
const withoutColor = util.inspect(input, false, 0, false);
const withColor = util.inspect(input, false, 0, true);
const expect = `\u001b[${color[0]}m${withoutColor}\u001b[${color[1]}m`;
assert.strictEqual(
withColor,
expect,
`util.inspect color for style ${style}`);
}
testColorStyle('special', function() {});
testColorStyle('number', 123.456);
testColorStyle('boolean', true);
testColorStyle('undefined', undefined);
testColorStyle('null', null);
testColorStyle('string', 'test string');
testColorStyle('date', new Date());
testColorStyle('regexp', /regexp/);
}
// An object with "hasOwnProperty" overwritten should not throw.
util.inspect({ hasOwnProperty: null });
// New API, accepts an "options" object.
{
const subject = { foo: 'bar', hello: 31, a: { b: { c: { d: 0 } } } };
Object.defineProperty(subject, 'hidden', { enumerable: false, value: null });
assert.strictEqual(
util.inspect(subject, { showHidden: false }).includes('hidden'),
false
);
assert.strictEqual(
util.inspect(subject, { showHidden: true }).includes('hidden'),
true
);
assert.strictEqual(
util.inspect(subject, { colors: false }).includes('\u001b[32m'),
false
);
assert.strictEqual(
util.inspect(subject, { colors: true }).includes('\u001b[32m'),
true
);
assert.strictEqual(
util.inspect(subject, { depth: 2 }).includes('c: [Object]'),
true
);
assert.strictEqual(
util.inspect(subject, { depth: 0 }).includes('a: [Object]'),
true
);
assert.strictEqual(
util.inspect(subject, { depth: null }).includes('{ d: 0 }'),
true
);
assert.strictEqual(
util.inspect(subject, { depth: undefined }).includes('{ d: 0 }'),
true
);
}
{
// "customInspect" option can enable/disable calling [util.inspect.custom]().
const subject = { [util.inspect.custom]: () => 123 };
assert.strictEqual(
util.inspect(subject, { customInspect: true }).includes('123'),
true
);
assert.strictEqual(
util.inspect(subject, { customInspect: true }).includes('inspect'),
false
);
assert.strictEqual(
util.inspect(subject, { customInspect: false }).includes('123'),
false
);
assert.strictEqual(
util.inspect(subject, { customInspect: false }).includes('inspect'),
true
);
// A custom [util.inspect.custom]() should be able to return other Objects.
subject[util.inspect.custom] = () => ({ foo: 'bar' });
assert.strictEqual(util.inspect(subject), "{ foo: 'bar' }");
subject[util.inspect.custom] = common.mustCall((depth, opts, inspect) => {
const clone = { ...opts };
// This might change at some point but for now we keep the stylize function.
// The function should either be documented or an alternative should be
// implemented.
assert.strictEqual(typeof opts.stylize, 'function');
assert.strictEqual(opts.seen, undefined);
assert.strictEqual(opts.budget, undefined);
assert.strictEqual(opts.indentationLvl, undefined);
assert.strictEqual(opts.showHidden, false);
assert.strictEqual(inspect, util.inspect);
assert.deepStrictEqual(
new Set(Object.keys(inspect.defaultOptions).concat(['stylize'])),
new Set(Object.keys(opts))
);
opts.showHidden = true;
return { [inspect.custom]: common.mustCall((depth, opts2) => {
assert.deepStrictEqual(clone, opts2);
}) };
});
util.inspect(subject);
// util.inspect.custom is a shared symbol which can be accessed as
// Symbol.for("nodejs.util.inspect.custom").
const inspect = Symbol.for('nodejs.util.inspect.custom');
subject[inspect] = () => ({ baz: 'quux' });
assert.strictEqual(util.inspect(subject), '{ baz: \'quux\' }');
subject[inspect] = (depth, opts) => {
assert.strictEqual(opts.customInspectOptions, true);
assert.strictEqual(opts.seen, null);
return {};
};
util.inspect(subject, { customInspectOptions: true, seen: null });
}
{
const subject = { [util.inspect.custom]: common.mustCall((depth, opts) => {
assert.strictEqual(depth, null);
assert.strictEqual(opts.compact, true);
}) };
util.inspect(subject, { depth: null, compact: true });
}
{
// Returning `this` from a custom inspection function works.
const subject = { a: 123, [util.inspect.custom]() { return this; } };
const UIC = 'nodejs.util.inspect.custom';
assert.strictEqual(
util.inspect(subject),
`{\n a: 123,\n Symbol(${UIC}): [Function: [${UIC}]]\n}`
);
}
// Verify that it's possible to use the stylize function to manipulate input.
assert.strictEqual(
util.inspect([1, 2, 3], { stylize() { return 'x'; } }),
'[ x, x, x ]'
);
// Using `util.inspect` with "colors" option should produce as many lines as
// without it.
{
function testLines(input) {
const countLines = (str) => (str.match(/\n/g) || []).length;
const withoutColor = util.inspect(input);
const withColor = util.inspect(input, { colors: true });
assert.strictEqual(countLines(withoutColor), countLines(withColor));
}
const bigArray = new Array(100).fill().map((value, index) => index);
testLines([1, 2, 3, 4, 5, 6, 7]);
testLines(bigArray);
testLines({ foo: 'bar', baz: 35, b: { a: 35 } });
testLines({ a: { a: 3, b: 1, c: 1, d: 1, e: 1, f: 1, g: 1, h: 1 }, b: 1 });
testLines({
foo: 'bar',
baz: 35,
b: { a: 35 },
veryLongKey: 'very long value',
evenLongerKey: ['with even longer value in array']
});
}
// Test boxed primitives output the correct values.
assert.strictEqual(util.inspect(new String('test')), "[String: 'test']");
assert.strictEqual(
util.inspect(new String('test'), { colors: true }),
"\u001b[32m[String: 'test']\u001b[39m"
);
assert.strictEqual(
util.inspect(Object(Symbol('test'))),
'[Symbol: Symbol(test)]'
);
assert.strictEqual(util.inspect(new Boolean(false)), '[Boolean: false]');
assert.strictEqual(
util.inspect(Object.setPrototypeOf(new Boolean(true), null)),
'[Boolean (null prototype): true]'
);
assert.strictEqual(util.inspect(new Number(0)), '[Number: 0]');
assert.strictEqual(
util.inspect(
Object.defineProperty(
Object.setPrototypeOf(new Number(-0), Array.prototype),
Symbol.toStringTag,
{ value: 'Foobar' }
)
),
'[Number (Array): -0] [Foobar]'
);
assert.strictEqual(util.inspect(new Number(-1.1)), '[Number: -1.1]');
assert.strictEqual(util.inspect(new Number(13.37)), '[Number: 13.37]');
// Test boxed primitives with own properties.
{
const str = new String('baz');
str.foo = 'bar';
assert.strictEqual(util.inspect(str), "[String: 'baz'] { foo: 'bar' }");
const bool = new Boolean(true);
bool.foo = 'bar';
assert.strictEqual(util.inspect(bool), "[Boolean: true] { foo: 'bar' }");
const num = new Number(13.37);
num.foo = 'bar';
assert.strictEqual(util.inspect(num), "[Number: 13.37] { foo: 'bar' }");
const sym = Object(Symbol('foo'));
sym.foo = 'bar';
assert.strictEqual(util.inspect(sym), "[Symbol: Symbol(foo)] { foo: 'bar' }");
const big = Object(BigInt(55));
big.foo = 'bar';
assert.strictEqual(util.inspect(big), "[BigInt: 55n] { foo: 'bar' }");
}
// Test es6 Symbol.
if (typeof Symbol !== 'undefined') {
assert.strictEqual(util.inspect(Symbol()), 'Symbol()');
assert.strictEqual(util.inspect(Symbol(123)), 'Symbol(123)');
assert.strictEqual(util.inspect(Symbol('hi')), 'Symbol(hi)');
assert.strictEqual(util.inspect([Symbol()]), '[ Symbol() ]');
assert.strictEqual(util.inspect({ foo: Symbol() }), '{ foo: Symbol() }');
const options = { showHidden: true };
let subject = {};
subject[Symbol('sym\nbol')] = 42;
assert.strictEqual(util.inspect(subject), '{ Symbol(sym\\nbol): 42 }');
assert.strictEqual(
util.inspect(subject, options),
'{ Symbol(sym\\nbol): 42 }'
);
Object.defineProperty(
subject,
Symbol(),
{ enumerable: false, value: 'non-enum' });
assert.strictEqual(util.inspect(subject), '{ Symbol(sym\\nbol): 42 }');
assert.strictEqual(
util.inspect(subject, options),
"{ Symbol(sym\\nbol): 42, [Symbol()]: 'non-enum' }"
);
subject = [1, 2, 3];
subject[Symbol('symbol')] = 42;
assert.strictEqual(
util.inspect(subject),
'[ 1, 2, 3, Symbol(symbol): 42 ]'
);
}
// Test Set.
{
assert.strictEqual(util.inspect(new Set()), 'Set(0) {}');
assert.strictEqual(util.inspect(new Set([1, 2, 3])), 'Set(3) { 1, 2, 3 }');
assert.strictEqual(util.inspect(new Set([1, 2, 3]), { maxArrayLength: 1 }), 'Set(3) { 1, ... 2 more items }');
const set = new Set(['foo']);
set.bar = 42;
assert.strictEqual(
util.inspect(set, { showHidden: true }),
"Set(1) { 'foo', bar: 42 }"
);
}
// Test circular Set.
{
const set = new Set();
set.add(set);
assert.strictEqual(util.inspect(set), '<ref *1> Set(1) { [Circular *1] }');
}
// Test Map.
{
assert.strictEqual(util.inspect(new Map()), 'Map(0) {}');
assert.strictEqual(util.inspect(new Map([[1, 'a'], [2, 'b'], [3, 'c']])),
"Map(3) { 1 => 'a', 2 => 'b', 3 => 'c' }");
assert.strictEqual(util.inspect(new Map([[1, 'a'], [2, 'b'], [3, 'c']]), { maxArrayLength: 1 }),
"Map(3) { 1 => 'a', ... 2 more items }");
const map = new Map([['foo', null]]);
map.bar = 42;
assert.strictEqual(util.inspect(map, true),
"Map(1) { 'foo' => null, bar: 42 }");
}
// Test circular Map.
{
const map = new Map();
map.set(map, 'map');
assert.strictEqual(
inspect(map),
"<ref *1> Map(1) { [Circular *1] => 'map' }"
);
map.set(map, map);
assert.strictEqual(
inspect(map),
'<ref *1> Map(1) { [Circular *1] => [Circular *1] }'
);
map.delete(map);
map.set('map', map);
assert.strictEqual(
inspect(map),
"<ref *1> Map(1) { 'map' => [Circular *1] }"
);
}
// Test multiple circular references.
{
const obj = {};
obj.a = [obj];
obj.b = {};
obj.b.inner = obj.b;
obj.b.obj = obj;
assert.strictEqual(
inspect(obj),
'<ref *1> {\n' +
' a: [ [Circular *1] ],\n' +
' b: <ref *2> { inner: [Circular *2], obj: [Circular *1] }\n' +
'}'
);
}
// Test Promise.
{
const resolved = Promise.resolve(3);
assert.strictEqual(util.inspect(resolved), 'Promise { 3 }');
const rejected = Promise.reject(3);
assert.strictEqual(util.inspect(rejected), 'Promise { <rejected> 3 }');
// Squelch UnhandledPromiseRejection.
rejected.catch(() => {});
const pending = new Promise(() => {});
assert.strictEqual(util.inspect(pending), 'Promise { <pending> }');
const promiseWithProperty = Promise.resolve('foo');
promiseWithProperty.bar = 42;
assert.strictEqual(util.inspect(promiseWithProperty),
"Promise { 'foo', bar: 42 }");
}
// Make sure it doesn't choke on polyfills. Unlike Set/Map, there is no standard
// interface to synchronously inspect a Promise, so our techniques only work on
// a bonafide native Promise.
{
const oldPromise = Promise;
global.Promise = function() { this.bar = 42; };
assert.strictEqual(util.inspect(new Promise()), '{ bar: 42 }');
global.Promise = oldPromise;
}
// Test Map iterators.
{
const map = new Map([['foo', 'bar']]);
assert.strictEqual(util.inspect(map.keys()), '[Map Iterator] { \'foo\' }');
const mapValues = map.values();
Object.defineProperty(mapValues, Symbol.toStringTag, { value: 'Foo' });
assert.strictEqual(
util.inspect(mapValues),
'[Foo] [Map Iterator] { \'bar\' }'
);
map.set('A', 'B!');
assert.strictEqual(util.inspect(map.entries(), { maxArrayLength: 1 }),
"[Map Entries] { [ 'foo', 'bar' ], ... 1 more item }");
// Make sure the iterator doesn't get consumed.
const keys = map.keys();
assert.strictEqual(util.inspect(keys), "[Map Iterator] { 'foo', 'A' }");
assert.strictEqual(util.inspect(keys), "[Map Iterator] { 'foo', 'A' }");
keys.extra = true;
assert.strictEqual(
util.inspect(keys, { maxArrayLength: 0 }),
'[Map Iterator] { ... 2 more items, extra: true }');
}
// Test Set iterators.
{
const aSet = new Set([1]);
assert.strictEqual(util.inspect(aSet.entries(), { compact: false }),
'[Set Entries] {\n [\n 1,\n 1\n ]\n}');
aSet.add(3);
assert.strictEqual(util.inspect(aSet.keys()), '[Set Iterator] { 1, 3 }');
assert.strictEqual(util.inspect(aSet.values()), '[Set Iterator] { 1, 3 }');
const setEntries = aSet.entries();
Object.defineProperty(setEntries, Symbol.toStringTag, { value: 'Foo' });
assert.strictEqual(util.inspect(setEntries),
'[Foo] [Set Entries] { [ 1, 1 ], [ 3, 3 ] }');
// Make sure the iterator doesn't get consumed.
const keys = aSet.keys();
Object.defineProperty(keys, Symbol.toStringTag, { value: null });
assert.strictEqual(util.inspect(keys), '[Set Iterator] { 1, 3 }');
assert.strictEqual(util.inspect(keys), '[Set Iterator] { 1, 3 }');
keys.extra = true;
assert.strictEqual(
util.inspect(keys, { maxArrayLength: 1 }),
'[Set Iterator] { 1, ... 1 more item, extra: true }');
}
// Minimal inspection should still return as much information as possible about
// the constructor and Symbol.toStringTag.
{
class Foo {
get [Symbol.toStringTag]() {
return 'ABC';
}
}
const a = new Foo();
assert.strictEqual(inspect(a, { depth: -1 }), 'Foo [ABC] {}');
a.foo = true;
assert.strictEqual(inspect(a, { depth: -1 }), '[Foo [ABC]]');
Object.defineProperty(a, Symbol.toStringTag, {
value: 'Foo',
configurable: true,
writable: true
});
assert.strictEqual(inspect(a, { depth: -1 }), '[Foo]');
delete a[Symbol.toStringTag];
Object.setPrototypeOf(a, null);
assert.strictEqual(inspect(a, { depth: -1 }), '[Foo: null prototype]');
delete a.foo;
assert.strictEqual(inspect(a, { depth: -1 }), '[Foo: null prototype] {}');
Object.defineProperty(a, Symbol.toStringTag, {
value: 'ABC',
configurable: true
});
assert.strictEqual(
inspect(a, { depth: -1 }),
'[Foo: null prototype] [ABC] {}'
);
Object.defineProperty(a, Symbol.toStringTag, {
value: 'Foo',
configurable: true
});
assert.strictEqual(
inspect(a, { depth: -1 }),
'[Object: null prototype] [Foo] {}'
);
}
// Test alignment of items in container.
// Assumes that the first numeric character is the start of an item.
{
function checkAlignment(container, start, lineX, end) {
const lines = util.inspect(container).split('\n');
lines.forEach((line, i) => {
if (i === 0) {
assert.strictEqual(line, start);
} else if (i === lines.length - 1) {
assert.strictEqual(line, end);
} else {
let expected = lineX.replace('X', i - 1);
if (i !== lines.length - 2)
expected += ',';
assert.strictEqual(line, expected);
}
});
}
const bigArray = [];
for (let i = 0; i < 100; i++) {
bigArray.push(i);
}
const obj = {};
bigArray.forEach((prop) => {
obj[prop] = null;
});
checkAlignment(obj, '{', " 'X': null", '}');
checkAlignment(new Set(bigArray), 'Set(100) {', ' X', '}');
checkAlignment(
new Map(bigArray.map((number) => [number, null])),
'Map(100) {', ' X => null', '}'
);
}
// Test display of constructors.
{
class ObjectSubclass {}
class ArraySubclass extends Array {}
class SetSubclass extends Set {}
class MapSubclass extends Map {}
class PromiseSubclass extends Promise {}
class SymbolNameClass {
static name = Symbol('name');
}
const x = new ObjectSubclass();
x.foo = 42;
assert.strictEqual(util.inspect(x),
'ObjectSubclass { foo: 42 }');
assert.strictEqual(util.inspect(new ArraySubclass(1, 2, 3)),
'ArraySubclass(3) [ 1, 2, 3 ]');
assert.strictEqual(util.inspect(new SetSubclass([1, 2, 3])),
'SetSubclass(3) [Set] { 1, 2, 3 }');
assert.strictEqual(util.inspect(new MapSubclass([['foo', 42]])),
"MapSubclass(1) [Map] { 'foo' => 42 }");
assert.strictEqual(util.inspect(new PromiseSubclass(() => {})),
'PromiseSubclass [Promise] { <pending> }');
assert.strictEqual(util.inspect(new SymbolNameClass()),
'Symbol(name) {}');
assert.strictEqual(
util.inspect({ a: { b: new ArraySubclass([1, [2], 3]) } }, { depth: 1 }),
'{ a: { b: [ArraySubclass] } }'
);
assert.strictEqual(
util.inspect(Object.setPrototypeOf(x, null)),
'[ObjectSubclass: null prototype] { foo: 42 }'
);
}
// Empty and circular before depth.
{
const arr = [[[[]]]];
assert.strictEqual(util.inspect(arr), '[ [ [ [] ] ] ]');
arr[0][0][0][0] = [];
assert.strictEqual(util.inspect(arr), '[ [ [ [Array] ] ] ]');
arr[0][0][0] = {};
assert.strictEqual(util.inspect(arr), '[ [ [ {} ] ] ]');
arr[0][0][0] = { a: 2 };
assert.strictEqual(util.inspect(arr), '[ [ [ [Object] ] ] ]');
arr[0][0][0] = arr;
assert.strictEqual(util.inspect(arr), '<ref *1> [ [ [ [Circular *1] ] ] ]');
arr[0][0][0] = arr[0][0];
assert.strictEqual(util.inspect(arr), '[ [ <ref *1> [ [Circular *1] ] ] ]');
}
// Corner cases.
{
const x = { constructor: 42 };
assert.strictEqual(util.inspect(x), '{ constructor: 42 }');
}
{
const x = {};
Object.defineProperty(x, 'constructor', {
get: function() {
throw new Error('should not access constructor');
},
enumerable: true
});
assert.strictEqual(util.inspect(x), '{ constructor: [Getter] }');
}
{
const x = new function() {}; // eslint-disable-line @stylistic/js/new-parens
assert.strictEqual(util.inspect(x), '{}');
}
{
const x = { __proto__: null };
assert.strictEqual(util.inspect(x), '[Object: null prototype] {}');
}
{
const x = [];
x[''] = 1;
assert.strictEqual(util.inspect(x), "[ '': 1 ]");
}
// The following maxArrayLength tests were introduced after v6.0.0 was released.
// Do not backport to v5/v4 unless all of
// https://github.com/nodejs/node/pull/6334 is backported.
{
const x = new Array(101).fill();
assert(util.inspect(x).endsWith('1 more item\n]'));
assert(!util.inspect(x, { maxArrayLength: 101 }).endsWith('1 more item\n]'));
assert.strictEqual(
util.inspect(x, { maxArrayLength: -1 }),
'[ ... 101 more items ]'
);
assert.strictEqual(util.inspect(x, { maxArrayLength: 0 }),
'[ ... 101 more items ]');
}
{
const x = Array(101);
assert.strictEqual(util.inspect(x, { maxArrayLength: 0 }),
'[ ... 101 more items ]');
assert(!util.inspect(x, { maxArrayLength: null }).endsWith('1 more item\n]'));
assert(!util.inspect(
x, { maxArrayLength: Infinity }
).endsWith('1 more item ]'));
}
{
const x = new Uint8Array(101);
assert(util.inspect(x).endsWith('1 more item\n]'));
assert(!util.inspect(x, { maxArrayLength: 101 }).includes('1 more item'));
assert.strictEqual(util.inspect(x, { maxArrayLength: 0 }),
'Uint8Array(101) [ ... 101 more items ]');
assert(!util.inspect(x, { maxArrayLength: null }).includes('1 more item'));
assert(util.inspect(x, { maxArrayLength: Infinity }).endsWith(' 0, 0\n]'));
}
{
const obj = { foo: 'abc', bar: 'xyz' };
const oneLine = util.inspect(obj, { breakLength: Infinity });
// Subtract four for the object's two curly braces and two spaces of padding.
// Add one more to satisfy the strictly greater than condition in the code.
const breakpoint = oneLine.length - 5;
const twoLines = util.inspect(obj, { breakLength: breakpoint });
assert.strictEqual(oneLine, "{ foo: 'abc', bar: 'xyz' }");
assert.strictEqual(
util.inspect(obj, { breakLength: breakpoint + 1 }),
twoLines
);
assert.strictEqual(twoLines, "{\n foo: 'abc',\n bar: 'xyz'\n}");
}
// util.inspect.defaultOptions tests.
{
const arr = new Array(101).fill();
const obj = { a: { a: { a: { a: 1 } } } };
const oldOptions = { ...util.inspect.defaultOptions };
// Set single option through property assignment.
util.inspect.defaultOptions.maxArrayLength = null;
assert.doesNotMatch(util.inspect(arr), /1 more item/);
util.inspect.defaultOptions.maxArrayLength = oldOptions.maxArrayLength;
assert.match(util.inspect(arr), /1 more item/);
util.inspect.defaultOptions.depth = null;
assert.doesNotMatch(util.inspect(obj), /Object/);
util.inspect.defaultOptions.depth = oldOptions.depth;
assert.match(util.inspect(obj), /Object/);
assert.strictEqual(
JSON.stringify(util.inspect.defaultOptions),
JSON.stringify(oldOptions)
);
// Set multiple options through object assignment.
util.inspect.defaultOptions = { maxArrayLength: null, depth: 2 };
assert.doesNotMatch(util.inspect(arr), /1 more item/);
assert.match(util.inspect(obj), /Object/);
util.inspect.defaultOptions = oldOptions;
assert.match(util.inspect(arr), /1 more item/);
assert.match(util.inspect(obj), /Object/);
assert.strictEqual(
JSON.stringify(util.inspect.defaultOptions),
JSON.stringify(oldOptions)
);
assert.throws(() => {
util.inspect.defaultOptions = null;
}, {
code: 'ERR_INVALID_ARG_TYPE',
name: 'TypeError',
message: 'The "options" argument must be of type object. ' +
'Received null'
}
);
assert.throws(() => {
util.inspect.defaultOptions = 'bad';
}, {
code: 'ERR_INVALID_ARG_TYPE',
name: 'TypeError',
message: 'The "options" argument must be of type object. ' +
"Received type string ('bad')"
}
);
}
util.inspect(process);
// Setting custom inspect property to a non-function should do nothing.
{
const obj = { [util.inspect.custom]: 'fhqwhgads' };
assert.strictEqual(
util.inspect(obj),
"{ Symbol(nodejs.util.inspect.custom): 'fhqwhgads' }"
);
}
{
// @@toStringTag
const obj = { [Symbol.toStringTag]: 'a' };
assert.strictEqual(
util.inspect(obj),
"{ Symbol(Symbol.toStringTag): 'a' }"
);
Object.defineProperty(obj, Symbol.toStringTag, {
value: 'a',
enumerable: false
});
assert.strictEqual(util.inspect(obj), 'Object [a] {}');
assert.strictEqual(
util.inspect(obj, { showHidden: true }),
"{ [Symbol(Symbol.toStringTag)]: 'a' }"
);
class Foo {
constructor() {
this.foo = 'bar';
}
get [Symbol.toStringTag]() {
return this.foo;
}
}
assert.strictEqual(util.inspect(
Object.create(null, { [Symbol.toStringTag]: { value: 'foo' } })),
'[Object: null prototype] [foo] {}');
assert.strictEqual(util.inspect(new Foo()), "Foo [bar] { foo: 'bar' }");
assert.strictEqual(
util.inspect(new (class extends Foo {})()),
"Foo [bar] { foo: 'bar' }");
assert.strictEqual(
util.inspect(Object.create({ __proto__: Foo.prototype }, {
foo: { value: 'bar', enumerable: true }
})),
"Foo [bar] { foo: 'bar' }");
class ThrowingClass {
get [Symbol.toStringTag]() {
throw new Error('toStringTag error');
}
}
assert.throws(() => util.inspect(new ThrowingClass()), /toStringTag error/);
const y = {
get [Symbol.toStringTag]() {
return JSON.stringify(this);
}
};
const x = { y };
y.x = x;
assert.throws(() => util.inspect(x), /TypeError: Converting circular structure to JSON/);
class NotStringClass {
get [Symbol.toStringTag]() {
return null;
}
}
assert.strictEqual(util.inspect(new NotStringClass()),
'NotStringClass {}');
}
{
const o = {
a: [1, 2, [[
'Lorem ipsum dolor\nsit amet,\tconsectetur adipiscing elit, sed do ' +
'eiusmod tempor incididunt ut labore et dolore magna aliqua.',
'test',
'foo']], 4],
b: new Map([['za', 1], ['zb', 'test']])
};
let out = util.inspect(o, { compact: true, depth: 5, breakLength: 80 });
let expect = [
'{ a:',
' [ 1,',
' 2,',
" [ [ 'Lorem ipsum dolor\\nsit amet,\\tconsectetur adipiscing elit, " +
"sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.',",
" 'test',",
" 'foo' ] ],",
' 4 ],',
" b: Map(2) { 'za' => 1, 'zb' => 'test' } }",
].join('\n');
assert.strictEqual(out, expect);
out = util.inspect(o, { compact: false, depth: 5, breakLength: 60 });
expect = [
'{',
' a: [',
' 1,',
' 2,',
' [',
' [',
" 'Lorem ipsum dolor\\n' +",
" 'sit amet,\\tconsectetur adipiscing elit, sed do eiusmod " +
"tempor incididunt ut labore et dolore magna aliqua.',",
" 'test',",
" 'foo'",
' ]',
' ],',
' 4',
' ],',
' b: Map(2) {',
" 'za' => 1,",
" 'zb' => 'test'",
' }',
'}',
].join('\n');
assert.strictEqual(out, expect);
out = util.inspect(o.a[2][0][0], { compact: false, breakLength: 30 });
expect = [
"'Lorem ipsum dolor\\n' +",
" 'sit amet,\\tconsectetur adipiscing elit, sed do eiusmod tempor " +
"incididunt ut labore et dolore magna aliqua.'",
].join('\n');
assert.strictEqual(out, expect);
out = util.inspect(
'12345678901234567890123456789012345678901234567890',
{ compact: false, breakLength: 3 });
expect = "'12345678901234567890123456789012345678901234567890'";
assert.strictEqual(out, expect);
out = util.inspect(
'12 45 78 01 34 67 90 23 56 89 123456789012345678901234567890',
{ compact: false, breakLength: 3 });
expect = [
"'12 45 78 01 34 67 90 23 56 89 123456789012345678901234567890'",
].join('\n');
assert.strictEqual(out, expect);
o.a = () => {};
o.b = new Number(3);
out = util.inspect(o, { compact: false, breakLength: 3 });
expect = [
'{',
' a: [Function (anonymous)],',
' b: [Number: 3]',
'}',
].join('\n');
assert.strictEqual(out, expect);
out = util.inspect(o, { compact: false, breakLength: 3, showHidden: true });
expect = [
'{',
' a: [Function (anonymous)] {',
' [length]: 0,',
" [name]: ''",
' },',
' b: [Number: 3]',
'}',
].join('\n');
assert.strictEqual(out, expect);
o[util.inspect.custom] = () => 42;
out = util.inspect(o, { compact: false, breakLength: 3 });
expect = '42';
assert.strictEqual(out, expect);
o[util.inspect.custom] = () => '12 45 78 01 34 67 90 23';
out = util.inspect(o, { compact: false, breakLength: 3 });
expect = '12 45 78 01 34 67 90 23';
assert.strictEqual(out, expect);
o[util.inspect.custom] = () => ({ a: '12 45 78 01 34 67 90 23' });
out = util.inspect(o, { compact: false, breakLength: 3 });
expect = "{\n a: '12 45 78 01 34 67 90 23'\n}";
assert.strictEqual(out, expect);
}
// Check compact indentation.
{
const typed = new Uint8Array();
typed.buffer.foo = true;
const set = new Set([[1, 2]]);
const promise = Promise.resolve([[1, set]]);
const map = new Map([[promise, typed]]);
map.set(set.values(), map.values());
let out = util.inspect(map, { compact: false, showHidden: true, depth: 9 });
let expected = [
'Map(2) {',
' Promise {',
' [',
' [',
' 1,',
' Set(1) {',
' [',
' 1,',
' 2,',
' [length]: 2',
' ]',
' },',
' [length]: 2',
' ],',
' [length]: 1',
' ]',
' } => Uint8Array(0) [',
' [BYTES_PER_ELEMENT]: 1,',
' [length]: 0,',
' [byteLength]: 0,',
' [byteOffset]: 0,',
' [buffer]: ArrayBuffer {',
' byteLength: 0,',
' foo: true',
' }',
' ],',
' [Set Iterator] {',
' [',
' 1,',
' 2,',
' [length]: 2',
' ],',
" [Symbol(Symbol.toStringTag)]: 'Set Iterator'",
' } => <ref *1> [Map Iterator] {',
' Uint8Array(0) [',
' [BYTES_PER_ELEMENT]: 1,',
' [length]: 0,',
' [byteLength]: 0,',
' [byteOffset]: 0,',
' [buffer]: ArrayBuffer {',
' byteLength: 0,',
' foo: true',
' }',
' ],',
' [Circular *1],',
" [Symbol(Symbol.toStringTag)]: 'Map Iterator'",
' }',
'}',
].join('\n');
assert.strict.equal(out, expected);
out = util.inspect(map, { compact: 2, showHidden: true, depth: 9 });
expected = [
'Map(2) {',
' Promise {',
' [',
' [',
' 1,',
' Set(1) { [ 1, 2, [length]: 2 ] },',
' [length]: 2',
' ],',
' [length]: 1',
' ]',
' } => Uint8Array(0) [',
' [BYTES_PER_ELEMENT]: 1,',
' [length]: 0,',
' [byteLength]: 0,',
' [byteOffset]: 0,',
' [buffer]: ArrayBuffer { byteLength: 0, foo: true }',
' ],',
' [Set Iterator] {',
' [ 1, 2, [length]: 2 ],',
" [Symbol(Symbol.toStringTag)]: 'Set Iterator'",
' } => <ref *1> [Map Iterator] {',
' Uint8Array(0) [',
' [BYTES_PER_ELEMENT]: 1,',
' [length]: 0,',
' [byteLength]: 0,',
' [byteOffset]: 0,',
' [buffer]: ArrayBuffer { byteLength: 0, foo: true }',
' ],',
' [Circular *1],',
" [Symbol(Symbol.toStringTag)]: 'Map Iterator'",
' }',
'}',
].join('\n');
assert.strict.equal(out, expected);
out = util.inspect(map, {
showHidden: true, depth: 9, breakLength: 4, compact: true
});
expected = [
'Map(2) {',
' Promise {',
' [ [ 1,',
' Set(1) {',
' [ 1,',
' 2,',
' [length]: 2 ] },',
' [length]: 2 ],',
' [length]: 1 ] } => Uint8Array(0) [',
' [BYTES_PER_ELEMENT]: 1,',
' [length]: 0,',
' [byteLength]: 0,',
' [byteOffset]: 0,',
' [buffer]: ArrayBuffer {',
' byteLength: 0,',
' foo: true } ],',
' [Set Iterator] {',
' [ 1,',
' 2,',
' [length]: 2 ],',
' [Symbol(Symbol.toStringTag)]:',
" 'Set Iterator' } => <ref *1> [Map Iterator] {",
' Uint8Array(0) [',
' [BYTES_PER_ELEMENT]: 1,',
' [length]: 0,',
' [byteLength]: 0,',
' [byteOffset]: 0,',
' [buffer]: ArrayBuffer {',
' byteLength: 0,',
' foo: true } ],',
' [Circular *1],',
' [Symbol(Symbol.toStringTag)]:',
" 'Map Iterator' } }",
].join('\n');
assert.strict.equal(out, expected);
}
{ // Test WeakMap && WeakSet
const obj = {};
const arr = [];
const weakMap = new WeakMap([[obj, arr], [arr, obj]]);
let out = util.inspect(weakMap, { showHidden: true });
let expect = 'WeakMap { [ [length]: 0 ] => {}, {} => [ [length]: 0 ] }';
assert.strictEqual(out, expect);
out = util.inspect(weakMap);
expect = 'WeakMap { <items unknown> }';
assert.strictEqual(out, expect);
out = util.inspect(weakMap, { maxArrayLength: 0, showHidden: true });
expect = 'WeakMap { ... 2 more items }';
assert.strictEqual(out, expect);
weakMap.extra = true;
out = util.inspect(weakMap, { maxArrayLength: 1, showHidden: true });
// It is not possible to determine the output reliable.
expect = 'WeakMap { [ [length]: 0 ] => {}, ... 1 more item, extra: true }';
let expectAlt = 'WeakMap { {} => [ [length]: 0 ], ... 1 more item, ' +
'extra: true }';
assert(out === expect || out === expectAlt,
`Found: "${out}"\nrather than: "${expect}"\nor: "${expectAlt}"`);
// Test WeakSet
arr.push(1);
const weakSet = new WeakSet([obj, arr]);
out = util.inspect(weakSet, { showHidden: true });
expect = 'WeakSet { [ 1, [length]: 1 ], {} }';
assert.strictEqual(out, expect);
out = util.inspect(weakSet);
expect = 'WeakSet { <items unknown> }';
assert.strictEqual(out, expect);
out = util.inspect(weakSet, { maxArrayLength: -2, showHidden: true });
expect = 'WeakSet { ... 2 more items }';
assert.strictEqual(out, expect);
weakSet.extra = true;
out = util.inspect(weakSet, { maxArrayLength: 1, showHidden: true });
// It is not possible to determine the output reliable.
expect = 'WeakSet { {}, ... 1 more item, extra: true }';
expectAlt = 'WeakSet { [ 1, [length]: 1 ], ... 1 more item, extra: true }';
assert(out === expect || out === expectAlt,
`Found: "${out}"\nrather than: "${expect}"\nor: "${expectAlt}"`);
// Keep references to the WeakMap entries, otherwise they could be GCed too
// early.
assert(obj && arr);
}
{ // Test argument objects.
const args = (function() { return arguments; })('a');
assert.strictEqual(util.inspect(args), "[Arguments] { '0': 'a' }");
}
{
// Test that a long linked list can be inspected without throwing an error.
const list = {};
let head = list;
// A linked list of length 100k should be inspectable in some way, even though
// the real cutoff value is much lower than 100k.
for (let i = 0; i < 100000; i++)
head = head.next = {};
assert.strictEqual(
util.inspect(list),
'{ next: { next: { next: [Object] } } }'
);
const longList = util.inspect(list, { depth: Infinity });
const match = longList.match(/next/g);
assert(match.length > 500 && match.length < 10000);
assert(longList.includes('[Object: Inspection interrupted ' +
'prematurely. Maximum call stack size exceeded.]'));
}
// Do not escape single quotes if no double quote or backtick is present.
assert.strictEqual(util.inspect("'"), '"\'"');
assert.strictEqual(util.inspect('"\''), '`"\'`');
// eslint-disable-next-line no-template-curly-in-string
assert.strictEqual(util.inspect('"\'${a}'), "'\"\\'${a}'");
// Errors should visualize as much information as possible.
// If the name is not included in the stack, visualize it as well.
[
[class Foo extends TypeError {}, 'test'],
[class Foo extends TypeError {}, undefined],
[class BarError extends Error {}, 'test'],
[class BazError extends Error {
get name() {
return 'BazError';
}
}, undefined],
].forEach(([Class, message], i) => {
console.log('Test %i', i);
const foo = new Class(message);
const name = foo.name;
const extra = Class.name.includes('Error') ? '' : ` [${foo.name}]`;
assert(
util.inspect(foo).startsWith(
`${Class.name}${extra}${message ? `: ${message}` : '\n'}`),
util.inspect(foo)
);
Object.defineProperty(foo, Symbol.toStringTag, {
value: 'WOW',
writable: true,
configurable: true
});
const stack = foo.stack;
foo.stack = 'This is a stack';
assert.strictEqual(
util.inspect(foo),
'[This is a stack]'
);
foo.stack = stack;
assert(
util.inspect(foo).startsWith(
`${Class.name} [WOW]${extra}${message ? `: ${message}` : '\n'}`),
util.inspect(foo)
);
Object.setPrototypeOf(foo, null);
assert(
util.inspect(foo).startsWith(
`[${name}: null prototype] [WOW]${message ? `: ${message}` : '\n'}`
),
util.inspect(foo)
);
foo.bar = true;
delete foo[Symbol.toStringTag];
assert(
util.inspect(foo).startsWith(
`[${name}: null prototype]${message ? `: ${message}` : '\n'}`),
util.inspect(foo)
);
foo.stack = 'This is a stack';
assert.strictEqual(
util.inspect(foo),
'[[Error: null prototype]: This is a stack] { bar: true }'
);
foo.stack = stack.split('\n')[0];
assert.strictEqual(
util.inspect(foo),
`[[${name}: null prototype]${message ? `: ${message}` : ''}] { bar: true }`
);
});
// Verify that classes are properly inspected.
[
/* eslint-disable @stylistic/js/spaced-comment, @stylistic/js/no-multi-spaces, @stylistic/js/brace-style */
// The whitespace is intentional.
[class { }, '[class (anonymous)]'],
[class extends Error { log() {} }, '[class (anonymous) extends Error]'],
[class A { constructor(a) { this.a = a; } log() { return this.a; } },
'[class A]'],
[class
// Random { // comments /* */ are part of the toString() result
/* eslint-disable-next-line @stylistic/js/space-before-blocks */
äß/**/extends/*{*/TypeError{}, '[class äß extends TypeError]'],
/* The whitespace and new line is intended! */
// Foobar !!!
[class X extends /****/ Error
// More comments
{}, '[class X extends Error]'],
/* eslint-enable @stylistic/js/spaced-comment, @stylistic/js/no-multi-spaces, @stylistic/js/brace-style */
].forEach(([clazz, string]) => {
const inspected = util.inspect(clazz);
assert.strictEqual(inspected, string);
Object.defineProperty(clazz, Symbol.toStringTag, {
value: 'Woohoo'
});
const parts = inspected.slice(0, -1).split(' ');
const [, name, ...rest] = parts;
rest.unshift('[Woohoo]');
if (rest.length) {
rest[rest.length - 1] += ']';
}
assert.strictEqual(
util.inspect(clazz),
['[class', name, ...rest].join(' ')
);
if (rest.length) {
rest[rest.length - 1] = rest[rest.length - 1].slice(0, -1);
rest.length = 1;
}
Object.setPrototypeOf(clazz, Map.prototype);
assert.strictEqual(
util.inspect(clazz),
['[class', name, '[Map]', ...rest].join(' ') + ']'
);
Object.setPrototypeOf(clazz, null);
assert.strictEqual(
util.inspect(clazz),
['[class', name, ...rest, 'extends [null prototype]]'].join(' ')
);
Object.defineProperty(clazz, 'name', { value: 'Foo' });
const res = ['[class', 'Foo', ...rest, 'extends [null prototype]]'].join(' ');
assert.strictEqual(util.inspect(clazz), res);
clazz.foo = true;
assert.strictEqual(util.inspect(clazz), `${res} { foo: true }`);
});
// "class" properties should not be detected as "class".
{
// eslint-disable-next-line @stylistic/js/space-before-function-paren
let obj = { class () {} };
assert.strictEqual(
util.inspect(obj),
'{ class: [Function: class] }'
);
obj = { class: () => {} };
assert.strictEqual(
util.inspect(obj),
'{ class: [Function: class] }'
);
obj = { ['class Foo {}']() {} };
assert.strictEqual(
util.inspect(obj),
"{ 'class Foo {}': [Function: class Foo {}] }"
);
function Foo() {}
Object.defineProperty(Foo, 'toString', { value: () => 'class Foo {}' });
assert.strictEqual(
util.inspect(Foo),
'[Function: Foo]'
);
function fn() {}
Object.defineProperty(fn, 'name', { value: 'class Foo {}' });
assert.strictEqual(
util.inspect(fn),
'[Function: class Foo {}]'
);
}
// Verify that throwing in valueOf and toString still produces nice results.
[
[new String(55), "[String: '55']"],
[new Boolean(true), '[Boolean: true]'],
[new Number(55), '[Number: 55]'],
[Object(BigInt(55)), '[BigInt: 55n]'],
[Object(Symbol('foo')), '[Symbol: Symbol(foo)]'],
[function() {}, '[Function (anonymous)]'],
[() => {}, '[Function (anonymous)]'],
[[1, 2], '[ 1, 2 ]'],
// eslint-disable-next-line no-sparse-arrays
[[, , 5, , , , ], '[ <2 empty items>, 5, <3 empty items> ]'],
[{ a: 5 }, '{ a: 5 }'],
[new Set([1, 2]), 'Set(2) { 1, 2 }'],
[new Map([[1, 2]]), 'Map(1) { 1 => 2 }'],
[new Set([1, 2]).entries(), '[Set Entries] { [ 1, 1 ], [ 2, 2 ] }'],
[new Map([[1, 2]]).keys(), '[Map Iterator] { 1 }'],
[new Date(2000), '1970-01-01T00:00:02.000Z'],
[new Uint8Array(2), 'Uint8Array(2) [ 0, 0 ]'],
[new Promise((resolve) => setTimeout(resolve, 10)), 'Promise { <pending> }'],
[new WeakSet(), 'WeakSet { <items unknown> }'],
[new WeakMap(), 'WeakMap { <items unknown> }'],
[/foobar/g, '/foobar/g'],
].forEach(([value, expected]) => {
Object.defineProperty(value, 'valueOf', {
get() {
throw new Error('valueOf');
}
});
Object.defineProperty(value, 'toString', {
get() {
throw new Error('toString');
}
});
assert.strictEqual(util.inspect(value), expected);
value.foo = 'bar';
assert.notStrictEqual(util.inspect(value), expected);
delete value.foo;
value[Symbol('foo')] = 'yeah';
assert.notStrictEqual(util.inspect(value), expected);
});
// Verify that having no prototype still produces nice results.
[
[[1, 3, 4], '[Array(3): null prototype] [ 1, 3, 4 ]'],
[new Set([1, 2]), '[Set(2): null prototype] { 1, 2 }'],
[new Map([[1, 2]]), '[Map(1): null prototype] { 1 => 2 }'],
[new Promise((resolve) => setTimeout(resolve, 10)),
'[Promise: null prototype] { <pending> }'],
[new WeakSet(), '[WeakSet: null prototype] { <items unknown> }'],
[new WeakMap(), '[WeakMap: null prototype] { <items unknown> }'],
[new Uint8Array(2), '[Uint8Array(2): null prototype] [ 0, 0 ]'],
[new Uint16Array(2), '[Uint16Array(2): null prototype] [ 0, 0 ]'],
[new Uint32Array(2), '[Uint32Array(2): null prototype] [ 0, 0 ]'],
[new Int8Array(2), '[Int8Array(2): null prototype] [ 0, 0 ]'],
[new Int16Array(2), '[Int16Array(2): null prototype] [ 0, 0 ]'],
[new Int32Array(2), '[Int32Array(2): null prototype] [ 0, 0 ]'],
[new Float32Array(2), '[Float32Array(2): null prototype] [ 0, 0 ]'],
[new Float64Array(2), '[Float64Array(2): null prototype] [ 0, 0 ]'],
[new BigInt64Array(2), '[BigInt64Array(2): null prototype] [ 0n, 0n ]'],
[new BigUint64Array(2), '[BigUint64Array(2): null prototype] [ 0n, 0n ]'],
[new ArrayBuffer(16), '[ArrayBuffer: null prototype] {\n' +
' [Uint8Contents]: <00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00>,\n' +
' byteLength: undefined\n}'],
[new DataView(new ArrayBuffer(16)),
'[DataView: null prototype] {\n byteLength: undefined,\n ' +
'byteOffset: undefined,\n buffer: undefined\n}'],
[new SharedArrayBuffer(2), '[SharedArrayBuffer: null prototype] ' +
'{\n [Uint8Contents]: <00 00>,\n byteLength: undefined\n}'],
[/foobar/, '[RegExp: null prototype] /foobar/'],
[new Date('Sun, 14 Feb 2010 11:48:40 GMT'),
'[Date: null prototype] 2010-02-14T11:48:40.000Z'],
].forEach(([value, expected]) => {
assert.strictEqual(
util.inspect(Object.setPrototypeOf(value, null)),
expected
);
value.foo = 'bar';
assert.notStrictEqual(util.inspect(value), expected);
delete value.foo;
value[Symbol('foo')] = 'yeah';
assert.notStrictEqual(util.inspect(value), expected);
});
// Verify that subclasses with and without prototype produce nice results.
[
[RegExp, ['foobar', 'g'], '/foobar/g'],
[WeakSet, [[{}]], '{ <items unknown> }'],
[WeakMap, [[[{}, {}]]], '{ <items unknown> }'],
[BigInt64Array,
[10],
'[\n 0n, 0n, 0n, 0n, 0n,\n 0n, 0n, 0n, 0n, 0n\n]'],
[Date, ['Sun, 14 Feb 2010 11:48:40 GMT'], '2010-02-14T11:48:40.000Z'],
[Date, ['invalid_date'], 'Invalid Date'],
].forEach(([base, input, rawExpected]) => {
class Foo extends base {}
const value = new Foo(...input);
const symbol = value[Symbol.toStringTag];
const size = base.name.includes('Array') ? `(${input[0]})` : '';
const expected = `Foo${size} ${symbol ? `[${symbol}] ` : ''}${rawExpected}`;
const expectedWithoutProto =
`[${base.name}${size}: null prototype] ${rawExpected}`;
assert.strictEqual(util.inspect(value), expected);
value.foo = 'bar';
assert.notStrictEqual(util.inspect(value), expected);
delete value.foo;
assert.strictEqual(
util.inspect(Object.setPrototypeOf(value, null)),
expectedWithoutProto
);
value.foo = 'bar';
let res = util.inspect(value);
assert.notStrictEqual(res, expectedWithoutProto);
assert.match(res, /foo: 'bar'/);
delete value.foo;
value[Symbol('foo')] = 'yeah';
res = util.inspect(value);
assert.notStrictEqual(res, expectedWithoutProto);
assert.match(res, /Symbol\(foo\): 'yeah'/);
});
assert.strictEqual(inspect(1n), '1n');
assert.strictEqual(inspect(Object(-1n)), '[BigInt: -1n]');
assert.strictEqual(inspect(Object(13n)), '[BigInt: 13n]');
assert.strictEqual(inspect(new BigInt64Array([0n])), 'BigInt64Array(1) [ 0n ]');
assert.strictEqual(
inspect(new BigUint64Array([0n])), 'BigUint64Array(1) [ 0n ]');
// Verify non-enumerable keys get escaped.
{
const obj = {};
Object.defineProperty(obj, 'Non\nenumerable\tkey', { value: true });
assert.strictEqual(
util.inspect(obj, { showHidden: true }),
'{ [\'Non\\nenumerable\\tkey\']: true }'
);
}
// Check for special colors.
{
const special = inspect.colors[inspect.styles.special];
const string = inspect.colors[inspect.styles.string];
assert.strictEqual(
inspect(new WeakSet(), { colors: true }),
`WeakSet { \u001b[${special[0]}m<items unknown>\u001b[${special[1]}m }`
);
assert.strictEqual(
inspect(new WeakMap(), { colors: true }),
`WeakMap { \u001b[${special[0]}m<items unknown>\u001b[${special[1]}m }`
);
assert.strictEqual(
inspect(new Promise(() => {}), { colors: true }),
`Promise { \u001b[${special[0]}m<pending>\u001b[${special[1]}m }`
);
const rejection = Promise.reject('Oh no!');
assert.strictEqual(
inspect(rejection, { colors: true }),
`Promise { \u001b[${special[0]}m<rejected>\u001b[${special[1]}m ` +
`\u001b[${string[0]}m'Oh no!'\u001b[${string[1]}m }`
);
rejection.catch(() => {});
// Verify that aliases do not show up as key while checking `inspect.colors`.
const colors = Object.keys(inspect.colors);
const aliases = Object.getOwnPropertyNames(inspect.colors)
.filter((c) => !colors.includes(c));
assert(!colors.includes('grey'));
assert(colors.includes('gray'));
// Verify that all aliases are correctly mapped.
for (const alias of aliases) {
assert(Array.isArray(inspect.colors[alias]));
}
// Check consistent naming.
[
'black',
'red',
'green',
'yellow',
'blue',
'magenta',
'cyan',
'white',
].forEach((color, i) => {
assert.deepStrictEqual(inspect.colors[color], [30 + i, 39]);
assert.deepStrictEqual(inspect.colors[`${color}Bright`], [90 + i, 39]);
const bgColor = `bg${color[0].toUpperCase()}${color.slice(1)}`;
assert.deepStrictEqual(inspect.colors[bgColor], [40 + i, 49]);
assert.deepStrictEqual(inspect.colors[`${bgColor}Bright`], [100 + i, 49]);
});
// Unknown colors are handled gracefully:
const stringStyle = inspect.styles.string;
inspect.styles.string = 'UNKNOWN';
assert.strictEqual(inspect('foobar', { colors: true }), "'foobar'");
inspect.styles.string = stringStyle;
}
assert.strictEqual(
inspect([1, 3, 2], { sorted: true }),
inspect([1, 3, 2])
);
assert.strictEqual(
inspect({ c: 3, a: 1, b: 2 }, { sorted: true }),
'{ a: 1, b: 2, c: 3 }'
);
assert.strictEqual(
inspect(
{ a200: 4, a100: 1, a102: 3, a101: 2 },
{ sorted(a, b) { return b.localeCompare(a); } }
),
'{ a200: 4, a102: 3, a101: 2, a100: 1 }'
);
// Non-indices array properties are sorted as well.
{
const arr = [3, 2, 1];
arr.b = 2;
arr.c = 3;
arr.a = 1;
arr[Symbol('b')] = true;
arr[Symbol('a')] = false;
assert.strictEqual(
inspect(arr, { sorted: true }),
'[ 3, 2, 1, Symbol(a): false, Symbol(b): true, a: 1, b: 2, c: 3 ]'
);
}
// Manipulate the prototype in weird ways.
{
let obj = { a: true };
let value = (function() { return function() {}; })();
Object.setPrototypeOf(value, null);
Object.setPrototypeOf(obj, value);
assert.strictEqual(
util.inspect(obj),
'Object <[Function (null prototype) (anonymous)]> { a: true }'
);
assert.strictEqual(
util.inspect(obj, { colors: true }),
'Object <\u001b[36m[Function (null prototype) (anonymous)]\u001b[39m> ' +
'{ a: \u001b[33mtrue\u001b[39m }'
);
obj = { a: true };
value = [];
Object.setPrototypeOf(value, null);
Object.setPrototypeOf(obj, value);
assert.strictEqual(
util.inspect(obj),
'Object <[Array(0): null prototype] []> { a: true }'
);
function StorageObject() {}
StorageObject.prototype = { __proto__: null };
assert.strictEqual(
util.inspect(new StorageObject()),
'StorageObject <[Object: null prototype] {}> {}'
);
obj = [1, 2, 3];
Object.setPrototypeOf(obj, Number.prototype);
assert.strictEqual(inspect(obj), "Number { '0': 1, '1': 2, '2': 3 }");
Object.setPrototypeOf(obj, { __proto__: null });
assert.strictEqual(
inspect(obj),
"Array <[Object: null prototype] {}> { '0': 1, '1': 2, '2': 3 }"
);
StorageObject.prototype = { __proto__: null };
Object.setPrototypeOf(StorageObject.prototype, { __proto__: null });
Object.setPrototypeOf(
Object.getPrototypeOf(StorageObject.prototype),
{ __proto__: null }
);
assert.strictEqual(
util.inspect(new StorageObject()),
'StorageObject <Object <Object <[Object: null prototype] {}>>> {}'
);
assert.strictEqual(
util.inspect(new StorageObject(), { depth: 1 }),
'StorageObject <Object <Object <Complex prototype>>> {}'
);
}
// Check that the fallback always works.
{
const obj = new Set([1, 2]);
const iterator = obj[Symbol.iterator];
Object.setPrototypeOf(obj, null);
Object.defineProperty(obj, Symbol.iterator, {
value: iterator,
configurable: true
});
assert.strictEqual(util.inspect(obj), '[Set(2): null prototype] { 1, 2 }');
Object.defineProperty(obj, Symbol.iterator, {
value: true,
configurable: true
});
Object.defineProperty(obj, 'size', {
value: NaN,
configurable: true,
enumerable: true
});
assert.strictEqual(
util.inspect(obj),
'[Set(2): null prototype] { 1, 2, size: NaN }'
);
}
// Check the getter option.
{
let foo = 1;
const get = { get foo() { return foo; } };
const getset = {
get foo() { return foo; },
set foo(val) { foo = val; },
get inc() { return ++foo; }
};
const thrower = { get foo() { throw new Error('Oops'); } };
assert.strictEqual(
inspect(get, { getters: true, colors: true }),
'{ foo: \u001b[36m[Getter:\u001b[39m ' +
'\u001b[33m1\u001b[39m\u001b[36m]\u001b[39m }');
assert.strictEqual(
inspect(thrower, { getters: true }),
'{ foo: [Getter: <Inspection threw (Oops)>] }');
assert.strictEqual(
inspect(getset, { getters: true }),
'{ foo: [Getter/Setter: 1], inc: [Getter: 2] }');
assert.strictEqual(
inspect(getset, { getters: 'get' }),
'{ foo: [Getter/Setter], inc: [Getter: 3] }');
assert.strictEqual(
inspect(getset, { getters: 'set' }),
'{ foo: [Getter/Setter: 3], inc: [Getter] }');
getset.foo = new Set([[{ a: true }, 2, {}], 'foobar', { x: 1 }]);
assert.strictEqual(
inspect(getset, { getters: true }),
'{\n foo: [Getter/Setter] Set(3) { [ [Object], 2, {} ], ' +
"'foobar', { x: 1 } },\n inc: [Getter: NaN]\n}");
}
// Check compact number mode.
{
let obj = {
a: {
b: {
x: 5,
c: {
x: '10000000000000000 00000000000000000 '.repeat(1e1),
d: 2,
e: 3
}
}
},
b: [
1,
2,
[ 1, 2, { a: 1, b: 2, c: 3 } ],
],
c: ['foo', 4, 444444],
d: Array.from({ length: 101 }).map((e, i) => {
return i % 2 === 0 ? i * i : i;
}),
e: Array(6).fill('foobar'),
f: Array(9).fill('foobar'),
g: Array(21).fill('foobar baz'),
h: [100].concat(Array.from({ length: 9 }).map((e, n) => (n))),
long: Array(9).fill('This text is too long for grouping!')
};
let out = util.inspect(obj, { compact: 3, depth: 10, breakLength: 60 });
let expected = [
'{',
' a: {',
' b: {',
' x: 5,',
' c: {',
" x: '10000000000000000 00000000000000000 10000000000000000 " +
'00000000000000000 10000000000000000 00000000000000000 ' +
'10000000000000000 00000000000000000 10000000000000000 ' +
'00000000000000000 10000000000000000 00000000000000000 ' +
'10000000000000000 00000000000000000 10000000000000000 ' +
'00000000000000000 10000000000000000 00000000000000000 ' +
"10000000000000000 00000000000000000 ',",
' d: 2,',
' e: 3',
' }',
' }',
' },',
' b: [ 1, 2, [ 1, 2, { a: 1, b: 2, c: 3 } ] ],',
" c: [ 'foo', 4, 444444 ],",
' d: [',
' 0, 1, 4, 3, 16, 5, 36, 7, 64,',
' 9, 100, 11, 144, 13, 196, 15, 256, 17,',
' 324, 19, 400, 21, 484, 23, 576, 25, 676,',
' 27, 784, 29, 900, 31, 1024, 33, 1156, 35,',
' 1296, 37, 1444, 39, 1600, 41, 1764, 43, 1936,',
' 45, 2116, 47, 2304, 49, 2500, 51, 2704, 53,',
' 2916, 55, 3136, 57, 3364, 59, 3600, 61, 3844,',
' 63, 4096, 65, 4356, 67, 4624, 69, 4900, 71,',
' 5184, 73, 5476, 75, 5776, 77, 6084, 79, 6400,',
' 81, 6724, 83, 7056, 85, 7396, 87, 7744, 89,',
' 8100, 91, 8464, 93, 8836, 95, 9216, 97, 9604,',
' 99,',
' ... 1 more item',
' ],',
' e: [',
" 'foobar',",
" 'foobar',",
" 'foobar',",
" 'foobar',",
" 'foobar',",
" 'foobar'",
' ],',
' f: [',
" 'foobar', 'foobar',",
" 'foobar', 'foobar',",
" 'foobar', 'foobar',",
" 'foobar', 'foobar',",
" 'foobar'",
' ],',
' g: [',
" 'foobar baz', 'foobar baz',",
" 'foobar baz', 'foobar baz',",
" 'foobar baz', 'foobar baz',",
" 'foobar baz', 'foobar baz',",
" 'foobar baz', 'foobar baz',",
" 'foobar baz', 'foobar baz',",
" 'foobar baz', 'foobar baz',",
" 'foobar baz', 'foobar baz',",
" 'foobar baz', 'foobar baz',",
" 'foobar baz', 'foobar baz',",
" 'foobar baz'",
' ],',
' h: [',
' 100, 0, 1, 2, 3,',
' 4, 5, 6, 7, 8',
' ],',
' long: [',
" 'This text is too long for grouping!',",
" 'This text is too long for grouping!',",
" 'This text is too long for grouping!',",
" 'This text is too long for grouping!',",
" 'This text is too long for grouping!',",
" 'This text is too long for grouping!',",
" 'This text is too long for grouping!',",
" 'This text is too long for grouping!',",
" 'This text is too long for grouping!'",
' ]',
'}',
].join('\n');
assert.strictEqual(out, expected);
obj = [
1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 123456789,
];
out = util.inspect(obj, { compact: 3 });
expected = [
'[',
' 1, 1, 1, 1,',
' 1, 1, 1, 1,',
' 1, 1, 1, 1,',
' 1, 1, 1, 1,',
' 1, 1, 1, 1,',
' 1, 1, 1, 1,',
' 1, 1, 123456789',
']',
].join('\n');
assert.strictEqual(out, expected);
// Unicode support. あ has a length of one and a width of two.
obj = [
'123', '123', '123', '123', 'あああ',
'123', '123', '123', '123', 'あああ',
];
out = util.inspect(obj, { compact: 3 });
expected = [
'[',
" '123', '123',",
" '123', '123',",
" 'あああ', '123',",
" '123', '123',",
" '123', 'あああ'",
']',
].join('\n');
assert.strictEqual(out, expected);
// Array grouping should prevent lining up outer elements on a single line.
obj = [[[1, 2, 3, 4, 5, 6, 7, 8, 9]]];
out = util.inspect(obj, { compact: 3 });
expected = [
'[',
' [',
' [',
' 1, 2, 3, 4, 5,',
' 6, 7, 8, 9',
' ]',
' ]',
']',
].join('\n');
assert.strictEqual(out, expected);
// Verify that array grouping and line consolidation does not happen together.
obj = {
a: {
b: {
x: 5,
c: {
d: 2,
e: 3
}
}
},
b: Array.from({ length: 9 }).map((e, n) => {
return n % 2 === 0 ? 'foobar' : 'baz';
})
};
out = util.inspect(obj, { compact: 1, breakLength: Infinity, colors: true });
expected = [
'{',
' a: {',
' b: { x: \u001b[33m5\u001b[39m, c: \u001b[36m[Object]\u001b[39m }',
' },',
' b: [',
" \u001b[32m'foobar'\u001b[39m, \u001b[32m'baz'\u001b[39m,",
" \u001b[32m'foobar'\u001b[39m, \u001b[32m'baz'\u001b[39m,",
" \u001b[32m'foobar'\u001b[39m, \u001b[32m'baz'\u001b[39m,",
" \u001b[32m'foobar'\u001b[39m, \u001b[32m'baz'\u001b[39m,",
" \u001b[32m'foobar'\u001b[39m",
' ]',
'}',
].join('\n');
assert.strictEqual(out, expected);
obj = Array.from({ length: 60 }).map((e, i) => i);
out = util.inspect(obj, { compact: 1, breakLength: Infinity, colors: true });
expected = [
'[',
' \u001b[33m0\u001b[39m, \u001b[33m1\u001b[39m, \u001b[33m2\u001b[39m, \u001b[33m3\u001b[39m,',
' \u001b[33m4\u001b[39m, \u001b[33m5\u001b[39m, \u001b[33m6\u001b[39m, \u001b[33m7\u001b[39m,',
' \u001b[33m8\u001b[39m, \u001b[33m9\u001b[39m, \u001b[33m10\u001b[39m, \u001b[33m11\u001b[39m,',
' \u001b[33m12\u001b[39m, \u001b[33m13\u001b[39m, \u001b[33m14\u001b[39m, \u001b[33m15\u001b[39m,',
' \u001b[33m16\u001b[39m, \u001b[33m17\u001b[39m, \u001b[33m18\u001b[39m, \u001b[33m19\u001b[39m,',
' \u001b[33m20\u001b[39m, \u001b[33m21\u001b[39m, \u001b[33m22\u001b[39m, \u001b[33m23\u001b[39m,',
' \u001b[33m24\u001b[39m, \u001b[33m25\u001b[39m, \u001b[33m26\u001b[39m, \u001b[33m27\u001b[39m,',
' \u001b[33m28\u001b[39m, \u001b[33m29\u001b[39m, \u001b[33m30\u001b[39m, \u001b[33m31\u001b[39m,',
' \u001b[33m32\u001b[39m, \u001b[33m33\u001b[39m, \u001b[33m34\u001b[39m, \u001b[33m35\u001b[39m,',
' \u001b[33m36\u001b[39m, \u001b[33m37\u001b[39m, \u001b[33m38\u001b[39m, \u001b[33m39\u001b[39m,',
' \u001b[33m40\u001b[39m, \u001b[33m41\u001b[39m, \u001b[33m42\u001b[39m, \u001b[33m43\u001b[39m,',
' \u001b[33m44\u001b[39m, \u001b[33m45\u001b[39m, \u001b[33m46\u001b[39m, \u001b[33m47\u001b[39m,',
' \u001b[33m48\u001b[39m, \u001b[33m49\u001b[39m, \u001b[33m50\u001b[39m, \u001b[33m51\u001b[39m,',
' \u001b[33m52\u001b[39m, \u001b[33m53\u001b[39m, \u001b[33m54\u001b[39m, \u001b[33m55\u001b[39m,',
' \u001b[33m56\u001b[39m, \u001b[33m57\u001b[39m, \u001b[33m58\u001b[39m, \u001b[33m59\u001b[39m',
']',
].join('\n');
assert.strictEqual(out, expected);
out = util.inspect([1, 2, 3, 4], { compact: 1, colors: true });
expected = '[ \u001b[33m1\u001b[39m, \u001b[33m2\u001b[39m, ' +
'\u001b[33m3\u001b[39m, \u001b[33m4\u001b[39m ]';
assert.strictEqual(out, expected);
obj = [
'Object', 'Function', 'Array',
'Number', 'parseFloat', 'parseInt',
'Infinity', 'NaN', 'undefined',
'Boolean', 'String', 'Symbol',
'Date', 'Promise', 'RegExp',
'Error', 'EvalError', 'RangeError',
'ReferenceError', 'SyntaxError', 'TypeError',
'URIError', 'JSON', 'Math',
'console', 'Intl', 'ArrayBuffer',
'Uint8Array', 'Int8Array', 'Uint16Array',
'Int16Array', 'Uint32Array', 'Int32Array',
'Float32Array', 'Float64Array', 'Uint8ClampedArray',
'BigUint64Array', 'BigInt64Array', 'DataView',
'Map', 'BigInt', 'Set',
'WeakMap', 'WeakSet', 'Proxy',
'Reflect', 'decodeURI', 'decodeURIComponent',
'encodeURI', 'encodeURIComponent', 'escape',
'unescape', 'eval', 'isFinite',
'isNaN', 'SharedArrayBuffer', 'Atomics',
'globalThis', 'WebAssembly', 'global',
'process', 'Buffer', 'URL',
'URLSearchParams', 'TextEncoder', 'TextDecoder',
'clearInterval', 'clearTimeout', 'setInterval',
'setTimeout', 'queueMicrotask', 'clearImmediate',
'setImmediate', 'module', 'require',
'assert', 'async_hooks', 'buffer',
'child_process', 'cluster', 'crypto',
'dgram', 'dns', 'domain',
'events', 'fs', 'http',
'http2', 'https', 'inspector',
'net', 'os', 'path',
'perf_hooks', 'punycode', 'querystring',
'readline', 'repl', 'stream',
'string_decoder', 'tls', 'trace_events',
'tty', 'url', 'v8',
'vm', 'worker_threads', 'zlib',
'_', '_error', 'util',
];
out = util.inspect(
obj,
{ compact: 3, breakLength: 80, maxArrayLength: 250 }
);
expected = [
'[',
" 'Object', 'Function', 'Array',",
" 'Number', 'parseFloat', 'parseInt',",
" 'Infinity', 'NaN', 'undefined',",
" 'Boolean', 'String', 'Symbol',",
" 'Date', 'Promise', 'RegExp',",
" 'Error', 'EvalError', 'RangeError',",
" 'ReferenceError', 'SyntaxError', 'TypeError',",
" 'URIError', 'JSON', 'Math',",
" 'console', 'Intl', 'ArrayBuffer',",
" 'Uint8Array', 'Int8Array', 'Uint16Array',",
" 'Int16Array', 'Uint32Array', 'Int32Array',",
" 'Float32Array', 'Float64Array', 'Uint8ClampedArray',",
" 'BigUint64Array', 'BigInt64Array', 'DataView',",
" 'Map', 'BigInt', 'Set',",
" 'WeakMap', 'WeakSet', 'Proxy',",
" 'Reflect', 'decodeURI', 'decodeURIComponent',",
" 'encodeURI', 'encodeURIComponent', 'escape',",
" 'unescape', 'eval', 'isFinite',",
" 'isNaN', 'SharedArrayBuffer', 'Atomics',",
" 'globalThis', 'WebAssembly', 'global',",
" 'process', 'Buffer', 'URL',",
" 'URLSearchParams', 'TextEncoder', 'TextDecoder',",
" 'clearInterval', 'clearTimeout', 'setInterval',",
" 'setTimeout', 'queueMicrotask', 'clearImmediate',",
" 'setImmediate', 'module', 'require',",
" 'assert', 'async_hooks', 'buffer',",
" 'child_process', 'cluster', 'crypto',",
" 'dgram', 'dns', 'domain',",
" 'events', 'fs', 'http',",
" 'http2', 'https', 'inspector',",
" 'net', 'os', 'path',",
" 'perf_hooks', 'punycode', 'querystring',",
" 'readline', 'repl', 'stream',",
" 'string_decoder', 'tls', 'trace_events',",
" 'tty', 'url', 'v8',",
" 'vm', 'worker_threads', 'zlib',",
" '_', '_error', 'util'",
']',
].join('\n');
assert.strictEqual(out, expected);
}
{
const originalCWD = process.cwd();
process.cwd = () => (process.platform === 'win32' ?
'C:\\workspace\\node-test-binary-windows js-suites-%percent-encoded\\node' :
'/home/user directory/repository%encoded/node');
// Use a fake stack to verify the expected colored outcome.
const stack = [
'Error: CWD is grayed out, even cwd that are percent encoded!',
' at A.<anonymous> (/test/node_modules/foo/node_modules/bar/baz.js:2:7)',
' at Module._compile (node:internal/modules/cjs/loader:827:30)',
' at Fancy (node:vm:697:32)',
// This file is not an actual Node.js core file.
' at tryModuleLoad (node:internal/modules/cjs/foo:629:12)',
' at Function.Module._load (node:internal/modules/cjs/loader:621:3)',
// This file is not an actual Node.js core file.
' at Module.require [as weird/name] (node:internal/aaaaa/loader:735:19)',
' at require (node:internal/modules/helpers:14:16)',
' at Array.forEach (<anonymous>)',
` at ${process.cwd()}/test/parallel/test-util-inspect.js:2760:12`,
` at Object.<anonymous> (${process.cwd()}/node_modules/hyper_module/folder/file.js:2753:10)`,
' at /test/test-util-inspect.js:2239:9',
' at getActual (node:assert:592:5)',
];
const err = new Error('CWD is grayed out, even cwd that are percent encoded!');
err.stack = stack.join('\n');
if (process.platform === 'win32') {
err.stack = stack.map((frame) => (frame.includes('node:') ?
frame :
frame.replaceAll('/', '\\'))
).join('\n');
}
const escapedCWD = util.inspect(process.cwd()).slice(1, -1);
util.inspect(err, { colors: true }).split('\n').forEach((line, i) => {
let expected = stack[i].replace(/node_modules\/([^/]+)/gi, (_, m) => {
return `node_modules/\u001b[4m${m}\u001b[24m`;
}).replaceAll(new RegExp(`(\\(?${escapedCWD}(\\\\|/))`, 'gi'), (_, m) => {
return `\x1B[90m${m}\x1B[39m`;
});
if (expected.includes(process.cwd()) && expected.endsWith(')')) {
expected = `${expected.slice(0, -1)}\x1B[90m)\x1B[39m`;
}
if (line.includes('node:')) {
if (!line.includes('foo') && !line.includes('aaa')) {
expected = `\u001b[90m${expected}\u001b[39m`;
}
} else if (process.platform === 'win32') {
expected = expected.replaceAll('/', '\\');
}
assert.strictEqual(line, expected);
});
// Check ESM
const encodedCwd = url.pathToFileURL(process.cwd());
const sl = process.platform === 'win32' ? '\\' : '/';
// Use a fake stack to verify the expected colored outcome.
err.stack = 'Error: ESM and CJS mixed are both grayed out!\n' +
` at ${encodedCwd}/test/parallel/test-esm.mjs:2760:12\n` +
` at Object.<anonymous> (${encodedCwd}/node_modules/esm_module/folder/file.js:2753:10)\n` +
` at ${process.cwd()}${sl}test${sl}parallel${sl}test-cjs.js:2760:12\n` +
` at Object.<anonymous> (${process.cwd()}${sl}node_modules${sl}cjs_module${sl}folder${sl}file.js:2753:10)`;
let actual = util.inspect(err, { colors: true });
let expected = 'Error: ESM and CJS mixed are both grayed out!\n' +
` at \x1B[90m${encodedCwd}/\x1B[39mtest/parallel/test-esm.mjs:2760:12\n` +
` at Object.<anonymous> \x1B[90m(${encodedCwd}/\x1B[39mnode_modules/\x1B[4mesm_module\x1B[24m/folder/file.js:2753:10\x1B[90m)\x1B[39m\n` +
` at \x1B[90m${process.cwd()}${sl}\x1B[39mtest${sl}parallel${sl}test-cjs.js:2760:12\n` +
` at Object.<anonymous> \x1B[90m(${process.cwd()}${sl}\x1B[39mnode_modules${sl}\x1B[4mcjs_module\x1B[24m${sl}folder${sl}file.js:2753:10\x1B[90m)\x1B[39m`;
assert.strictEqual(actual, expected);
// ESM without need for encoding
process.cwd = () => (process.platform === 'win32' ?
'C:\\workspace\\node-test-binary-windows-js-suites\\node' :
'/home/user/repository/node');
let expectedCwd = process.cwd();
if (process.platform === 'win32') {
expectedCwd = `/${expectedCwd.replaceAll('\\', '/')}`;
}
// Use a fake stack to verify the expected colored outcome.
err.stack = 'Error: ESM without need for encoding!\n' +
` at file://${expectedCwd}/file.js:15:15`;
actual = util.inspect(err, { colors: true });
expected = 'Error: ESM without need for encoding!\n' +
` at \x1B[90mfile://${expectedCwd}/\x1B[39mfile.js:15:15`;
assert.strictEqual(actual, expected);
process.cwd = originalCWD;
}
{
// Cross platform checks.
const err = new Error('foo');
util.inspect(err, { colors: true }).split('\n').forEach((line, i) => {
assert(i < 2 || line.startsWith('\u001b[90m'));
});
}
{
// Tracing class respects inspect depth.
try {
const trace = require('trace_events').createTracing({ categories: ['fo'] });
const actualDepth0 = util.inspect({ trace }, { depth: 0 });
assert.strictEqual(actualDepth0, '{ trace: Tracing {} }');
const actualDepth1 = util.inspect({ trace }, { depth: 1 });
assert.strictEqual(
actualDepth1,
"{ trace: Tracing { enabled: false, categories: 'fo' } }"
);
} catch (err) {
if (err.code !== 'ERR_TRACE_EVENTS_UNAVAILABLE')
throw err;
}
}
// Inspect prototype properties.
{
class Foo extends Map {
prop = false;
prop2 = true;
get abc() {
return true;
}
get def() {
return false;
}
set def(v) {}
get xyz() {
return 'Should be ignored';
}
func(a) {}
[util.inspect.custom]() {
return this;
}
}
class Bar extends Foo {
abc = true;
prop = true;
get xyz() {
return 'YES!';
}
[util.inspect.custom]() {
return this;
}
}
const bar = new Bar();
assert.strictEqual(
inspect(bar),
'Bar(0) [Map] { prop: true, prop2: true, abc: true }'
);
assert.strictEqual(
inspect(bar, { showHidden: true, getters: true, colors: false }),
'Bar(0) [Map] {\n' +
' prop: true,\n' +
' prop2: true,\n' +
' abc: true,\n' +
" [xyz]: [Getter: 'YES!'],\n" +
' [def]: [Getter/Setter: false]\n' +
'}'
);
assert.strictEqual(
inspect(bar, { showHidden: true, getters: false, colors: true }),
'Bar(0) [Map] {\n' +
' prop: \x1B[33mtrue\x1B[39m,\n' +
' prop2: \x1B[33mtrue\x1B[39m,\n' +
' abc: \x1B[33mtrue\x1B[39m,\n' +
' \x1B[2m[xyz]: \x1B[36m[Getter]\x1B[39m\x1B[22m,\n' +
' \x1B[2m[def]: \x1B[36m[Getter/Setter]\x1B[39m\x1B[22m\n' +
'}'
);
const obj = { __proto__: { abc: true, def: 5, toString() {} } };
assert.strictEqual(
inspect(obj, { showHidden: true, colors: true }),
'{ \x1B[2mabc: \x1B[33mtrue\x1B[39m\x1B[22m, ' +
'\x1B[2mdef: \x1B[33m5\x1B[39m\x1B[22m }'
);
assert.strictEqual(
inspect(Object.getPrototypeOf(bar), { showHidden: true, getters: true }),
'<ref *1> Foo [Map] {\n' +
' [constructor]: [class Bar extends Foo] {\n' +
' [length]: 0,\n' +
" [name]: 'Bar',\n" +
' [prototype]: [Circular *1],\n' +
' [Symbol(Symbol.species)]: [Getter: <Inspection threw ' +
"(Symbol.prototype.toString requires that 'this' be a Symbol)>]\n" +
' },\n' +
" [xyz]: [Getter: 'YES!'],\n" +
' [Symbol(nodejs.util.inspect.custom)]: ' +
'[Function: [nodejs.util.inspect.custom]] {\n' +
' [length]: 0,\n' +
" [name]: '[nodejs.util.inspect.custom]'\n" +
' },\n' +
' [abc]: [Getter: true],\n' +
' [def]: [Getter/Setter: false]\n' +
' }'
);
assert.strictEqual(
inspect(Object.getPrototypeOf(bar)),
'Foo [Map] {}'
);
assert.strictEqual(
inspect(Object.getPrototypeOf(new Foo())),
'Map {}'
);
}
// Check that prototypes with a null prototype are inspectable.
// Regression test for https://github.com/nodejs/node/issues/35730
{
function Func() {}
Func.prototype = null;
const object = {};
object.constructor = Func;
assert.strictEqual(util.inspect(object), '{ constructor: [Function: Func] }');
}
// Test changing util.inspect.colors colors and aliases.
{
const colors = util.inspect.colors;
const originalValue = colors.gray;
// "grey" is reference-equal alias of "gray".
assert.strictEqual(colors.grey, colors.gray);
// Assigninging one should assign the other. This tests that the alias setter
// function keeps things reference-equal.
colors.gray = [0, 0];
assert.deepStrictEqual(colors.gray, [0, 0]);
assert.strictEqual(colors.grey, colors.gray);
colors.grey = [1, 1];
assert.deepStrictEqual(colors.grey, [1, 1]);
assert.strictEqual(colors.grey, colors.gray);
// Restore original value to avoid side effects in other tests.
colors.gray = originalValue;
assert.deepStrictEqual(colors.gray, originalValue);
assert.strictEqual(colors.grey, colors.gray);
}
// https://github.com/nodejs/node/issues/31889
{
v8.setFlagsFromString('--allow-natives-syntax');
const undetectable = vm.runInThisContext('%GetUndetectable()');
v8.setFlagsFromString('--no-allow-natives-syntax');
assert.strictEqual(inspect(undetectable), '{}');
}
// Truncate output for Primitives with 1 character left
{
assert.strictEqual(util.inspect('bl', { maxStringLength: 1 }),
"'b'... 1 more character");
}
{
const x = 'a'.repeat(1e6);
assert(util.inspect(x).endsWith('... 990000 more characters'));
assert.strictEqual(
util.inspect(x, { maxStringLength: 4 }),
"'aaaa'... 999996 more characters"
);
assert.match(util.inspect(x, { maxStringLength: null }), /a'$/);
}
{
// Verify that util.inspect() invokes custom inspect functions on objects
// from other vm.Contexts but does not pass data from its own Context to that
// function.
const target = vm.runInNewContext(`
({
[Symbol.for('nodejs.util.inspect.custom')](depth, ctx) {
this.depth = depth;
this.ctx = ctx;
try {
this.stylized = ctx.stylize('🐈');
} catch (e) {
this.stylizeException = e;
}
return this.stylized;
}
})
`, { __proto__: null });
assert.strictEqual(target.ctx, undefined);
{
// Subtest 1: Just try to inspect the object with default options.
assert.strictEqual(util.inspect(target), '🐈');
assert.strictEqual(typeof target.ctx, 'object');
const objectGraph = fullObjectGraph(target);
assert(!objectGraph.has(Object));
assert(!objectGraph.has(Function));
}
{
// Subtest 2: Use a stylize function that returns a non-primitive.
const output = util.inspect(target, {
stylize: common.mustCall((str) => {
return {};
})
});
assert.strictEqual(output, '[object Object]');
assert.strictEqual(typeof target.ctx, 'object');
const objectGraph = fullObjectGraph(target);
assert(!objectGraph.has(Object));
assert(!objectGraph.has(Function));
}
{
// Subtest 3: Use a stylize function that throws an exception.
const output = util.inspect(target, {
stylize: common.mustCall((str) => {
throw new Error('oops');
})
});
assert.strictEqual(output, '🐈');
assert.strictEqual(typeof target.ctx, 'object');
const objectGraph = fullObjectGraph(target);
assert(!objectGraph.has(Object));
assert(!objectGraph.has(Function));
}
function fullObjectGraph(value) {
const graph = new Set([value]);
for (const entry of graph) {
if ((typeof entry !== 'object' && typeof entry !== 'function') ||
entry === null) {
continue;
}
graph.add(Object.getPrototypeOf(entry));
const descriptors = Object.values(
Object.getOwnPropertyDescriptors(entry));
for (const descriptor of descriptors) {
graph.add(descriptor.value);
graph.add(descriptor.set);
graph.add(descriptor.get);
}
}
return graph;
}
// Consistency check.
assert(fullObjectGraph(global).has(Function.prototype));
}
{
// Confirm that own constructor value displays correctly.
function Fhqwhgads() {}
const sterrance = new Fhqwhgads();
sterrance.constructor = Fhqwhgads;
assert.strictEqual(
util.inspect(sterrance, { showHidden: true }),
'Fhqwhgads {\n' +
' constructor: <ref *1> [Function: Fhqwhgads] {\n' +
' [length]: 0,\n' +
" [name]: 'Fhqwhgads',\n" +
' [prototype]: { [constructor]: [Circular *1] }\n' +
' }\n' +
'}'
);
}
{
// Confirm null prototype of generator prototype displays as expected.
function getProtoOfProto() {
return Object.getPrototypeOf(Object.getPrototypeOf(function* () {}));
}
function* generator() {}
const generatorPrototype = Object.getPrototypeOf(generator);
const originalProtoOfProto = Object.getPrototypeOf(generatorPrototype);
assert.strictEqual(getProtoOfProto(), originalProtoOfProto);
Object.setPrototypeOf(generatorPrototype, null);
assert.notStrictEqual(getProtoOfProto, originalProtoOfProto);
// This is the actual test. The other assertions in this block are about
// making sure the test is set up correctly and isn't polluting other tests.
assert.strictEqual(
util.inspect(generator, { showHidden: true }),
'[GeneratorFunction: generator] {\n' +
' [length]: 0,\n' +
" [name]: 'generator',\n" +
" [prototype]: Object [Generator] { [Symbol(Symbol.toStringTag)]: 'Generator' },\n" + // eslint-disable-line @stylistic/js/max-len
" [Symbol(Symbol.toStringTag)]: 'GeneratorFunction'\n" +
'}'
);
// Reset so we don't pollute other tests
Object.setPrototypeOf(generatorPrototype, originalProtoOfProto);
assert.strictEqual(getProtoOfProto(), originalProtoOfProto);
}
{
// Test for when breakLength results in a single column.
const obj = Array(9).fill('fhqwhgadshgnsdhjsdbkhsdabkfabkveybvf');
assert.strictEqual(
util.inspect(obj, { breakLength: 256 }),
'[\n' +
" 'fhqwhgadshgnsdhjsdbkhsdabkfabkveybvf',\n" +
" 'fhqwhgadshgnsdhjsdbkhsdabkfabkveybvf',\n" +
" 'fhqwhgadshgnsdhjsdbkhsdabkfabkveybvf',\n" +
" 'fhqwhgadshgnsdhjsdbkhsdabkfabkveybvf',\n" +
" 'fhqwhgadshgnsdhjsdbkhsdabkfabkveybvf',\n" +
" 'fhqwhgadshgnsdhjsdbkhsdabkfabkveybvf',\n" +
" 'fhqwhgadshgnsdhjsdbkhsdabkfabkveybvf',\n" +
" 'fhqwhgadshgnsdhjsdbkhsdabkfabkveybvf',\n" +
" 'fhqwhgadshgnsdhjsdbkhsdabkfabkveybvf'\n" +
']'
);
}
{
assert.strictEqual(
util.inspect({ ['__proto__']: { a: 1 } }),
"{ ['__proto__']: { a: 1 } }"
);
const o = { ['__proto__']: { a: 1 } };
Object.defineProperty(o, '__proto__', { enumerable: false });
assert.strictEqual(
util.inspect(o, { showHidden: true }),
"{ [['__proto__']]: { a: 1 } }"
);
}
{
const { numericSeparator } = util.inspect.defaultOptions;
util.inspect.defaultOptions.numericSeparator = true;
assert.strictEqual(
// eslint-disable-next-line no-loss-of-precision
util.inspect(1234567891234567891234),
'1.234567891234568e+21'
);
assert.strictEqual(
util.inspect(123456789.12345678),
'123_456_789.123_456_78'
);
assert.strictEqual(util.inspect(10_000_000), '10_000_000');
assert.strictEqual(util.inspect(1_000_000), '1_000_000');
assert.strictEqual(util.inspect(100_000), '100_000');
assert.strictEqual(util.inspect(99_999.9), '99_999.9');
assert.strictEqual(util.inspect(9_999), '9_999');
assert.strictEqual(util.inspect(999), '999');
assert.strictEqual(util.inspect(NaN), 'NaN');
assert.strictEqual(util.inspect(Infinity), 'Infinity');
assert.strictEqual(util.inspect(-Infinity), '-Infinity');
assert.strictEqual(
util.inspect(new Float64Array([100_000_000])),
'Float64Array(1) [ 100_000_000 ]'
);
assert.strictEqual(
util.inspect(new BigInt64Array([9_100_000_100n])),
'BigInt64Array(1) [ 9_100_000_100n ]'
);
assert.strictEqual(
util.inspect(123456789),
'123_456_789'
);
assert.strictEqual(
util.inspect(123456789n),
'123_456_789n'
);
util.inspect.defaultOptions.numericSeparator = numericSeparator;
assert.strictEqual(
util.inspect(123456789.12345678, { numericSeparator: true }),
'123_456_789.123_456_78'
);
assert.strictEqual(
util.inspect(-123456789.12345678, { numericSeparator: true }),
'-123_456_789.123_456_78'
);
}
// Regression test for https://github.com/nodejs/node/issues/41244
{
assert.strictEqual(util.inspect({
get [Symbol.iterator]() {
throw new Error();
}
}), '{ Symbol(Symbol.iterator): [Getter] }');
}
{
const sym = Symbol('bar()');
const o = {
'foo': 0,
'Symbol(foo)': 0,
[Symbol('foo')]: 0,
[Symbol('foo()')]: 0,
[sym]: 0,
};
Object.defineProperty(o, sym, { enumerable: false });
assert.strictEqual(
util.inspect(o, { showHidden: true }),
'{\n' +
' foo: 0,\n' +
" 'Symbol(foo)': 0,\n" +
' Symbol(foo): 0,\n' +
' Symbol(foo()): 0,\n' +
' [Symbol(bar())]: 0\n' +
'}',
);
}