assert: move AssertionError into own file

This moves the `assert` parts from `internal/errors` into an own
file. `internal/errors` got bigger and bigger and it was difficult
to keep a good overview of what was going on. While doing so it
also removes the `internalAssert` function and just lazy loads
`assert`.

PR-URL: https://github.com/nodejs/node/pull/20486
Reviewed-By: James M Snell <jasnell@gmail.com>
Reviewed-By: Joyee Cheung <joyeec9h3@gmail.com>
Reviewed-By: Trivikram Kamat <trivikr.dev@gmail.com>
pull/20637/merge
Ruben Bridgewater 2018-05-02 16:21:40 +02:00
parent 4c6df73ae4
commit b304096a14
No known key found for this signature in database
GPG Key ID: F07496B3EB3C1762
5 changed files with 301 additions and 306 deletions

View File

@ -25,15 +25,12 @@ const {
isDeepEqual,
isDeepStrictEqual
} = require('internal/util/comparisons');
const {
AssertionError,
errorCache,
codes: {
ERR_AMBIGUOUS_ARGUMENT,
ERR_INVALID_ARG_TYPE,
ERR_INVALID_RETURN_VALUE
}
} = require('internal/errors');
const { codes: {
ERR_AMBIGUOUS_ARGUMENT,
ERR_INVALID_ARG_TYPE,
ERR_INVALID_RETURN_VALUE
} } = require('internal/errors');
const { AssertionError, errorCache } = require('internal/assert');
const { openSync, closeSync, readSync } = require('fs');
const { inspect, types: { isPromise, isRegExp } } = require('util');
const { EOL } = require('os');

View File

