Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 40 additions & 4 deletions doc/api/assert.md
Original file line number Diff line number Diff line change
Expand Up @@ -645,6 +645,11 @@ parameter is an instance of an [`Error`][] then it will be thrown instead of the
## assert.ok(value[, message])
<!-- YAML
added: v0.1.21
changes:
- version: REPLACEME
pr-url: https://github.com/nodejs/node/pull/17581
description: assert.ok() will throw a `ERR_MISSING_ARGS` error.
Use assert.fail() instead.
-->
* `value` {any}
* `message` {any}
Expand All @@ -658,19 +663,50 @@ parameter is `undefined`, a default error message is assigned. If the `message`
parameter is an instance of an [`Error`][] then it will be thrown instead of the
`AssertionError`.

Be aware that in the `repl` the error message will be different to the one
thrown in a file! See below for further details.

```js
const assert = require('assert').strict;

assert.ok(true);
// OK
assert.ok(1);
// OK
assert.ok(false);
// throws "AssertionError: false == true"
assert.ok(0);
// throws "AssertionError: 0 == true"

assert.ok(false, 'it\'s false');
// throws "AssertionError: it's false"

// In the repl:
assert.ok(typeof 123 === 'string');
// throws:
// "AssertionError: false == true

// In a file (e.g. test.js):
assert.ok(typeof 123 === 'string');
// throws:
// "AssertionError: The expression evaluated to a falsy value:
//
// assert.ok(typeof 123 === 'string')

assert.ok(false);
// throws:
// "AssertionError: The expression evaluated to a falsy value:
//
// assert.ok(false)

assert.ok(0);
// throws:
// "AssertionError: The expression evaluated to a falsy value:
//
// assert.ok(0)

// Using `assert()` works the same:
assert(0);
// throws:
// "AssertionError: The expression evaluated to a falsy value:
//
// assert(0)
```

## assert.strictEqual(actual, expected[, message])
Expand Down
1 change: 0 additions & 1 deletion doc/api/deprecations.md
Original file line number Diff line number Diff line change
Expand Up @@ -788,7 +788,6 @@ The AsyncHooks Sensitive API was never documented and had various of minor
issues, see https://github.com/nodejs/node/issues/15572. Use the `AsyncResource`
API instead.


<a id="DEP0086"></a>
### DEP0086: Remove runInAsyncIdScope

Expand Down
153 changes: 137 additions & 16 deletions lib/assert.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,33 @@

'use strict';

const { isDeepEqual, isDeepStrictEqual } =
require('internal/util/comparisons');
const { Buffer } = require('buffer');
const {
isDeepEqual,
isDeepStrictEqual
} = require('internal/util/comparisons');
const errors = require('internal/errors');
const { openSync, closeSync, readSync } = require('fs');
const { parseExpressionAt } = require('internal/deps/acorn/dist/acorn');
const { inspect } = require('util');
const { EOL } = require('os');

const codeCache = new Map();
// 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',
'\\u001e', '\\u001f'
];

const escapeFn = (str) => meta[str.charCodeAt(0)];

// The assert module provides functions that throw
// AssertionError's when particular conditions are not met. The
Expand Down Expand Up @@ -74,20 +97,126 @@ assert.fail = fail;
// expected: expected });
assert.AssertionError = errors.AssertionError;

function getBuffer(fd, assertLine) {
var lines = 0;
// Prevent blocking the event loop by limiting the maximum amount of
// data that may be read.
var maxReads = 64; // bytesPerRead * maxReads = 512 kb
var bytesRead = 0;
var startBuffer = 0; // Start reading from that char on
const bytesPerRead = 8192;
const buffers = [];
do {
const buffer = Buffer.allocUnsafe(bytesPerRead);
bytesRead = readSync(fd, buffer, 0, bytesPerRead);
for (var i = 0; i < bytesRead; i++) {
if (buffer[i] === 10) {
lines++;
if (lines === assertLine) {
startBuffer = i + 1;
// Read up to 15 more lines to make sure all code gets matched
} else if (lines === assertLine + 16) {
buffers.push(buffer.slice(startBuffer, i));
return buffers;
}
}
}
if (lines >= assertLine) {
buffers.push(buffer.slice(startBuffer, bytesRead));
// Reset the startBuffer in case we need more than one chunk
startBuffer = 0;
}
} while (--maxReads !== 0 && bytesRead !== 0);
return buffers;
}

