Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

errors: improve invalid arg type #13834

Merged
merged 2 commits into from
Jul 3, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 35 additions & 9 deletions doc/api/assert.md
Original file line number Diff line number Diff line change
Expand Up @@ -257,37 +257,62 @@ property set equal to the value of the `message` parameter. If the `message`
parameter is undefined, a default error message is assigned.

## assert.fail([message])
## assert.fail(actual, expected, message, operator)
## assert.fail(actual, expected[, message[, operator[, stackStartFunction]]])
<!-- YAML
added: v0.1.21
-->
* `actual` {any}
* `expected` {any}
* `message` {any} (default: 'Failed')
* `operator` {string} (default: '!=')
* `stackStartFunction` {function} (default: `assert.fail`)

Throws an `AssertionError`. If `message` is falsy, the error message is set as
the values of `actual` and `expected` separated by the provided `operator`.
Otherwise, the error message is the value of `message`.
If no arguments are provided at all, a default message will be used instead.
If just the two `actual` and `expected` arguments are provided, `operator` will
default to `'!='`. If `message` is provided only it will be used as the error
message, the other arguments will be stored as properties on the thrown object.
If `stackStartFunction` is provided, all stack frames above that function will
be removed from stacktrace (see [`Error.captureStackTrace`]). If no arguments
are given, the default message `Failed` will be used.

```js
const assert = require('assert');

assert.fail(1, 2, undefined, '>');
// AssertionError: 1 > 2
// AssertionError [ERR_ASSERTION]: 1 > 2

assert.fail(1, 2, 'fail');
// AssertionError [ERR_ASSERTION]: fail

assert.fail(1, 2, 'whoops', '>');
// AssertionError: whoops
// AssertionError [ERR_ASSERTION]: whoops
```

*Note*: Is the last two cases `actual`, `expected`, and `operator` have no
influence on the error message.

```js
assert.fail();
// AssertionError [ERR_ASSERTION]: Failed

assert.fail('boom');
// AssertionError: boom
// AssertionError [ERR_ASSERTION]: boom

assert.fail('a', 'b');
// AssertionError: 'a' != 'b'
// AssertionError [ERR_ASSERTION]: 'a' != 'b'
```

assert.fail();
// AssertionError: Failed
Example use of `stackStartFunction` for truncating the exception's stacktrace:
```js
function suppressFrame() {
assert.fail('a', 'b', undefined, '!==', suppressFrame);
}
suppressFrame();
// AssertionError [ERR_ASSERTION]: 'a' !== 'b'
// at repl:1:1
// at ContextifyScript.Script.runInThisContext (vm.js:44:33)
// ...
```

