Skip to content

Commit

Permalink
assert: move AssertionError into own file
Browse files Browse the repository at this point in the history
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: #20486
Reviewed-By: James M Snell <[email protected]>
Reviewed-By: Joyee Cheung <[email protected]>
Reviewed-By: Trivikram Kamat <[email protected]>
  • Loading branch information
BridgeAR authored and targos committed May 12, 2018
1 parent 01abed1 commit 5d06c1e
Show file tree
Hide file tree
Showing 5 changed files with 301 additions and 306 deletions.
15 changes: 6 additions & 9 deletions lib/assert.js
Original file line number Diff line number Diff line change
Expand Up @@ -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');
Expand Down
275 changes: 275 additions & 0 deletions lib/internal/assert.js
Original file line number Diff line number Diff line change
@@ -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()
};
Loading

0 comments on commit 5d06c1e

Please sign in to comment.