diff options
author | Rich Trott <rtrott@gmail.com> | 2019-02-06 07:46:35 +0300 |
---|---|---|
committer | Rich Trott <rtrott@gmail.com> | 2019-02-08 11:01:07 +0300 |
commit | 64745c3ade8bad694fffba06736b461ab9a05a1d (patch) | |
tree | 1f23c20e23b5dceb97eec9cf8b9016201ce6d8e7 /lib/internal/assert.js | |
parent | 7182aca108fac7a7e464e9450461975e6ef4a4e3 (diff) |
assert: refactor internal assert.js
Move lib/internal/assert.js to lib/internal/assert/assertion_error.js.
This is in preparation for making lib/internal/assert.js a tiny module
for use in Node.js built-ins so that we can use `assert()` without
having to load the entire ~1200 line `assert` module.
PR-URL: https://github.com/nodejs/node/pull/25956
Reviewed-By: Ruben Bridgewater <ruben@bridgewater.de>
Reviewed-By: Minwoo Jung <minwoo@nodesource.com>
Diffstat (limited to 'lib/internal/assert.js')
-rw-r--r-- | lib/internal/assert.js | 410 |
1 files changed, 0 insertions, 410 deletions
diff --git a/lib/internal/assert.js b/lib/internal/assert.js deleted file mode 100644 index 4e059db6b9d..00000000000 --- a/lib/internal/assert.js +++ /dev/null @@ -1,410 +0,0 @@ -'use strict'; - -const { inspect } = require('util'); -const { codes: { - ERR_INVALID_ARG_TYPE -} } = require('internal/errors'); - -let blue = ''; -let green = ''; -let red = ''; -let white = ''; - -const kReadableOperator = { - deepStrictEqual: 'Expected values to be strictly deep-equal:', - strictEqual: 'Expected values to be strictly equal:', - strictEqualObject: 'Expected "actual" to be reference-equal to "expected":', - deepEqual: 'Expected values to be loosely deep-equal:', - equal: 'Expected values to be loosely equal:', - notDeepStrictEqual: 'Expected "actual" not to be strictly deep-equal to:', - notStrictEqual: 'Expected "actual" to be strictly unequal to:', - // eslint-disable-next-line max-len - notStrictEqualObject: 'Expected "actual" not to be reference-equal to "expected":', - notDeepEqual: 'Expected "actual" not to be loosely deep-equal to:', - notEqual: 'Expected "actual" to be loosely unequal to:', - notIdentical: 'Values identical but not reference-equal:', -}; - -// Comparing short primitives should just show === / !== instead of using the -// diff. -const kMaxShortLength = 10; - -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, - sorted: true, - // Inspect getters as we also check them when comparing entries. - getters: true - } - ); -} - -function createErrDiff(actual, expected, operator) { - let other = ''; - let res = ''; - let lastPos = 0; - let end = ''; - let skipped = false; - const actualInspected = inspectValue(actual); - const actualLines = actualInspected.split('\n'); - const expectedLines = inspectValue(expected).split('\n'); - - let i = 0; - let indicator = ''; - - // In case both values are objects explicitly mark them as not reference equal - // for the `strictEqual` operator. - if (operator === 'strictEqual' && - typeof actual === 'object' && - typeof expected === 'object' && - actual !== null && - expected !== null) { - operator = 'strictEqualObject'; - } - - // If "actual" and "expected" fit on a single line and they are not strictly - // equal, check further special handling. - if (actualLines.length === 1 && expectedLines.length === 1 && - actualLines[0] !== expectedLines[0]) { - const inputLength = actualLines[0].length + expectedLines[0].length; - // If the character length of "actual" and "expected" together is less than - // kMaxShortLength and if neither is an object and at least one of them is - // not `zero`, use the strict equal comparison to visualize the output. - if (inputLength <= kMaxShortLength) { - if ((typeof actual !== 'object' || actual === null) && - (typeof expected !== 'object' || expected === null) && - (actual !== 0 || expected !== 0)) { // -0 === +0 - return `${kReadableOperator[operator]}\n\n` + - `${actualLines[0]} !== ${expectedLines[0]}\n`; - } - } else if (operator !== 'strictEqualObject') { - // If the stderr is a tty and the input length is lower than the current - // columns per line, add a mismatch indicator below the output. If it is - // not a tty, use a default value of 80 characters. - const maxLength = process.stderr.isTTY ? process.stderr.columns : 80; - if (inputLength < maxLength) { - while (actualLines[0][i] === expectedLines[0][i]) { - i++; - } - // Ignore the first characters. - if (i > 2) { - // Add position indicator for the first mismatch in case it is a - // single line and the input length is less than the column length. - indicator = `\n ${' '.repeat(i)}^`; - i = 0; - } - } - } - } - - // Remove all ending lines that match (this optimizes the output for - // readability by reducing the number of total changed lines). - let a = actualLines[actualLines.length - 1]; - let b = expectedLines[expectedLines.length - 1]; - 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]; - } - - const maxLines = Math.max(actualLines.length, expectedLines.length); - // Strict equal with identical objects that are not identical by reference. - // E.g., assert.deepStrictEqual({ a: Symbol() }, { a: Symbol() }) - if (maxLines === 0) { - // We have to get the result again. The lines were all removed before. - const actualLines = actualInspected.split('\n'); - - // 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 `${kReadableOperator.notIdentical}\n\n${actualLines.join('\n')}\n`; - } - - if (i > 3) { - end = `\n${blue}...${white}${end}`; - skipped = true; - } - if (other !== '') { - end = `\n ${other}${end}`; - other = ''; - } - - let printedLines = 0; - const msg = kReadableOperator[operator] + - `\n${green}+ actual${white} ${red}- expected${white}`; - const skippedMsg = ` ${blue}...${white} Lines skipped`; - - for (i = 0; i < maxLines; i++) { - // Only extra expected lines exist - const cur = i - lastPos; - if (actualLines.length < i + 1) { - // If the last diverging line is more than one line above and the - // current line is at least line three, add some of the former lines and - // also add dots to indicate skipped entries. - 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++; - } - // Mark the current line as the last diverging one. - lastPos = i; - // Add the expected line to the cache. - other += `\n${red}-${white} ${expectedLines[i]}`; - printedLines++; - // Only extra actual lines exist - } else if (expectedLines.length < i + 1) { - // If the last diverging line is more than one line above and the - // current line is at least line three, add some of the former lines and - // also add dots to indicate skipped entries. - 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++; - } - // Mark the current line as the last diverging one. - lastPos = i; - // Add the actual line to the result. - res += `\n${green}+${white} ${actualLines[i]}`; - printedLines++; - // Lines diverge - } else { - const expectedLine = expectedLines[i]; - let actualLine = actualLines[i]; - // If the lines diverge, specifically check for lines that only diverge by - // a trailing comma. In that case it is actually identical and we should - // mark it as such. - let divergingLines = actualLine !== expectedLine && - (!actualLine.endsWith(',') || - actualLine.slice(0, -1) !== expectedLine); - // If the expected line has a trailing comma but is otherwise identical, - // add a comma at the end of the actual line. Otherwise the output could - // look weird as in: - // - // [ - // 1 // No comma at the end! - // + 2 - // ] - // - if (divergingLines && - expectedLine.endsWith(',') && - expectedLine.slice(0, -1) === actualLine) { - divergingLines = false; - actualLine += ','; - } - if (divergingLines) { - // If the last diverging line is more than one line above and the - // current line is at least line three, add some of the former lines and - // also add dots to indicate skipped entries. - 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++; - } - // Mark the current line as the last diverging one. - lastPos = i; - // Add the actual line to the result and cache the expected diverging - // line so consecutive diverging lines show up as +++--- and not +-+-+-. - res += `\n${green}+${white} ${actualLine}`; - other += `\n${red}-${white} ${expectedLine}`; - printedLines += 2; - // Lines are identical - } else { - // Add all cached information to the result before adding other things - // and reset the cache. - res += other; - other = ''; - // If the last diverging line is exactly one line above or if it is the - // very first line, add the line to the result. - if (cur === 1 || i === 0) { - res += `\n ${actualLine}`; - printedLines++; - } - } - } - // 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}`; - } - } - - return `${msg}${skipped ? skippedMsg : ''}\n${res}${other}${end}${indicator}`; -} - -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(String(message)); - } else { - if (process.stderr.isTTY) { - // Reset on each call to make sure we handle dynamically set environment - // variables correct. - if (process.stderr.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 - let base = kReadableOperator[operator]; - const res = inspectValue(actual).split('\n'); - - // In case "actual" is an object, it should not be reference equal. - if (operator === 'notStrictEqual' && - typeof actual === 'object' && - actual !== null) { - base = kReadableOperator.notStrictEqualObject; - } - - // 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 = inspectValue(actual); - let other = ''; - const knownOperators = kReadableOperator[operator]; - if (operator === 'notDeepEqual' || operator === 'notEqual') { - res = `${kReadableOperator[operator]}\n\n${res}`; - if (res.length > 1024) { - res = `${res.slice(0, 1021)}...`; - } - } else { - other = `${inspectValue(expected)}`; - if (res.length > 512) { - res = `${res.slice(0, 509)}...`; - } - if (other.length > 512) { - other = `${other.slice(0, 509)}...`; - } - if (operator === 'deepEqual' || operator === 'equal') { - res = `${knownOperators}\n\n${res}\n\nshould equal\n\n`; - } else { - other = ` ${operator} ${other}`; - } - } - super(`${res}${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); - } - - [inspect.custom](recurseTimes, ctx) { - // This limits the `actual` and `expected` property default inspection to - // the minimum depth. Otherwise those values would be too verbose compared - // to the actual error message which contains a combined view of these two - // input values. - return inspect(this, { ...ctx, customInspect: false, depth: 0 }); - } -} - -module.exports = { - AssertionError -}; |