## assert.ifError(value)
Expand Down Expand Up @@ -594,6 +619,7 @@ For more information, see
[MDN's guide on equality comparisons and sameness][mdn-equality-guide].

[`Error`]: errors.html#errors_class_error
[`Error.captureStackTrace`]: errors.html#errors_error_capturestacktrace_targetobject_constructoropt
[`Map`]: https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Map
[`Object.is()`]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/is
[`RegExp`]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions
Expand Down
118 changes: 50 additions & 68 deletions lib/assert.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,11 @@

'use strict';

// UTILITY
const compare = process.binding('buffer').compare;
const { compare } = process.binding('buffer');
const util = require('util');
const { isSet, isMap } = process.binding('util');
const objectToString = require('internal/util').objectToString;
const Buffer = require('buffer').Buffer;
const { objectToString } = require('internal/util');
const { Buffer } = require('buffer');

var errors;
function lazyErrors() {
Expand All @@ -47,10 +46,21 @@ const assert = module.exports = ok;

// 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
// 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) {
const errors = lazyErrors();
throw new errors.AssertionError({
message,
actual,
expected,
operator,
stackStartFunction
});
}

function fail(actual, expected, message, operator, stackStartFunction) {
if (arguments.length === 0) {
message = 'Failed';
Expand All @@ -59,19 +69,11 @@ function fail(actual, expected, message, operator, stackStartFunction) {
message = actual;
actual = undefined;
}
if (arguments.length === 2)
if (arguments.length === 2) {
operator = '!=';
const errors = lazyErrors();
throw new errors.AssertionError({
message: message,
actual: actual,
expected: expected,
operator: operator,
stackStartFunction: stackStartFunction
});
}
innerFail(actual, expected, message, operator, stackStartFunction || fail);
}

// EXTENSION! allows for well behaved errors defined elsewhere.
assert.fail = fail;

// The AssertionError is defined in internal/error.
Expand All @@ -82,50 +84,39 @@ assert.AssertionError = lazyErrors().AssertionError;


// Pure assertion tests whether a value is truthy, as determined
// by !!guard.
// assert.ok(guard, message_opt);
// This statement is equivalent to assert.equal(true, !!guard,
// message_opt);. To test strictly for the value true, use
// assert.strictEqual(true, guard, message_opt);.

// by !!value.
function ok(value, message) {
if (!value) fail(value, true, message, '==', assert.ok);
if (!value) innerFail(value, true, message, '==', ok);
}
assert.ok = ok;

// The equality assertion tests shallow, coercive equality with
// ==.
// assert.equal(actual, expected, message_opt);
// The equality assertion tests shallow, coercive equality with ==.
/* eslint-disable no-restricted-properties */
assert.equal = function equal(actual, expected, message) {
// eslint-disable-next-line eqeqeq
if (actual != expected) fail(actual, expected, message, '==', assert.equal);
if (actual != expected) innerFail(actual, expected, message, '==', equal);
};

// The non-equality assertion tests for whether two objects are not
// equal with !=.
// assert.notEqual(actual, expected, message_opt);

assert.notEqual = function notEqual(actual, expected, message) {
// eslint-disable-next-line eqeqeq
if (actual == expected) {
fail(actual, expected, message, '!=', assert.notEqual);
innerFail(actual, expected, message, '!=', notEqual);
}
};

// The equivalence assertion tests a deep equality relation.
// assert.deepEqual(actual, expected, message_opt);

assert.deepEqual = function deepEqual(actual, expected, message) {
if (!_deepEqual(actual, expected, false)) {
fail(actual, expected, message, 'deepEqual', assert.deepEqual);
if (!innerDeepEqual(actual, expected, false)) {
innerFail(actual, expected, message, 'deepEqual', deepEqual);
}
};
/* eslint-enable */

assert.deepStrictEqual = function deepStrictEqual(actual, expected, message) {
if (!_deepEqual(actual, expected, true)) {
fail(actual, expected, message, 'deepStrictEqual', assert.deepStrictEqual);
if (!innerDeepEqual(actual, expected, true)) {
innerFail(actual, expected, message, 'deepStrictEqual', deepStrictEqual);
}
};

Expand Down Expand Up @@ -154,7 +145,7 @@ function isArguments(tag) {
return tag === '[object Arguments]';
}

function _deepEqual(actual, expected, strict, memos) {
function innerDeepEqual(actual, expected, strict, memos) {
// All identical values are equivalent, as determined by ===.
if (actual === expected) {
return true;
Expand Down Expand Up @@ -307,7 +298,7 @@ function setHasSimilarElement(set, val1, usedEntries, strict, memo) {
if (usedEntries && usedEntries.has(val2))
continue;

if (_deepEqual(val1, val2, strict, memo)) {
if (innerDeepEqual(val1, val2, strict, memo)) {
if (usedEntries)
usedEntries.add(val2);
return true;
Expand Down Expand Up @@ -364,7 +355,7 @@ function mapHasSimilarEntry(map, key1, item1, usedEntries, strict, memo) {
// This check is not strictly necessary. The loop performs this check, but
// doing it here improves performance of the common case when reference-equal
// keys exist (which includes all primitive-valued keys).
if (map.has(key1) && _deepEqual(item1, map.get(key1), strict, memo)) {
if (map.has(key1) && innerDeepEqual(item1, map.get(key1), strict, memo)) {
if (usedEntries)
usedEntries.add(key1);
return true;
Expand All @@ -381,8 +372,8 @@ function mapHasSimilarEntry(map, key1, item1, usedEntries, strict, memo) {
if (usedEntries && usedEntries.has(key2))
continue;

if (_deepEqual(key1, key2, strict, memo) &&
_deepEqual(item1, item2, strict, memo)) {
if (innerDeepEqual(key1, key2, strict, memo) &&
innerDeepEqual(item1, item2, strict, memo)) {
if (usedEntries)
usedEntries.add(key2);
return true;
Expand Down Expand Up @@ -459,44 +450,39 @@ function objEquiv(a, b, strict, actualVisitedObjects) {
// Possibly expensive deep test:
for (i = aKeys.length - 1; i >= 0; i--) {
key = aKeys[i];
if (!_deepEqual(a[key], b[key], strict, actualVisitedObjects))
if (!innerDeepEqual(a[key], b[key], strict, actualVisitedObjects))
return false;
}
return true;
}

// The non-equivalence assertion tests for any deep inequality.
// assert.notDeepEqual(actual, expected, message_opt);

assert.notDeepEqual = function notDeepEqual(actual, expected, message) {
if (_deepEqual(actual, expected, false)) {
fail(actual, expected, message, 'notDeepEqual', assert.notDeepEqual);
if (innerDeepEqual(actual, expected, false)) {
innerFail(actual, expected, message, 'notDeepEqual', notDeepEqual);
}
};

assert.notDeepStrictEqual = notDeepStrictEqual;
function notDeepStrictEqual(actual, expected, message) {
if (_deepEqual(actual, expected, true)) {
fail(actual, expected, message, 'notDeepStrictEqual', notDeepStrictEqual);
if (innerDeepEqual(actual, expected, true)) {
innerFail(actual, expected, message, 'notDeepStrictEqual',
notDeepStrictEqual);
}
}

// The strict equality assertion tests strict equality, as determined by ===.
// assert.strictEqual(actual, expected, message_opt);

assert.strictEqual = function strictEqual(actual, expected, message) {
if (actual !== expected) {
fail(actual, expected, message, '===', assert.strictEqual);
innerFail(actual, expected, message, '===', strictEqual);
}
};

// The strict non-equality assertion tests for strict inequality, as
// determined by !==.
// assert.notStrictEqual(actual, expected, message_opt);

assert.notStrictEqual = function notStrictEqual(actual, expected, message) {
if (actual === expected) {
fail(actual, expected, message, '!==', assert.notStrictEqual);
innerFail(actual, expected, message, '!==', notStrictEqual);
}
};

Expand Down Expand Up @@ -525,7 +511,7 @@ function expectedException(actual, expected) {
return expected.call({}, actual) === true;
}

function _tryBlock(block) {
function tryBlock(block) {
var error;
try {
block();
Expand All @@ -535,7 +521,7 @@ function _tryBlock(block) {
return error;
}

function _throws(shouldThrow, block, expected, message) {
function innerThrows(shouldThrow, block, expected, message) {
var actual;

if (typeof block !== 'function') {
Expand All @@ -549,13 +535,13 @@ function _throws(shouldThrow, block, expected, message) {
expected = null;
}

actual = _tryBlock(block);
actual = tryBlock(block);

message = (expected && expected.name ? ' (' + expected.name + ')' : '') +
(message ? ': ' + message : '.');

if (shouldThrow && !actual) {
fail(actual, expected, 'Missing expected exception' + message);
innerFail(actual, expected, 'Missing expected exception' + message, fail);
}

const userProvidedMessage = typeof message === 'string';
Expand All @@ -566,7 +552,7 @@ function _throws(shouldThrow, block, expected, message) {
userProvidedMessage &&
expectedException(actual, expected)) ||
isUnexpectedException) {
fail(actual, expected, 'Got unwanted exception' + message);
innerFail(actual, expected, 'Got unwanted exception' + message, fail);
}

if ((shouldThrow && actual && expected &&
Expand All @@ -576,16 +562,12 @@ function _throws(shouldThrow, block, expected, message) {
}

// Expected to throw an error.
// assert.throws(block, Error_opt, message_opt);

assert.throws = function throws(block, /*optional*/error, /*optional*/message) {
_throws(true, block, error, message);
assert.throws = function throws(block, error, message) {
innerThrows(true, block, error, message);
};

// EXTENSION! This is annoying to write outside this module.
assert.doesNotThrow = doesNotThrow;
function doesNotThrow(block, /*optional*/error, /*optional*/message) {
_throws(false, block, error, message);
}
assert.doesNotThrow = function doesNotThrow(block, error, message) {
innerThrows(false, block, error, message);
};

assert.ifError = function ifError(err) { if (err) throw err; };
Loading