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

[v6.x backport] assert: refactor the code #15516

Closed
wants to merge 2 commits into from
Closed
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
21 changes: 20 additions & 1 deletion doc/api/assert.md
Original file line number Diff line number Diff line change
Expand Up @@ -192,29 +192,47 @@ If the values are not equal, an `AssertionError` is thrown with a `message`
property set equal to the value of the `message` parameter. If the `message`
parameter is undefined, a default error message is assigned.

## assert.fail(actual, expected, message, operator)
## assert.fail(actual, expected[, message[, operator[, stackStartFunction]]])
<!-- YAML
added: v0.1.21
-->
* `actual` {any}
* `expected` {any}
* `message` {any}
* `operator` {string}
* `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 `stackStartFunction` is provided, all stack frames above that function will
be removed from stacktrace (see [`Error.captureStackTrace`]).

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

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

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

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

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

## assert.ifError(value)
<!-- YAML
added: v0.1.97
Expand Down Expand Up @@ -492,5 +510,6 @@ assert.throws(myFunction, /missing foo/, 'did not throw with expected message');
[`assert.ok()`]: #assert_assert_ok_value_message
[`assert.throws()`]: #assert_assert_throws_block_error_message
[`Error`]: errors.html#errors_class_error
[`Error.captureStackTrace`]: errors.html#errors_error_capturestacktrace_targetobject_constructoropt
[`RegExp`]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions
[`TypeError`]: errors.html#errors_class_typeerror
84 changes: 30 additions & 54 deletions lib/assert.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,7 @@

'use strict';

// UTILITY
const compare = process.binding('buffer').compare;
const { compare } = process.binding('buffer');
const util = require('util');
const Buffer = require('buffer').Buffer;
const pToString = (obj) => Object.prototype.toString.call(obj);
Expand Down Expand Up @@ -49,7 +48,7 @@ assert.AssertionError = function AssertionError(options) {
this.message = getMessage(this);
this.generatedMessage = true;
}
var stackStartFunction = options.stackStartFunction || fail;
const stackStartFunction = options.stackStartFunction || fail;
Error.captureStackTrace(this, stackStartFunction);
};

Expand All @@ -73,7 +72,7 @@ function getMessage(self) {

// 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.

Expand All @@ -86,57 +85,44 @@ function fail(actual, expected, message, operator, stackStartFunction) {
stackStartFunction: stackStartFunction
});
}

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

// 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) fail(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) {
if (actual != expected) fail(actual, expected, message, '==', assert.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) {
if (actual == expected) {
fail(actual, expected, message, '!=', assert.notEqual);
fail(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)) {
fail(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)) {
fail(actual, expected, message, 'deepStrictEqual', deepStrictEqual);
}
};

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 @@ -247,45 +233,40 @@ function objEquiv(a, b, strict, actualVisitedObjects) {
// Possibly expensive deep test:
for (i = ka.length - 1; i >= 0; i--) {
key = ka[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)) {
fail(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)) {
fail(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);
fail(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);
fail(actual, expected, message, '!==', notStrictEqual);
}
};

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

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

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

function innerThrows(shouldThrow, block, expected, message) {
if (typeof block !== 'function') {
throw new TypeError('"block" argument must be a function');
}
Expand All @@ -336,13 +315,13 @@ function _throws(shouldThrow, block, expected, message) {
expected = null;
}

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

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

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

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

if ((shouldThrow && actual && expected &&
Expand All @@ -363,15 +342,12 @@ function _throws(shouldThrow, block, expected, message) {
}

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

assert.throws = function(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 = function(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(err) { if (err) throw err; };
assert.ifError = function ifError(err) { if (err) throw err; };
10 changes: 10 additions & 0 deletions test/parallel/test-assert-fail.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
'use strict';

require('../common');
const assert = require('assert');

// The stackFrameFunction should exclude the foo frame
assert.throws(
function foo() { assert.fail('first', 'second', 'message', '!==', foo); },
(err) => !/foo/m.test(err.stack)
);