function innerOk(args, fn) {
var [value, message] = args;

if (args.length === 0)
throw new errors.TypeError('ERR_MISSING_ARGS', 'value');

// Pure assertion tests whether a value is truthy, as determined
// by !!value.
function ok(value, message) {
if (!value) {
if (message == null) {
// Use the call as error message if possible.
// This does not work with e.g. the repl.
const err = new Error();
// Make sure the limit is set to 1. Otherwise it could fail (<= 0) or it
// does to much work.
const tmpLimit = Error.stackTraceLimit;
Error.stackTraceLimit = 1;
Error.captureStackTrace(err, fn);
Error.stackTraceLimit = tmpLimit;

const tmpPrepare = Error.prepareStackTrace;
Error.prepareStackTrace = (_, stack) => stack;
const call = err.stack[0];
Error.prepareStackTrace = tmpPrepare;

const filename = call.getFileName();
const line = call.getLineNumber() - 1;
const column = call.getColumnNumber() - 1;
const identifier = `${filename}${line}${column}`;

if (codeCache.has(identifier)) {
message = codeCache.get(identifier);
} else {
var fd;
try {
fd = openSync(filename, 'r', 0o666);
const buffers = getBuffer(fd, line);
const code = Buffer.concat(buffers).toString('utf8');
const nodes = parseExpressionAt(code, column);
// Node type should be "CallExpression" and some times
// "SequenceExpression".
const node = nodes.type === 'CallExpression' ?
nodes :
nodes.expressions[0];
// TODO: fix the "generatedMessage property"
// Since this is actually a generated message, it has to be
// determined differently from now on.

const name = node.callee.name;
// Calling `ok` with .apply or .call is uncommon but we use a simple
// safeguard nevertheless.
if (name !== 'apply' && name !== 'call') {
// Only use `assert` and `assert.ok` to reference the "real API" and
// not user defined function names.
const ok = name === 'ok' ? '.ok' : '';
const args = node.arguments;
message = code
.slice(args[0].start, args[args.length - 1].end)
.replace(escapeSequencesRegExp, escapeFn);
message = 'The expression evaluated to a falsy value:' +
`${EOL}${EOL} assert${ok}(${message})${EOL}`;
}
// Make sure to always set the cache! No matter if the message is
// undefined or not
codeCache.set(identifier, message);
} catch (e) {
// Invalidate cache to prevent trying to read this part again.
codeCache.set(identifier, undefined);
} finally {
if (fd !== undefined)
closeSync(fd);
}
}
}
innerFail({
actual: value,
expected: true,
message,
operator: '==',
stackStartFn: ok
stackStartFn: fn
});
}
}

// Pure assertion tests whether a value is truthy, as determined
// by !!value.
function ok(...args) {
innerOk(args, ok);
}
assert.ok = ok;

// The equality assertion tests shallow, coercive equality with ==.
Expand Down Expand Up @@ -318,16 +447,8 @@ assert.doesNotThrow = function doesNotThrow(block, error, message) {
assert.ifError = function ifError(err) { if (err) throw err; };

// Expose a strict only variant of assert
function strict(value, message) {
if (!value) {
innerFail({
actual: value,
expected: true,
message,
operator: '==',
stackStartFn: strict
});
}
function strict(...args) {
innerOk(args, strict);
}
assert.strict = Object.assign(strict, assert, {
equal: assert.strictEqual,
Expand Down
2 changes: 1 addition & 1 deletion lib/internal/errors.js
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,7 @@ class AssertionError extends Error {
throw new exports.TypeError('ERR_INVALID_ARG_TYPE', 'options', 'Object');
}
var { actual, expected, message, operator, stackStartFn } = options;
if (message) {
if (message != null) {
super(message);
} else {
if (actual && actual.stack && actual instanceof Error)
Expand Down
Loading