Skip to content

Commit b048eae

Browse files
avivkellertargos
authored andcommittedOct 2, 2024
test_runner: reimplement assert.ok to allow stack parsing
PR-URL: #54776 Refs: nodejs/help#4461 Reviewed-By: Matteo Collina <matteo.collina@gmail.com> Reviewed-By: Chemi Atlow <chemi@atlow.co.il> Reviewed-By: Colin Ihrig <cjihrig@gmail.com>
1 parent a983726 commit b048eae

File tree

4 files changed

+303
-269
lines changed

4 files changed

+303
-269
lines changed
 

‎lib/assert.js

+1-268
Original file line numberDiff line numberDiff line change
@@ -24,31 +24,21 @@ const {
2424
ArrayPrototypeIndexOf,
2525
ArrayPrototypeJoin,
2626
ArrayPrototypePush,
27-
ArrayPrototypeShift,
2827
ArrayPrototypeSlice,
2928
Error,
30-
ErrorCaptureStackTrace,
31-
FunctionPrototypeBind,
3229
NumberIsNaN,
3330
ObjectAssign,
3431
ObjectIs,
3532
ObjectKeys,
3633
ObjectPrototypeIsPrototypeOf,
3734
ReflectApply,
3835
RegExpPrototypeExec,
39-
RegExpPrototypeSymbolReplace,
40-
SafeMap,
4136
String,
42-
StringPrototypeCharCodeAt,
43-
StringPrototypeIncludes,
4437
StringPrototypeIndexOf,
45-
StringPrototypeReplace,
4638
StringPrototypeSlice,
4739
StringPrototypeSplit,
48-
StringPrototypeStartsWith,
4940
} = primordials;
5041

