From cdf02340cce2579ed06a475dccd401ebbff05470 Mon Sep 17 00:00:00 2001 From: Rich Trott Date: Fri, 19 Aug 2016 14:24:39 -0700 Subject: [PATCH 1/4] test: refactor test-util-inspect MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * favor `assert.strictEqual()` and friends of `assert.equal()` etc. * favor `.includes()` over `.indexOf()` for existence checks PR-URL: https://github.com/nodejs/node/pull/8189 Reviewed-By: Franziska Hinkelmann Reviewed-By: Michaël Zasso Reviewed-By: James M Snell --- test/parallel/test-util-inspect.js | 342 ++++++++++++++++++----------- 1 file changed, 209 insertions(+), 133 deletions(-) diff --git a/test/parallel/test-util-inspect.js b/test/parallel/test-util-inspect.js index d7c8462e367e2c..3632df86654fb0 100644 --- a/test/parallel/test-util-inspect.js +++ b/test/parallel/test-util-inspect.js @@ -1,44 +1,44 @@ 'use strict'; -require('../common'); +const common = require('../common'); const assert = require('assert'); const util = require('util'); const vm = require('vm'); -assert.equal(util.inspect(1), '1'); -assert.equal(util.inspect(false), 'false'); -assert.equal(util.inspect(''), "''"); -assert.equal(util.inspect('hello'), "'hello'"); -assert.equal(util.inspect(function() {}), '[Function]'); -assert.equal(util.inspect(undefined), 'undefined'); -assert.equal(util.inspect(null), 'null'); -assert.equal(util.inspect(/foo(bar\n)?/gi), '/foo(bar\\n)?/gi'); +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() {}), '[Function]'); +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.equal(util.inspect('\n\u0001'), "'\\n\\u0001'"); +assert.strictEqual(util.inspect('\n\u0001'), "'\\n\\u0001'"); -assert.equal(util.inspect([]), '[]'); -assert.equal(util.inspect(Object.create([])), 'Array {}'); -assert.equal(util.inspect([1, 2]), '[ 1, 2 ]'); -assert.equal(util.inspect([1, [2, 3]]), '[ 1, [ 2, 3 ] ]'); +assert.strictEqual(util.inspect([]), '[]'); +assert.strictEqual(util.inspect(Object.create([])), 'Array {}'); +assert.strictEqual(util.inspect([1, 2]), '[ 1, 2 ]'); +assert.strictEqual(util.inspect([1, [2, 3]]), '[ 1, [ 2, 3 ] ]'); -assert.equal(util.inspect({}), '{}'); -assert.equal(util.inspect({a: 1}), '{ a: 1 }'); -assert.equal(util.inspect({a: function() {}}), '{ a: [Function: a] }'); -assert.equal(util.inspect({a: 1, b: 2}), '{ a: 1, b: 2 }'); -assert.equal(util.inspect({'a': {}}), '{ a: {} }'); -assert.equal(util.inspect({'a': {'b': 2}}), '{ a: { b: 2 } }'); -assert.equal(util.inspect({'a': {'b': { 'c': { 'd': 2 }}}}), +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: 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.equal(util.inspect({'a': {'b': { 'c': { 'd': 2 }}}}, false, null), +assert.strictEqual(util.inspect({'a': {'b': { 'c': { 'd': 2 }}}}, false, null), '{ a: { b: { c: { d: 2 } } } }'); -assert.equal(util.inspect([1, 2, 3], true), '[ 1, 2, 3, [length]: 3 ]'); -assert.equal(util.inspect({'a': {'b': { 'c': 2}}}, false, 0), +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.equal(util.inspect({'a': {'b': { 'c': 2}}}, false, 1), +assert.strictEqual(util.inspect({'a': {'b': { 'c': 2}}}, false, 1), '{ a: { b: [Object] } }'); -assert.equal(util.inspect(Object.create({}, +assert.strictEqual(util.inspect(Object.create({}, {visible: {value: 1, enumerable: true}, hidden: {value: 2}})), '{ visible: 1 }' ); @@ -53,23 +53,29 @@ assert(!/Object/.test( for (const showHidden of [true, false]) { const ab = new ArrayBuffer(4); const dv = new DataView(ab, 1, 2); - assert.equal(util.inspect(ab, showHidden), 'ArrayBuffer { byteLength: 4 }'); - assert.equal(util.inspect(new DataView(ab, 1, 2), showHidden), + assert.strictEqual( + util.inspect(ab, showHidden), + 'ArrayBuffer { byteLength: 4 }' + ); + assert.strictEqual(util.inspect(new DataView(ab, 1, 2), showHidden), 'DataView {\n' + ' byteLength: 2,\n' + ' byteOffset: 1,\n' + ' buffer: ArrayBuffer { byteLength: 4 } }'); - assert.equal(util.inspect(ab, showHidden), 'ArrayBuffer { byteLength: 4 }'); - assert.equal(util.inspect(dv, showHidden), + assert.strictEqual( + util.inspect(ab, showHidden), + 'ArrayBuffer { byteLength: 4 }' + ); + assert.strictEqual(util.inspect(dv, showHidden), 'DataView {\n' + ' byteLength: 2,\n' + ' byteOffset: 1,\n' + ' buffer: ArrayBuffer { byteLength: 4 } }'); ab.x = 42; dv.y = 1337; - assert.equal(util.inspect(ab, showHidden), + assert.strictEqual(util.inspect(ab, showHidden), 'ArrayBuffer { byteLength: 4, x: 42 }'); - assert.equal(util.inspect(dv, showHidden), + assert.strictEqual(util.inspect(dv, showHidden), 'DataView {\n' + ' byteLength: 2,\n' + ' byteOffset: 1,\n' + @@ -81,23 +87,29 @@ for (const showHidden of [true, false]) { for (const showHidden of [true, false]) { const ab = vm.runInNewContext('new ArrayBuffer(4)'); const dv = vm.runInNewContext('new DataView(ab, 1, 2)', { ab: ab }); - assert.equal(util.inspect(ab, showHidden), 'ArrayBuffer { byteLength: 4 }'); - assert.equal(util.inspect(new DataView(ab, 1, 2), showHidden), + assert.strictEqual( + util.inspect(ab, showHidden), + 'ArrayBuffer { byteLength: 4 }' + ); + assert.strictEqual(util.inspect(new DataView(ab, 1, 2), showHidden), 'DataView {\n' + ' byteLength: 2,\n' + ' byteOffset: 1,\n' + ' buffer: ArrayBuffer { byteLength: 4 } }'); - assert.equal(util.inspect(ab, showHidden), 'ArrayBuffer { byteLength: 4 }'); - assert.equal(util.inspect(dv, showHidden), + assert.strictEqual( + util.inspect(ab, showHidden), + 'ArrayBuffer { byteLength: 4 }' + ); + assert.strictEqual(util.inspect(dv, showHidden), 'DataView {\n' + ' byteLength: 2,\n' + ' byteOffset: 1,\n' + ' buffer: ArrayBuffer { byteLength: 4 } }'); ab.x = 42; dv.y = 1337; - assert.equal(util.inspect(ab, showHidden), + assert.strictEqual(util.inspect(ab, showHidden), 'ArrayBuffer { byteLength: 4, x: 42 }'); - assert.equal(util.inspect(dv, showHidden), + assert.strictEqual(util.inspect(dv, showHidden), 'DataView {\n' + ' byteLength: 2,\n' + ' byteOffset: 1,\n' + @@ -120,7 +132,7 @@ for (const showHidden of [true, false]) { const array = new constructor(new ArrayBuffer(byteLength), 0, length); array[0] = 65; array[1] = 97; - assert.equal(util.inspect(array, true), + assert.strictEqual(util.inspect(array, true), `${constructor.name} [\n` + ` 65,\n` + ` 97,\n` + @@ -129,7 +141,10 @@ for (const showHidden of [true, false]) { ` [byteLength]: ${byteLength},\n` + ` [byteOffset]: 0,\n` + ` [buffer]: ArrayBuffer { byteLength: ${byteLength} } ]`); - assert.equal(util.inspect(array, false), `${constructor.name} [ 65, 97 ]`); + assert.strictEqual( + util.inspect(array, false), + `${constructor.name} [ 65, 97 ]` + ); }); // Now check that declaring a TypedArray in a different context works the same @@ -152,7 +167,7 @@ for (const showHidden of [true, false]) { }); array[0] = 65; array[1] = 97; - assert.equal(util.inspect(array, true), + assert.strictEqual(util.inspect(array, true), `${constructor.name} [\n` + ` 65,\n` + ` 97,\n` + @@ -161,7 +176,10 @@ for (const showHidden of [true, false]) { ` [byteLength]: ${byteLength},\n` + ` [byteOffset]: 0,\n` + ` [buffer]: ArrayBuffer { byteLength: ${byteLength} } ]`); - assert.equal(util.inspect(array, false), `${constructor.name} [ 65, 97 ]`); + assert.strictEqual( + util.inspect(array, false), + `${constructor.name} [ 65, 97 ]` + ); }); // Due to the hash seed randomization it's not deterministic the order that @@ -173,7 +191,7 @@ for (const showHidden of [true, false]) { {visible: {value: 1, enumerable: true}, hidden: {value: 2}}), true); if (out !== '{ [hidden]: 2, visible: 1 }' && out !== '{ visible: 1, [hidden]: 2 }') { - assert.ok(false); + common.fail(`unexpected value for out ${out}`); } } @@ -184,11 +202,11 @@ for (const showHidden of [true, false]) { hidden: {value: 'secret'}}), true); if (out !== "{ [hidden]: 'secret', name: 'Tim' }" && out !== "{ name: 'Tim', [hidden]: 'secret' }") { - assert(false); + common.fail(`unexpected value for out ${out}`); } } -assert.equal( +assert.strictEqual( util.inspect(Object.create(null, {name: {value: 'Tim', enumerable: true}, hidden: {value: 'secret'}})), @@ -197,18 +215,18 @@ assert.equal( // Dynamic properties -assert.equal(util.inspect({get readonly() {}}), +assert.strictEqual(util.inspect({get readonly() {}}), '{ readonly: [Getter] }'); -assert.equal(util.inspect({get readwrite() {}, set readwrite(val) {}}), +assert.strictEqual(util.inspect({get readwrite() {}, set readwrite(val) {}}), '{ readwrite: [Getter/Setter] }'); -assert.equal(util.inspect({set writeonly(val) {}}), +assert.strictEqual(util.inspect({set writeonly(val) {}}), '{ writeonly: [Setter] }'); var value = {}; value['a'] = value; -assert.equal(util.inspect(value), '{ a: [Circular] }'); +assert.strictEqual(util.inspect(value), '{ a: [Circular] }'); // Array with dynamic properties value = [1, 2, 3]; @@ -220,22 +238,22 @@ Object.defineProperty( get: () => { this.push(true); return this.length; } } ); -assert.equal(util.inspect(value), '[ 1, 2, 3, growingLength: [Getter] ]'); +assert.strictEqual(util.inspect(value), '[ 1, 2, 3, growingLength: [Getter] ]'); // Function with properties value = function() {}; value.aprop = 42; -assert.equal(util.inspect(value), '{ [Function: value] aprop: 42 }'); +assert.strictEqual(util.inspect(value), '{ [Function: value] aprop: 42 }'); // Regular expressions with properties value = /123/ig; value.aprop = 42; -assert.equal(util.inspect(value), '{ /123/gi aprop: 42 }'); +assert.strictEqual(util.inspect(value), '{ /123/gi aprop: 42 }'); // Dates with properties value = new Date('Sun, 14 Feb 2010 11:48:40 GMT'); value.aprop = 42; -assert.equal(util.inspect(value), '{ 2010-02-14T11:48:40.000Z aprop: 42 }' +assert.strictEqual(util.inspect(value), '{ 2010-02-14T11:48:40.000Z aprop: 42 }' ); // test the internal isDate implementation @@ -244,19 +262,22 @@ var d = new Date2(); var orig = util.inspect(d); Date2.prototype.foo = 'bar'; var after = util.inspect(d); -assert.equal(orig, after); +assert.strictEqual(orig, after); // test positive/negative zero -assert.equal(util.inspect(0), '0'); -assert.equal(util.inspect(-0), '-0'); +assert.strictEqual(util.inspect(0), '0'); +assert.strictEqual(util.inspect(-0), '-0'); // test for sparse array var a = ['foo', 'bar', 'baz']; -assert.equal(util.inspect(a), '[ \'foo\', \'bar\', \'baz\' ]'); +assert.strictEqual(util.inspect(a), '[ \'foo\', \'bar\', \'baz\' ]'); delete a[1]; -assert.equal(util.inspect(a), '[ \'foo\', , \'baz\' ]'); -assert.equal(util.inspect(a, true), '[ \'foo\', , \'baz\', [length]: 3 ]'); -assert.equal(util.inspect(new Array(5)), '[ , , , , ]'); +assert.strictEqual(util.inspect(a), '[ \'foo\', , \'baz\' ]'); +assert.strictEqual( + util.inspect(a, true), + '[ \'foo\', , \'baz\', [length]: 3 ]' +); +assert.strictEqual(util.inspect(new Array(5)), '[ , , , , ]'); // test for Array constructor in different context { @@ -300,9 +321,12 @@ var getterAndSetter = Object.create(null, { set: function() {} } }); -assert.equal(util.inspect(getter, true), '{ [a]: [Getter] }'); -assert.equal(util.inspect(setter, true), '{ [b]: [Setter] }'); -assert.equal(util.inspect(getterAndSetter, true), '{ [c]: [Getter/Setter] }'); +assert.strictEqual(util.inspect(getter, true), '{ [a]: [Getter] }'); +assert.strictEqual(util.inspect(setter, true), '{ [b]: [Setter] }'); +assert.strictEqual( + util.inspect(getterAndSetter, true), + '{ [c]: [Getter/Setter] }' +); // exceptions should print the error message, not '{}' const errors = []; @@ -311,17 +335,17 @@ errors.push(new Error('FAIL')); errors.push(new TypeError('FAIL')); errors.push(new SyntaxError('FAIL')); errors.forEach(function(err) { - assert.equal(util.inspect(err), err.stack); + assert.strictEqual(util.inspect(err), err.stack); }); try { undef(); // eslint-disable-line no-undef } catch (e) { - assert.equal(util.inspect(e), e.stack); + assert.strictEqual(util.inspect(e), e.stack); } var ex = util.inspect(new Error('FAIL'), true); -assert.ok(ex.indexOf('Error: FAIL') != -1); -assert.ok(ex.indexOf('[stack]') != -1); -assert.ok(ex.indexOf('[message]') != -1); +assert(ex.includes('Error: FAIL')); +assert(ex.includes('[stack]')); +assert(ex.includes('[message]')); // Doesn't capture stack trace function BadCustomError(msg) { Error.call(this); @@ -331,11 +355,14 @@ function BadCustomError(msg) { { value: 'BadCustomError', enumerable: false }); } util.inherits(BadCustomError, Error); -assert.equal(util.inspect(new BadCustomError('foo')), '[BadCustomError: foo]'); +assert.strictEqual( + util.inspect(new BadCustomError('foo')), + '[BadCustomError: foo]' +); // GH-1941 // should not throw: -assert.equal(util.inspect(Object.create(Date.prototype)), 'Date {}'); +assert.strictEqual(util.inspect(Object.create(Date.prototype)), 'Date {}'); // GH-1944 assert.doesNotThrow(function() { @@ -366,7 +393,7 @@ assert.doesNotThrow(function() { // GH-2225 { const x = { inspect: util.inspect }; - assert.ok(util.inspect(x).indexOf('inspect') != -1); + assert.strictEqual(util.inspect(x).includes('inspect'), true); } // util.inspect should not display the escaped value of a key. @@ -380,9 +407,14 @@ var w = { var y = ['a', 'b', 'c']; y['\\\\\\'] = 'd'; -assert.ok(util.inspect(w), - '{ \'\\\': 1, \'\\\\\': 2, \'\\\\\\\': 3, \'\\\\\\\\\': 4 }'); -assert.ok(util.inspect(y), '[ \'a\', \'b\', \'c\', \'\\\\\\\': \'d\' ]'); +assert.strictEqual( + util.inspect(w), + '{ \'\\\': 1, \'\\\\\': 2, \'\\\\\\\': 3, \'\\\\\\\\\': 4 }' +); +assert.strictEqual( + util.inspect(y), + '[ \'a\', \'b\', \'c\', \'\\\\\\\': \'d\' ]' +); // util.inspect.styles and util.inspect.colors function test_color_style(style, input, implicit) { @@ -395,7 +427,10 @@ function test_color_style(style, input, implicit) { var with_color = util.inspect(input, false, 0, true); var expect = '\u001b[' + color[0] + 'm' + without_color + '\u001b[' + color[1] + 'm'; - assert.equal(with_color, expect, 'util.inspect color for style ' + style); + assert.strictEqual( + with_color, + expect, + `util.inspect color for style ${style}`); } test_color_style('special', function() {}); @@ -419,30 +454,59 @@ assert.doesNotThrow(function() { let subject = { foo: 'bar', hello: 31, a: { b: { c: { d: 0 } } } }; Object.defineProperty(subject, 'hidden', { enumerable: false, value: null }); - assert(util.inspect(subject, { showHidden: false }).indexOf('hidden') === -1); - assert(util.inspect(subject, { showHidden: true }).indexOf('hidden') !== -1); - assert(util.inspect(subject, { colors: false }).indexOf('\u001b[32m') === -1); - assert(util.inspect(subject, { colors: true }).indexOf('\u001b[32m') !== -1); - assert(util.inspect(subject, { depth: 2 }).indexOf('c: [Object]') !== -1); - assert(util.inspect(subject, { depth: 0 }).indexOf('a: [Object]') !== -1); - assert(util.inspect(subject, { depth: null }).indexOf('{ d: 0 }') !== -1); + 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 + ); // "customInspect" option can enable/disable calling inspect() on objects subject = { inspect: function() { return 123; } }; - assert(util.inspect(subject, - { customInspect: true }).indexOf('123') !== -1); - assert(util.inspect(subject, - { customInspect: true }).indexOf('inspect') === -1); - assert(util.inspect(subject, - { customInspect: false }).indexOf('123') === -1); - assert(util.inspect(subject, - { customInspect: false }).indexOf('inspect') !== -1); + 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 + ); // custom inspect() functions should be able to return other Objects subject.inspect = function() { return { foo: 'bar' }; }; - assert.equal(util.inspect(subject), '{ foo: \'bar\' }'); + assert.strictEqual(util.inspect(subject), '{ foo: \'bar\' }'); subject.inspect = function(depth, opts) { assert.strictEqual(opts.customInspectOptions, true); @@ -459,7 +523,7 @@ function test_lines(input) { var without_color = util.inspect(input); var with_color = util.inspect(input, {colors: true}); - assert.equal(count_lines(without_color), count_lines(with_color)); + assert.strictEqual(count_lines(without_color), count_lines(with_color)); } test_lines([1, 2, 3, 4, 5, 6, 7]); @@ -480,84 +544,96 @@ test_lines({ }); // test boxed primitives output the correct values -assert.equal(util.inspect(new String('test')), '[String: \'test\']'); -assert.equal(util.inspect(Object(Symbol('test'))), '[Symbol: Symbol(test)]'); -assert.equal(util.inspect(new Boolean(false)), '[Boolean: false]'); -assert.equal(util.inspect(new Boolean(true)), '[Boolean: true]'); -assert.equal(util.inspect(new Number(0)), '[Number: 0]'); -assert.equal(util.inspect(new Number(-0)), '[Number: -0]'); -assert.equal(util.inspect(new Number(-1.1)), '[Number: -1.1]'); -assert.equal(util.inspect(new Number(13.37)), '[Number: 13.37]'); +assert.strictEqual(util.inspect(new String('test')), '[String: \'test\']'); +assert.strictEqual( + util.inspect(Object(Symbol('test'))), + '[Symbol: Symbol(test)]' +); +assert.strictEqual(util.inspect(new Boolean(false)), '[Boolean: false]'); +assert.strictEqual(util.inspect(new Boolean(true)), '[Boolean: true]'); +assert.strictEqual(util.inspect(new Number(0)), '[Number: 0]'); +assert.strictEqual(util.inspect(new Number(-0)), '[Number: -0]'); +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 var str = new String('baz'); str.foo = 'bar'; -assert.equal(util.inspect(str), '{ [String: \'baz\'] foo: \'bar\' }'); +assert.strictEqual(util.inspect(str), '{ [String: \'baz\'] foo: \'bar\' }'); var bool = new Boolean(true); bool.foo = 'bar'; -assert.equal(util.inspect(bool), '{ [Boolean: true] foo: \'bar\' }'); +assert.strictEqual(util.inspect(bool), '{ [Boolean: true] foo: \'bar\' }'); var num = new Number(13.37); num.foo = 'bar'; -assert.equal(util.inspect(num), '{ [Number: 13.37] foo: \'bar\' }'); +assert.strictEqual(util.inspect(num), '{ [Number: 13.37] foo: \'bar\' }'); // test es6 Symbol if (typeof Symbol !== 'undefined') { - assert.equal(util.inspect(Symbol()), 'Symbol()'); - assert.equal(util.inspect(Symbol(123)), 'Symbol(123)'); - assert.equal(util.inspect(Symbol('hi')), 'Symbol(hi)'); - assert.equal(util.inspect([Symbol()]), '[ Symbol() ]'); - assert.equal(util.inspect({ foo: Symbol() }), '{ foo: Symbol() }'); + 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('symbol')] = 42; - assert.equal(util.inspect(subject), '{}'); - assert.equal(util.inspect(subject, options), '{ [Symbol(symbol)]: 42 }'); + assert.strictEqual(util.inspect(subject), '{}'); + assert.strictEqual( + util.inspect(subject, options), + '{ [Symbol(symbol)]: 42 }' + ); subject = [1, 2, 3]; subject[Symbol('symbol')] = 42; - assert.equal(util.inspect(subject), '[ 1, 2, 3 ]'); - assert.equal(util.inspect(subject, options), + assert.strictEqual(util.inspect(subject), '[ 1, 2, 3 ]'); + assert.strictEqual(util.inspect(subject, options), '[ 1, 2, 3, [length]: 3, [Symbol(symbol)]: 42 ]'); } // test Set -assert.equal(util.inspect(new Set()), 'Set {}'); -assert.equal(util.inspect(new Set([1, 2, 3])), 'Set { 1, 2, 3 }'); +assert.strictEqual(util.inspect(new Set()), 'Set {}'); +assert.strictEqual(util.inspect(new Set([1, 2, 3])), 'Set { 1, 2, 3 }'); var set = new Set(['foo']); set.bar = 42; -assert.equal(util.inspect(set, true), 'Set { \'foo\', [size]: 1, bar: 42 }'); +assert.strictEqual( + util.inspect(set, true), + 'Set { \'foo\', [size]: 1, bar: 42 }' +); // test Map { - assert.equal(util.inspect(new Map()), 'Map {}'); - assert.equal(util.inspect(new Map([[1, 'a'], [2, 'b'], [3, 'c']])), + assert.strictEqual(util.inspect(new Map()), 'Map {}'); + assert.strictEqual(util.inspect(new Map([[1, 'a'], [2, 'b'], [3, 'c']])), 'Map { 1 => \'a\', 2 => \'b\', 3 => \'c\' }'); const map = new Map([['foo', null]]); map.bar = 42; - assert.equal(util.inspect(map, true), + assert.strictEqual(util.inspect(map, true), 'Map { \'foo\' => null, [size]: 1, bar: 42 }'); } // test Promise -assert.equal(util.inspect(Promise.resolve(3)), 'Promise { 3 }'); -assert.equal(util.inspect(Promise.reject(3)), 'Promise { 3 }'); -assert.equal(util.inspect(new Promise(function() {})), 'Promise { }'); +assert.strictEqual(util.inspect(Promise.resolve(3)), 'Promise { 3 }'); +assert.strictEqual(util.inspect(Promise.reject(3)), 'Promise { 3 }'); +assert.strictEqual( + util.inspect(new Promise(function() {})), + 'Promise { }' +); var promise = Promise.resolve('foo'); promise.bar = 42; -assert.equal(util.inspect(promise), 'Promise { \'foo\', bar: 42 }'); +assert.strictEqual(util.inspect(promise), '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. var oldPromise = Promise; global.Promise = function() { this.bar = 42; }; -assert.equal(util.inspect(new Promise()), '{ bar: 42 }'); +assert.strictEqual(util.inspect(new Promise()), '{ bar: 42 }'); global.Promise = oldPromise; // Map/Set Iterators @@ -591,7 +667,7 @@ function checkAlignment(container) { var npos = line.search(/\d/); if (npos !== -1) { if (pos !== undefined) - assert.equal(pos, npos, 'container items not aligned'); + assert.strictEqual(pos, npos, 'container items not aligned'); pos = npos; } }); @@ -624,22 +700,22 @@ checkAlignment(new Map(big_array.map(function(y) { return [y, null]; }))); const x = new ObjectSubclass(); x.foo = 42; - assert.equal(util.inspect(x), + assert.strictEqual(util.inspect(x), 'ObjectSubclass { foo: 42 }'); - assert.equal(util.inspect(new ArraySubclass(1, 2, 3)), + assert.strictEqual(util.inspect(new ArraySubclass(1, 2, 3)), 'ArraySubclass [ 1, 2, 3 ]'); - assert.equal(util.inspect(new SetSubclass([1, 2, 3])), + assert.strictEqual(util.inspect(new SetSubclass([1, 2, 3])), 'SetSubclass { 1, 2, 3 }'); - assert.equal(util.inspect(new MapSubclass([['foo', 42]])), + assert.strictEqual(util.inspect(new MapSubclass([['foo', 42]])), 'MapSubclass { \'foo\' => 42 }'); - assert.equal(util.inspect(new PromiseSubclass(function() {})), + assert.strictEqual(util.inspect(new PromiseSubclass(function() {})), 'PromiseSubclass { }'); } // Corner cases. { const x = { constructor: 42 }; - assert.equal(util.inspect(x), '{ constructor: 42 }'); + assert.strictEqual(util.inspect(x), '{ constructor: 42 }'); } { @@ -650,17 +726,17 @@ checkAlignment(new Map(big_array.map(function(y) { return [y, null]; }))); }, enumerable: true }); - assert.equal(util.inspect(x), '{ constructor: [Getter] }'); + assert.strictEqual(util.inspect(x), '{ constructor: [Getter] }'); } { const x = new function() {}; - assert.equal(util.inspect(x), '{}'); + assert.strictEqual(util.inspect(x), '{}'); } { const x = Object.create(null); - assert.equal(util.inspect(x), '{}'); + assert.strictEqual(util.inspect(x), '{}'); } // The following maxArrayLength tests were introduced after v6.0.0 was released. From 5647c8c829b8f20e523a2270b95506b6dd94541a Mon Sep 17 00:00:00 2001 From: Anna Henningsen Date: Fri, 19 Aug 2016 00:26:36 +0200 Subject: [PATCH 2/4] util: allow symbol-based custom inspection methods MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add a `util.inspect.custom` Symbol which can be used to customize `util.inspect()` output. Providing `obj[util.inspect.custom]` works like providing `obj.inspect`, except that the former allows avoiding name clashes with other `inspect()` methods. Fixes: https://github.com/nodejs/node/issues/8071 PR-URL: https://github.com/nodejs/node/pull/8174 Reviewed-By: James M Snell Reviewed-By: Michaël Zasso --- doc/api/util.md | 37 ++++++++++++++++---- lib/buffer.js | 5 +-- lib/internal/util.js | 4 +++ lib/util.js | 27 ++++++++------ test/parallel/test-util-inspect.js | 56 ++++++++++++++++++++++++++++-- 5 files changed, 108 insertions(+), 21 deletions(-) diff --git a/doc/api/util.md b/doc/api/util.md index ace6a7d3d0d9f7..6b6514debc820f 100644 --- a/doc/api/util.md +++ b/doc/api/util.md @@ -275,18 +275,19 @@ The predefined color codes are: `white`, `grey`, `black`, `blue`, `cyan`, Color styling uses ANSI control codes that may not be supported on all terminals. -### Custom `inspect()` function on Objects +### Custom inspection functions on Objects -Objects may also define their own `inspect(depth, opts)` function that -`util.inspect()` will invoke and use the result of when inspecting the object: +Objects may also define their own `[util.inspect.custom](depth, opts)` +(or, equivalently `inspect(depth, opts)`) function that `util.inspect()` will +invoke and use the result of when inspecting the object: ```js const util = require('util'); const obj = { name: 'nate' }; -obj.inspect = function(depth) { +obj[util.inspect.custom] = function(depth) { return `{${this.name}}`; }; @@ -294,13 +295,28 @@ util.inspect(obj); // "{nate}" ``` -Custom `inspect(depth, opts)` functions typically return a string but may -return a value of any type that will be formatted accordingly by +Custom `[util.inspect.custom](depth, opts)` functions typically return a string +but may return a value of any type that will be formatted accordingly by `util.inspect()`. ```js const util = require('util'); +const obj = { foo: 'this will not show up in the inspect() output' }; +obj[util.inspect.custom] = function(depth) { + return { bar: 'baz' }; +}; + +util.inspect(obj); + // "{ bar: 'baz' }" +``` + +A custom inspection method can alternatively be provided by exposing +an `inspect(depth, opts)` method on the object: + +```js +const util = require('util'); + const obj = { foo: 'this will not show up in the inspect() output' }; obj.inspect = function(depth) { return { bar: 'baz' }; @@ -330,6 +346,14 @@ util.inspect.defaultOptions.maxArrayLength = null; console.log(arr); // logs the full array ``` +### util.inspect.custom + + +A Symbol that can be used to declare custom inspect functions, see +[Custom inspection functions on Objects][]. + ## Deprecated APIs The following APIs have been deprecated and should no longer be used. Existing @@ -807,6 +831,7 @@ similar built-in functionality through [`Object.assign()`]. [semantically incompatible]: https://github.com/nodejs/node/issues/4179 [`util.inspect()`]: #util_util_inspect_object_options [Customizing `util.inspect` colors]: #util_customizing_util_inspect_colors +[Custom inspection functions on Objects]: #util_custom_inspection_functions_on_objects [`Error`]: errors.html#errors_class_error [`console.log()`]: console.html#console_console_log_data [`console.error()`]: console.html#console_console_error_data diff --git a/lib/buffer.js b/lib/buffer.js index 8489ca0b985e7f..1fc32bfc9f3297 100644 --- a/lib/buffer.js +++ b/lib/buffer.js @@ -516,8 +516,8 @@ Buffer.prototype.equals = function equals(b) { }; -// Inspect -Buffer.prototype.inspect = function inspect() { +// Override how buffers are presented by util.inspect(). +Buffer.prototype[internalUtil.inspectSymbol] = function inspect() { var str = ''; var max = exports.INSPECT_MAX_BYTES; if (this.length > 0) { @@ -527,6 +527,7 @@ Buffer.prototype.inspect = function inspect() { } return '<' + this.constructor.name + ' ' + str + '>'; }; +Buffer.prototype.inspect = Buffer.prototype[internalUtil.inspectSymbol]; Buffer.prototype.compare = function compare(target, start, diff --git a/lib/internal/util.js b/lib/internal/util.js index 468a426ac5040c..f0e5d1b548f467 100644 --- a/lib/internal/util.js +++ b/lib/internal/util.js @@ -6,6 +6,10 @@ const prefix = `(${process.release.name}:${process.pid}) `; exports.getHiddenValue = binding.getHiddenValue; exports.setHiddenValue = binding.setHiddenValue; +// The `buffer` module uses this. Defining it here instead of in the public +// `util` module makes it accessible without having to `require('util')` there. +exports.customInspectSymbol = Symbol('util.inspect.custom'); + // All the internal deprecations have to use this function only, as this will // prepend the prefix to the actual message. exports.deprecate = function(fn, msg) { diff --git a/lib/util.js b/lib/util.js index ed417036e87e7d..256fafc65edfa9 100644 --- a/lib/util.js +++ b/lib/util.js @@ -242,7 +242,10 @@ inspect.styles = { 'regexp': 'red' }; +const customInspectSymbol = internalUtil.customInspectSymbol; + exports.inspect = inspect; +exports.inspect.custom = customInspectSymbol; function stylizeWithColor(str, styleType) { var style = inspect.styles[styleType]; @@ -350,18 +353,20 @@ function formatValue(ctx, value, recurseTimes) { // Provide a hook for user-specified inspect functions. // Check that value is an object with an inspect function on it - if (ctx.customInspect && - value && - typeof value.inspect === 'function' && - // Filter out the util module, it's inspect function is special - value.inspect !== exports.inspect && - // Also filter out any prototype objects using the circular check. - !(value.constructor && value.constructor.prototype === value)) { - var ret = value.inspect(recurseTimes, ctx); - if (typeof ret !== 'string') { - ret = formatValue(ctx, ret, recurseTimes); + if (ctx.customInspect && value) { + const maybeCustomInspect = value[customInspectSymbol] || value.inspect; + + if (typeof maybeCustomInspect === 'function' && + // Filter out the util module, its inspect function is special + maybeCustomInspect !== exports.inspect && + // Also filter out any prototype objects using the circular check. + !(value.constructor && value.constructor.prototype === value)) { + let ret = maybeCustomInspect.call(value, recurseTimes, ctx); + if (typeof ret !== 'string') { + ret = formatValue(ctx, ret, recurseTimes); + } + return ret; } - return ret; } // Primitive types cannot have properties diff --git a/test/parallel/test-util-inspect.js b/test/parallel/test-util-inspect.js index 3632df86654fb0..4f0c3aebf63d12 100644 --- a/test/parallel/test-util-inspect.js +++ b/test/parallel/test-util-inspect.js @@ -451,7 +451,7 @@ assert.doesNotThrow(function() { // new API, accepts an "options" object { - let subject = { foo: 'bar', hello: 31, a: { b: { c: { d: 0 } } } }; + const subject = { foo: 'bar', hello: 31, a: { b: { c: { d: 0 } } } }; Object.defineProperty(subject, 'hidden', { enumerable: false, value: null }); assert.strictEqual( @@ -482,9 +482,11 @@ assert.doesNotThrow(function() { util.inspect(subject, { depth: null }).includes('{ d: 0 }'), true ); +} +{ // "customInspect" option can enable/disable calling inspect() on objects - subject = { inspect: function() { return 123; } }; + const subject = { inspect: function() { return 123; } }; assert.strictEqual( util.inspect(subject, { customInspect: true }).includes('123'), @@ -515,6 +517,56 @@ assert.doesNotThrow(function() { util.inspect(subject, { customInspectOptions: true }); } +{ + // "customInspect" option can enable/disable calling [util.inspect.custom]() + const subject = { [util.inspect.custom]: function() { return 123; } }; + + assert.strictEqual( + util.inspect(subject, { customInspect: true }).includes('123'), + true + ); + assert.strictEqual( + util.inspect(subject, { customInspect: false }).includes('123'), + false + ); + + // a custom [util.inspect.custom]() should be able to return other Objects + subject[util.inspect.custom] = function() { return { foo: 'bar' }; }; + + assert.strictEqual(util.inspect(subject), '{ foo: \'bar\' }'); + + subject[util.inspect.custom] = function(depth, opts) { + assert.strictEqual(opts.customInspectOptions, true); + }; + + util.inspect(subject, { customInspectOptions: true }); +} + +{ + // [util.inspect.custom] takes precedence over inspect + const subject = { + [util.inspect.custom]() { return 123; }, + inspect() { return 456; } + }; + + assert.strictEqual( + util.inspect(subject, { customInspect: true }).includes('123'), + true + ); + assert.strictEqual( + util.inspect(subject, { customInspect: false }).includes('123'), + false + ); + assert.strictEqual( + util.inspect(subject, { customInspect: true }).includes('456'), + false + ); + assert.strictEqual( + util.inspect(subject, { customInspect: false }).includes('456'), + false + ); +} + // util.inspect with "colors" option should produce as many lines as without it function test_lines(input) { var count_lines = function(str) { From 763e8b96a000fc0c6a3f2df2e69b7bb9e46f2405 Mon Sep 17 00:00:00 2001 From: Anna Henningsen Date: Wed, 7 Sep 2016 23:55:49 +0200 Subject: [PATCH 3/4] [squash] fixup for v6 by adding missing require() steal a line from #7207 to make things work --- lib/buffer.js | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/buffer.js b/lib/buffer.js index 1fc32bfc9f3297..a2216d8f028255 100644 --- a/lib/buffer.js +++ b/lib/buffer.js @@ -4,6 +4,7 @@ const binding = process.binding('buffer'); const { isArrayBuffer } = process.binding('util'); const bindingObj = {}; +const internalUtil = require('internal/util'); class FastBuffer extends Uint8Array {} From b0f14e66716485fa6761bb47025acacfb92d624a Mon Sep 17 00:00:00 2001 From: Anna Henningsen Date: Fri, 19 Aug 2016 00:44:38 +0200 Subject: [PATCH 4/4] util: allow returning `this` from custom inspect MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit If a custom inspection function returned `this`, use that value for further formatting instead of going into infinite recursion. This is particularly useful when combined with `util.inspect.custom` because returning `this` from such a method makes it easy to have an `inspect()` function that is ignored by `util.inspect` without actually having to provide an alternative for custom inspection. PR-URL: https://github.com/nodejs/node/pull/8174 Reviewed-By: James M Snell Reviewed-By: Michaël Zasso --- lib/util.js | 11 ++++++++--- test/parallel/test-util-inspect.js | 10 ++++++++++ 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/lib/util.js b/lib/util.js index 256fafc65edfa9..b4ff85f9a6edb1 100644 --- a/lib/util.js +++ b/lib/util.js @@ -362,10 +362,15 @@ function formatValue(ctx, value, recurseTimes) { // Also filter out any prototype objects using the circular check. !(value.constructor && value.constructor.prototype === value)) { let ret = maybeCustomInspect.call(value, recurseTimes, ctx); - if (typeof ret !== 'string') { - ret = formatValue(ctx, ret, recurseTimes); + + // If the custom inspection method returned `this`, don't go into + // infinite recursion. + if (ret !== value) { + if (typeof ret !== 'string') { + ret = formatValue(ctx, ret, recurseTimes); + } + return ret; } - return ret; } } diff --git a/test/parallel/test-util-inspect.js b/test/parallel/test-util-inspect.js index 4f0c3aebf63d12..15a444fdab648e 100644 --- a/test/parallel/test-util-inspect.js +++ b/test/parallel/test-util-inspect.js @@ -567,6 +567,16 @@ assert.doesNotThrow(function() { ); } +{ + // Returning `this` from a custom inspection function works. + assert.strictEqual(util.inspect({ a: 123, inspect() { return this; } }), + '{ a: 123, inspect: [Function: inspect] }'); + + const subject = { a: 123, [util.inspect.custom]() { return this; } }; + assert.strictEqual(util.inspect(subject), + '{ a: 123 }'); +} + // util.inspect with "colors" option should produce as many lines as without it function test_lines(input) { var count_lines = function(str) {