Skip to content

Commit b540361

Browse files
committed
test_runner: stringify AssertError expected and actual
1 parent ca5c602 commit b540361

File tree

7 files changed

+194
-29
lines changed

7 files changed

+194
-29
lines changed

lib/internal/test_runner/reporter/tap.js

Lines changed: 22 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ const {
77
ObjectEntries,
88
RegExpPrototypeSymbolReplace,
99
SafeMap,
10+
SafeSet,
1011
StringPrototypeReplaceAll,
1112
StringPrototypeSplit,
1213
StringPrototypeRepeat,
@@ -112,8 +113,8 @@ function reportDetails(nesting, data = kEmptyObject) {
112113
const _indent = indent(nesting);
113114
let details = `${_indent} ---\n`;
114115

115-
details += jsToYaml(_indent, 'duration_ms', duration_ms);
116-
details += jsToYaml(_indent, 'type', data.type);
116+
details += jsToYaml(_indent, 'duration_ms', duration_ms, null);
117+
details += jsToYaml(_indent, 'type', data.type, null);
117118
details += jsToYaml(_indent, null, error);
118119
details += `${_indent} ...\n`;
119120
return details;
@@ -144,7 +145,7 @@ function tapEscape(input) {
144145
return result;
145146
}
146147

147-
function jsToYaml(indent, name, value) {
148+
function jsToYaml(indent, name, value, seen = new SafeSet()) {
148149
if (value === null || value === undefined) {
149150
return '';
150151
}
@@ -171,18 +172,29 @@ function jsToYaml(indent, name, value) {
171172
return str;
172173
}
173174

175+
seen.add(value);
174176
const entries = ObjectEntries(value);
175177
const isErrorObj = isError(value);
176178
let result = '';
179+
let propsIndent = indent;
180+
181+
if (name != null) {
182+
result += `${indent} ${name}:\n`;
183+
propsIndent += ' ';
184+
}
177185

178186
for (let i = 0; i < entries.length; i++) {
179187
const { 0: key, 1: value } = entries[i];
180188

181189
if (isErrorObj && (key === 'cause' || key === 'code')) {
182190
continue;
183191
}
192+
if (seen.has(value)) {
193+
result += `${propsIndent} ${key}: <Circular>\n`;
194+
continue;
195+
}
184196

185-
result += jsToYaml(indent, key, value);
197+
result += jsToYaml(propsIndent, key, value, seen);
186198
}
187199

188200
if (isErrorObj) {
@@ -224,20 +236,20 @@ function jsToYaml(indent, name, value) {
224236
}
225237
}
226238

227-
result += jsToYaml(indent, 'error', errMsg);
239+
result += jsToYaml(indent, 'error', errMsg, seen);
228240

229241
if (errCode) {
230-
result += jsToYaml(indent, 'code', errCode);
242+
result += jsToYaml(indent, 'code', errCode, seen);
231243
}
232244
if (errName && errName !== 'Error') {
233-
result += jsToYaml(indent, 'name', errName);
245+
result += jsToYaml(indent, 'name', errName, seen);
234246
}
235247

236248
if (errIsAssertion) {
237-
result += jsToYaml(indent, 'expected', errExpected);
238-
result += jsToYaml(indent, 'actual', errActual);
249+
result += jsToYaml(indent, 'expected', errExpected, seen);
250+
result += jsToYaml(indent, 'actual', errActual, seen);
239251
if (errOperator) {
240-
result += jsToYaml(indent, 'operator', errOperator);
252+
result += jsToYaml(indent, 'operator', errOperator, seen);
241253
}
242254
}
243255

lib/internal/test_runner/yaml_to_js.js

Lines 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);

test/message/test_runner_output.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -389,3 +389,10 @@ 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+
assert.deepEqual({ foo: 1, bar: 1 }, circular); // eslint-disable-line no-restricted-properties
398+
});

test/message/test_runner_output.out

Lines changed: 42 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -624,8 +624,46 @@ 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+
*
658+
*
659+
*
660+
*
661+
*
662+
*
663+
*
664+
...
627665
# Subtest: invalid subtest fail
628-
not ok 65 - invalid subtest fail
666+
not ok 66 - invalid subtest fail
629667
---
630668
duration_ms: *
631669
failureType: 'parentAlreadyFinished'
@@ -634,18 +672,18 @@ not ok 65 - invalid subtest fail
634672
stack: |-
635673
*
636674
...
637-
1..65
675+
1..66
638676
# 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.
639677
# 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.
640678
# 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.
641679
# 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.
642680
# 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.
643681
# 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.
644682
# 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
683+
# tests 80
646684
# suites 0
647685
# pass 37
648-
# fail 24
686+
# fail 25
649687
# cancelled 3
650688
# skipped 10
651689
# todo 5

test/message/test_runner_output_cli.out

Lines changed: 42 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -624,8 +624,46 @@ 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+
*
658+
*
659+
*
660+
*
661+
*
662+
*
663+
*
664+
...
627665
# Subtest: invalid subtest fail
628-
not ok 65 - invalid subtest fail
666+
not ok 66 - invalid subtest fail
629667
---
630668
duration_ms: *
631669
failureType: 'parentAlreadyFinished'
@@ -641,11 +679,11 @@ not ok 65 - invalid subtest fail
641679
# 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.
642680
# 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.
643681
# 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
682+
1..66
683+
# tests 80
646684
# suites 0
647685
# pass 37
648-
# fail 24
686+
# fail 25
649687
# cancelled 3
650688
# skipped 10
651689
# todo 5
Lines 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+

test/message/test_runner_output_spec_reporter.out

Lines changed: 60 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -265,6 +265,35 @@
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+
*
284+
*
285+
*
286+
*
287+
*
288+
*
289+
* {
290+
generatedMessage: true,
291+
code: 'ERR_ASSERTION',
292+
actual: [Object],
293+
expected: [Object],
294+
operator: 'deepEqual'
295+
}
296+
268297
invalid subtest fail (*ms)
269298
'test could not be started because its parent finished'
270299

@@ -275,10 +304,10 @@
275304
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.
276305
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.
277306
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
307+
tests 80
279308
suites 0
280309
pass 37
281-
fail 24
310+
fail 25
282311
cancelled 3
283312
skipped 10
284313
todo 5
@@ -490,5 +519,34 @@
490519
*
491520
*
492521

522+
assertion errors display actual and expected properly (*ms)
523+
AssertionError [ERR_ASSERTION]: Expected values to be loosely deep-equal:
524+
525+
{
526+
bar: 1,
527+
foo: 1
528+
}
529+
530+
should loosely deep-equal
531+
532+
<ref *1> {
533+
bar: 2,
534+
c: [Circular *1]
535+
}
536+
*
537+
*
538+
*
539+
*
540+
*
541+
*
542+
*
543+
* {
544+
generatedMessage: true,
545+
code: 'ERR_ASSERTION',
546+
actual: [Object],
547+
expected: [Object],
548+
operator: 'deepEqual'
549+
}
550+
493551
invalid subtest fail (*ms)
494552
'test could not be started because its parent finished'

0 commit comments

Comments
 (0)