@ -0,0 +1,275 @@
'use strict';
const { inspect } = require('util');
const { codes: {
ERR_INVALID_ARG_TYPE
} } = require('internal/errors');
let blue = '';
let green = '';
let red = '';
let white = '';
const READABLE_OPERATOR = {
deepStrictEqual: 'Input A expected to strictly deep-equal input B',
notDeepStrictEqual: 'Input A expected to strictly not deep-equal input B',
strictEqual: 'Input A expected to strictly equal input B',
notStrictEqual: 'Input A expected to strictly not equal input B'
};
function copyError(source) {
const keys = Object.keys(source);
const target = Object.create(Object.getPrototypeOf(source));
for (const key of keys) {
target[key] = source[key];
}
Object.defineProperty(target, 'message', { value: source.message });
return target;
}
function inspectValue(val) {
// The util.inspect default values could be changed. This makes sure the
// error messages contain the necessary information nevertheless.
return inspect(
val,
{
compact: false,
customInspect: false,
depth: 1000,
maxArrayLength: Infinity,
// Assert compares only enumerable properties (with a few exceptions).
showHidden: false,
// Having a long line as error is better than wrapping the line for
// comparison.
breakLength: Infinity,
// Assert does not detect proxies currently.
showProxy: false
}
).split('\n');
}
function createErrDiff(actual, expected, operator) {
var other = '';
var res = '';
var lastPos = 0;
var end = '';
var skipped = false;
const actualLines = inspectValue(actual);
const expectedLines = inspectValue(expected);
const msg = READABLE_OPERATOR[operator] +
`:\n${green}+ expected${white} ${red}- actual${white}`;
const skippedMsg = ` ${blue}...${white} 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${blue}...${white}${end}`;
skipped = true;
}
if (other !== '') {
end = `\n ${other}${end}`;
other = '';
}
const maxLines = Math.max(actualLines.length, expectedLines.length);
var printedLines = 0;
var identical = 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${blue}...${white}`;
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${blue}...${white}`;
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${blue}...${white}`;
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++;
}
identical++;
}
// Inspected object to big (Show ~20 rows max)
if (printedLines > 20 && i < maxLines - 2) {
return `${msg}${skippedMsg}\n${res}\n${blue}...${white}${other}\n` +
`${blue}...${white}`;
}
}
// Strict equal with identical objects that are not identical by reference.
if (identical === maxLines) {
// E.g., assert.deepStrictEqual(Symbol(), Symbol())
const base = operator === 'strictEqual' ?
'Input objects identical but not reference equal:' :
'Input objects not identical:';
// We have to get the result again. The lines were all removed before.
const actualLines = inspectValue(actual);
// Only remove lines in case it makes sense to collapse those.
// TODO: Accept env to always show the full error.
if (actualLines.length > 30) {
actualLines[26] = `${blue}...${white}`;
while (actualLines.length > 27) {
actualLines.pop();
}
}
return `${base}\n\n${actualLines.join('\n')}\n`;
}
return `${msg}${skipped ? skippedMsg : ''}\n${res}${other}${end}`;
}
class AssertionError extends Error {
constructor(options) {
if (typeof options !== 'object' || options === null) {
throw new ERR_INVALID_ARG_TYPE('options', 'Object', options);
}
var {
actual,
expected,
message,
operator,
stackStartFn
} = options;
if (message != null) {
super(message);
} else {
if (process.stdout.isTTY) {
// Reset on each call to make sure we handle dynamically set environment
// variables correct.
if (process.stdout.getColorDepth() !== 1) {
blue = '\u001b[34m';
green = '\u001b[32m';
white = '\u001b[39m';
red = '\u001b[31m';
} else {
blue = '';
green = '';
white = '';
red = '';
}
}
// Prevent the error stack from being visible by duplicating the error
// in a very close way to the original in case both sides are actually
// instances of Error.
if (typeof actual === 'object' && actual !== null &&
typeof expected === 'object' && expected !== null &&
'stack' in actual && actual instanceof Error &&
'stack' in expected && expected instanceof Error) {
actual = copyError(actual);
expected = copyError(expected);
}
if (operator === 'deepStrictEqual' || operator === 'strictEqual') {
super(createErrDiff(actual, expected, operator));
} else if (operator === 'notDeepStrictEqual' ||
operator === 'notStrictEqual') {
// In case the objects are equal but the operator requires unequal, show
// the first object and say A equals B
const res = inspectValue(actual);
const base = `Identical input passed to ${operator}:`;
// Only remove lines in case it makes sense to collapse those.
// TODO: Accept env to always show the full error.
if (res.length > 30) {
res[26] = `${blue}...${white}`;
while (res.length > 27) {
res.pop();
}
}
// Only print a single input.
if (res.length === 1) {
super(`${base} ${res[0]}`);
} else {
super(`${base}\n\n${res.join('\n')}\n`);
}
} else {
let res = inspect(actual);
let other = inspect(expected);
if (res.length > 128)
res = `${res.slice(0, 125)}...`;
if (other.length > 128)
other = `${other.slice(0, 125)}...`;
super(`${res} ${operator} ${other}`);
}
}
this.generatedMessage = !message;
this.name = 'AssertionError [ERR_ASSERTION]';
this.code = 'ERR_ASSERTION';
this.actual = actual;
this.expected = expected;
this.operator = operator;
Error.captureStackTrace(this, stackStartFn);
}
}
module.exports = {
AssertionError,
errorCache: new Map()
};

View File

@ -15,18 +15,6 @@ const kInfo = Symbol('info');
const messages = new Map();
const codes = {};
let blue = '';
let green = '';
let red = '';
let white = '';
const READABLE_OPERATOR = {
deepStrictEqual: 'Input A expected to strictly deep-equal input B',
notDeepStrictEqual: 'Input A expected to strictly not deep-equal input B',
strictEqual: 'Input A expected to strictly equal input B',
notStrictEqual: 'Input A expected to strictly not equal input B'
};
const {
errmap,
UV_EAI_NODATA,
@ -36,10 +24,10 @@ const { kMaxLength } = process.binding('buffer');
const { defineProperty } = Object;
// Lazily loaded
var util;
var buffer;
let util;
let assert;
var internalUtil = null;
let internalUtil = null;
function lazyInternalUtil() {
if (!internalUtil) {
internalUtil = require('internal/util');
@ -47,35 +35,11 @@ function lazyInternalUtil() {
return internalUtil;
}
function copyError(source) {
const keys = Object.keys(source);
const target = Object.create(Object.getPrototypeOf(source));
for (const key of keys) {
target[key] = source[key];
}
Object.defineProperty(target, 'message', { value: source.message });
return target;
}
function inspectValue(val) {
// The util.inspect default values could be changed. This makes sure the
// error messages contain the necessary information nevertheless.
return util.inspect(
val,
{
compact: false,
customInspect: false,
depth: 1000,
maxArrayLength: Infinity,
// Assert compares only enumerable properties (with a few exceptions).
showHidden: false,
// Having a long line as error is better than wrapping the line for
// comparison.
breakLength: Infinity,
// Assert does not detect proxies currently.
showProxy: false
}
).split('\n');
let buffer;
function lazyBuffer() {
if (buffer === undefined)
buffer = require('buffer').Buffer;
return buffer;
}
// A specialized Error that includes an additional info property with
@ -240,254 +204,14 @@ function E(sym, val, def, ...otherClasses) {
codes[sym] = def;
}
function lazyBuffer() {
if (buffer === undefined)
buffer = require('buffer').Buffer;
return buffer;
}
function createErrDiff(actual, expected, operator) {
var other = '';
var res = '';
var lastPos = 0;
var end = '';
var skipped = false;
if (util === undefined) util = require('util');
const actualLines = inspectValue(actual);
const expectedLines = inspectValue(expected);
const msg = READABLE_OPERATOR[operator] +
`:\n${green}+ expected${white} ${red}- actual${white}`;
const skippedMsg = ` ${blue}...${white} 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${blue}...${white}${end}`;
skipped = true;
}
if (other !== '') {
end = `\n ${other}${end}`;
other = '';
}
const maxLines = Math.max(actualLines.length, expectedLines.length);
var printedLines = 0;
var identical = 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${blue}...${white}`;
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${blue}...${white}`;
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${blue}...${white}`;
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++;
}
identical++;
}
// Inspected object to big (Show ~20 rows max)
if (printedLines > 20 && i < maxLines - 2) {
return `${msg}${skippedMsg}\n${res}\n${blue}...${white}${other}\n` +
`${blue}...${white}`;
}
}
// Strict equal with identical objects that are not identical by reference.
if (identical === maxLines) {
// E.g., assert.deepStrictEqual(Symbol(), Symbol())
const base = operator === 'strictEqual' ?
'Input objects identical but not reference equal:' :
'Input objects not identical:';
// We have to get the result again. The lines were all removed before.
const actualLines = inspectValue(actual);
// Only remove lines in case it makes sense to collapse those.
// TODO: Accept env to always show the full error.
if (actualLines.length > 30) {
actualLines[26] = `${blue}...${white}`;
while (actualLines.length > 27) {
actualLines.pop();
}
}
return `${base}\n\n${actualLines.join('\n')}\n`;
}
return `${msg}${skipped ? skippedMsg : ''}\n${res}${other}${end}`;
}
class AssertionError extends Error {
constructor(options) {
if (typeof options !== 'object' || options === null) {
throw new codes.ERR_INVALID_ARG_TYPE('options', 'Object', options);
}
var {
actual,
expected,
message,
operator,
stackStartFn
} = options;
if (message != null) {
super(message);
} else {
if (process.stdout.isTTY) {
// Reset on each call to make sure we handle dynamically set environment
// variables correct.
if (process.stdout.getColorDepth() !== 1) {
blue = '\u001b[34m';
green = '\u001b[32m';
white = '\u001b[39m';
red = '\u001b[31m';
} else {
blue = '';
green = '';
white = '';
red = '';
}
}
if (util === undefined) util = require('util');
// Prevent the error stack from being visible by duplicating the error
// in a very close way to the original in case both sides are actually
// instances of Error.
if (typeof actual === 'object' && actual !== null &&
typeof expected === 'object' && expected !== null &&
'stack' in actual && actual instanceof Error &&
'stack' in expected && expected instanceof Error) {
actual = copyError(actual);
expected = copyError(expected);
}
if (operator === 'deepStrictEqual' || operator === 'strictEqual') {
super(createErrDiff(actual, expected, operator));
} else if (operator === 'notDeepStrictEqual' ||
operator === 'notStrictEqual') {
// In case the objects are equal but the operator requires unequal, show
// the first object and say A equals B
const res = inspectValue(actual);
const base = `Identical input passed to ${operator}:`;
// Only remove lines in case it makes sense to collapse those.
// TODO: Accept env to always show the full error.
if (res.length > 30) {
res[26] = `${blue}...${white}`;
while (res.length > 27) {
res.pop();
}
}
// Only print a single input.
if (res.length === 1) {
super(`${base} ${res[0]}`);
} else {
super(`${base}\n\n${res.join('\n')}\n`);
}
} else {
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}`);
}
}
this.generatedMessage = !message;
this.name = 'AssertionError [ERR_ASSERTION]';
this.code = 'ERR_ASSERTION';
this.actual = actual;
this.expected = expected;
this.operator = operator;
Error.captureStackTrace(this, stackStartFn);
}
}
// This is defined here instead of using the assert module to avoid a
// circular dependency. The effect is largely the same.
function internalAssert(condition, message) {
if (!condition) {
throw new AssertionError({
message,
actual: false,
expected: true,
operator: '=='
});
}
}
function getMessage(key, args) {
const msg = messages.get(key);
if (util === undefined) util = require('util');
if (assert === undefined) assert = require('assert');
if (typeof msg === 'function') {
internalAssert(
assert(
msg.length <= args.length, // Default options do not count.
`Code: ${key}; The provided arguments length (${args.length}) does not ` +
`match the required ones (${msg.length}).`
@ -496,7 +220,7 @@ function getMessage(key, args) {
}
const expectedLength = (msg.match(/%[dfijoOs]/g) || []).length;
internalAssert(
assert(
expectedLength === args.length,
`Code: ${key}; The provided arguments length (${args.length}) does not ` +
`match the required ones (${expectedLength}).`
@ -690,11 +414,9 @@ module.exports = {
uvException,
isStackOverflowError,
getMessage,
AssertionError,
SystemError,
codes,
E, // This is exported only to facilitate testing.
errorCache: new Map() // This is in here only to facilitate testing.
E // This is exported only to facilitate testing.
};
// To declare an error message, use the E(sym, val, def) function above. The sym
@ -1038,7 +760,7 @@ E('ERR_VM_MODULE_STATUS', 'Module status %s', Error);
E('ERR_ZLIB_INITIALIZATION_FAILED', 'Initialization failed', Error);
function invalidArgType(name, expected, actual) {
internalAssert(typeof name === 'string', 'name must be a string');
assert(typeof name === 'string', "'name' must be a string");
// determiner: 'must be' or 'must not be'
let determiner;
@ -1064,7 +786,7 @@ function invalidArgType(name, expected, actual) {
}
function missingArgs(...args) {
internalAssert(args.length > 0, 'At least one arg needs to be specified');
assert(args.length > 0, 'At least one arg needs to be specified');
let msg = 'The ';
const len = args.length;
args = args.map((a) => `"${a}"`);
@ -1084,11 +806,11 @@ function missingArgs(...args) {
}
function oneOf(expected, thing) {
internalAssert(typeof thing === 'string', '`thing` has to be of type string');
assert(typeof thing === 'string', '`thing` has to be of type string');
if (Array.isArray(expected)) {
const len = expected.length;
internalAssert(len > 0,
'At least one expected value needs to be specified');
assert(len > 0,
'At least one expected value needs to be specified');
expected = expected.map((i) => String(i));
if (len > 2) {
return `one of ${thing} ${expected.slice(0, len - 1).join(', ')}, or ` +

View File

@ -79,6 +79,7 @@
'lib/v8.js',
'lib/vm.js',
'lib/zlib.js',
'lib/internal/assert.js',
'lib/internal/async_hooks.js',
'lib/internal/buffer.js',
'lib/internal/cli_table.js',

View File

@ -29,7 +29,7 @@ const common = require('../common');
const assert = require('assert');
const { EOL } = require('os');
const EventEmitter = require('events');
const { errorCache } = require('internal/errors');
const { errorCache } = require('internal/assert');
const { writeFileSync, unlinkSync } = require('fs');
const { inspect } = require('util');
const a = assert;