nodejs/lib/assert.js

1071 lines
31 KiB
JavaScript
Raw Normal View History

2022-08-16 11:12:47 +08:00
// 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.
//
// 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';
const {
2024-06-03 09:55:04 +08:00
ArrayPrototypeIndexOf,
ArrayPrototypeJoin,
ArrayPrototypePush,
ArrayPrototypeShift,
ArrayPrototypeSlice,
2022-08-16 11:12:47 +08:00
Error,
2024-06-03 09:55:04 +08:00
ErrorCaptureStackTrace,
FunctionPrototypeBind,
NumberIsNaN,
2022-08-16 11:12:47 +08:00
ObjectAssign,
ObjectIs,
ObjectKeys,
ObjectPrototypeIsPrototypeOf,
2024-06-03 09:55:04 +08:00
ReflectApply,
RegExpPrototypeExec,
RegExpPrototypeSymbolReplace,
SafeMap,
String,
StringPrototypeCharCodeAt,
StringPrototypeIncludes,
StringPrototypeIndexOf,
StringPrototypeReplace,
StringPrototypeSlice,
StringPrototypeSplit,
StringPrototypeStartsWith,
2022-08-16 11:12:47 +08:00
} = primordials;
const { Buffer } = require('buffer');
const {
codes: {
ERR_AMBIGUOUS_ARGUMENT,
ERR_INVALID_ARG_TYPE,
ERR_INVALID_ARG_VALUE,
ERR_INVALID_RETURN_VALUE,
ERR_MISSING_ARGS,
},
2024-06-03 09:55:04 +08:00
isErrorStackTraceLimitWritable,
2022-08-16 11:12:47 +08:00
overrideStackTrace,
} = require('internal/errors');
const AssertionError = require('internal/assert/assertion_error');
const { openSync, closeSync, readSync } = require('fs');
const { inspect } = require('internal/util/inspect');
const { isPromise, isRegExp } = require('internal/util/types');
const { EOL } = require('internal/constants');
2024-06-03 09:55:04 +08:00
const { BuiltinModule } = require('internal/bootstrap/realm');
const { isError } = require('internal/util');
2022-08-16 11:12:47 +08:00
2024-06-03 09:55:04 +08:00
const errorCache = new SafeMap();
2022-08-16 11:12:47 +08:00
const CallTracker = require('internal/assert/calltracker');
2024-06-03 09:55:04 +08:00
const {
validateFunction,
} = require('internal/validators');
2022-08-16 11:12:47 +08:00
let isDeepEqual;
let isDeepStrictEqual;
let parseExpressionAt;
let findNodeAround;
2024-06-03 09:55:04 +08:00
let tokenizer;
2022-08-16 11:12:47 +08:00
let decoder;
function lazyLoadComparison() {
const comparison = require('internal/util/comparisons');
isDeepEqual = comparison.isDeepEqual;
isDeepStrictEqual = comparison.isDeepStrictEqual;
}
// Escape control characters but not \n and \t to keep the line breaks and
// indentation intact.
// eslint-disable-next-line no-control-regex
const escapeSequencesRegExp = /[\x00-\x08\x0b\x0c\x0e-\x1f]/g;
const meta = [
'\\u0000', '\\u0001', '\\u0002', '\\u0003', '\\u0004',
'\\u0005', '\\u0006', '\\u0007', '\\b', '',
'', '\\u000b', '\\f', '', '\\u000e',
'\\u000f', '\\u0010', '\\u0011', '\\u0012', '\\u0013',
'\\u0014', '\\u0015', '\\u0016', '\\u0017', '\\u0018',
'\\u0019', '\\u001a', '\\u001b', '\\u001c', '\\u001d',
2024-06-03 09:55:04 +08:00
'\\u001e', '\\u001f',
2022-08-16 11:12:47 +08:00
];
2024-06-03 09:55:04 +08:00
const escapeFn = (str) => meta[StringPrototypeCharCodeAt(str, 0)];
2022-08-16 11:12:47 +08:00
let warned = false;
// 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;
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(obj) {
if (obj.message instanceof Error) throw obj.message;
throw new AssertionError(obj);
}
2024-06-03 09:55:04 +08:00
/**
* @param {any} actual
* @param {any} expected
* @param {string | Error} [message]
* @param {string} [operator]
* @param {Function} [stackStartFn]
*/
2022-08-16 11:12:47 +08:00
function fail(actual, expected, message, operator, stackStartFn) {
const argsLen = arguments.length;
let internalMessage = false;
if (actual == null && argsLen <= 1) {
internalMessage = true;
message = 'Failed';
} else if (argsLen === 1) {
message = actual;
actual = undefined;
} else {
if (warned === false) {
warned = true;
process.emitWarning(
'assert.fail() with more than one argument is deprecated. ' +
'Please use assert.strictEqual() instead or only pass a message.',
'DeprecationWarning',
2024-06-03 09:55:04 +08:00
'DEP0094',
2022-08-16 11:12:47 +08:00
);
}
if (argsLen === 2)
operator = '!=';
}
if (message instanceof Error) throw message;
const errArgs = {
actual,
expected,
operator: operator === undefined ? 'fail' : operator,
stackStartFn: stackStartFn || fail,
2024-06-03 09:55:04 +08:00
message,
2022-08-16 11:12:47 +08:00
};
const err = new AssertionError(errArgs);
if (internalMessage) {
err.generatedMessage = true;
}
throw err;
}
assert.fail = fail;
// The AssertionError is defined in internal/error.
assert.AssertionError = AssertionError;
function findColumn(fd, column, code) {
if (code.length > column + 100) {
try {
return parseCode(code, column);
} catch {
// End recursion in case no code could be parsed. The expression should
// have been found after 2500 characters, so stop trying.
if (code.length - column > 2500) {
// eslint-disable-next-line no-throw-literal
throw null;
}
}
}
// Read up to 2500 bytes more than necessary in columns. That way we address
// multi byte characters and read enough data to parse the code.
const bytesToRead = column - code.length + 2500;
const buffer = Buffer.allocUnsafe(bytesToRead);
const bytesRead = readSync(fd, buffer, 0, bytesToRead);
code += decoder.write(buffer.slice(0, bytesRead));
// EOF: fast path.
if (bytesRead < bytesToRead) {
return parseCode(code, column);
}
// Read potentially missing code.
return findColumn(fd, column, code);
}
function getCode(fd, line, column) {
let bytesRead = 0;
if (line === 0) {
// Special handle line number one. This is more efficient and simplifies the
// rest of the algorithm. Read more than the regular column number in bytes
// to prevent multiple reads in case multi byte characters are used.
return findColumn(fd, column, '');
}
let lines = 0;
// Prevent blocking the event loop by limiting the maximum amount of
// data that may be read.
2024-06-03 09:55:04 +08:00
let maxReads = 32; // bytesPerRead * maxReads = 512 KiB
2022-08-16 11:12:47 +08:00
const bytesPerRead = 16384;
// Use a single buffer up front that is reused until the call site is found.
let buffer = Buffer.allocUnsafe(bytesPerRead);
while (maxReads-- !== 0) {
// Only allocate a new buffer in case the needed line is found. All data
// before that can be discarded.
buffer = lines < line ? buffer : Buffer.allocUnsafe(bytesPerRead);
bytesRead = readSync(fd, buffer, 0, bytesPerRead);
// Read the buffer until the required code line is found.
for (let i = 0; i < bytesRead; i++) {
if (buffer[i] === 10 && ++lines === line) {
// If the end of file is reached, directly parse the code and return.
if (bytesRead < bytesPerRead) {
return parseCode(buffer.toString('utf8', i + 1, bytesRead), column);
}
// Check if the read code is sufficient or read more until the whole
// expression is read. Make sure multi byte characters are preserved
// properly by using the decoder.
const code = decoder.write(buffer.slice(i + 1, bytesRead));
return findColumn(fd, column, code);
}
}
}
}
function parseCode(code, offset) {
// Lazy load acorn.
if (parseExpressionAt === undefined) {
2024-06-03 09:55:04 +08:00
const Parser = require('internal/deps/acorn/acorn/dist/acorn').Parser;
2022-08-16 11:12:47 +08:00
({ findNodeAround } = require('internal/deps/acorn/acorn-walk/dist/walk'));
2024-06-03 09:55:04 +08:00
parseExpressionAt = FunctionPrototypeBind(Parser.parseExpressionAt, Parser);
tokenizer = FunctionPrototypeBind(Parser.tokenizer, Parser);
2022-08-16 11:12:47 +08:00
}
let node;
2024-06-03 09:55:04 +08:00
let start;
2022-08-16 11:12:47 +08:00
// Parse the read code until the correct expression is found.
2024-06-03 09:55:04 +08:00
for (const token of tokenizer(code, { ecmaVersion: 'latest' })) {
start = token.start;
if (start > offset) {
// No matching expression found. This could happen if the assert
// expression is bigger than the provided buffer.
break;
}
2022-08-16 11:12:47 +08:00
try {
2024-06-03 09:55:04 +08:00
node = parseExpressionAt(code, start, { ecmaVersion: 'latest' });
2022-08-16 11:12:47 +08:00
// Find the CallExpression in the tree.
node = findNodeAround(node, offset, 'CallExpression');
2024-06-03 09:55:04 +08:00
if (node?.node.end >= offset) {
return [
node.node.start,
StringPrototypeReplace(StringPrototypeSlice(code,
node.node.start, node.node.end),
escapeSequencesRegExp, escapeFn),
];
2022-08-16 11:12:47 +08:00
}
2024-06-03 09:55:04 +08:00
// eslint-disable-next-line no-unused-vars
} catch (err) {
continue;
2022-08-16 11:12:47 +08:00
}
2024-06-03 09:55:04 +08:00
}
// eslint-disable-next-line no-throw-literal
throw null;
2022-08-16 11:12:47 +08:00
}
function getErrMessage(message, fn) {
const tmpLimit = Error.stackTraceLimit;
2024-06-03 09:55:04 +08:00
const errorStackTraceLimitIsWritable = isErrorStackTraceLimitWritable();
2022-08-16 11:12:47 +08:00
// Make sure the limit is set to 1. Otherwise it could fail (<= 0) or it
// does to much work.
2024-06-03 09:55:04 +08:00
if (errorStackTraceLimitIsWritable) Error.stackTraceLimit = 1;
2022-08-16 11:12:47 +08:00
// We only need the stack trace. To minimize the overhead use an object
// instead of an error.
const err = {};
2024-06-03 09:55:04 +08:00
ErrorCaptureStackTrace(err, fn);
if (errorStackTraceLimitIsWritable) Error.stackTraceLimit = tmpLimit;
2022-08-16 11:12:47 +08:00
overrideStackTrace.set(err, (_, stack) => stack);
const call = err.stack[0];
const filename = call.getFileName();
2024-06-03 09:55:04 +08:00
const line = call.getLineNumber() - 1;
2022-08-16 11:12:47 +08:00
let column = call.getColumnNumber() - 1;
let identifier;
let code;
if (filename) {
identifier = `${filename}${line}${column}`;
// Skip Node.js modules!
2024-06-03 09:55:04 +08:00
if (StringPrototypeStartsWith(filename, 'node:') &&
BuiltinModule.exists(StringPrototypeSlice(filename, 5))) {
2022-08-16 11:12:47 +08:00
errorCache.set(identifier, undefined);
return;
}
} else {
2024-06-03 09:55:04 +08:00
return message;
2022-08-16 11:12:47 +08:00
}
if (errorCache.has(identifier)) {
return errorCache.get(identifier);
}
let fd;
try {
// Set the stack trace limit to zero. This makes sure unexpected token
// errors are handled faster.
2024-06-03 09:55:04 +08:00
if (errorStackTraceLimitIsWritable) Error.stackTraceLimit = 0;
2022-08-16 11:12:47 +08:00
if (filename) {
if (decoder === undefined) {
const { StringDecoder } = require('string_decoder');
decoder = new StringDecoder('utf8');
}
fd = openSync(filename, 'r', 0o666);
// Reset column and message.
2024-06-03 09:55:04 +08:00
({ 0: column, 1: message } = getCode(fd, line, column));
2022-08-16 11:12:47 +08:00
// Flush unfinished multi byte characters.
decoder.end();
} else {
for (let i = 0; i < line; i++) {
2024-06-03 09:55:04 +08:00
code = StringPrototypeSlice(code,
StringPrototypeIndexOf(code, '\n') + 1);
2022-08-16 11:12:47 +08:00
}
2024-06-03 09:55:04 +08:00
({ 0: column, 1: message } = parseCode(code, column));
2022-08-16 11:12:47 +08:00
}
// Always normalize indentation, otherwise the message could look weird.
2024-06-03 09:55:04 +08:00
if (StringPrototypeIncludes(message, '\n')) {
2022-08-16 11:12:47 +08:00
if (EOL === '\r\n') {
2024-06-03 09:55:04 +08:00
message = RegExpPrototypeSymbolReplace(/\r\n/g, message, '\n');
2022-08-16 11:12:47 +08:00
}
2024-06-03 09:55:04 +08:00
const frames = StringPrototypeSplit(message, '\n');
message = ArrayPrototypeShift(frames);
2022-08-16 11:12:47 +08:00
for (const frame of frames) {
let pos = 0;
while (pos < column && (frame[pos] === ' ' || frame[pos] === '\t')) {
pos++;
}
2024-06-03 09:55:04 +08:00
message += `\n ${StringPrototypeSlice(frame, pos)}`;
2022-08-16 11:12:47 +08:00
}
}
message = `The expression evaluated to a falsy value:\n\n ${message}\n`;
// Make sure to always set the cache! No matter if the message is
// undefined or not
errorCache.set(identifier, message);
return message;
} catch {
// Invalidate cache to prevent trying to read this part again.
errorCache.set(identifier, undefined);
} finally {
// Reset limit.
2024-06-03 09:55:04 +08:00
if (errorStackTraceLimitIsWritable) Error.stackTraceLimit = tmpLimit;
2022-08-16 11:12:47 +08:00
if (fd !== undefined)
closeSync(fd);
}
}
function innerOk(fn, argLen, value, message) {
if (!value) {
let generatedMessage = false;
if (argLen === 0) {
generatedMessage = true;
message = 'No value argument passed to `assert.ok()`';
} else if (message == null) {
generatedMessage = true;
message = getErrMessage(message, fn);
} else if (message instanceof Error) {
throw message;
}
const err = new AssertionError({
actual: value,
expected: true,
message,
operator: '==',
2024-06-03 09:55:04 +08:00
stackStartFn: fn,
2022-08-16 11:12:47 +08:00
});
err.generatedMessage = generatedMessage;
throw err;
}
}
2024-06-03 09:55:04 +08:00
/**
* Pure assertion tests whether a value is truthy, as determined
* by !!value.
* @param {...any} args
* @returns {void}
*/
2022-08-16 11:12:47 +08:00
function ok(...args) {
innerOk(ok, args.length, ...args);
}
assert.ok = ok;
2024-06-03 09:55:04 +08:00
/**
* The equality assertion tests shallow, coercive equality with ==.
* @param {any} actual
* @param {any} expected
* @param {string | Error} [message]
* @returns {void}
*/
2022-08-16 11:12:47 +08:00
/* eslint-disable no-restricted-properties */
assert.equal = function equal(actual, expected, message) {
if (arguments.length < 2) {
throw new ERR_MISSING_ARGS('actual', 'expected');
}
// eslint-disable-next-line eqeqeq
2024-06-03 09:55:04 +08:00
if (actual != expected && (!NumberIsNaN(actual) || !NumberIsNaN(expected))) {
2022-08-16 11:12:47 +08:00
innerFail({
actual,
expected,
message,
operator: '==',
2024-06-03 09:55:04 +08:00
stackStartFn: equal,
2022-08-16 11:12:47 +08:00
});
}
};
2024-06-03 09:55:04 +08:00
/**
* The non-equality assertion tests for whether two objects are not
* equal with !=.
* @param {any} actual
* @param {any} expected
* @param {string | Error} [message]
* @returns {void}
*/
2022-08-16 11:12:47 +08:00
assert.notEqual = function notEqual(actual, expected, message) {
if (arguments.length < 2) {
throw new ERR_MISSING_ARGS('actual', 'expected');
}
// eslint-disable-next-line eqeqeq
2024-06-03 09:55:04 +08:00
if (actual == expected || (NumberIsNaN(actual) && NumberIsNaN(expected))) {
2022-08-16 11:12:47 +08:00
innerFail({
actual,
expected,
message,
operator: '!=',
2024-06-03 09:55:04 +08:00
stackStartFn: notEqual,
2022-08-16 11:12:47 +08:00
});
}
};
2024-06-03 09:55:04 +08:00
/**
* The deep equivalence assertion tests a deep equality relation.
* @param {any} actual
* @param {any} expected
* @param {string | Error} [message]
* @returns {void}
*/
2022-08-16 11:12:47 +08:00
assert.deepEqual = function deepEqual(actual, expected, message) {
if (arguments.length < 2) {
throw new ERR_MISSING_ARGS('actual', 'expected');
}
if (isDeepEqual === undefined) lazyLoadComparison();
if (!isDeepEqual(actual, expected)) {
innerFail({
actual,
expected,
message,
operator: 'deepEqual',
2024-06-03 09:55:04 +08:00
stackStartFn: deepEqual,
2022-08-16 11:12:47 +08:00
});
}
};
2024-06-03 09:55:04 +08:00
/**
* The deep non-equivalence assertion tests for any deep inequality.
* @param {any} actual
* @param {any} expected
* @param {string | Error} [message]
* @returns {void}
*/
2022-08-16 11:12:47 +08:00
assert.notDeepEqual = function notDeepEqual(actual, expected, message) {
if (arguments.length < 2) {
throw new ERR_MISSING_ARGS('actual', 'expected');
}
if (isDeepEqual === undefined) lazyLoadComparison();
if (isDeepEqual(actual, expected)) {
innerFail({
actual,
expected,
message,
operator: 'notDeepEqual',
2024-06-03 09:55:04 +08:00
stackStartFn: notDeepEqual,
2022-08-16 11:12:47 +08:00
});
}
};
/* eslint-enable */
2024-06-03 09:55:04 +08:00
/**
* The deep strict equivalence assertion tests a deep strict equality
* relation.
* @param {any} actual
* @param {any} expected
* @param {string | Error} [message]
* @returns {void}
*/
2022-08-16 11:12:47 +08:00
assert.deepStrictEqual = function deepStrictEqual(actual, expected, message) {
if (arguments.length < 2) {
throw new ERR_MISSING_ARGS('actual', 'expected');
}
if (isDeepEqual === undefined) lazyLoadComparison();
if (!isDeepStrictEqual(actual, expected)) {
innerFail({
actual,
expected,
message,
operator: 'deepStrictEqual',
2024-06-03 09:55:04 +08:00
stackStartFn: deepStrictEqual,
2022-08-16 11:12:47 +08:00
});
}
};
2024-06-03 09:55:04 +08:00
/**
* The deep strict non-equivalence assertion tests for any deep strict
* inequality.
* @param {any} actual
* @param {any} expected
* @param {string | Error} [message]
* @returns {void}
*/
2022-08-16 11:12:47 +08:00
assert.notDeepStrictEqual = notDeepStrictEqual;
function notDeepStrictEqual(actual, expected, message) {
if (arguments.length < 2) {
throw new ERR_MISSING_ARGS('actual', 'expected');
}
if (isDeepEqual === undefined) lazyLoadComparison();
if (isDeepStrictEqual(actual, expected)) {
innerFail({
actual,
expected,
message,
operator: 'notDeepStrictEqual',
2024-06-03 09:55:04 +08:00
stackStartFn: notDeepStrictEqual,
2022-08-16 11:12:47 +08:00
});
}
}
2024-06-03 09:55:04 +08:00
/**
* The strict equivalence assertion tests a strict equality relation.
* @param {any} actual
* @param {any} expected
* @param {string | Error} [message]
* @returns {void}
*/
2022-08-16 11:12:47 +08:00
assert.strictEqual = function strictEqual(actual, expected, message) {
if (arguments.length < 2) {
throw new ERR_MISSING_ARGS('actual', 'expected');
}
if (!ObjectIs(actual, expected)) {
innerFail({
actual,
expected,
message,
operator: 'strictEqual',
2024-06-03 09:55:04 +08:00
stackStartFn: strictEqual,
2022-08-16 11:12:47 +08:00
});
}
};
2024-06-03 09:55:04 +08:00
/**
* The strict non-equivalence assertion tests for any strict inequality.
* @param {any} actual
* @param {any} expected
* @param {string | Error} [message]
* @returns {void}
*/
2022-08-16 11:12:47 +08:00
assert.notStrictEqual = function notStrictEqual(actual, expected, message) {
if (arguments.length < 2) {
throw new ERR_MISSING_ARGS('actual', 'expected');
}
if (ObjectIs(actual, expected)) {
innerFail({
actual,
expected,
message,
operator: 'notStrictEqual',
2024-06-03 09:55:04 +08:00
stackStartFn: notStrictEqual,
2022-08-16 11:12:47 +08:00
});
}
};
class Comparison {
constructor(obj, keys, actual) {
for (const key of keys) {
if (key in obj) {
if (actual !== undefined &&
typeof actual[key] === 'string' &&
isRegExp(obj[key]) &&
2024-06-03 09:55:04 +08:00
RegExpPrototypeExec(obj[key], actual[key]) !== null) {
2022-08-16 11:12:47 +08:00
this[key] = actual[key];
} else {
this[key] = obj[key];
}
}
}
}
}
function compareExceptionKey(actual, expected, key, message, keys, fn) {
if (!(key in actual) || !isDeepStrictEqual(actual[key], expected[key])) {
if (!message) {
// Create placeholder objects to create a nice output.
const a = new Comparison(actual, keys);
const b = new Comparison(expected, keys, actual);
const err = new AssertionError({
actual: a,
expected: b,
operator: 'deepStrictEqual',
2024-06-03 09:55:04 +08:00
stackStartFn: fn,
2022-08-16 11:12:47 +08:00
});
err.actual = actual;
err.expected = expected;
err.operator = fn.name;
throw err;
}
innerFail({
actual,
expected,
message,
operator: fn.name,
2024-06-03 09:55:04 +08:00
stackStartFn: fn,
2022-08-16 11:12:47 +08:00
});
}
}
function expectedException(actual, expected, message, fn) {
let generatedMessage = false;
let throwError = false;
if (typeof expected !== 'function') {
// Handle regular expressions.
if (isRegExp(expected)) {
const str = String(actual);
2024-06-03 09:55:04 +08:00
if (RegExpPrototypeExec(expected, str) !== null)
2022-08-16 11:12:47 +08:00
return;
if (!message) {
generatedMessage = true;
message = 'The input did not match the regular expression ' +
`${inspect(expected)}. Input:\n\n${inspect(str)}\n`;
}
throwError = true;
// Handle primitives properly.
} else if (typeof actual !== 'object' || actual === null) {
const err = new AssertionError({
actual,
expected,
message,
operator: 'deepStrictEqual',
2024-06-03 09:55:04 +08:00
stackStartFn: fn,
2022-08-16 11:12:47 +08:00
});
err.operator = fn.name;
throw err;
} else {
// Handle validation objects.
const keys = ObjectKeys(expected);
// Special handle errors to make sure the name and the message are
// compared as well.
if (expected instanceof Error) {
2024-06-03 09:55:04 +08:00
ArrayPrototypePush(keys, 'name', 'message');
2022-08-16 11:12:47 +08:00
} else if (keys.length === 0) {
throw new ERR_INVALID_ARG_VALUE('error',
expected, 'may not be an empty object');
}
if (isDeepEqual === undefined) lazyLoadComparison();
for (const key of keys) {
if (typeof actual[key] === 'string' &&
isRegExp(expected[key]) &&
2024-06-03 09:55:04 +08:00
RegExpPrototypeExec(expected[key], actual[key]) !== null) {
2022-08-16 11:12:47 +08:00
continue;
}
compareExceptionKey(actual, expected, key, message, keys, fn);
}
return;
}
// Guard instanceof against arrow functions as they don't have a prototype.
// Check for matching Error classes.
} else if (expected.prototype !== undefined && actual instanceof expected) {
return;
} else if (ObjectPrototypeIsPrototypeOf(Error, expected)) {
2024-06-03 09:55:04 +08:00
if (!message) {
generatedMessage = true;
message = 'The error is expected to be an instance of ' +
`"${expected.name}". Received `;
if (isError(actual)) {
const name = (actual.constructor && actual.constructor.name) ||
actual.name;
if (expected.name === name) {
message += 'an error with identical name but a different prototype.';
} else {
message += `"${name}"`;
}
if (actual.message) {
message += `\n\nError message:\n\n${actual.message}`;
}
} else {
message += `"${inspect(actual, { depth: -1 })}"`;
}
}
throwError = true;
2022-08-16 11:12:47 +08:00
} else {
// Check validation functions return value.
2024-06-03 09:55:04 +08:00
const res = ReflectApply(expected, {}, [actual]);
2022-08-16 11:12:47 +08:00
if (res !== true) {
2024-06-03 09:55:04 +08:00
if (!message) {
generatedMessage = true;
const name = expected.name ? `"${expected.name}" ` : '';
message = `The ${name}validation function is expected to return` +
` "true". Received ${inspect(res)}`;
if (isError(actual)) {
message += `\n\nCaught error:\n\n${actual}`;
}
}
throwError = true;
2022-08-16 11:12:47 +08:00
}
}
if (throwError) {
const err = new AssertionError({
actual,
expected,
message,
operator: fn.name,
2024-06-03 09:55:04 +08:00
stackStartFn: fn,
2022-08-16 11:12:47 +08:00
});
err.generatedMessage = generatedMessage;
throw err;
}
}
function getActual(fn) {
2024-06-03 09:55:04 +08:00
validateFunction(fn, 'fn');
2022-08-16 11:12:47 +08:00
try {
fn();
} catch (e) {
return e;
}
return NO_EXCEPTION_SENTINEL;
}
function checkIsPromise(obj) {
// Accept native ES6 promises and promises that are implemented in a similar
// way. Do not accept thenables that use a function as `obj` and that have no
// `catch` handler.
return isPromise(obj) ||
(obj !== null && typeof obj === 'object' &&
typeof obj.then === 'function' &&
typeof obj.catch === 'function');
}
async function waitForActual(promiseFn) {
let resultPromise;
if (typeof promiseFn === 'function') {
// Return a rejected promise if `promiseFn` throws synchronously.
resultPromise = promiseFn();
// Fail in case no promise is returned.
if (!checkIsPromise(resultPromise)) {
throw new ERR_INVALID_RETURN_VALUE('instance of Promise',
'promiseFn', resultPromise);
}
} else if (checkIsPromise(promiseFn)) {
resultPromise = promiseFn;
} else {
throw new ERR_INVALID_ARG_TYPE(
'promiseFn', ['Function', 'Promise'], promiseFn);
}
try {
await resultPromise;
} catch (e) {
return e;
}
return NO_EXCEPTION_SENTINEL;
}
function expectsError(stackStartFn, actual, error, message) {
if (typeof error === 'string') {
if (arguments.length === 4) {
throw new ERR_INVALID_ARG_TYPE('error',
['Object', 'Error', 'Function', 'RegExp'],
error);
}
if (typeof actual === 'object' && actual !== null) {
if (actual.message === error) {
throw new ERR_AMBIGUOUS_ARGUMENT(
'error/message',
2024-06-03 09:55:04 +08:00
`The error message "${actual.message}" is identical to the message.`,
2022-08-16 11:12:47 +08:00
);
}
} else if (actual === error) {
throw new ERR_AMBIGUOUS_ARGUMENT(
'error/message',
2024-06-03 09:55:04 +08:00
`The error "${actual}" is identical to the message.`,
2022-08-16 11:12:47 +08:00
);
}
message = error;
error = undefined;
} else if (error != null &&
typeof error !== 'object' &&
typeof error !== 'function') {
throw new ERR_INVALID_ARG_TYPE('error',
['Object', 'Error', 'Function', 'RegExp'],
error);
}
if (actual === NO_EXCEPTION_SENTINEL) {
let details = '';
if (error && error.name) {
details += ` (${error.name})`;
}
details += message ? `: ${message}` : '.';
2024-06-03 09:55:04 +08:00
const fnType = stackStartFn === assert.rejects ? 'rejection' : 'exception';
2022-08-16 11:12:47 +08:00
innerFail({
actual: undefined,
expected: error,
operator: stackStartFn.name,
message: `Missing expected ${fnType}${details}`,
2024-06-03 09:55:04 +08:00
stackStartFn,
2022-08-16 11:12:47 +08:00
});
}
if (!error)
return;
expectedException(actual, error, message, stackStartFn);
}
function hasMatchingError(actual, expected) {
if (typeof expected !== 'function') {
if (isRegExp(expected)) {
const str = String(actual);
2024-06-03 09:55:04 +08:00
return RegExpPrototypeExec(expected, str) !== null;
2022-08-16 11:12:47 +08:00
}
throw new ERR_INVALID_ARG_TYPE(
2024-06-03 09:55:04 +08:00
'expected', ['Function', 'RegExp'], expected,
2022-08-16 11:12:47 +08:00
);
}
// Guard instanceof against arrow functions as they don't have a prototype.
if (expected.prototype !== undefined && actual instanceof expected) {
return true;
}
2024-06-03 09:55:04 +08:00
if (ObjectPrototypeIsPrototypeOf(Error, expected)) {
2022-08-16 11:12:47 +08:00
return false;
}
2024-06-03 09:55:04 +08:00
return ReflectApply(expected, {}, [actual]) === true;
2022-08-16 11:12:47 +08:00
}
function expectsNoError(stackStartFn, actual, error, message) {
if (actual === NO_EXCEPTION_SENTINEL)
return;
if (typeof error === 'string') {
message = error;
error = undefined;
}
if (!error || hasMatchingError(actual, error)) {
const details = message ? `: ${message}` : '.';
2024-06-03 09:55:04 +08:00
const fnType = stackStartFn === assert.doesNotReject ?
2022-08-16 11:12:47 +08:00
'rejection' : 'exception';
innerFail({
actual,
expected: error,
operator: stackStartFn.name,
message: `Got unwanted ${fnType}${details}\n` +
`Actual message: "${actual && actual.message}"`,
2024-06-03 09:55:04 +08:00
stackStartFn,
2022-08-16 11:12:47 +08:00
});
}
throw actual;
}
2024-06-03 09:55:04 +08:00
/**
* Expects the function `promiseFn` to throw an error.
* @param {() => any} promiseFn
* @param {...any} [args]
* @returns {void}
*/
2022-08-16 11:12:47 +08:00
assert.throws = function throws(promiseFn, ...args) {
expectsError(throws, getActual(promiseFn), ...args);
};
2024-06-03 09:55:04 +08:00
/**
* Expects `promiseFn` function or its value to reject.
* @param {() => Promise<any>} promiseFn
* @param {...any} [args]
* @returns {Promise<void>}
*/
2022-08-16 11:12:47 +08:00
assert.rejects = async function rejects(promiseFn, ...args) {
expectsError(rejects, await waitForActual(promiseFn), ...args);
};
2024-06-03 09:55:04 +08:00
/**
* Asserts that the function `fn` does not throw an error.
* @param {() => any} fn
* @param {...any} [args]
* @returns {void}
*/
2022-08-16 11:12:47 +08:00
assert.doesNotThrow = function doesNotThrow(fn, ...args) {
expectsNoError(doesNotThrow, getActual(fn), ...args);
};
2024-06-03 09:55:04 +08:00
/**
* Expects `fn` or its value to not reject.
* @param {() => Promise<any>} fn
* @param {...any} [args]
* @returns {Promise<void>}
*/
2022-08-16 11:12:47 +08:00
assert.doesNotReject = async function doesNotReject(fn, ...args) {
expectsNoError(doesNotReject, await waitForActual(fn), ...args);
};
2024-06-03 09:55:04 +08:00
/**
* Throws `value` if the value is not `null` or `undefined`.
* @param {any} err
* @returns {void}
*/
2022-08-16 11:12:47 +08:00
assert.ifError = function ifError(err) {
if (err !== null && err !== undefined) {
let message = 'ifError got unwanted exception: ';
if (typeof err === 'object' && typeof err.message === 'string') {
if (err.message.length === 0 && err.constructor) {
message += err.constructor.name;
} else {
message += err.message;
}
} else {
message += inspect(err);
}
const newErr = new AssertionError({
actual: err,
expected: null,
operator: 'ifError',
message,
2024-06-03 09:55:04 +08:00
stackStartFn: ifError,
2022-08-16 11:12:47 +08:00
});
// Make sure we actually have a stack trace!
const origStack = err.stack;
if (typeof origStack === 'string') {
// This will remove any duplicated frames from the error frames taken
// from within `ifError` and add the original error frames to the newly
// created ones.
2024-06-03 09:55:04 +08:00
const origStackStart = StringPrototypeIndexOf(origStack, '\n at');
if (origStackStart !== -1) {
const originalFrames = StringPrototypeSplit(
StringPrototypeSlice(origStack, origStackStart + 1),
'\n',
);
// Filter all frames existing in err.stack.
let newFrames = StringPrototypeSplit(newErr.stack, '\n');
for (const errFrame of originalFrames) {
// Find the first occurrence of the frame.
const pos = ArrayPrototypeIndexOf(newFrames, errFrame);
if (pos !== -1) {
// Only keep new frames.
newFrames = ArrayPrototypeSlice(newFrames, 0, pos);
break;
}
2022-08-16 11:12:47 +08:00
}
2024-06-03 09:55:04 +08:00
const stackStart = ArrayPrototypeJoin(newFrames, '\n');
const stackEnd = ArrayPrototypeJoin(originalFrames, '\n');
newErr.stack = `${stackStart}\n${stackEnd}`;
2022-08-16 11:12:47 +08:00
}
}
throw newErr;
}
};
function internalMatch(string, regexp, message, fn) {
if (!isRegExp(regexp)) {
throw new ERR_INVALID_ARG_TYPE(
2024-06-03 09:55:04 +08:00
'regexp', 'RegExp', regexp,
2022-08-16 11:12:47 +08:00
);
}
2024-06-03 09:55:04 +08:00
const match = fn === assert.match;
2022-08-16 11:12:47 +08:00
if (typeof string !== 'string' ||
2024-06-03 09:55:04 +08:00
RegExpPrototypeExec(regexp, string) !== null !== match) {
2022-08-16 11:12:47 +08:00
if (message instanceof Error) {
throw message;
}
const generatedMessage = !message;
// 'The input was expected to not match the regular expression ' +
message = message || (typeof string !== 'string' ?
'The "string" argument must be of type string. Received type ' +
`${typeof string} (${inspect(string)})` :
(match ?
'The input did not match the regular expression ' :
'The input was expected to not match the regular expression ') +
`${inspect(regexp)}. Input:\n\n${inspect(string)}\n`);
const err = new AssertionError({
actual: string,
expected: regexp,
message,
operator: fn.name,
2024-06-03 09:55:04 +08:00
stackStartFn: fn,
2022-08-16 11:12:47 +08:00
});
err.generatedMessage = generatedMessage;
throw err;
}
}
2024-06-03 09:55:04 +08:00
/**
* Expects the `string` input to match the regular expression.
* @param {string} string
* @param {RegExp} regexp
* @param {string | Error} [message]
* @returns {void}
*/
2022-08-16 11:12:47 +08:00
assert.match = function match(string, regexp, message) {
internalMatch(string, regexp, message, match);
};
2024-06-03 09:55:04 +08:00
/**
* Expects the `string` input not to match the regular expression.
* @param {string} string
* @param {RegExp} regexp
* @param {string | Error} [message]
* @returns {void}
*/
2022-08-16 11:12:47 +08:00
assert.doesNotMatch = function doesNotMatch(string, regexp, message) {
internalMatch(string, regexp, message, doesNotMatch);
};
assert.CallTracker = CallTracker;
2024-06-03 09:55:04 +08:00
/**
* Expose a strict only variant of assert.
* @param {...any} args
* @returns {void}
*/
2022-08-16 11:12:47 +08:00
function strict(...args) {
innerOk(strict, args.length, ...args);
}
2024-06-03 09:55:04 +08:00
2022-08-16 11:12:47 +08:00
assert.strict = ObjectAssign(strict, assert, {
equal: assert.strictEqual,
deepEqual: assert.deepStrictEqual,
notEqual: assert.notStrictEqual,
2024-06-03 09:55:04 +08:00
notDeepEqual: assert.notDeepStrictEqual,
2022-08-16 11:12:47 +08:00
});
2024-06-03 09:55:04 +08:00
2022-08-16 11:12:47 +08:00
assert.strict.strict = assert.strict;