You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

575 lines
19 KiB

// Originally from narwhal.js (http://narwhaljs.org)
// Copyright (c) 2009 Thomas Robinson <280north.com>
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the 'Software'), to
// deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
// sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
14 years ago
// THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
// ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
'use strict';
// UTILITY
const compare = process.binding('buffer').compare;
const util = require('util');
assert: add support for Map and Set in deepEqual assert.deepEqual and assert.deepStrictEqual currently return true for any pair of Maps and Sets regardless of content. This patch adds support in deepEqual and deepStrictEqual to verify the contents of Maps and Sets. Deeo equivalence checking is currently an O(n^2) operation, and worse, it gets slower exponentially if maps and sets were nested. Note that this change breaks compatibility with previous versions of deepEqual and deepStrictEqual if consumers were depending on all maps and sets to be seen as equivalent. The old behaviour was never documented, but nevertheless there are certainly some tests out there which depend on it. Support has stalled because the assert API was frozen, but was recently unfrozen in CTC#63. --- Later squashed in: This change updates the checks for deep equality checking on Map and Set to check all set values / all map keys to see if any of them match the expected result. This change is much slower, but based on the conversation in the pull request its probably the right approach. Fixes: https://github.com/nodejs/node/issues/2309 Refs: https://github.com/substack/tape/issues/342 Refs: https://github.com/nodejs/node/pull/2315 Refs: https://github.com/nodejs/CTC/issues/63 PR-URL: https://github.com/nodejs/node/pull/12142 Reviewed-By: Anna Henningsen &lt;anna@addaleax.net&gt; Reviewed-By: Rich Trott &lt;rtrott@gmail.com&gt; Reviewed-By: Matteo Collina &lt;matteo.collina@gmail.com&gt; Reviewed-By: Joyee Cheung &lt;joyeec9h3@gmail.com&gt;
8 years ago
const { isSet, isMap } = process.binding('util');
const objectToString = require('internal/util').objectToString;
const Buffer = require('buffer').Buffer;
var errors;
function lazyErrors() {
if (!errors)
errors = require('internal/errors');
return errors;
}
// The assert module provides functions that throw
// AssertionError's when particular conditions are not met. The
// assert module must conform to the following interface.
const assert = module.exports = ok;
// The AssertionError is defined in assert.
14 years ago
// new assert.AssertionError({ message: message,
// actual: actual,
// expected: expected });
// TODO(jasnell): Consider moving AssertionError into internal/errors.js
class AssertionError extends Error {
constructor(options) {
if (typeof options !== 'object' || options === null) {
// Lazy because the errors module itself uses assertions, leading to
// a circular dependency. This can be eliminated by moving this class
// into internal/errors.js
const errors = lazyErrors();
throw new errors.TypeError('ERR_INVALID_ARG_TYPE', 'options', 'object');
}
const message = options.message ||
`${util.inspect(options.actual).slice(0, 128)} ` +
`${options.operator} ` +
util.inspect(options.expected).slice(0, 128);
super(message);
this.generatedMessage = !options.message;
this.name = 'AssertionError [ERR_ASSERTION]';
this.code = 'ERR_ASSERTION';
this.actual = options.actual;
this.expected = options.expected;
this.operator = options.operator;
var stackStartFunction = options.stackStartFunction || fail;
Error.captureStackTrace(this, stackStartFunction);
}
}
assert.AssertionError = AssertionError;
// At present only the three keys mentioned above are used and
// understood by the spec. Implementations or sub modules can pass
// other keys to the AssertionError's constructor - they will be
// ignored.
// 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 fail(actual, expected, message, operator, stackStartFunction) {
if (arguments.length === 1)
message = actual;
if (arguments.length === 2)
operator = '!=';
throw new AssertionError({
message: message,
actual: actual,
expected: expected,
operator: operator,
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);.
function ok(value, message) {
if (!value) fail(value, true, message, '==', assert.ok);
}
assert.ok = ok;
// The equality assertion tests shallow, coercive equality with
// ==.
// assert.equal(actual, expected, message_opt);
/* eslint-disable no-restricted-properties */
assert.equal = function equal(actual, expected, message) {
// eslint-disable-next-line eqeqeq
14 years ago
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) {
// eslint-disable-next-line eqeqeq
if (actual == expected) {
14 years ago
fail(actual, expected, message, '!=', assert.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)) {
14 years ago
fail(actual, expected, message, 'deepEqual', assert.deepEqual);
}
};
/* eslint-enable */
assert.deepStrictEqual = function deepStrictEqual(actual, expected, message) {
if (!_deepEqual(actual, expected, true)) {
fail(actual, expected, message, 'deepStrictEqual', assert.deepStrictEqual);
}
};
function areSimilarRegExps(a, b) {
return a.source === b.source && a.flags === b.flags;
}
function areSimilarTypedArrays(a, b) {
return compare(Buffer.from(a.buffer,
a.byteOffset,
a.byteLength),
Buffer.from(b.buffer,
b.byteOffset,
b.byteLength)) === 0;
}
function isNullOrNonObj(object) {
return object === null || typeof object !== 'object';
}
function isFloatTypedArrayTag(tag) {
return tag === '[object Float32Array]' || tag === '[object Float64Array]';
}
function isArguments(tag) {
return tag === '[object Arguments]';
}
function _deepEqual(actual, expected, strict, memos) {
// All identical values are equivalent, as determined by ===.
if (actual === expected) {
return true;
}
// For primitives / functions
// (determined by typeof value !== 'object'),
// or null, equivalence is determined by === or ==.
if (isNullOrNonObj(actual) && isNullOrNonObj(expected)) {
// eslint-disable-next-line eqeqeq
return strict ? actual === expected : actual == expected;
}
// If they bypass the previous check, then at least
// one of them must be an non-null object.
// If the other one is null or undefined, they must not be equal.
if (actual === null || actual === undefined ||
expected === null || expected === undefined)
return false;
// Notes: Type tags are historical [[Class]] properties that can be set by
// FunctionTemplate::SetClassName() in C++ or Symbol.toStringTag in JS
// and retrieved using Object.prototype.toString.call(obj) in JS
// See https://tc39.github.io/ecma262/#sec-object.prototype.tostring
// for a list of tags pre-defined in the spec.
// There are some unspecified tags in the wild too (e.g. typed array tags).
// Since tags can be altered, they only serve fast failures
const actualTag = objectToString(actual);
const expectedTag = objectToString(expected);
// Passing null or undefined to Object.getPrototypeOf() will throw
// so this must done after previous checks.
// For strict comparison, objects should have
// a) The same prototypes.
// b) The same built-in type tags
if (strict) {
if (Object.getPrototypeOf(actual) !== Object.getPrototypeOf(expected)) {
return false;
}
if (actualTag !== expectedTag) {
return false;
}
}
// Do fast checks for builtin types.
// If they don't match, they must not be equal.
// If they match, return true for non-strict comparison.
// For strict comparison we need to exam further.
// If both values are Date objects,
// check if the time underneath are equal first.
if (util.isDate(actual) && util.isDate(expected)) {
if (actual.getTime() !== expected.getTime()) {
return false;
} else if (!strict) {
return true; // Skip further checks for non-strict comparison.
}
}
// If both values are RegExp, check if they have
// the same source and flags first
if (util.isRegExp(actual) && util.isRegExp(expected)) {
if (!areSimilarRegExps(actual, expected)) {
return false;
} else if (!strict) {
return true; // Skip further checks for non-strict comparison.
}
}
// Ensure reflexivity of deepEqual with `arguments` objects.
// See https://github.com/nodejs/node-v0.x-archive/pull/7178
if (isArguments(actualTag) !== isArguments(expectedTag)) {
return false;
}
// Check typed arrays and buffers by comparing the content in their
// underlying ArrayBuffer. This optimization requires that it's
// reasonable to interpret their underlying memory in the same way,
// which is checked by comparing their type tags.
// (e.g. a Uint8Array and a Uint16Array with the same memory content
// could still be different because they will be interpreted differently)
// Never perform binary comparisons for Float*Arrays, though,
// since e.g. +0 === -0 is true despite the two values' bit patterns
// not being identical.
if (ArrayBuffer.isView(actual) && ArrayBuffer.isView(expected) &&
actualTag === expectedTag && !isFloatTypedArrayTag(actualTag)) {
if (!areSimilarTypedArrays(actual, expected)) {
return false;
} else if (!strict) {
return true; // Skip further checks for non-strict comparison.
}
// Buffer.compare returns true, so actual.length === expected.length
// if they both only contain numeric keys, we don't need to exam further
if (Object.keys(actual).length === actual.length &&
Object.keys(expected).length === expected.length) {
return true;
}
}
assert: add support for Map and Set in deepEqual assert.deepEqual and assert.deepStrictEqual currently return true for any pair of Maps and Sets regardless of content. This patch adds support in deepEqual and deepStrictEqual to verify the contents of Maps and Sets. Deeo equivalence checking is currently an O(n^2) operation, and worse, it gets slower exponentially if maps and sets were nested. Note that this change breaks compatibility with previous versions of deepEqual and deepStrictEqual if consumers were depending on all maps and sets to be seen as equivalent. The old behaviour was never documented, but nevertheless there are certainly some tests out there which depend on it. Support has stalled because the assert API was frozen, but was recently unfrozen in CTC#63. --- Later squashed in: This change updates the checks for deep equality checking on Map and Set to check all set values / all map keys to see if any of them match the expected result. This change is much slower, but based on the conversation in the pull request its probably the right approach. Fixes: https://github.com/nodejs/node/issues/2309 Refs: https://github.com/substack/tape/issues/342 Refs: https://github.com/nodejs/node/pull/2315 Refs: https://github.com/nodejs/CTC/issues/63 PR-URL: https://github.com/nodejs/node/pull/12142 Reviewed-By: Anna Henningsen &lt;anna@addaleax.net&gt; Reviewed-By: Rich Trott &lt;rtrott@gmail.com&gt; Reviewed-By: Matteo Collina &lt;matteo.collina@gmail.com&gt; Reviewed-By: Joyee Cheung &lt;joyeec9h3@gmail.com&gt;
8 years ago
// For all other Object pairs, including Array objects and Maps,
// equivalence is determined by having:
// a) The same number of owned enumerable properties
// b) The same set of keys/indexes (although not necessarily the same order)
// c) Equivalent values for every corresponding key/index
assert: add support for Map and Set in deepEqual assert.deepEqual and assert.deepStrictEqual currently return true for any pair of Maps and Sets regardless of content. This patch adds support in deepEqual and deepStrictEqual to verify the contents of Maps and Sets. Deeo equivalence checking is currently an O(n^2) operation, and worse, it gets slower exponentially if maps and sets were nested. Note that this change breaks compatibility with previous versions of deepEqual and deepStrictEqual if consumers were depending on all maps and sets to be seen as equivalent. The old behaviour was never documented, but nevertheless there are certainly some tests out there which depend on it. Support has stalled because the assert API was frozen, but was recently unfrozen in CTC#63. --- Later squashed in: This change updates the checks for deep equality checking on Map and Set to check all set values / all map keys to see if any of them match the expected result. This change is much slower, but based on the conversation in the pull request its probably the right approach. Fixes: https://github.com/nodejs/node/issues/2309 Refs: https://github.com/substack/tape/issues/342 Refs: https://github.com/nodejs/node/pull/2315 Refs: https://github.com/nodejs/CTC/issues/63 PR-URL: https://github.com/nodejs/node/pull/12142 Reviewed-By: Anna Henningsen &lt;anna@addaleax.net&gt; Reviewed-By: Rich Trott &lt;rtrott@gmail.com&gt; Reviewed-By: Matteo Collina &lt;matteo.collina@gmail.com&gt; Reviewed-By: Joyee Cheung &lt;joyeec9h3@gmail.com&gt;
8 years ago
// d) For Sets and Maps, equal contents
// Note: this accounts for both named and indexed properties on Arrays.
// Use memos to handle cycles.
if (!memos) {
memos = {
actual: { map: new Map(), position: 0 },
expected: { map: new Map(), position: 0 }
};
}
const actualPosition = memos.actual.map.get(actual);
if (actualPosition !== undefined) {
if (actualPosition === memos.expected.map.get(expected)) {
return true;
}
} else {
memos.actual.map.set(actual, memos.actual.position++);
}
if (!memos.expected.map.has(expected)) {
memos.expected.map.set(expected, memos.expected.position++);
}
return objEquiv(actual, expected, strict, memos);
}
assert: add support for Map and Set in deepEqual assert.deepEqual and assert.deepStrictEqual currently return true for any pair of Maps and Sets regardless of content. This patch adds support in deepEqual and deepStrictEqual to verify the contents of Maps and Sets. Deeo equivalence checking is currently an O(n^2) operation, and worse, it gets slower exponentially if maps and sets were nested. Note that this change breaks compatibility with previous versions of deepEqual and deepStrictEqual if consumers were depending on all maps and sets to be seen as equivalent. The old behaviour was never documented, but nevertheless there are certainly some tests out there which depend on it. Support has stalled because the assert API was frozen, but was recently unfrozen in CTC#63. --- Later squashed in: This change updates the checks for deep equality checking on Map and Set to check all set values / all map keys to see if any of them match the expected result. This change is much slower, but based on the conversation in the pull request its probably the right approach. Fixes: https://github.com/nodejs/node/issues/2309 Refs: https://github.com/substack/tape/issues/342 Refs: https://github.com/nodejs/node/pull/2315 Refs: https://github.com/nodejs/CTC/issues/63 PR-URL: https://github.com/nodejs/node/pull/12142 Reviewed-By: Anna Henningsen &lt;anna@addaleax.net&gt; Reviewed-By: Rich Trott &lt;rtrott@gmail.com&gt; Reviewed-By: Matteo Collina &lt;matteo.collina@gmail.com&gt; Reviewed-By: Joyee Cheung &lt;joyeec9h3@gmail.com&gt;
8 years ago
function setHasSimilarElement(set, val1, strict, memo) {
if (set.has(val1))
return true;
// In strict mode the only things which can match a primitive or a function
// will already be detected by set.has(val1).
if (strict && (util.isPrimitive(val1) || util.isFunction(val1)))
return false;
// Otherwise go looking.
for (const val2 of set) {
if (_deepEqual(val1, val2, strict, memo))
return true;
}
return false;
}
function setEquiv(a, b, strict, memo) {
// This code currently returns false for this pair of sets:
// assert.deepEqual(new Set(['1', 1]), new Set([1]))
//
// In theory, all the items in the first set have a corresponding == value in
// the second set, but the sets have different sizes. Its a silly case,
// and more evidence that deepStrictEqual should always be preferred over
// deepEqual.
if (a.size !== b.size)
return false;
for (const val1 of a) {
// If the value doesn't exist in the second set by reference, and its an
// object or an array we'll need to go hunting for something thats
// deep-equal to it. Note that this is O(n^2) complexity, and will get
// slower if large, very similar sets / maps are nested inside.
// Unfortunately there's no real way around this.
if (!setHasSimilarElement(b, val1, strict, memo)) {
return false;
}
}
return true;
}
function mapHasSimilarEntry(map, key1, item1, strict, memo) {
// To be able to handle cases like:
// Map([[1, 'a'], ['1', 'b']]) vs Map([['1', 'a'], [1, 'b']])
// or:
// Map([[{}, 'a'], [{}, 'b']]) vs Map([[{}, 'b'], [{}, 'a']])
// ... we need to consider *all* matching keys, not just the first we find.
// 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))
return true;
if (strict && (util.isPrimitive(key1) || util.isFunction(key1)))
return false;
for (const [key2, item2] of map) {
// This case is checked above.
if (key2 === key1)
continue;
if (_deepEqual(key1, key2, strict, memo) &&
_deepEqual(item1, item2, strict, memo)) {
return true;
}
}
return false;
}
function mapEquiv(a, b, strict, memo) {
// Caveat: In non-strict mode, this implementation does not handle cases
// where maps contain two equivalent-but-not-reference-equal keys.
//
// For example, maps like this are currently considered not equivalent:
if (a.size !== b.size)
return false;
for (const [key1, item1] of a) {
// Just like setEquiv above, this hunt makes this function O(n^2) when
// using objects and lists as keys
if (!mapHasSimilarEntry(b, key1, item1, strict, memo))
return false;
}
return true;
}
function objEquiv(a, b, strict, actualVisitedObjects) {
// If one of them is a primitive, the other must be the same.
if (util.isPrimitive(a) || util.isPrimitive(b))
return a === b;
const aKeys = Object.keys(a);
const bKeys = Object.keys(b);
var key, i;
// The pair must have the same number of owned properties
// (keys incorporates hasOwnProperty).
if (aKeys.length !== bKeys.length)
return false;
// The pair must have the same set of keys (although not
// necessarily in the same order).
aKeys.sort();
bKeys.sort();
// Cheap key test:
for (i = aKeys.length - 1; i >= 0; i--) {
if (aKeys[i] !== bKeys[i])
return false;
}
assert: add support for Map and Set in deepEqual assert.deepEqual and assert.deepStrictEqual currently return true for any pair of Maps and Sets regardless of content. This patch adds support in deepEqual and deepStrictEqual to verify the contents of Maps and Sets. Deeo equivalence checking is currently an O(n^2) operation, and worse, it gets slower exponentially if maps and sets were nested. Note that this change breaks compatibility with previous versions of deepEqual and deepStrictEqual if consumers were depending on all maps and sets to be seen as equivalent. The old behaviour was never documented, but nevertheless there are certainly some tests out there which depend on it. Support has stalled because the assert API was frozen, but was recently unfrozen in CTC#63. --- Later squashed in: This change updates the checks for deep equality checking on Map and Set to check all set values / all map keys to see if any of them match the expected result. This change is much slower, but based on the conversation in the pull request its probably the right approach. Fixes: https://github.com/nodejs/node/issues/2309 Refs: https://github.com/substack/tape/issues/342 Refs: https://github.com/nodejs/node/pull/2315 Refs: https://github.com/nodejs/CTC/issues/63 PR-URL: https://github.com/nodejs/node/pull/12142 Reviewed-By: Anna Henningsen &lt;anna@addaleax.net&gt; Reviewed-By: Rich Trott &lt;rtrott@gmail.com&gt; Reviewed-By: Matteo Collina &lt;matteo.collina@gmail.com&gt; Reviewed-By: Joyee Cheung &lt;joyeec9h3@gmail.com&gt;
8 years ago
// Sets and maps don't have their entries accessible via normal object
// properties.
if (isSet(a)) {
if (!isSet(b) || !setEquiv(a, b, strict, actualVisitedObjects))
return false;
} else if (isSet(b)) {
return false;
}
if (isMap(a)) {
if (!isMap(b) || !mapEquiv(a, b, strict, actualVisitedObjects))
return false;
} else if (isMap(b)) {
return false;
}
// The pair must have equivalent values for every corresponding key.
// Possibly expensive deep test:
for (i = aKeys.length - 1; i >= 0; i--) {
key = aKeys[i];
if (!_deepEqual(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)) {
14 years ago
fail(actual, expected, message, 'notDeepEqual', assert.notDeepEqual);
}
};
assert.notDeepStrictEqual = notDeepStrictEqual;
function notDeepStrictEqual(actual, expected, message) {
if (_deepEqual(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) {
14 years ago
fail(actual, expected, message, '===', assert.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) {
14 years ago
fail(actual, expected, message, '!==', assert.notStrictEqual);
}
};
function expectedException(actual, expected) {
// actual is guaranteed to be an Error object, but we need to check expected.
if (!expected) {
return false;
}
if (objectToString(expected) === '[object RegExp]') {
14 years ago
return expected.test(actual);
}
14 years ago
try {
if (actual instanceof expected) {
return true;
}
} catch (e) {
// Ignore. The instanceof check doesn't work for arrow functions.
}
if (Error.isPrototypeOf(expected)) {
return false;
}
return expected.call({}, actual) === true;
}
function _tryBlock(block) {
var error;
try {
block();
} catch (e) {
error = e;
}
return error;
}
14 years ago
function _throws(shouldThrow, block, expected, message) {
var actual;
if (typeof block !== 'function') {
const errors = lazyErrors();
throw new errors.TypeError('ERR_INVALID_ARG_TYPE', 'block', 'function',
typeof block);
}
if (typeof expected === 'string') {
message = expected;
expected = null;
}
actual = _tryBlock(block);
message = (expected && expected.name ? ' (' + expected.name + ')' : '') +
(message ? ': ' + message : '.');
if (shouldThrow && !actual) {
fail(actual, expected, 'Missing expected exception' + message);
}
const userProvidedMessage = typeof message === 'string';
const isUnwantedException = !shouldThrow && util.isError(actual);
const isUnexpectedException = !shouldThrow && actual && !expected;
if ((isUnwantedException &&
userProvidedMessage &&
expectedException(actual, expected)) ||
isUnexpectedException) {
fail(actual, expected, 'Got unwanted exception' + message);
}
14 years ago
if ((shouldThrow && actual && expected &&
!expectedException(actual, expected)) || (!shouldThrow && actual)) {
throw actual;
}
}
// 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);
};
// 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.ifError = function ifError(err) { if (err) throw err; };