Skip to content

Commit eb837ce

Browse files
MoLowRafaelGSS
authored andcommitted
test_runner: stringify AssertError expected and actual
PR-URL: #47088 Fixes: #47075 Reviewed-By: Colin Ihrig <[email protected]>
1 parent 2cea7d8 commit eb837ce

7 files changed

+173
-29
lines changed

lib/internal/test_runner/reporter/tap.js

+21-9
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

lib/internal/test_runner/yaml_to_js.js

+19-8
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);

test/message/test_runner_output.js

+14
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+
});

test/message/test_runner_output.out

+35-4
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

test/message/test_runner_output_cli.out

+35-4
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
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+

test/message/test_runner_output_spec_reporter.out

+47-3
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)