51-
const { Buffer } = require('buffer');
5242
const {
5343
codes: {
5444
ERR_AMBIGUOUS_ARGUMENT,
@@ -57,53 +47,27 @@ const {
5747
ERR_INVALID_RETURN_VALUE,
5848
ERR_MISSING_ARGS,
5949
},
60-
isErrorStackTraceLimitWritable,
61-
overrideStackTrace,
6250
} = require('internal/errors');
6351
const AssertionError = require('internal/assert/assertion_error');
64-
const { openSync, closeSync, readSync } = require('fs');
6552
const { inspect } = require('internal/util/inspect');
6653
const { isPromise, isRegExp } = require('internal/util/types');
67-
const { EOL } = require('internal/constants');
68-
const { BuiltinModule } = require('internal/bootstrap/realm');
6954
const { isError, deprecate } = require('internal/util');
55+
const { innerOk } = require('internal/assert/utils');
7056

71-
const errorCache = new SafeMap();
7257
const CallTracker = require('internal/assert/calltracker');
7358
const {
7459
validateFunction,
7560
} = require('internal/validators');
76-
const { fileURLToPath } = require('internal/url');
7761

7862
let isDeepEqual;
7963
let isDeepStrictEqual;
80-
let parseExpressionAt;
81-
let findNodeAround;
82-
let tokenizer;
83-
let decoder;
8464

8565
function lazyLoadComparison() {
8666
const comparison = require('internal/util/comparisons');
8767
isDeepEqual = comparison.isDeepEqual;
8868
isDeepStrictEqual = comparison.isDeepStrictEqual;
8969
}
9070

91-
// Escape control characters but not \n and \t to keep the line breaks and
92-
// indentation intact.
93-
// eslint-disable-next-line no-control-regex
94-
const escapeSequencesRegExp = /[\x00-\x08\x0b\x0c\x0e-\x1f]/g;
95-
const meta = [
96-
'\\u0000', '\\u0001', '\\u0002', '\\u0003', '\\u0004',
97-
'\\u0005', '\\u0006', '\\u0007', '\\b', '',
98-
'', '\\u000b', '\\f', '', '\\u000e',
99-
'\\u000f', '\\u0010', '\\u0011', '\\u0012', '\\u0013',
100-
'\\u0014', '\\u0015', '\\u0016', '\\u0017', '\\u0018',
101-
'\\u0019', '\\u001a', '\\u001b', '\\u001c', '\\u001d',
102-
'\\u001e', '\\u001f',
103-
];
104-
105-
const escapeFn = (str) => meta[StringPrototypeCharCodeAt(str, 0)];
106-
10771
let warned = false;
10872

10973
// The assert module provides functions that throw
@@ -178,237 +142,6 @@ assert.fail = fail;
178142
// The AssertionError is defined in internal/error.
179143
assert.AssertionError = AssertionError;
180144

181-
function findColumn(fd, column, code) {
182-
if (code.length > column + 100) {
183-
try {
184-
return parseCode(code, column);
185-
} catch {
186-
// End recursion in case no code could be parsed. The expression should
187-
// have been found after 2500 characters, so stop trying.
188-
if (code.length - column > 2500) {
189-
// eslint-disable-next-line no-throw-literal
190-
throw null;
191-
}
192-
}
193-
}
194-
// Read up to 2500 bytes more than necessary in columns. That way we address
195-
// multi byte characters and read enough data to parse the code.
196-
const bytesToRead = column - code.length + 2500;
197-
const buffer = Buffer.allocUnsafe(bytesToRead);
198-
const bytesRead = readSync(fd, buffer, 0, bytesToRead);
199-
code += decoder.write(buffer.slice(0, bytesRead));
200-
// EOF: fast path.
201-
if (bytesRead < bytesToRead) {
202-
return parseCode(code, column);
203-
}
204-
// Read potentially missing code.
205-
return findColumn(fd, column, code);
206-
}
207-
208-
function getCode(fd, line, column) {
209-
let bytesRead = 0;
210-
if (line === 0) {
211-
// Special handle line number one. This is more efficient and simplifies the
212-
// rest of the algorithm. Read more than the regular column number in bytes
213-
// to prevent multiple reads in case multi byte characters are used.
214-
return findColumn(fd, column, '');
215-
}
216-
let lines = 0;
217-
// Prevent blocking the event loop by limiting the maximum amount of
218-
// data that may be read.
219-
let maxReads = 32; // bytesPerRead * maxReads = 512 KiB
220-
const bytesPerRead = 16384;
221-
// Use a single buffer up front that is reused until the call site is found.
222-
let buffer = Buffer.allocUnsafe(bytesPerRead);
223-
while (maxReads-- !== 0) {
224-
// Only allocate a new buffer in case the needed line is found. All data
225-
// before that can be discarded.
226-
buffer = lines < line ? buffer : Buffer.allocUnsafe(bytesPerRead);
227-
bytesRead = readSync(fd, buffer, 0, bytesPerRead);
228-
// Read the buffer until the required code line is found.
229-
for (let i = 0; i < bytesRead; i++) {
230-
if (buffer[i] === 10 && ++lines === line) {
231-
// If the end of file is reached, directly parse the code and return.
232-
if (bytesRead < bytesPerRead) {
233-
return parseCode(buffer.toString('utf8', i + 1, bytesRead), column);
234-
}
235-
// Check if the read code is sufficient or read more until the whole
236-
// expression is read. Make sure multi byte characters are preserved
237-
// properly by using the decoder.
238-
const code = decoder.write(buffer.slice(i + 1, bytesRead));
239-
return findColumn(fd, column, code);
240-
}
241-
}
242-
}
243-
}
244-
245-
function parseCode(code, offset) {
246-
// Lazy load acorn.
247-
if (parseExpressionAt === undefined) {
248-
const Parser = require('internal/deps/acorn/acorn/dist/acorn').Parser;
249-
({ findNodeAround } = require('internal/deps/acorn/acorn-walk/dist/walk'));
250-
251-
parseExpressionAt = FunctionPrototypeBind(Parser.parseExpressionAt, Parser);
252-
tokenizer = FunctionPrototypeBind(Parser.tokenizer, Parser);
253-
}
254-
let node;
255-
let start;
256-
// Parse the read code until the correct expression is found.
257-
for (const token of tokenizer(code, { ecmaVersion: 'latest' })) {
258-
start = token.start;
259-
if (start > offset) {
260-
// No matching expression found. This could happen if the assert
261-
// expression is bigger than the provided buffer.
262-
break;
263-
}
264-
try {
265-
node = parseExpressionAt(code, start, { ecmaVersion: 'latest' });
266-
// Find the CallExpression in the tree.
267-
node = findNodeAround(node, offset, 'CallExpression');
268-
if (node?.node.end >= offset) {
269-
return [
270-
node.node.start,
271-
StringPrototypeReplace(StringPrototypeSlice(code,
272-
node.node.start, node.node.end),
273-
escapeSequencesRegExp, escapeFn),
274-
];
275-
}
276-
// eslint-disable-next-line no-unused-vars
277-
} catch (err) {
278-
continue;
279-
}
280-
}
281-
// eslint-disable-next-line no-throw-literal
282-
throw null;
283-
}
284-
285-
function getErrMessage(message, fn) {
286-
const tmpLimit = Error.stackTraceLimit;
287-
const errorStackTraceLimitIsWritable = isErrorStackTraceLimitWritable();
288-
// Make sure the limit is set to 1. Otherwise it could fail (<= 0) or it
289-
// does to much work.
290-
if (errorStackTraceLimitIsWritable) Error.stackTraceLimit = 1;
291-
// We only need the stack trace. To minimize the overhead use an object
292-
// instead of an error.
293-
const err = {};
294-
ErrorCaptureStackTrace(err, fn);
295-
if (errorStackTraceLimitIsWritable) Error.stackTraceLimit = tmpLimit;
296-
297-
overrideStackTrace.set(err, (_, stack) => stack);
298-
const call = err.stack[0];
299-
300-
let filename = call.getFileName();
301-
const line = call.getLineNumber() - 1;
302-
let column = call.getColumnNumber() - 1;
303-
let identifier;
304-
let code;
305-
306-
if (filename) {
307-
identifier = `${filename}${line}${column}`;
308-
309-
// Skip Node.js modules!
310-
if (StringPrototypeStartsWith(filename, 'node:') &&
311-
BuiltinModule.exists(StringPrototypeSlice(filename, 5))) {
312-
errorCache.set(identifier, undefined);
313-
return;
314-
}
315-
} else {
316-
return message;
317-
}
318-
319-
if (errorCache.has(identifier)) {
320-
return errorCache.get(identifier);
321-
}
322-
323-
let fd;
324-
try {
325-
// Set the stack trace limit to zero. This makes sure unexpected token
326-
// errors are handled faster.
327-
if (errorStackTraceLimitIsWritable) Error.stackTraceLimit = 0;
328-
329-
if (filename) {
330-
if (decoder === undefined) {
331-
const { StringDecoder } = require('string_decoder');
332-
decoder = new StringDecoder('utf8');
333-
}
334-
335-
// ESM file prop is a file proto. Convert that to path.
336-
// This ensure opensync will not throw ENOENT for ESM files.
337-
const fileProtoPrefix = 'file://';
338-
if (StringPrototypeStartsWith(filename, fileProtoPrefix)) {
339-
filename = fileURLToPath(filename);
340-
}
341-
342-
fd = openSync(filename, 'r', 0o666);
343-
// Reset column and message.
344-
({ 0: column, 1: message } = getCode(fd, line, column));
345-
// Flush unfinished multi byte characters.
346-
decoder.end();
347-
} else {
348-
for (let i = 0; i < line; i++) {
349-
code = StringPrototypeSlice(code,
350-
StringPrototypeIndexOf(code, '\n') + 1);
351-
}
352-
({ 0: column, 1: message } = parseCode(code, column));
353-
}
354-
// Always normalize indentation, otherwise the message could look weird.
355-
if (StringPrototypeIncludes(message, '\n')) {
356-
if (EOL === '\r\n') {
357-
message = RegExpPrototypeSymbolReplace(/\r\n/g, message, '\n');
358-
}
359-
const frames = StringPrototypeSplit(message, '\n');
360-
message = ArrayPrototypeShift(frames);
361-
for (const frame of frames) {
362-
let pos = 0;
363-
while (pos < column && (frame[pos] === ' ' || frame[pos] === '\t')) {
364-
pos++;
365-
}
366-
message += `\n ${StringPrototypeSlice(frame, pos)}`;
367-
}
368-
}
369-
message = `The expression evaluated to a falsy value:\n\n ${message}\n`;
370-
// Make sure to always set the cache! No matter if the message is
371-
// undefined or not
372-
errorCache.set(identifier, message);
373-
374-
return message;
375-
} catch {
376-
// Invalidate cache to prevent trying to read this part again.
377-
errorCache.set(identifier, undefined);
378-
} finally {
379-
// Reset limit.
380-
if (errorStackTraceLimitIsWritable) Error.stackTraceLimit = tmpLimit;
381-
if (fd !== undefined)
382-
closeSync(fd);
383-
}
384-
}
385-
386-
function innerOk(fn, argLen, value, message) {
387-
if (!value) {
388-
let generatedMessage = false;
389-
390-
if (argLen === 0) {
391-
generatedMessage = true;
392-
message = 'No value argument passed to `assert.ok()`';
393-
} else if (message == null) {
394-
generatedMessage = true;
395-
message = getErrMessage(message, fn);
396-
} else if (isError(message)) {
397-
throw message;
398-
}
399-
400-
const err = new AssertionError({
401-
actual: value,
402-
expected: true,
403-
message,
404-
operator: '==',
405-
stackStartFn: fn,
406-
});
407-
err.generatedMessage = generatedMessage;
408-
throw err;
409-
}
410-
}
411-
412145
/**
413146
* Pure assertion tests whether a value is truthy, as determined
414147
* by !!value.

‎lib/internal/assert/utils.js

+287
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,287 @@
1+
'use strict';
2+
3+
const {
4+
ArrayPrototypeShift,
5+
Error,
6+
ErrorCaptureStackTrace,
7+
FunctionPrototypeBind,
8+
RegExpPrototypeSymbolReplace,
9+
SafeMap,
10+
StringPrototypeCharCodeAt,
11+
StringPrototypeIncludes,
12+
StringPrototypeIndexOf,
13+
StringPrototypeReplace,
14+
StringPrototypeSlice,
15+
StringPrototypeSplit,
16+
StringPrototypeStartsWith,
17+
} = primordials;
18+
19+
const { Buffer } = require('buffer');
20+
const {
21+
isErrorStackTraceLimitWritable,
22+
overrideStackTrace,
23+
} = require('internal/errors');
24+
const AssertionError = require('internal/assert/assertion_error');
25+
const { openSync, closeSync, readSync } = require('fs');
26+
const { EOL } = require('internal/constants');
27+
const { BuiltinModule } = require('internal/bootstrap/realm');
28+
const { isError } = require('internal/util');
29+
30+
const errorCache = new SafeMap();
31+
const { fileURLToPath } = require('internal/url');
32+
33+
let parseExpressionAt;
34+
let findNodeAround;
35+
let tokenizer;
36+
let decoder;
37+
38+
// Escape control characters but not \n and \t to keep the line breaks and
39+
// indentation intact.
40+
// eslint-disable-next-line no-control-regex
41+
const escapeSequencesRegExp = /[\x00-\x08\x0b\x0c\x0e-\x1f]/g;
42+
const meta = [
43+
'\\u0000', '\\u0001', '\\u0002', '\\u0003', '\\u0004',
44+
'\\u0005', '\\u0006', '\\u0007', '\\b', '',
45+
'', '\\u000b', '\\f', '', '\\u000e',
46+
'\\u000f', '\\u0010', '\\u0011', '\\u0012', '\\u0013',
47+
'\\u0014', '\\u0015', '\\u0016', '\\u0017', '\\u0018',
48+
'\\u0019', '\\u001a', '\\u001b', '\\u001c', '\\u001d',
49+
'\\u001e', '\\u001f',
50+
];
51+
52+
const escapeFn = (str) => meta[StringPrototypeCharCodeAt(str, 0)];
53+
54+
function findColumn(fd, column, code) {
55+
if (code.length > column + 100) {
56+
try {
57+
return parseCode(code, column);
58+
} catch {
59+
// End recursion in case no code could be parsed. The expression should
60+
// have been found after 2500 characters, so stop trying.
61+
if (code.length - column > 2500) {
62+
// eslint-disable-next-line no-throw-literal
63+
throw null;
64+
}
65+
}
66+
}
67+
// Read up to 2500 bytes more than necessary in columns. That way we address
68+
// multi byte characters and read enough data to parse the code.
69+
const bytesToRead = column - code.length + 2500;
70+
const buffer = Buffer.allocUnsafe(bytesToRead);
71+
const bytesRead = readSync(fd, buffer, 0, bytesToRead);
72+
code += decoder.write(buffer.slice(0, bytesRead));
73+
// EOF: fast path.
74+
if (bytesRead < bytesToRead) {
75+
return parseCode(code, column);
76+
}
77+
// Read potentially missing code.
78+
return findColumn(fd, column, code);
79+
}
80+
81+
function getCode(fd, line, column) {
82+
let bytesRead = 0;
83+
if (line === 0) {
84+
// Special handle line number one. This is more efficient and simplifies the
85+
// rest of the algorithm. Read more than the regular column number in bytes
86+
// to prevent multiple reads in case multi byte characters are used.
87+
return findColumn(fd, column, '');
88+
}
89+
let lines = 0;
90+
// Prevent blocking the event loop by limiting the maximum amount of
91+
// data that may be read.
92+
let maxReads = 32; // bytesPerRead * maxReads = 512 KiB
93+
const bytesPerRead = 16384;
94+
// Use a single buffer up front that is reused until the call site is found.
95+
let buffer = Buffer.allocUnsafe(bytesPerRead);
96+
while (maxReads-- !== 0) {
97+
// Only allocate a new buffer in case the needed line is found. All data
98+
// before that can be discarded.
99+
buffer = lines < line ? buffer : Buffer.allocUnsafe(bytesPerRead);
100+
bytesRead = readSync(fd, buffer, 0, bytesPerRead);
101+
// Read the buffer until the required code line is found.
102+
for (let i = 0; i < bytesRead; i++) {
103+
if (buffer[i] === 10 && ++lines === line) {
104+
// If the end of file is reached, directly parse the code and return.
105+
if (bytesRead < bytesPerRead) {
106+
return parseCode(buffer.toString('utf8', i + 1, bytesRead), column);
107+
}
108+
// Check if the read code is sufficient or read more until the whole
109+
// expression is read. Make sure multi byte characters are preserved
110+
// properly by using the decoder.
111+
const code = decoder.write(buffer.slice(i + 1, bytesRead));
112+
return findColumn(fd, column, code);
113+
}
114+
}
115+
}
116+
}
117+
118+
function parseCode(code, offset) {
119+
// Lazy load acorn.
120+
if (parseExpressionAt === undefined) {
121+
const Parser = require('internal/deps/acorn/acorn/dist/acorn').Parser;
122+
({ findNodeAround } = require('internal/deps/acorn/acorn-walk/dist/walk'));
123+
124+
parseExpressionAt = FunctionPrototypeBind(Parser.parseExpressionAt, Parser);
125+
tokenizer = FunctionPrototypeBind(Parser.tokenizer, Parser);
126+
}
127+
let node;
128+
let start;
129+
// Parse the read code until the correct expression is found.
130+
for (const token of tokenizer(code, { ecmaVersion: 'latest' })) {
131+
start = token.start;
132+
if (start > offset) {
133+
// No matching expression found. This could happen if the assert
134+
// expression is bigger than the provided buffer.
135+
break;
136+
}
137+
try {
138+
node = parseExpressionAt(code, start, { ecmaVersion: 'latest' });
139+
// Find the CallExpression in the tree.
140+
node = findNodeAround(node, offset, 'CallExpression');
141+
if (node?.node.end >= offset) {
142+
return [
143+
node.node.start,
144+
StringPrototypeReplace(StringPrototypeSlice(code,
145+
node.node.start, node.node.end),
146+
escapeSequencesRegExp, escapeFn),
147+
];
148+
}
149+
// eslint-disable-next-line no-unused-vars
150+
} catch (err) {
151+
continue;
152+
}
153+
}
154+
// eslint-disable-next-line no-throw-literal
155+
throw null;
156+
}
157+
158+
function getErrMessage(message, fn) {
159+
const tmpLimit = Error.stackTraceLimit;
160+
const errorStackTraceLimitIsWritable = isErrorStackTraceLimitWritable();
161+
// Make sure the limit is set to 1. Otherwise it could fail (<= 0) or it
162+
// does to much work.
163+
if (errorStackTraceLimitIsWritable) Error.stackTraceLimit = 1;
164+
// We only need the stack trace. To minimize the overhead use an object
165+
// instead of an error.
166+
const err = {};
167+
ErrorCaptureStackTrace(err, fn);
168+
if (errorStackTraceLimitIsWritable) Error.stackTraceLimit = tmpLimit;
169+
170+
overrideStackTrace.set(err, (_, stack) => stack);
171+
const call = err.stack[0];
172+
173+
let filename = call.getFileName();
174+
const line = call.getLineNumber() - 1;
175+
let column = call.getColumnNumber() - 1;
176+
let identifier;
177+
let code;
178+
179+
if (filename) {
180+
identifier = `${filename}${line}${column}`;
181+
182+
// Skip Node.js modules!
183+
if (StringPrototypeStartsWith(filename, 'node:') &&
184+
BuiltinModule.exists(StringPrototypeSlice(filename, 5))) {
185+
errorCache.set(identifier, undefined);
186+
return;
187+
}
188+
} else {
189+
return message;
190+
}
191+
192+
if (errorCache.has(identifier)) {
193+
return errorCache.get(identifier);
194+
}
195+
196+
let fd;
197+
try {
198+
// Set the stack trace limit to zero. This makes sure unexpected token
199+
// errors are handled faster.
200+
if (errorStackTraceLimitIsWritable) Error.stackTraceLimit = 0;
201+
202+
if (filename) {
203+
if (decoder === undefined) {
204+
const { StringDecoder } = require('string_decoder');
205+
decoder = new StringDecoder('utf8');
206+
}
207+
208+
// ESM file prop is a file proto. Convert that to path.
209+
// This ensure opensync will not throw ENOENT for ESM files.
210+
const fileProtoPrefix = 'file://';
211+
if (StringPrototypeStartsWith(filename, fileProtoPrefix)) {
212+
filename = fileURLToPath(filename);
213+
}
214+
215+
fd = openSync(filename, 'r', 0o666);
216+
// Reset column and message.
217+
({ 0: column, 1: message } = getCode(fd, line, column));
218+
// Flush unfinished multi byte characters.
219+
decoder.end();
220+
} else {
221+
for (let i = 0; i < line; i++) {
222+
code = StringPrototypeSlice(code,
223+
StringPrototypeIndexOf(code, '\n') + 1);
224+
}
225+
({ 0: column, 1: message } = parseCode(code, column));
226+
}
227+
// Always normalize indentation, otherwise the message could look weird.
228+
if (StringPrototypeIncludes(message, '\n')) {
229+
if (EOL === '\r\n') {
230+
message = RegExpPrototypeSymbolReplace(/\r\n/g, message, '\n');
231+
}
232+
const frames = StringPrototypeSplit(message, '\n');
233+
message = ArrayPrototypeShift(frames);
234+
for (const frame of frames) {
235+
let pos = 0;
236+
while (pos < column && (frame[pos] === ' ' || frame[pos] === '\t')) {
237+
pos++;
238+
}
239+
message += `\n ${StringPrototypeSlice(frame, pos)}`;
240+
}
241+
}
242+
message = `The expression evaluated to a falsy value:\n\n ${message}\n`;
243+
// Make sure to always set the cache! No matter if the message is
244+
// undefined or not
245+
errorCache.set(identifier, message);
246+
247+
return message;
248+
} catch {
249+
// Invalidate cache to prevent trying to read this part again.
250+
errorCache.set(identifier, undefined);
251+
} finally {
252+
// Reset limit.
253+
if (errorStackTraceLimitIsWritable) Error.stackTraceLimit = tmpLimit;
254+
if (fd !== undefined)
255+
closeSync(fd);
256+
}
257+
}
258+
259+
function innerOk(fn, argLen, value, message) {
260+
if (!value) {
261+
let generatedMessage = false;
262+
263+
if (argLen === 0) {
264+
generatedMessage = true;
265+
message = 'No value argument passed to `assert.ok()`';
266+
} else if (message == null) {
267+
generatedMessage = true;
268+
message = getErrMessage(message, fn);
269+
} else if (isError(message)) {
270+
throw message;
271+
}
272+
273+
const err = new AssertionError({
274+
actual: value,
275+
expected: true,
276+
message,
277+
operator: '==',
278+
stackStartFn: fn,
279+
});
280+
err.generatedMessage = generatedMessage;
281+
throw err;
282+
}
283+
}
284+
285+
module.exports = {
286+
innerOk,
287+
};

‎lib/internal/test_runner/test.js

+11-1
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ const { setTimeout } = require('timers');
6161
const { TIMEOUT_MAX } = require('internal/timers');
6262
const { fileURLToPath } = require('internal/url');
6363
const { availableParallelism } = require('os');
64+
const { innerOk } = require('internal/assert/utils');
6465
const { bigint: hrtime } = process.hrtime;
6566
const kCallbackAndPromisePresent = 'callbackAndPromisePresent';
6667
const kCancelledByParent = 'cancelledByParent';
@@ -117,7 +118,6 @@ function lazyAssertObject() {
117118
'notDeepStrictEqual',
118119
'notEqual',
119120
'notStrictEqual',
120-
'ok',
121121
'rejects',
122122
'strictEqual',
123123
'throws',
@@ -243,6 +243,16 @@ class TestContext {
243243
return ReflectApply(method, assert, args);
244244
};
245245
});
246+
247+
// This is a hack. It allows the innerOk function to collect the stacktrace from the correct starting point.
248+
function ok(...args) {
249+
if (plan !== null) {
250+
plan.actual++;
251+
}
252+
innerOk(ok, args.length, ...args);
253+
}
254+
255+
assert.ok = ok;
246256
}
247257
return this.#assert;
248258
}

‎test/parallel/test-runner-assert.js

+4
Original file line numberDiff line numberDiff line change
@@ -24,3 +24,7 @@ test('only methods from node:assert are on t.assert', (t) => {
2424
'throws',
2525
]);
2626
});
27+
28+
test('t.assert.ok correctly parses the stacktrace', (t) => {
29+
t.assert.throws(() => t.assert.ok(1 === 2), /t\.assert\.ok\(1 === 2\)/);
30+
});

0 commit comments

Comments
 (0)
Please sign in to comment.