diff --git a/doc/api/assert.md b/doc/api/assert.md
index 6935a9db4bda64..6eccfa7ee115c3 100644
--- a/doc/api/assert.md
+++ b/doc/api/assert.md
@@ -7,6 +7,93 @@
The `assert` module provides a simple set of assertion tests that can be used to
test invariants.
+A `strict` and a `legacy` mode exist, while it is recommended to only use
+[`strict mode`][].
+
+For more information about the used equality comparisons see
+[MDN's guide on equality comparisons and sameness][mdn-equality-guide].
+
+## Strict mode
+
+
+When using the `strict mode`, any `assert` function will use the equality used in
+the strict function mode. So [`assert.deepEqual()`][] will, for example, work the
+same as [`assert.deepStrictEqual()`][].
+
+On top of that, error messages which involve objects produce an error diff
+instead of displaying both objects. That is not the case for the legacy mode.
+
+It can be accessed using:
+
+```js
+const assert = require('assert').strict;
+```
+
+Example error diff (the `expected`, `actual`, and `Lines skipped` will be on a
+single row):
+
+```js
+const assert = require('assert').strict;
+
+assert.deepEqual([[[1, 2, 3]], 4, 5], [[[1, 2, '3']], 4, 5]);
+```
+
+```diff
+AssertionError [ERR_ASSERTION]: Input A expected to deepStrictEqual input B:
++ expected
+- actual
+... Lines skipped
+
+ [
+ [
+...
+ 2,
+- 3
++ '3'
+ ],
+...
+ 5
+ ]
+```
+
+To deactivate the colors, use the `NODE_DISABLE_COLORS` environment variable.
+Please note that this will also deactivate the colors in the REPL.
+
+## Legacy mode
+
+> Stability: 0 - Deprecated: Use strict mode instead.
+
+When accessing `assert` directly instead of using the `strict` property, the
+[Abstract Equality Comparison][] will be used for any function without a
+"strict" in its name (e.g. [`assert.deepEqual()`][]).
+
+It can be accessed using:
+
+```js
+const assert = require('assert');
+```
+
+It is recommended to use the [`strict mode`][] instead as the
+[Abstract Equality Comparison][] can often have surprising results. Especially
+in case of [`assert.deepEqual()`][] as the used comparison rules there are very
+lax.
+
+E.g.
+
+```js
+// WARNING: This does not throw an AssertionError!
+assert.deepEqual(/a/gi, new Date());
+```
+
## assert(value[, message])
* `block` {Function}
-* `error` {RegExp|Function}
+* `error` {RegExp|Function|object}
* `message` {any}
Expects the function `block` to throw an error.
-If specified, `error` can be a constructor, [`RegExp`][], or validation
-function.
+If specified, `error` can be a constructor, [`RegExp`][], a validation
+function, or an object where each property will be tested for.
If specified, `message` will be the message provided by the `AssertionError` if
the block fails to throw.
@@ -658,12 +789,15 @@ assert.throws(
Validate error message using [`RegExp`][]:
+Using a regular expression runs `.toString` on the error object, and will
+therefore also include the error name.
+
```js
assert.throws(
() => {
throw new Error('Wrong value');
},
- /value/
+ /^Error: Wrong value$/
);
```
@@ -683,19 +817,61 @@ assert.throws(
);
```
+Custom error object / error instance:
+
+```js
+assert.throws(
+ () => {
+ const err = new TypeError('Wrong value');
+ err.code = 404;
+ throw err;
+ },
+ {
+ name: 'TypeError',
+ message: 'Wrong value'
+ // Note that only properties on the error object will be tested!
+ }
+);
+```
+
Note that `error` can not be a string. If a string is provided as the second
argument, then `error` is assumed to be omitted and the string will be used for
-`message` instead. This can lead to easy-to-miss mistakes:
+`message` instead. This can lead to easy-to-miss mistakes. Please read the
+example below carefully if using a string as the second argument gets
+considered:
```js
-// THIS IS A MISTAKE! DO NOT DO THIS!
-assert.throws(myFunction, 'missing foo', 'did not throw with expected message');
-
-// Do this instead.
-assert.throws(myFunction, /missing foo/, 'did not throw with expected message');
+function throwingFirst() {
+ throw new Error('First');
+}
+function throwingSecond() {
+ throw new Error('Second');
+}
+function notThrowing() {}
+
+// The second argument is a string and the input function threw an Error.
+// In that case both cases do not throw as neither is going to try to
+// match for the error message thrown by the input function!
+assert.throws(throwingFirst, 'Second');
+assert.throws(throwingSecond, 'Second');
+
+// The string is only used (as message) in case the function does not throw:
+assert.throws(notThrowing, 'Second');
+// AssertionError [ERR_ASSERTION]: Missing expected exception: Second
+
+// If it was intended to match for the error message do this instead:
+assert.throws(throwingSecond, /Second$/);
+// Does not throw because the error messages match.
+assert.throws(throwingFirst, /Second$/);
+// Throws a error:
+// Error: First
+// at throwingFirst (repl:2:9)
```
+Due to the confusing notation, it is recommended not to use a string as the
+second argument. This might lead to difficult-to-spot errors.
+
## Caveats
For the following cases, consider using ES2015 [`Object.is()`][],
@@ -729,15 +905,21 @@ For more information, see
[`Set`]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set
[`Symbol`]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Symbol
[`TypeError`]: errors.html#errors_class_typeerror
+[`WeakMap`]: https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/WeakMap
+[`WeakSet`]: https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/WeakSet
[`assert.deepEqual()`]: #assert_assert_deepequal_actual_expected_message
[`assert.deepStrictEqual()`]: #assert_assert_deepstrictequal_actual_expected_message
+[`assert.notDeepStrictEqual()`]: #assert_assert_notdeepstrictequal_actual_expected_message
+[`assert.notStrictEqual()`]: #assert_assert_notstrictequal_actual_expected_message
[`assert.ok()`]: #assert_assert_ok_value_message
+[`assert.strictEqual()`]: #assert_assert_strictequal_actual_expected_message
[`assert.throws()`]: #assert_assert_throws_block_error_message
+[`strict mode`]: #assert_strict_mode
[Abstract Equality Comparison]: https://tc39.github.io/ecma262/#sec-abstract-equality-comparison
[Object.prototype.toString()]: https://tc39.github.io/ecma262/#sec-object.prototype.tostring
[SameValueZero]: https://tc39.github.io/ecma262/#sec-samevaluezero
+[SameValue Comparison]: https://tc39.github.io/ecma262/#sec-samevalue
[Strict Equality Comparison]: https://tc39.github.io/ecma262/#sec-strict-equality-comparison
-[caveats]: #assert_caveats
[enumerable "own" properties]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Enumerability_and_ownership_of_properties
[mdn-equality-guide]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Equality_comparisons_and_sameness
[prototype-spec]: https://tc39.github.io/ecma262/#sec-ordinary-object-internal-methods-and-internal-slots
diff --git a/doc/api/deprecations.md b/doc/api/deprecations.md
index c2441f69956275..ac1cb7cace45a3 100644
--- a/doc/api/deprecations.md
+++ b/doc/api/deprecations.md
@@ -774,19 +774,15 @@ Type: Runtime
cause a lot of issues. See https://github.com/nodejs/node/issues/14328 for more
details.
-
-### DEP0098: AsyncHooks Embedder AsyncResource.emit{Before,After} APIs
+
+### DEP0089: require('assert')
-Type: Runtime
-
-The embedded API provided by AsyncHooks exposes emit{Before,After} methods
-which are very easy to use incorrectly which can lead to unrecoverable errors.
+Type: Documentation-only
-Use [`asyncResource.runInAsyncScope()`][] API instead which provides a much
-safer, and more convenient, alternative. See
-https://github.com/nodejs/node/pull/18513 for more details.
+Importing assert directly is not recommended as the exposed functions will use
+loose equality checks. Use `require('assert').strict` instead. The API is the
+same as the legacy assert but it will always use strict equality checks.
-[`--pending-deprecation`]: cli.html#cli_pending_deprecation
[`Buffer.allocUnsafeSlow(size)`]: buffer.html#buffer_class_method_buffer_allocunsafeslow_size
[`Buffer.from(array)`]: buffer.html#buffer_class_method_buffer_from_array
[`Buffer.from(buffer)`]: buffer.html#buffer_class_method_buffer_from_buffer
diff --git a/doc/api/tty.md b/doc/api/tty.md
index 8b757c0f02751a..ce6dbae8fa6e7d 100644
--- a/doc/api/tty.md
+++ b/doc/api/tty.md
@@ -121,6 +121,32 @@ added: v0.7.7
A `number` specifying the number of rows the TTY currently has. This property
is updated whenever the `'resize'` event is emitted.
+### writeStream.getColorDepth([env])
+
+
+* `env` {object} A object containing the environment variables to check.
+ Defaults to `process.env`.
+* Returns: {number}
+
+Returns:
+* 1 for 2,
+* 4 for 16,
+* 8 for 256,
+* 24 for 16,777,216
+colors supported.
+
+Use this to determine what colors the terminal supports. Due to the nature of
+colors in terminals it is possible to either have false positives or false
+negatives. It depends on process information and the environment variables that
+may lie about what terminal is used.
+To enforce a specific behavior without relying on `process.env` it is possible
+to pass in an object with different settings.
+
+Use the `NODE_DISABLE_COLORS` environment variable to enforce this function to
+always return 1.
+
## tty.isatty(fd)
diff --git a/lib/assert.js b/lib/assert.js
index 0d061dbff617f7..6c33af3fd4b2ca 100644
--- a/lib/assert.js
+++ b/lib/assert.js
@@ -22,7 +22,12 @@
const { isDeepEqual, isDeepStrictEqual } =
require('internal/util/comparisons');
-const errors = require('internal/errors');
+const { AssertionError, TypeError } = require('internal/errors');
+const { inspect } = require('util');
+
+const ERR_DIFF_DEACTIVATED = 0;
+const ERR_DIFF_NOT_EQUAL = 1;
+const ERR_DIFF_EQUAL = 2;
// The assert module provides functions that throw
// AssertionError's when particular conditions are not met. The
@@ -30,25 +35,21 @@ const errors = require('internal/errors');
const assert = module.exports = ok;
+const NO_EXCEPTION_SENTINEL = {};
+
// All of the following functions must throw an AssertionError
// when a corresponding condition is not met, with a message that
// may be undefined if not provided. All assertion methods provide
// both the actual and expected values to the assertion error for
// display purposes.
-function innerFail(actual, expected, message, operator, stackStartFunction) {
- if (message instanceof Error) throw message;
+function innerFail(obj) {
+ if (obj.message instanceof Error) throw obj.message;
- throw new errors.AssertionError({
- message,
- actual,
- expected,
- operator,
- stackStartFunction
- });
+ throw new AssertionError(obj);
}
-function fail(actual, expected, message, operator, stackStartFunction) {
+function fail(actual, expected, message, operator, stackStartFn) {
const argsLen = arguments.length;
if (argsLen === 0) {
@@ -60,7 +61,13 @@ function fail(actual, expected, message, operator, stackStartFunction) {
operator = '!=';
}
- innerFail(actual, expected, message, operator, stackStartFunction || fail);
+ innerFail({
+ actual,
+ expected,
+ message,
+ operator,
+ stackStartFn: stackStartFn || fail
+ });
}
assert.fail = fail;
@@ -69,13 +76,21 @@ assert.fail = fail;
// new assert.AssertionError({ message: message,
// actual: actual,
// expected: expected });
-assert.AssertionError = errors.AssertionError;
+assert.AssertionError = AssertionError;
// Pure assertion tests whether a value is truthy, as determined
// by !!value.
function ok(value, message) {
- if (!value) innerFail(value, true, message, '==', ok);
+ if (!value) {
+ innerFail({
+ actual: value,
+ expected: true,
+ message,
+ operator: '==',
+ stackStartFn: ok
+ });
+ }
}
assert.ok = ok;
@@ -83,7 +98,15 @@ assert.ok = ok;
/* eslint-disable no-restricted-properties */
assert.equal = function equal(actual, expected, message) {
// eslint-disable-next-line eqeqeq
- if (actual != expected) innerFail(actual, expected, message, '==', equal);
+ if (actual != expected) {
+ innerFail({
+ actual,
+ expected,
+ message,
+ operator: '==',
+ stackStartFn: equal
+ });
+ }
};
// The non-equality assertion tests for whether two objects are not
@@ -91,43 +114,81 @@ assert.equal = function equal(actual, expected, message) {
assert.notEqual = function notEqual(actual, expected, message) {
// eslint-disable-next-line eqeqeq
if (actual == expected) {
- innerFail(actual, expected, message, '!=', notEqual);
+ innerFail({
+ actual,
+ expected,
+ message,
+ operator: '!=',
+ stackStartFn: notEqual
+ });
}
};
// The equivalence assertion tests a deep equality relation.
assert.deepEqual = function deepEqual(actual, expected, message) {
if (!isDeepEqual(actual, expected)) {
- innerFail(actual, expected, message, 'deepEqual', deepEqual);
+ innerFail({
+ actual,
+ expected,
+ message,
+ operator: 'deepEqual',
+ stackStartFn: deepEqual
+ });
}
};
// The non-equivalence assertion tests for any deep inequality.
assert.notDeepEqual = function notDeepEqual(actual, expected, message) {
if (isDeepEqual(actual, expected)) {
- innerFail(actual, expected, message, 'notDeepEqual', notDeepEqual);
+ innerFail({
+ actual,
+ expected,
+ message,
+ operator: 'notDeepEqual',
+ stackStartFn: notDeepEqual
+ });
}
};
/* eslint-enable */
assert.deepStrictEqual = function deepStrictEqual(actual, expected, message) {
if (!isDeepStrictEqual(actual, expected)) {
- innerFail(actual, expected, message, 'deepStrictEqual', deepStrictEqual);
+ innerFail({
+ actual,
+ expected,
+ message,
+ operator: 'deepStrictEqual',
+ stackStartFn: deepStrictEqual,
+ errorDiff: this === strict ? ERR_DIFF_EQUAL : ERR_DIFF_DEACTIVATED
+ });
}
};
assert.notDeepStrictEqual = notDeepStrictEqual;
function notDeepStrictEqual(actual, expected, message) {
if (isDeepStrictEqual(actual, expected)) {
- innerFail(actual, expected, message, 'notDeepStrictEqual',
- notDeepStrictEqual);
+ innerFail({
+ actual,
+ expected,
+ message,
+ operator: 'notDeepStrictEqual',
+ stackStartFn: notDeepStrictEqual,
+ errorDiff: this === strict ? ERR_DIFF_NOT_EQUAL : ERR_DIFF_DEACTIVATED
+ });
}
}
// The strict equality assertion tests strict equality, as determined by ===.
assert.strictEqual = function strictEqual(actual, expected, message) {
if (actual !== expected) {
- innerFail(actual, expected, message, '===', strictEqual);
+ innerFail({
+ actual,
+ expected,
+ message,
+ operator: '===',
+ stackStartFn: strictEqual,
+ errorDiff: this === strict ? ERR_DIFF_EQUAL : ERR_DIFF_DEACTIVATED
+ });
}
};
@@ -135,14 +196,51 @@ assert.strictEqual = function strictEqual(actual, expected, message) {
// determined by !==.
assert.notStrictEqual = function notStrictEqual(actual, expected, message) {
if (actual === expected) {
- innerFail(actual, expected, message, '!==', notStrictEqual);
+ innerFail({
+ actual,
+ expected,
+ message,
+ operator: '!==',
+ stackStartFn: notStrictEqual,
+ errorDiff: this === strict ? ERR_DIFF_NOT_EQUAL : ERR_DIFF_DEACTIVATED
+ });
}
};
-function expectedException(actual, expected) {
+function compareExceptionKey(actual, expected, key, msg) {
+ if (!isDeepStrictEqual(actual[key], expected[key])) {
+ innerFail({
+ actual: actual[key],
+ expected: expected[key],
+ message: msg || `${key}: expected ${inspect(expected[key])}, ` +
+ `not ${inspect(actual[key])}`,
+ operator: 'throws',
+ stackStartFn: assert.throws
+ });
+ }
+}
+
+function expectedException(actual, expected, msg) {
if (typeof expected !== 'function') {
- // Should be a RegExp, if not fail hard
- return expected.test(actual);
+ if (expected instanceof RegExp)
+ return expected.test(actual);
+ // assert.doesNotThrow does not accept objects.
+ if (arguments.length === 2) {
+ throw new TypeError('ERR_INVALID_ARG_TYPE', 'expected',
+ ['Function', 'RegExp'], expected);
+ }
+ // The name and message could be non enumerable. Therefore test them
+ // explicitly.
+ if ('name' in expected) {
+ compareExceptionKey(actual, expected, 'name', msg);
+ }
+ if ('message' in expected) {
+ compareExceptionKey(actual, expected, 'message', msg);
+ }
+ for (const key of Object.keys(expected)) {
+ compareExceptionKey(actual, expected, key, msg);
+ }
+ return true;
}
// Guard instanceof against arrow functions as they don't have a prototype.
if (expected.prototype !== undefined && actual instanceof expected) {
@@ -154,59 +252,94 @@ function expectedException(actual, expected) {
return expected.call({}, actual) === true;
}
-function tryBlock(block) {
+function getActual(block) {
+ if (typeof block !== 'function') {
+ throw new TypeError('ERR_INVALID_ARG_TYPE', 'block', 'Function',
+ block);
+ }
try {
block();
} catch (e) {
return e;
}
+ return NO_EXCEPTION_SENTINEL;
}
-function innerThrows(shouldThrow, block, expected, message) {
- var details = '';
+// Expected to throw an error.
+assert.throws = function throws(block, error, message) {
+ const actual = getActual(block);
- if (typeof block !== 'function') {
- throw new errors.TypeError('ERR_INVALID_ARG_TYPE', 'block', 'Function',
- block);
- }
+ if (typeof error === 'string') {
+ if (arguments.length === 3)
+ throw new TypeError('ERR_INVALID_ARG_TYPE',
+ 'error',
+ ['Function', 'RegExp'],
+ error);
- if (typeof expected === 'string') {
- message = expected;
- expected = null;
+ message = error;
+ error = null;
}
- const actual = tryBlock(block);
-
- if (shouldThrow === true) {
- if (actual === undefined) {
- if (expected && expected.name) {
- details += ` (${expected.name})`;
- }
- details += message ? `: ${message}` : '.';
- fail(actual, expected, `Missing expected exception${details}`, 'throws');
- }
- if (expected && expectedException(actual, expected) === false) {
- throw actual;
- }
- } else if (actual !== undefined) {
- if (!expected || expectedException(actual, expected)) {
- details = message ? `: ${message}` : '.';
- fail(actual,
- expected,
- `Got unwanted exception${details}\n${actual.message}`,
- 'doesNotThrow');
+ if (actual === NO_EXCEPTION_SENTINEL) {
+ let details = '';
+ if (error && error.name) {
+ details += ` (${error.name})`;
}
+ details += message ? `: ${message}` : '.';
+ innerFail({
+ actual,
+ expected: error,
+ operator: 'throws',
+ message: `Missing expected exception${details}`,
+ stackStartFn: throws
+ });
+ }
+ if (error && expectedException(actual, error, message) === false) {
throw actual;
}
-}
-
-// Expected to throw an error.
-assert.throws = function throws(block, error, message) {
- innerThrows(true, block, error, message);
};
assert.doesNotThrow = function doesNotThrow(block, error, message) {
- innerThrows(false, block, error, message);
+ const actual = getActual(block);
+ if (actual === NO_EXCEPTION_SENTINEL)
+ return;
+
+ if (typeof error === 'string') {
+ message = error;
+ error = null;
+ }
+
+ if (!error || expectedException(actual, error)) {
+ const details = message ? `: ${message}` : '.';
+ innerFail({
+ actual,
+ expected: error,
+ operator: 'doesNotThrow',
+ message: `Got unwanted exception${details}\n${actual && actual.message}`,
+ stackStartFn: doesNotThrow
+ });
+ }
+ throw actual;
};
assert.ifError = function ifError(err) { if (err) throw err; };
+
+// Expose a strict only variant of assert
+function strict(value, message) {
+ if (!value) {
+ innerFail({
+ actual: value,
+ expected: true,
+ message,
+ operator: '==',
+ stackStartFn: strict
+ });
+ }
+}
+assert.strict = Object.assign(strict, assert, {
+ equal: assert.strictEqual,
+ deepEqual: assert.deepStrictEqual,
+ notEqual: assert.notStrictEqual,
+ notDeepEqual: assert.notDeepStrictEqual
+});
+assert.strict.strict = assert.strict;
diff --git a/lib/internal/errors.js b/lib/internal/errors.js
index f4a77e037bc491..f83458a6a1d70f 100644
--- a/lib/internal/errors.js
+++ b/lib/internal/errors.js
@@ -13,6 +13,10 @@
const kCode = Symbol('code');
const messages = new Map();
+var green = '';
+var red = '';
+var white = '';
+
const {
UV_EAI_MEMORY,
UV_EAI_NODATA,
@@ -78,22 +82,175 @@ function makeNodeError(Base) {
};
}
+function createErrDiff(actual, expected, operator) {
+ var other = '';
+ var res = '';
+ var lastPos = 0;
+ var end = '';
+ var skipped = false;
+ const util = lazyUtil();
+ const actualLines = util
+ .inspect(actual, { compact: false, customInspect: false }).split('\n');
+ const expectedLines = util
+ .inspect(expected, { compact: false, customInspect: false }).split('\n');
+ const msg = `Input A expected to ${operator} input B:\n` +
+ `${green}+ expected${white} ${red}- actual${white}`;
+ const skippedMsg = ' ... Lines skipped';
+
+ // Remove all ending lines that match (this optimizes the output for
+ // readability by reducing the number of total changed lines).
+ var a = actualLines[actualLines.length - 1];
+ var b = expectedLines[expectedLines.length - 1];
+ var i = 0;
+ while (a === b) {
+ if (i++ < 2) {
+ end = `\n ${a}${end}`;
+ } else {
+ other = a;
+ }
+ actualLines.pop();
+ expectedLines.pop();
+ if (actualLines.length === 0 || expectedLines.length === 0)
+ break;
+ a = actualLines[actualLines.length - 1];
+ b = expectedLines[expectedLines.length - 1];
+ }
+ if (i > 3) {
+ end = `\n...${end}`;
+ skipped = true;
+ }
+ if (other !== '') {
+ end = `\n ${other}${end}`;
+ other = '';
+ }
+
+ const maxLines = Math.max(actualLines.length, expectedLines.length);
+ var printedLines = 0;
+ for (i = 0; i < maxLines; i++) {
+ // Only extra expected lines exist
+ const cur = i - lastPos;
+ if (actualLines.length < i + 1) {
+ if (cur > 1 && i > 2) {
+ if (cur > 4) {
+ res += '\n...';
+ skipped = true;
+ } else if (cur > 3) {
+ res += `\n ${expectedLines[i - 2]}`;
+ printedLines++;
+ }
+ res += `\n ${expectedLines[i - 1]}`;
+ printedLines++;
+ }
+ lastPos = i;
+ other += `\n${green}+${white} ${expectedLines[i]}`;
+ printedLines++;
+ // Only extra actual lines exist
+ } else if (expectedLines.length < i + 1) {
+ if (cur > 1 && i > 2) {
+ if (cur > 4) {
+ res += '\n...';
+ skipped = true;
+ } else if (cur > 3) {
+ res += `\n ${actualLines[i - 2]}`;
+ printedLines++;
+ }
+ res += `\n ${actualLines[i - 1]}`;
+ printedLines++;
+ }
+ lastPos = i;
+ res += `\n${red}-${white} ${actualLines[i]}`;
+ printedLines++;
+ // Lines diverge
+ } else if (actualLines[i] !== expectedLines[i]) {
+ if (cur > 1 && i > 2) {
+ if (cur > 4) {
+ res += '\n...';
+ skipped = true;
+ } else if (cur > 3) {
+ res += `\n ${actualLines[i - 2]}`;
+ printedLines++;
+ }
+ res += `\n ${actualLines[i - 1]}`;
+ printedLines++;
+ }
+ lastPos = i;
+ res += `\n${red}-${white} ${actualLines[i]}`;
+ other += `\n${green}+${white} ${expectedLines[i]}`;
+ printedLines += 2;
+ // Lines are identical
+ } else {
+ res += other;
+ other = '';
+ if (cur === 1 || i === 0) {
+ res += `\n ${actualLines[i]}`;
+ printedLines++;
+ }
+ }
+ // Inspected object to big (Show ~20 rows max)
+ if (printedLines > 20 && i < maxLines - 2) {
+ return `${msg}${skippedMsg}\n${res}\n...${other}\n...`;
+ }
+ }
+ return `${msg}${skipped ? skippedMsg : ''}\n${res}${other}${end}`;
+}
+
class AssertionError extends Error {
constructor(options) {
if (typeof options !== 'object' || options === null) {
throw new exports.TypeError('ERR_INVALID_ARG_TYPE', 'options', 'Object');
}
- var { actual, expected, message, operator, stackStartFunction } = options;
- if (message) {
+ var {
+ actual,
+ expected,
+ message,
+ operator,
+ stackStartFn,
+ errorDiff = 0
+ } = options;
+
+ if (message != null) {
super(message);
} else {
+ if (util_ === null &&
+ process.stdout.isTTY &&
+ process.stdout.getColorDepth() !== 1) {
+ green = '\u001b[32m';
+ white = '\u001b[39m';
+ red = '\u001b[31m';
+ }
const util = lazyUtil();
if (actual && actual.stack && actual instanceof Error)
actual = `${actual.name}: ${actual.message}`;
if (expected && expected.stack && expected instanceof Error)
expected = `${expected.name}: ${expected.message}`;
- super(`${util.inspect(actual).slice(0, 128)} ` +
- `${operator} ${util.inspect(expected).slice(0, 128)}`);
+
+ if (errorDiff === 0) {
+ let res = util.inspect(actual);
+ let other = util.inspect(expected);
+ if (res.length > 128)
+ res = `${res.slice(0, 125)}...`;
+ if (other.length > 128)
+ other = `${other.slice(0, 125)}...`;
+ super(`${res} ${operator} ${other}`);
+ } else if (errorDiff === 1) {
+ // In case the objects are equal but the operator requires unequal, show
+ // the first object and say A equals B
+ const res = util.inspect(
+ actual,
+ { compact: false, customInspect: false }
+ ).split('\n');
+
+ if (res.length > 20) {
+ res[19] = '...';
+ while (res.length > 20) {
+ res.pop();
+ }
+ }
+ // Only print a single object.
+ super(`Identical input passed to ${operator}:\n${res.join('\n')}`);
+ } else {
+ super(createErrDiff(actual, expected, operator));
+ }
}
this.generatedMessage = !message;
@@ -102,7 +259,7 @@ class AssertionError extends Error {
this.actual = actual;
this.expected = expected;
this.operator = operator;
- Error.captureStackTrace(this, stackStartFunction);
+ Error.captureStackTrace(this, stackStartFn);
}
}
diff --git a/lib/tty.js b/lib/tty.js
index 29440d6d96e3d5..12d0836cde5db5 100644
--- a/lib/tty.js
+++ b/lib/tty.js
@@ -26,12 +26,19 @@ const net = require('net');
const { TTY, isTTY } = process.binding('tty_wrap');
const errors = require('internal/errors');
const readline = require('readline');
+const { release } = require('os');
+
+const OSRelease = release().split('.');
+
+const COLORS_2 = 1;
+const COLORS_16 = 4;
+const COLORS_256 = 8;
+const COLORS_16m = 24;
function isatty(fd) {
return Number.isInteger(fd) && fd >= 0 && isTTY(fd);
}
-
function ReadStream(fd, options) {
if (!(this instanceof ReadStream))
return new ReadStream(fd, options);
@@ -58,7 +65,6 @@ ReadStream.prototype.setRawMode = function(flag) {
this.isRaw = flag;
};
-
function WriteStream(fd) {
if (!(this instanceof WriteStream))
return new WriteStream(fd);
@@ -78,8 +84,8 @@ function WriteStream(fd) {
// Ref: https://github.com/nodejs/node/pull/1771#issuecomment-119351671
this._handle.setBlocking(true);
- var winSize = new Array(2);
- var err = this._handle.getWindowSize(winSize);
+ const winSize = new Array(2);
+ const err = this._handle.getWindowSize(winSize);
if (!err) {
this.columns = winSize[0];
this.rows = winSize[1];
@@ -87,21 +93,83 @@ function WriteStream(fd) {
}
inherits(WriteStream, net.Socket);
-
WriteStream.prototype.isTTY = true;
+WriteStream.prototype.getColorDepth = function(env = process.env) {
+ if (env.NODE_DISABLE_COLORS || env.TERM === 'dumb' && !env.COLORTERM) {
+ return COLORS_2;
+ }
+
+ if (process.platform === 'win32') {
+ // Windows 10 build 10586 is the first Windows release that supports 256
+ // colors. Windows 10 build 14931 is the first release that supports
+ // 16m/TrueColor.
+ if (+OSRelease[0] >= 10) {
+ const build = +OSRelease[2];
+ if (build >= 14931)
+ return COLORS_16m;
+ if (build >= 10586)
+ return COLORS_256;
+ }
+
+ return COLORS_16;
+ }
+
+ if (env.TMUX) {
+ return COLORS_256;
+ }
+
+ if (env.CI) {
+ if ('TRAVIS' in env || 'CIRCLECI' in env || 'APPVEYOR' in env ||
+ 'GITLAB_CI' in env || env.CI_NAME === 'codeship') {
+ return COLORS_256;
+ }
+ return COLORS_2;
+ }
+
+ if ('TEAMCITY_VERSION' in env) {
+ return /^(9\.(0*[1-9]\d*)\.|\d{2,}\.)/.test(env.TEAMCITY_VERSION) ?
+ COLORS_16 : COLORS_2;
+ }
+
+ switch (env.TERM_PROGRAM) {
+ case 'iTerm.app':
+ if (!env.TERM_PROGRAM_VERSION ||
+ /^[0-2]\./.test(env.TERM_PROGRAM_VERSION)) {
+ return COLORS_256;
+ }
+ return COLORS_16m;
+ case 'HyperTerm':
+ case 'Hyper':
+ case 'MacTerm':
+ return COLORS_16m;
+ case 'Apple_Terminal':
+ return COLORS_256;
+ }
+
+ if (env.TERM) {
+ if (/^xterm-256/.test(env.TERM))
+ return COLORS_256;
+ if (/^screen|^xterm|^vt100|color|ansi|cygwin|linux/i.test(env.TERM))
+ return COLORS_16;
+ }
+
+ if (env.COLORTERM)
+ return COLORS_16;
+
+ return COLORS_2;
+};
WriteStream.prototype._refreshSize = function() {
- var oldCols = this.columns;
- var oldRows = this.rows;
- var winSize = new Array(2);
- var err = this._handle.getWindowSize(winSize);
+ const oldCols = this.columns;
+ const oldRows = this.rows;
+ const winSize = new Array(2);
+ const err = this._handle.getWindowSize(winSize);
if (err) {
this.emit('error', errors.errnoException(err, 'getWindowSize'));
return;
}
- var newCols = winSize[0];
- var newRows = winSize[1];
+ const [newCols, newRows] = winSize;
if (oldCols !== newCols || oldRows !== newRows) {
this.columns = newCols;
this.rows = newRows;
@@ -109,8 +177,7 @@ WriteStream.prototype._refreshSize = function() {
}
};
-
-// backwards-compat
+// Backwards-compat
WriteStream.prototype.cursorTo = function(x, y) {
readline.cursorTo(this, x, y);
};
@@ -127,5 +194,4 @@ WriteStream.prototype.getWindowSize = function() {
return [this.columns, this.rows];
};
-
module.exports = { isatty, ReadStream, WriteStream };
diff --git a/lib/util.js b/lib/util.js
index cd6321cfe5270e..70fd1a05564389 100644
--- a/lib/util.js
+++ b/lib/util.js
@@ -68,7 +68,8 @@ const inspectDefaultOptions = Object.seal({
customInspect: true,
showProxy: false,
maxArrayLength: 100,
- breakLength: 60
+ breakLength: 60,
+ compact: true
});
const propertyIsEnumerable = Object.prototype.propertyIsEnumerable;
@@ -86,6 +87,10 @@ const strEscapeSequencesReplacer = /[\x00-\x1f\x27\x5c]/g;
const keyStrRegExp = /^[a-zA-Z_][a-zA-Z_0-9]*$/;
const numberRegExp = /^(0|[1-9][0-9]*)$/;
+const readableRegExps = {};
+
+const MIN_LINE_LENGTH = 16;
+
// Escaped special characters. Use empty strings to fill up unused entries.
const meta = [
'\\u0000', '\\u0001', '\\u0002', '\\u0003', '\\u0004',
@@ -257,14 +262,14 @@ function debuglog(set) {
}
/**
- * Echos the value of a value. Tries to print the value out
+ * Echos the value of any input. Tries to print the value out
* in the best way possible given the different types.
*
- * @param {Object} obj The object to print out.
+ * @param {any} value The value to print out.
* @param {Object} opts Optional options object that alters the output.
*/
-/* Legacy: obj, showHidden, depth, colors*/
-function inspect(obj, opts) {
+/* Legacy: value, showHidden, depth, colors*/
+function inspect(value, opts) {
// Default options
const ctx = {
seen: [],
@@ -276,7 +281,8 @@ function inspect(obj, opts) {
showProxy: inspectDefaultOptions.showProxy,
maxArrayLength: inspectDefaultOptions.maxArrayLength,
breakLength: inspectDefaultOptions.breakLength,
- indentationLvl: 0
+ indentationLvl: 0,
+ compact: inspectDefaultOptions.compact
};
// Legacy...
if (arguments.length > 2) {
@@ -298,7 +304,7 @@ function inspect(obj, opts) {
}
if (ctx.colors) ctx.stylize = stylizeWithColor;
if (ctx.maxArrayLength === null) ctx.maxArrayLength = Infinity;
- return formatValue(ctx, obj, ctx.depth);
+ return formatValue(ctx, value, ctx.depth);
}
inspect.custom = customInspectSymbol;
@@ -374,7 +380,7 @@ function ensureDebugIsInitialized() {
function formatValue(ctx, value, recurseTimes, ln) {
// Primitive types cannot have properties
if (typeof value !== 'object' && typeof value !== 'function') {
- return formatPrimitive(ctx.stylize, value);
+ return formatPrimitive(ctx.stylize, value, ctx);
}
if (value === null) {
return ctx.stylize('null', 'null');
@@ -481,10 +487,10 @@ function formatValue(ctx, value, recurseTimes, ln) {
} catch (e) { /* ignore */ }
if (typeof raw === 'string') {
- const formatted = formatPrimitive(stylizeNoColor, raw);
+ const formatted = formatPrimitive(stylizeNoColor, raw, ctx);
if (keyLength === raw.length)
return ctx.stylize(`[String: ${formatted}]`, 'string');
- base = ` [String: ${formatted}]`;
+ base = `[String: ${formatted}]`;
// For boxed Strings, we have to remove the 0-n indexed entries,
// since they just noisy up the output and are redundant
// Make boxed primitive Strings look like such
@@ -505,12 +511,12 @@ function formatValue(ctx, value, recurseTimes, ln) {
const name = `${constructor.name}${value.name ? `: ${value.name}` : ''}`;
if (keyLength === 0)
return ctx.stylize(`[${name}]`, 'special');
- base = ` [${name}]`;
+ base = `[${name}]`;
} else if (isRegExp(value)) {
// Make RegExps say that they are RegExps
if (keyLength === 0 || recurseTimes < 0)
return ctx.stylize(regExpToString.call(value), 'regexp');
- base = ` ${regExpToString.call(value)}`;
+ base = `${regExpToString.call(value)}`;
} else if (isDate(value)) {
if (keyLength === 0) {
if (Number.isNaN(value.getTime()))
@@ -518,12 +524,12 @@ function formatValue(ctx, value, recurseTimes, ln) {
return ctx.stylize(dateToISOString.call(value), 'date');
}
// Make dates with properties first say the date
- base = ` ${dateToISOString.call(value)}`;
+ base = `${dateToISOString.call(value)}`;
} else if (isError(value)) {
// Make error with message first say the error
if (keyLength === 0)
return formatError(value);
- base = ` ${formatError(value)}`;
+ base = `${formatError(value)}`;
} else if (isAnyArrayBuffer(value)) {
// Fast path for ArrayBuffer and SharedArrayBuffer.
// Can't do the same for DataView because it has a non-primitive
@@ -553,13 +559,13 @@ function formatValue(ctx, value, recurseTimes, ln) {
const formatted = formatPrimitive(stylizeNoColor, raw);
if (keyLength === 0)
return ctx.stylize(`[Number: ${formatted}]`, 'number');
- base = ` [Number: ${formatted}]`;
+ base = `[Number: ${formatted}]`;
} else if (typeof raw === 'boolean') {
// Make boxed primitive Booleans look like such
const formatted = formatPrimitive(stylizeNoColor, raw);
if (keyLength === 0)
return ctx.stylize(`[Boolean: ${formatted}]`, 'boolean');
- base = ` [Boolean: ${formatted}]`;
+ base = `[Boolean: ${formatted}]`;
} else if (typeof raw === 'symbol') {
const formatted = formatPrimitive(stylizeNoColor, raw);
return ctx.stylize(`[Symbol: ${formatted}]`, 'symbol');
@@ -603,9 +609,42 @@ function formatNumber(fn, value) {
return fn(`${value}`, 'number');
}
-function formatPrimitive(fn, value) {
- if (typeof value === 'string')
+function formatPrimitive(fn, value, ctx) {
+ if (typeof value === 'string') {
+ if (ctx.compact === false &&
+ value.length > MIN_LINE_LENGTH &&
+ ctx.indentationLvl + value.length > ctx.breakLength) {
+ // eslint-disable-next-line max-len
+ const minLineLength = Math.max(ctx.breakLength - ctx.indentationLvl, MIN_LINE_LENGTH);
+ // eslint-disable-next-line max-len
+ const averageLineLength = Math.ceil(value.length / Math.ceil(value.length / minLineLength));
+ const divisor = Math.max(averageLineLength, MIN_LINE_LENGTH);
+ var res = '';
+ if (readableRegExps[divisor] === undefined) {
+ // Build a new RegExp that naturally breaks text into multiple lines.
+ //
+ // Rules
+ // 1. Greedy match all text up the max line length that ends with a
+ // whitespace or the end of the string.
+ // 2. If none matches, non-greedy match any text up to a whitespace or
+ // the end of the string.
+ //
+ // eslint-disable-next-line max-len, no-unescaped-regexp-dot
+ readableRegExps[divisor] = new RegExp(`(.|\\n){1,${divisor}}(\\s|$)|(\\n|.)+?(\\s|$)`, 'gm');
+ }
+ const indent = ' '.repeat(ctx.indentationLvl);
+ const matches = value.match(readableRegExps[divisor]);
+ if (matches.length > 1) {
+ res += `${fn(strEscape(matches[0]), 'string')} +\n`;
+ for (var i = 1; i < matches.length - 1; i++) {
+ res += `${indent} ${fn(strEscape(matches[i]), 'string')} +\n`;
+ }
+ res += `${indent} ${fn(strEscape(matches[i]), 'string')}`;
+ return res;
+ }
+ }
return fn(strEscape(value), 'string');
+ }
if (typeof value === 'number')
return formatNumber(fn, value);
if (typeof value === 'boolean')
@@ -806,7 +845,7 @@ function formatProperty(ctx, value, recurseTimes, key, array) {
const desc = Object.getOwnPropertyDescriptor(value, key) ||
{ value: value[key], enumerable: true };
if (desc.value !== undefined) {
- const diff = array === 0 ? 3 : 2;
+ const diff = array !== 0 || ctx.compact === false ? 2 : 3;
ctx.indentationLvl += diff;
str = formatValue(ctx, desc.value, recurseTimes, array === 0);
ctx.indentationLvl -= diff;
@@ -839,9 +878,19 @@ function formatProperty(ctx, value, recurseTimes, key, array) {
function reduceToSingleString(ctx, output, base, braces, addLn) {
const breakLength = ctx.breakLength;
+ var i = 0;
+ if (ctx.compact === false) {
+ const indentation = ' '.repeat(ctx.indentationLvl);
+ var res = `${base ? `${base} ` : ''}${braces[0]}\n${indentation} `;
+ for (; i < output.length - 1; i++) {
+ res += `${output[i]},\n${indentation} `;
+ }
+ res += `${output[i]}\n${indentation}${braces[1]}`;
+ return res;
+ }
if (output.length * 2 <= breakLength) {
var length = 0;
- for (var i = 0; i < output.length && length <= breakLength; i++) {
+ for (; i < output.length && length <= breakLength; i++) {
if (ctx.colors) {
length += removeColors(output[i]).length + 1;
} else {
@@ -849,7 +898,8 @@ function reduceToSingleString(ctx, output, base, braces, addLn) {
}
}
if (length <= breakLength)
- return `${braces[0]}${base} ${join(output, ', ')} ${braces[1]}`;
+ return `${braces[0]}${base ? ` ${base}` : ''} ${join(output, ', ')} ` +
+ braces[1];
}
// If the opening "brace" is too large, like in the case of "Set {",
// we need to force the first item to be on the next line or the
@@ -857,7 +907,7 @@ function reduceToSingleString(ctx, output, base, braces, addLn) {
const indentation = ' '.repeat(ctx.indentationLvl);
const extraLn = addLn === true ? `\n${indentation}` : '';
const ln = base === '' && braces[0].length === 1 ?
- ' ' : `${base}\n${indentation} `;
+ ' ' : `${base ? ` ${base}` : base}\n${indentation} `;
const str = join(output, `,\n${indentation} `);
return `${extraLn}${braces[0]}${ln}${str} ${braces[1]}`;
}
diff --git a/test/message/assert_throws_stack.js b/test/message/assert_throws_stack.js
new file mode 100644
index 00000000000000..36bc5734cae37f
--- /dev/null
+++ b/test/message/assert_throws_stack.js
@@ -0,0 +1,6 @@
+'use strict';
+
+require('../common');
+const assert = require('assert').strict;
+
+assert.throws(() => { throw new Error('foo'); }, { bar: true });
diff --git a/test/message/assert_throws_stack.out b/test/message/assert_throws_stack.out
new file mode 100644
index 00000000000000..d34bdd24798190
--- /dev/null
+++ b/test/message/assert_throws_stack.out
@@ -0,0 +1,14 @@
+assert.js:*
+ throw new AssertionError(obj);
+ ^
+
+AssertionError [ERR_ASSERTION]: bar: expected true, not undefined
+ at Object. (*assert_throws_stack.js:*:*)
+ at *
+ at *
+ at *
+ at *
+ at *
+ at *
+ at *
+ at *
diff --git a/test/message/error_exit.out b/test/message/error_exit.out
index d6fbded760106b..8f03f08a7e0392 100644
--- a/test/message/error_exit.out
+++ b/test/message/error_exit.out
@@ -1,6 +1,6 @@
Exiting with code=1
assert.js:*
- throw new errors.AssertionError({
+ throw new AssertionError(obj);
^
AssertionError [ERR_ASSERTION]: 1 === 2
diff --git a/test/parallel/test-assert.js b/test/parallel/test-assert.js
index 971c99be918d6a..96d5ff20a5ce26 100644
--- a/test/parallel/test-assert.js
+++ b/test/parallel/test-assert.js
@@ -25,6 +25,7 @@
const common = require('../common');
const assert = require('assert');
+const { inspect } = require('util');
const a = assert;
function makeBlock(f) {
@@ -39,16 +40,6 @@ assert.ok(a.AssertionError.prototype instanceof Error,
assert.throws(makeBlock(a, false), a.AssertionError, 'ok(false)');
-// Using a object as second arg results in a failure
-assert.throws(
- () => { assert.throws(() => { throw new Error(); }, { foo: 'bar' }); },
- common.expectsError({
- type: TypeError,
- message: 'expected.test is not a function'
- })
-);
-
-
assert.doesNotThrow(makeBlock(a, true), a.AssertionError, 'ok(true)');
assert.doesNotThrow(makeBlock(a, 'test', 'ok(\'test\')'));
@@ -476,33 +467,21 @@ common.expectsError(
}
);
-{
- let threw = false;
- try {
- assert.doesNotThrow(makeBlock(thrower, Error), 'user message');
- } catch (e) {
- threw = true;
- common.expectsError({
- code: 'ERR_ASSERTION',
- message: /Got unwanted exception: user message\n\[object Object\]/
- })(e);
+common.expectsError(
+ () => assert.doesNotThrow(makeBlock(thrower, Error), 'user message'),
+ {
+ code: 'ERR_ASSERTION',
+ message: /Got unwanted exception: user message\n\[object Object\]/
}
- assert.ok(threw);
-}
+);
-{
- let threw = false;
- try {
- assert.doesNotThrow(makeBlock(thrower, Error));
- } catch (e) {
- threw = true;
- common.expectsError({
- code: 'ERR_ASSERTION',
- message: /Got unwanted exception\.\n\[object Object\]/
- })(e);
+common.expectsError(
+ () => assert.doesNotThrow(makeBlock(thrower, Error)),
+ {
+ code: 'ERR_ASSERTION',
+ message: /Got unwanted exception\.\n\[object Object\]/
}
- assert.ok(threw);
-}
+);
// make sure that validating using constructor really works
{
@@ -634,6 +613,7 @@ testAssertionMessage({ a: NaN, b: Infinity, c: -Infinity },
} catch (e) {
threw = true;
assert.strictEqual(e.message, 'Missing expected exception.');
+ assert.ok(!e.stack.includes('throws'), e.stack);
}
assert.ok(threw);
}
@@ -678,6 +658,7 @@ try {
threw = true;
assert.ok(e.message.includes(rangeError.message));
assert.ok(e instanceof assert.AssertionError);
+ assert.ok(!e.stack.includes('doesNotThrow'), e.stack);
}
assert.ok(threw);
}
@@ -689,21 +670,15 @@ try {
}
const testBlockTypeError = (method, block) => {
- let threw = true;
-
- try {
- method(block);
- threw = false;
- } catch (e) {
- common.expectsError({
+ common.expectsError(
+ () => method(block),
+ {
code: 'ERR_INVALID_ARG_TYPE',
type: TypeError,
message: 'The "block" argument must be of type Function. Received ' +
- `type ${typeName(block)}`
- })(e);
- }
-
- assert.ok(threw);
+ `type ${typeName(block)}`
+ }
+ );
};
testBlockTypeError(assert.throws, 'string');
@@ -736,7 +711,8 @@ assert.throws(() => {
assert.strictEqual('A'.repeat(1000), '');
}, common.expectsError({
code: 'ERR_ASSERTION',
- message: new RegExp(`^'${'A'.repeat(127)} === ''$`) }));
+ message: /^'A{124}\.\.\. === ''$/
+}));
{
// bad args to AssertionError constructor should throw TypeError
@@ -761,3 +737,294 @@ common.expectsError(
message: /^'Error: foo' === 'Error: foobar'$/
}
);
+
+// Test strict assert
+{
+ const a = require('assert');
+ const assert = require('assert').strict;
+ /* eslint-disable no-restricted-properties */
+ assert.throws(() => assert.equal(1, true), assert.AssertionError);
+ assert.notEqual(0, false);
+ assert.throws(() => assert.deepEqual(1, true), assert.AssertionError);
+ assert.notDeepEqual(0, false);
+ assert.equal(assert.strict, assert.strict.strict);
+ assert.equal(assert.equal, assert.strictEqual);
+ assert.equal(assert.deepEqual, assert.deepStrictEqual);
+ assert.equal(assert.notEqual, assert.notStrictEqual);
+ assert.equal(assert.notDeepEqual, assert.notDeepStrictEqual);
+ assert.equal(Object.keys(assert).length, Object.keys(a).length);
+ assert(7);
+ common.expectsError(
+ () => assert(),
+ {
+ code: 'ERR_ASSERTION',
+ type: assert.AssertionError,
+ message: 'undefined == true'
+ }
+ );
+
+ // Test error diffs
+ const colors = process.stdout.isTTY && process.stdout.getColorDepth() > 1;
+ const start = 'Input A expected to deepStrictEqual input B:';
+ const actExp = colors ?
+ '\u001b[32m+ expected\u001b[39m \u001b[31m- actual\u001b[39m' :
+ '+ expected - actual';
+ const plus = colors ? '\u001b[32m+\u001b[39m' : '+';
+ const minus = colors ? '\u001b[31m-\u001b[39m' : '-';
+ let message = [
+ start,
+ `${actExp} ... Lines skipped`,
+ '',
+ ' [',
+ ' [',
+ '...',
+ ' 2,',
+ `${minus} 3`,
+ `${plus} '3'`,
+ ' ]',
+ '...',
+ ' 5',
+ ' ]'].join('\n');
+ assert.throws(
+ () => assert.deepEqual([[[1, 2, 3]], 4, 5], [[[1, 2, '3']], 4, 5]),
+ { message });
+
+ message = [
+ start,
+ `${actExp} ... Lines skipped`,
+ '',
+ ' [',
+ ' 1,',
+ '...',
+ ' 0,',
+ `${plus} 1,`,
+ ' 1,',
+ '...',
+ ' 1',
+ ' ]'
+ ].join('\n');
+ assert.throws(
+ () => assert.deepEqual(
+ [1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1],
+ [1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1]),
+ { message });
+
+ message = [
+ start,
+ `${actExp} ... Lines skipped`,
+ '',
+ ' [',
+ ' 1,',
+ '...',
+ ' 0,',
+ `${minus} 1,`,
+ ' 1,',
+ '...',
+ ' 1',
+ ' ]'
+ ].join('\n');
+ assert.throws(
+ () => assert.deepEqual(
+ [1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1],
+ [1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1]),
+ { message });
+
+ message = [
+ start,
+ actExp,
+ '',
+ ' [',
+ ' 1,',
+ `${minus} 2,`,
+ `${plus} 1,`,
+ ' 1,',
+ ' 1,',
+ ' 0,',
+ `${minus} 1,`,
+ ' 1',
+ ' ]'
+ ].join('\n');
+ assert.throws(
+ () => assert.deepEqual(
+ [1, 2, 1, 1, 0, 1, 1],
+ [1, 1, 1, 1, 0, 1]),
+ { message });
+
+ message = [
+ start,
+ actExp,
+ '',
+ `${minus} [`,
+ `${minus} 1,`,
+ `${minus} 2,`,
+ `${minus} 1`,
+ `${minus} ]`,
+ `${plus} undefined`,
+ ].join('\n');
+ assert.throws(
+ () => assert.deepEqual([1, 2, 1]),
+ { message });
+
+ message = [
+ start,
+ actExp,
+ '',
+ ' [',
+ `${minus} 1,`,
+ ' 2,',
+ ' 1',
+ ' ]'
+ ].join('\n');
+ assert.throws(
+ () => assert.deepEqual([1, 2, 1], [2, 1]),
+ { message });
+
+ message = `${start}\n` +
+ `${actExp} ... Lines skipped\n` +
+ '\n' +
+ ' [\n' +
+ `${minus} 1,\n`.repeat(10) +
+ '...\n' +
+ `${plus} 2,\n`.repeat(10) +
+ '...';
+ assert.throws(
+ () => assert.deepEqual(Array(12).fill(1), Array(12).fill(2)),
+ { message });
+
+ const obj1 = {};
+ const obj2 = { loop: 'forever' };
+ obj2[inspect.custom] = () => '{}';
+ // No infinite loop and no custom inspect.
+ assert.throws(() => assert.deepEqual(obj1, obj2), {
+ message: `${start}\n` +
+ `${actExp}\n` +
+ '\n' +
+ `${minus} {}\n` +
+ `${plus} {\n` +
+ `${plus} loop: 'forever',\n` +
+ `${plus} [Symbol(util.inspect.custom)]: [Function]\n` +
+ `${plus} }`
+ });
+
+ // notDeepEqual tests
+ message = 'Identical input passed to notDeepStrictEqual:\n[\n 1\n]';
+ assert.throws(
+ () => assert.notDeepEqual([1], [1]),
+ { message });
+
+ message = 'Identical input passed to notDeepStrictEqual:' +
+ `\n[${'\n 1,'.repeat(18)}\n...`;
+ const data = Array(21).fill(1);
+ assert.throws(
+ () => assert.notDeepEqual(data, data),
+ { message });
+ /* eslint-enable no-restricted-properties */
+}
+
+common.expectsError(
+ () => assert.ok(null),
+ {
+ code: 'ERR_ASSERTION',
+ type: assert.AssertionError,
+ message: 'null == true'
+ }
+);
+
+common.expectsError(
+ // eslint-disable-next-line no-restricted-syntax
+ () => assert.throws(() => {}, 'Error message', 'message'),
+ {
+ code: 'ERR_INVALID_ARG_TYPE',
+ type: TypeError,
+ message: 'The "error" argument must be one of type Function or RegExp. ' +
+ 'Received type string'
+ }
+);
+
+{
+ const errFn = () => {
+ const err = new TypeError('Wrong value');
+ err.code = 404;
+ throw err;
+ };
+ const errObj = {
+ name: 'TypeError',
+ message: 'Wrong value'
+ };
+ assert.throws(errFn, errObj);
+
+ errObj.code = 404;
+ assert.throws(errFn, errObj);
+
+ errObj.code = '404';
+ common.expectsError(
+ // eslint-disable-next-line no-restricted-syntax
+ () => assert.throws(errFn, errObj),
+ {
+ code: 'ERR_ASSERTION',
+ type: assert.AssertionError,
+ message: 'code: expected \'404\', not 404'
+ }
+ );
+
+ errObj.code = 404;
+ errObj.foo = 'bar';
+ common.expectsError(
+ // eslint-disable-next-line no-restricted-syntax
+ () => assert.throws(errFn, errObj),
+ {
+ code: 'ERR_ASSERTION',
+ type: assert.AssertionError,
+ message: 'foo: expected \'bar\', not undefined'
+ }
+ );
+
+ common.expectsError(
+ () => assert.throws(() => { throw new Error(); }, { foo: 'bar' }, 'foobar'),
+ {
+ type: assert.AssertionError,
+ code: 'ERR_ASSERTION',
+ message: 'foobar'
+ }
+ );
+
+ common.expectsError(
+ () => assert.doesNotThrow(() => { throw new Error(); }, { foo: 'bar' }),
+ {
+ type: TypeError,
+ code: 'ERR_INVALID_ARG_TYPE',
+ message: 'The "expected" argument must be one of type Function or ' +
+ 'RegExp. Received type object'
+ }
+ );
+
+ assert.throws(() => { throw new Error('e'); }, new Error('e'));
+ common.expectsError(
+ () => assert.throws(() => { throw new TypeError('e'); }, new Error('e')),
+ {
+ type: assert.AssertionError,
+ code: 'ERR_ASSERTION',
+ message: "name: expected 'Error', not 'TypeError'"
+ }
+ );
+ common.expectsError(
+ () => assert.throws(() => { throw new Error('foo'); }, new Error('')),
+ {
+ type: assert.AssertionError,
+ code: 'ERR_ASSERTION',
+ message: "message: expected '', not 'foo'"
+ }
+ );
+
+ // eslint-disable-next-line no-throw-literal
+ assert.throws(() => { throw undefined; }, /undefined/);
+ common.expectsError(
+ // eslint-disable-next-line no-throw-literal
+ () => assert.doesNotThrow(() => { throw undefined; }),
+ {
+ type: assert.AssertionError,
+ code: 'ERR_ASSERTION',
+ message: 'Got unwanted exception.\nundefined'
+ }
+ );
+}
diff --git a/test/parallel/test-tty-get-color-depth.js b/test/parallel/test-tty-get-color-depth.js
new file mode 100644
index 00000000000000..a5a998b6a3ab78
--- /dev/null
+++ b/test/parallel/test-tty-get-color-depth.js
@@ -0,0 +1,52 @@
+'use strict';
+
+const common = require('../common');
+const assert = require('assert').strict;
+/* eslint-disable no-restricted-properties */
+const { openSync } = require('fs');
+const tty = require('tty');
+
+const { WriteStream } = require('tty');
+
+// Do our best to grab a tty fd.
+function getTTYfd() {
+ const ttyFd = [0, 1, 2, 4, 5].find(tty.isatty);
+ if (ttyFd === undefined) {
+ try {
+ return openSync('/dev/tty');
+ } catch (e) {
+ // There aren't any tty fd's available to use.
+ return -1;
+ }
+ }
+ return ttyFd;
+}
+
+const fd = getTTYfd();
+
+// Give up if we did not find a tty
+if (fd === -1)
+ common.skip();
+
+const writeStream = new WriteStream(fd);
+
+let depth = writeStream.getColorDepth();
+
+assert.equal(typeof depth, 'number');
+assert(depth >= 1 && depth <= 24);
+
+// If the terminal does not support colors, skip the rest
+if (depth === 1)
+ common.skip();
+
+assert.notEqual(writeStream.getColorDepth({ TERM: 'dumb' }), depth);
+
+// Deactivate colors
+const tmp = process.env.NODE_DISABLE_COLORS;
+process.env.NODE_DISABLE_COLORS = 1;
+
+depth = writeStream.getColorDepth();
+
+assert.equal(depth, 1);
+
+process.env.NODE_DISABLE_COLORS = tmp;
diff --git a/test/parallel/test-util-inspect.js b/test/parallel/test-util-inspect.js
index a99583d454b3f4..83f6b469d68055 100644
--- a/test/parallel/test-util-inspect.js
+++ b/test/parallel/test-util-inspect.js
@@ -1160,3 +1160,146 @@ if (typeof Symbol !== 'undefined') {
assert.doesNotThrow(() => util.inspect(process));
/* eslint-enable accessor-pairs */
+
+// Setting custom inspect property to a non-function should do nothing.
+{
+ const obj = { inspect: 'fhqwhgads' };
+ assert.strictEqual(util.inspect(obj), "{ inspect: 'fhqwhgads' }");
+}
+
+{
+ 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 { '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\\nsit amet,\\tconsectetur \' +',
+ ' \'adipiscing elit, sed do eiusmod tempor \' +',
+ ' \'incididunt ut labore et dolore magna \' +',
+ ' \'aliqua.\',',
+ ' \'test\',',
+ ' \'foo\'',
+ ' ]',
+ ' ],',
+ ' 4',
+ ' ],',
+ ' b: Map {',
+ ' \'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\\nsit \' +',
+ ' \'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);
+
+ out = util.inspect(
+ '12 45 78 01 34 67 90 23 56 89 1234567890123 0',
+ { compact: false, breakLength: 3 });
+ expect = [
+ '\'12 45 78 01 34 \' +',
+ ' \'67 90 23 56 89 \' +',
+ ' \'1234567890123 0\''
+ ].join('\n');
+ assert.strictEqual(out, expect);
+
+ out = util.inspect(
+ '12 45 78 01 34 67 90 23 56 89 12345678901234567 0',
+ { compact: false, breakLength: 3 });
+ expect = [
+ '\'12 45 78 01 34 \' +',
+ ' \'67 90 23 56 89 \' +',
+ ' \'12345678901234567 \' +',
+ ' \'0\''
+ ].join('\n');
+ assert.strictEqual(out, expect);
+
+ o.a = () => {};
+ o.b = new Number(3);
+ out = util.inspect(o, { compact: false, breakLength: 3 });
+ expect = [
+ '{',
+ ' a: [Function],',
+ ' b: [Number: 3]',
+ '}'
+ ].join('\n');
+ assert.strictEqual(out, expect);
+
+ out = util.inspect(o, { compact: false, breakLength: 3, showHidden: true });
+ expect = [
+ '{',
+ ' a: [Function] {',
+ ' [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 \' +\n \'67 90 23\'\n}';
+ assert.strictEqual(out, expect);
+}