Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit 1d453e4

Browse filesBrowse files
committed
test_runner: stringify AssertError expected and actual
PR-URL: #47088 Fixes: #47075 Reviewed-By: Colin Ihrig <cjihrig@gmail.com>
1 parent 44f08ed commit 1d453e4
Copy full SHA for 1d453e4

File tree

Expand file treeCollapse file tree

7 files changed

+173
-29
lines changed
Open diff view settings
Filter options
Expand file treeCollapse file tree

7 files changed

+173
-29
lines changed
Open diff view settings
Collapse file

‎lib/internal/test_runner/reporter/tap.js‎

Copy file name to clipboardExpand all lines: lib/internal/test_runner/reporter/tap.js
+21-9Lines changed: 21 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ const {
66
ObjectEntries,
77
RegExpPrototypeSymbolReplace,
88
SafeMap,
9+
SafeSet,
910
StringPrototypeReplaceAll,
1011
StringPrototypeSplit,
1112
StringPrototypeRepeat,
@@ -79,7 +80,7 @@ function reportDetails(nesting, data = kEmptyObject) {
7980

8081
details += jsToYaml(_indent, 'duration_ms', duration_ms);
8182
details += jsToYaml(_indent, 'type', data.type);
82-
details += jsToYaml(_indent, null, error);
83+
details += jsToYaml(_indent, null, error, new SafeSet());
8384
details += `${_indent} ...\n`;
8485
return details;
8586
}
@@ -109,7 +110,7 @@ function tapEscape(input) {
109110
return result;
110111
}
111112

112-
function jsToYaml(indent, name, value) {
113+
function jsToYaml(indent, name, value, seen) {
113114
if (value === null || value === undefined) {
114115
return '';
115116
}
@@ -136,18 +137,29 @@ function jsToYaml(indent, name, value) {
136137
return str;
137138
}
138139

140+
seen.add(value);
139141
const entries = ObjectEntries(value);
140142
const isErrorObj = isError(value);
141143
let result = '';
144+
let propsIndent = indent;
145+
146+
if (name != null) {
147+
result += `${indent} ${name}:\n`;
148+
propsIndent += ' ';
149+
}
142150

143151
for (let i = 0; i < entries.length; i++) {
144152
const { 0: key, 1: value } = entries[i];
145153

146154
if (isErrorObj && (key === 'cause' || key === 'code')) {
147155
continue;
148156
}
157+
if (seen.has(value)) {
158+
result += `${propsIndent} ${key}: <Circular>\n`;
159+
continue;
160+
}
149161

150-
result += jsToYaml(indent, key, value);
162+
result += jsToYaml(propsIndent, key, value, seen);
151163
}
152164

153165
if (isErrorObj) {
@@ -189,20 +201,20 @@ function jsToYaml(indent, name, value) {
189201
}
190202
}
191203

192-
result += jsToYaml(indent, 'error', errMsg);
204+
result += jsToYaml(indent, 'error', errMsg, seen);
193205

194206
if (errCode) {
195-
result += jsToYaml(indent, 'code', errCode);
207+
result += jsToYaml(indent, 'code', errCode, seen);
196208
}
197209
if (errName && errName !== 'Error') {
198-
result += jsToYaml(indent, 'name', errName);
210+
result += jsToYaml(indent, 'name', errName, seen);
199211
}
200212

201213
if (errIsAssertion) {
202-
result += jsToYaml(indent, 'expected', errExpected);
203-
result += jsToYaml(indent, 'actual', errActual);
214+
result += jsToYaml(indent, 'expected', errExpected, seen);
215+
result += jsToYaml(indent, 'actual', errActual, seen);
204216
if (errOperator) {
205-
result += jsToYaml(indent, 'operator', errOperator);
217+
result += jsToYaml(indent, 'operator', errOperator, seen);
206218
}
207219
}
208220

Collapse file

‎lib/internal/test_runner/yaml_to_js.js‎

Copy file name to clipboardExpand all lines: lib/internal/test_runner/yaml_to_js.js
+19-8Lines changed: 19 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ const {
1919
StringPrototypeSubstring,
2020
} = primordials;
2121

22-
const kYamlKeyRegex = /^(\s+)?(\w+):(\s)+([>|][-+])?(.*)$/;
22+
const kYamlKeyRegex = /^(\s+)?(\w+):(\s)*([>|][-+])?(.*)$/;
2323
const kStackDelimiter = ' at ';
2424

2525
function reConstructError(parsedYaml) {
@@ -91,28 +91,39 @@ function YAMLToJs(lines) {
9191
return undefined;
9292
}
9393
const result = { __proto__: null };
94+
let context = { __proto__: null, object: result, indent: 0, currentKey: null };
9495
let isInYamlBlock = false;
9596
for (let i = 0; i < lines.length; i++) {
9697
const line = lines[i];
9798
if (isInYamlBlock && !StringPrototypeStartsWith(line, StringPrototypeRepeat(' ', isInYamlBlock.indent))) {
98-
result[isInYamlBlock.key] = isInYamlBlock.key === 'stack' ?
99-
result[isInYamlBlock.key] : ArrayPrototypeJoin(result[isInYamlBlock.key], '\n');
99+
context.object[isInYamlBlock.key] = isInYamlBlock.key === 'stack' ?
100+
context.object[isInYamlBlock.key] : ArrayPrototypeJoin(context.object[isInYamlBlock.key], '\n');
100101
isInYamlBlock = false;
101102
}
102103
if (isInYamlBlock) {
103104
const blockLine = StringPrototypeSubstring(line, isInYamlBlock.indent);
104-
ArrayPrototypePush(result[isInYamlBlock.key], blockLine);
105+
ArrayPrototypePush(context.object[isInYamlBlock.key], blockLine);
105106
continue;
106107
}
107108
const match = RegExpPrototypeExec(kYamlKeyRegex, line);
108109
if (match !== null) {
109110
const { 1: leadingSpaces, 2: key, 4: block, 5: value } = match;
111+
const indent = leadingSpaces?.length ?? 0;
110112
if (block) {
111-
isInYamlBlock = { key, indent: (leadingSpaces?.length ?? 0) + 2 };
112-
result[key] = [];
113-
} else {
114-
result[key] = getYamlValue(value);
113+
isInYamlBlock = { key, indent: indent + 2 };
114+
context.object[key] = [];
115+
continue;
115116
}
117+
118+
if (indent > context.indent) {
119+
context.object[context.currentKey] ||= {};
120+
context = { __proto__: null, parent: context, object: context.object[context.currentKey], indent };
121+
} else if (indent < context.indent) {
122+
context = context.parent;
123+
}
124+
125+
context.currentKey = key;
126+
context.object[key] = getYamlValue(value);
116127
}
117128
}
118129
return reConstructError(result);
Collapse file

‎test/message/test_runner_output.js‎

Copy file name to clipboardExpand all lines: test/message/test_runner_output.js
+14Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -389,3 +389,17 @@ test('unfinished test with unhandledRejection', async () => {
389389
setImmediate(() => {
390390
throw new Error('uncaught from outside of a test');
391391
});
392+
393+
test('assertion errors display actual and expected properly', async () => {
394+
// Make sure the assert module is handled.
395+
const circular = { bar: 2 };
396+
circular.c = circular;
397+
const tmpLimit = Error.stackTraceLimit;
398+
Error.stackTraceLimit = 1;
399+
try {
400+
assert.deepEqual({ foo: 1, bar: 1 }, circular); // eslint-disable-line no-restricted-properties
401+
} catch (err) {
402+
Error.stackTraceLimit = tmpLimit;
403+
throw err;
404+
}
405+
});
Collapse file

‎test/message/test_runner_output.out‎

Copy file name to clipboardExpand all lines: test/message/test_runner_output.out
+35-4Lines changed: 35 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -624,8 +624,39 @@ not ok 64 - unfinished test with unhandledRejection
624624
*
625625
*
626626
...
627+
# Subtest: assertion errors display actual and expected properly
628+
not ok 65 - assertion errors display actual and expected properly
629+
---
630+
duration_ms: *
631+
failureType: 'testCodeFailure'
632+
error: |-
633+
Expected values to be loosely deep-equal:
634+
635+
{
636+
bar: 1,
637+
foo: 1
638+
}
639+
640+
should loosely deep-equal
641+
642+
<ref *1> {
643+
bar: 2,
644+
c: [Circular *1]
645+
}
646+
code: 'ERR_ASSERTION'
647+
name: 'AssertionError'
648+
expected:
649+
bar: 2
650+
c: <Circular>
651+
actual:
652+
foo: 1
653+
bar: 1
654+
operator: 'deepEqual'
655+
stack: |-
656+
*
657+
...
627658
# Subtest: invalid subtest fail
628-
not ok 65 - invalid subtest fail
659+
not ok 66 - invalid subtest fail
629660
---
630661
duration_ms: *
631662
failureType: 'parentAlreadyFinished'
@@ -634,18 +665,18 @@ not ok 65 - invalid subtest fail
634665
stack: |-
635666
*
636667
...
637-
1..65
668+
1..66
638669
# Warning: Test "unhandled rejection - passes but warns" generated asynchronous activity after the test ended. This activity created the error "Error: rejected from unhandled rejection fail" and would have caused the test to fail, but instead triggered an unhandledRejection event.
639670
# Warning: Test "async unhandled rejection - passes but warns" generated asynchronous activity after the test ended. This activity created the error "Error: rejected from async unhandled rejection fail" and would have caused the test to fail, but instead triggered an unhandledRejection event.
640671
# Warning: A resource generated asynchronous activity after the test ended. This activity created the error "Error: uncaught from outside of a test" which triggered an uncaughtException event, caught by the test runner.
641672
# Warning: Test "immediate throw - passes but warns" generated asynchronous activity after the test ended. This activity created the error "Error: thrown from immediate throw fail" and would have caused the test to fail, but instead triggered an uncaughtException event.
642673
# Warning: Test "immediate reject - passes but warns" generated asynchronous activity after the test ended. This activity created the error "Error: rejected from immediate reject fail" and would have caused the test to fail, but instead triggered an unhandledRejection event.
643674
# Warning: Test "callback called twice in different ticks" generated asynchronous activity after the test ended. This activity created the error "Error [ERR_TEST_FAILURE]: callback invoked multiple times" and would have caused the test to fail, but instead triggered an uncaughtException event.
644675
# Warning: Test "callback async throw after done" generated asynchronous activity after the test ended. This activity created the error "Error: thrown from callback async throw after done" and would have caused the test to fail, but instead triggered an uncaughtException event.
645-
# tests 79
676+
# tests 80
646677
# suites 0
647678
# pass 37
648-
# fail 24
679+
# fail 25
649680
# cancelled 3
650681
# skipped 10
651682
# todo 5
Collapse file

‎test/message/test_runner_output_cli.out‎

Copy file name to clipboardExpand all lines: test/message/test_runner_output_cli.out
+35-4Lines changed: 35 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -624,8 +624,39 @@ not ok 64 - unfinished test with unhandledRejection
624624
*
625625
*
626626
...
627+
# Subtest: assertion errors display actual and expected properly
628+
not ok 65 - assertion errors display actual and expected properly
629+
---
630+
duration_ms: *
631+
failureType: 'testCodeFailure'
632+
error: |-
633+
Expected values to be loosely deep-equal:
634+
635+
{
636+
bar: 1,
637+
foo: 1
638+
}
639+
640+
should loosely deep-equal
641+
642+
<ref *1> {
643+
bar: 2,
644+
c: [Circular *1]
645+
}
646+
code: 'ERR_ASSERTION'
647+
name: 'AssertionError'
648+
expected:
649+
bar: 2
650+
c: '<Circular>'
651+
actual:
652+
foo: 1
653+
bar: 1
654+
operator: 'deepEqual'
655+
stack: |-
656+
*
657+
...
627658
# Subtest: invalid subtest fail
628-
not ok 65 - invalid subtest fail
659+
not ok 66 - invalid subtest fail
629660
---
630661
duration_ms: *
631662
failureType: 'parentAlreadyFinished'
@@ -641,11 +672,11 @@ not ok 65 - invalid subtest fail
641672
# Warning: Test "immediate reject - passes but warns" generated asynchronous activity after the test ended. This activity created the error "Error: rejected from immediate reject fail" and would have caused the test to fail, but instead triggered an unhandledRejection event.
642673
# Warning: Test "callback called twice in different ticks" generated asynchronous activity after the test ended. This activity created the error "Error [ERR_TEST_FAILURE]: callback invoked multiple times" and would have caused the test to fail, but instead triggered an uncaughtException event.
643674
# Warning: Test "callback async throw after done" generated asynchronous activity after the test ended. This activity created the error "Error: thrown from callback async throw after done" and would have caused the test to fail, but instead triggered an uncaughtException event.
644-
1..65
645-
# tests 79
675+
1..66
676+
# tests 80
646677
# suites 0
647678
# pass 37
648-
# fail 24
679+
# fail 25
649680
# cancelled 3
650681
# skipped 10
651682
# todo 5
Collapse file
+2-1Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
..XX...X..XXX.X.....
22
XXX.....X..X...X....
33
.........X...XXX.XX.
4-
.....XXXXXXX...XXXX
4+
.....XXXXXXX...XXXXX
5+
Collapse file

‎test/message/test_runner_output_spec_reporter.out‎

Copy file name to clipboardExpand all lines: test/message/test_runner_output_spec_reporter.out
+47-3Lines changed: 47 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -184,7 +184,7 @@
184184
callback called twice in different ticks (*ms)
185185
callback called twice in future tick (*ms)
186186
Error [ERR_TEST_FAILURE]: callback invoked multiple times
187-
*
187+
* {
188188
failureType: 'multipleCallbackInvocations',
189189
cause: 'callback invoked multiple times',
190190
code: 'ERR_TEST_FAILURE'
@@ -265,6 +265,28 @@
265265
*
266266
*
267267

268+
assertion errors display actual and expected properly (*ms)
269+
AssertionError [ERR_ASSERTION]: Expected values to be loosely deep-equal:
270+
271+
{
272+
bar: 1,
273+
foo: 1
274+
}
275+
276+
should loosely deep-equal
277+
278+
<ref *1> {
279+
bar: 2,
280+
c: [Circular *1]
281+
}
282+
* {
283+
generatedMessage: true,
284+
code: 'ERR_ASSERTION',
285+
actual: [Object],
286+
expected: [Object],
287+
operator: 'deepEqual'
288+
}
289+
268290
invalid subtest fail (*ms)
269291
'test could not be started because its parent finished'
270292

@@ -275,10 +297,10 @@
275297
Warning: Test "immediate reject - passes but warns" generated asynchronous activity after the test ended. This activity created the error "Error: rejected from immediate reject fail" and would have caused the test to fail, but instead triggered an unhandledRejection event.
276298
Warning: Test "callback called twice in different ticks" generated asynchronous activity after the test ended. This activity created the error "Error [ERR_TEST_FAILURE]: callback invoked multiple times" and would have caused the test to fail, but instead triggered an uncaughtException event.
277299
Warning: Test "callback async throw after done" generated asynchronous activity after the test ended. This activity created the error "Error: thrown from callback async throw after done" and would have caused the test to fail, but instead triggered an uncaughtException event.
278-
tests 79
300+
tests 80
279301
suites 0
280302
pass 37
281-
fail 24
303+
fail 25
282304
cancelled 3
283305
skipped 10
284306
todo 5
@@ -490,5 +512,27 @@
490512
*
491513
*
492514

515+
assertion errors display actual and expected properly (*ms)
516+
AssertionError [ERR_ASSERTION]: Expected values to be loosely deep-equal:
517+
518+
{
519+
bar: 1,
520+
foo: 1
521+
}
522+
523+
should loosely deep-equal
524+
525+
<ref *1> {
526+
bar: 2,
527+
c: [Circular *1]
528+
}
529+
* {
530+
generatedMessage: true,
531+
code: 'ERR_ASSERTION',
532+
actual: [Object],
533+
expected: [Object],
534+
operator: 'deepEqual'
535+
}
536+
493537
invalid subtest fail (*ms)
494538
'test could not be started because its parent finished'

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.