GeezerVison is showing github.com — test_runner: stringify AssertError expected and actual · nodejs/node@eb837ce
File treeExpand file treeCollapse file tree Open diff view settings Expand file treeCollapse file tree Open diff view settings Collapse file Copy file name to clipboardExpand all lines: lib/internal/test_runner/reporter/tap.js +21-9Lines changed: 21 additions & 9 deletions | Original file line number | Diff line number | Diff line change |
|---|
| 6 | 6 | ObjectEntries, | 7 | 7 | RegExpPrototypeSymbolReplace, | 8 | 8 | SafeMap, | | 9 | + SafeSet, | 9 | 10 | StringPrototypeReplaceAll, | 10 | 11 | StringPrototypeSplit, | 11 | 12 | StringPrototypeRepeat, | @@ -79,7 +80,7 @@ function reportDetails(nesting, data = kEmptyObject) { | 79 | 80 |
| 80 | 81 | details += jsToYaml(_indent, 'duration_ms', duration_ms); | 81 | 82 | details += jsToYaml(_indent, 'type', data.type); | 82 | | - details += jsToYaml(_indent, null, error); | | 83 | + details += jsToYaml(_indent, null, error, new SafeSet()); | 83 | 84 | details += `${_indent} ...\n`; | 84 | 85 | return details; | 85 | 86 | } | @@ -109,7 +110,7 @@ function tapEscape(input) { | 109 | 110 | return result; | 110 | 111 | } | 111 | 112 |
| 112 | | -function jsToYaml(indent, name, value) { | | 113 | +function jsToYaml(indent, name, value, seen) { | 113 | 114 | if (value === null || value === undefined) { | 114 | 115 | return ''; | 115 | 116 | } | @@ -136,18 +137,29 @@ function jsToYaml(indent, name, value) { | 136 | 137 | return str; | 137 | 138 | } | 138 | 139 |
| | 140 | + seen.add(value); | 139 | 141 | const entries = ObjectEntries(value); | 140 | 142 | const isErrorObj = isError(value); | 141 | 143 | let result = ''; | | 144 | + let propsIndent = indent; | | 145 | + | | 146 | + if (name != null) { | | 147 | + result += `${indent} ${name}:\n`; | | 148 | + propsIndent += ' '; | | 149 | + } | 142 | 150 |
| 143 | 151 | for (let i = 0; i < entries.length; i++) { | 144 | 152 | const { 0: key, 1: value } = entries[i]; | 145 | 153 |
| 146 | 154 | if (isErrorObj && (key === 'cause' || key === 'code')) { | 147 | 155 | continue; | 148 | 156 | } | | 157 | + if (seen.has(value)) { | | 158 | + result += `${propsIndent} ${key}: <Circular>\n`; | | 159 | + continue; | | 160 | + } | 149 | 161 |
| 150 | | - result += jsToYaml(indent, key, value); | | 162 | + result += jsToYaml(propsIndent, key, value, seen); | 151 | 163 | } | 152 | 164 |
| 153 | 165 | if (isErrorObj) { | @@ -189,20 +201,20 @@ function jsToYaml(indent, name, value) { | 189 | 201 | } | 190 | 202 | } | 191 | 203 |
| 192 | | - result += jsToYaml(indent, 'error', errMsg); | | 204 | + result += jsToYaml(indent, 'error', errMsg, seen); | 193 | 205 |
| 194 | 206 | if (errCode) { | 195 | | - result += jsToYaml(indent, 'code', errCode); | | 207 | + result += jsToYaml(indent, 'code', errCode, seen); | 196 | 208 | } | 197 | 209 | if (errName && errName !== 'Error') { | 198 | | - result += jsToYaml(indent, 'name', errName); | | 210 | + result += jsToYaml(indent, 'name', errName, seen); | 199 | 211 | } | 200 | 212 |
| 201 | 213 | 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); | 204 | 216 | if (errOperator) { | 205 | | - result += jsToYaml(indent, 'operator', errOperator); | | 217 | + result += jsToYaml(indent, 'operator', errOperator, seen); | 206 | 218 | } | 207 | 219 | } | 208 | 220 |
| |
Collapse file 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 number | Diff line number | Diff line change |
|---|
@@ -19,7 +19,7 @@ const { | 19 | 19 | StringPrototypeSubstring, | 20 | 20 | } = primordials; | 21 | 21 |
| 22 | | -const kYamlKeyRegex = /^(\s+)?(\w+):(\s)+([>|][-+])?(.*)$/; | | 22 | +const kYamlKeyRegex = /^(\s+)?(\w+):(\s)*([>|][-+])?(.*)$/; | 23 | 23 | const kStackDelimiter = ' at '; | 24 | 24 |
| 25 | 25 | function reConstructError(parsedYaml) { | @@ -91,28 +91,39 @@ function YAMLToJs(lines) { | 91 | 91 | return undefined; | 92 | 92 | } | 93 | 93 | const result = { __proto__: null }; | | 94 | + let context = { __proto__: null, object: result, indent: 0, currentKey: null }; | 94 | 95 | let isInYamlBlock = false; | 95 | 96 | for (let i = 0; i < lines.length; i++) { | 96 | 97 | const line = lines[i]; | 97 | 98 | 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'); | 100 | 101 | isInYamlBlock = false; | 101 | 102 | } | 102 | 103 | if (isInYamlBlock) { | 103 | 104 | const blockLine = StringPrototypeSubstring(line, isInYamlBlock.indent); | 104 | | - ArrayPrototypePush(result[isInYamlBlock.key], blockLine); | | 105 | + ArrayPrototypePush(context.object[isInYamlBlock.key], blockLine); | 105 | 106 | continue; | 106 | 107 | } | 107 | 108 | const match = RegExpPrototypeExec(kYamlKeyRegex, line); | 108 | 109 | if (match !== null) { | 109 | 110 | const { 1: leadingSpaces, 2: key, 4: block, 5: value } = match; | | 111 | + const indent = leadingSpaces?.length ?? 0; | 110 | 112 | 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; | 115 | 116 | } | | 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); | 116 | 127 | } | 117 | 128 | } | 118 | 129 | return reConstructError(result); | |
Collapse file Copy file name to clipboardExpand all lines: test/message/test_runner_output.js +14Lines changed: 14 additions & 0 deletions | Original file line number | Diff line number | Diff line change |
|---|
@@ -389,3 +389,17 @@ test('unfinished test with unhandledRejection', async () => { | 389 | 389 | setImmediate(() => { | 390 | 390 | throw new Error('uncaught from outside of a test'); | 391 | 391 | }); | | 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 Copy file name to clipboardExpand all lines: test/message/test_runner_output.out +35-4Lines changed: 35 additions & 4 deletions | Original file line number | Diff line number | Diff line change |
|---|
@@ -624,8 +624,39 @@ not ok 64 - unfinished test with unhandledRejection | 624 | 624 | * | 625 | 625 | * | 626 | 626 | ... | | 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 | + ... | 627 | 658 | # Subtest: invalid subtest fail | 628 | | -not ok 65 - invalid subtest fail | | 659 | +not ok 66 - invalid subtest fail | 629 | 660 | --- | 630 | 661 | duration_ms: * | 631 | 662 | failureType: 'parentAlreadyFinished' | @@ -634,18 +665,18 @@ not ok 65 - invalid subtest fail | 634 | 665 | stack: |- | 635 | 666 | * | 636 | 667 | ... | 637 | | -1..65 | | 668 | +1..66 | 638 | 669 | # 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. | 639 | 670 | # 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. | 640 | 671 | # 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. | 641 | 672 | # 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. | 642 | 673 | # 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. | 643 | 674 | # 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. | 644 | 675 | # 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 | 646 | 677 | # suites 0 | 647 | 678 | # pass 37 | 648 | | -# fail 24 | | 679 | +# fail 25 | 649 | 680 | # cancelled 3 | 650 | 681 | # skipped 10 | 651 | 682 | # todo 5 | |
Collapse file Copy file name to clipboardExpand all lines: test/message/test_runner_output_cli.out +35-4Lines changed: 35 additions & 4 deletions | Original file line number | Diff line number | Diff line change |
|---|
@@ -624,8 +624,39 @@ not ok 64 - unfinished test with unhandledRejection | 624 | 624 | * | 625 | 625 | * | 626 | 626 | ... | | 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 | + ... | 627 | 658 | # Subtest: invalid subtest fail | 628 | | -not ok 65 - invalid subtest fail | | 659 | +not ok 66 - invalid subtest fail | 629 | 660 | --- | 630 | 661 | duration_ms: * | 631 | 662 | failureType: 'parentAlreadyFinished' | @@ -641,11 +672,11 @@ not ok 65 - invalid subtest fail | 641 | 672 | # 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. | 642 | 673 | # 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. | 643 | 674 | # 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 | 646 | 677 | # suites 0 | 647 | 678 | # pass 37 | 648 | | -# fail 24 | | 679 | +# fail 25 | 649 | 680 | # cancelled 3 | 650 | 681 | # skipped 10 | 651 | 682 | # todo 5 | |
Collapse file Copy file name to clipboard +2-1Lines changed: 2 additions & 1 deletion | Original file line number | Diff line number | Diff line change |
|---|
| 1 | 1 | ..XX...X..XXX.X..... | 2 | 2 | XXX.....X..X...X.... | 3 | 3 | .........X...XXX.XX. | 4 | | -.....XXXXXXX...XXXX | | 4 | +.....XXXXXXX...XXXXX | | 5 | + |
Collapse file 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 number | Diff line number | Diff line change |
|---|
| 184 | 184 | callback called twice in different ticks (*ms) | 185 | 185 | callback called twice in future tick (*ms) | 186 | 186 | Error [ERR_TEST_FAILURE]: callback invoked multiple times | 187 | | - * | | 187 | + * { | 188 | 188 | failureType: 'multipleCallbackInvocations', | 189 | 189 | cause: 'callback invoked multiple times', | 190 | 190 | code: 'ERR_TEST_FAILURE' | | 265 | 265 | * | 266 | 266 | * | 267 | 267 |
| | 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 | + | 268 | 290 | invalid subtest fail (*ms) | 269 | 291 | 'test could not be started because its parent finished' | 270 | 292 |
| | 275 | 297 | 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. | 276 | 298 | 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. | 277 | 299 | 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 | 279 | 301 | suites 0 | 280 | 302 | pass 37 | 281 | | - fail 24 | | 303 | + fail 25 | 282 | 304 | cancelled 3 | 283 | 305 | skipped 10 | 284 | 306 | todo 5 | | 490 | 512 | * | 491 | 513 | * | 492 | 514 |
| | 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 | + | 493 | 537 | invalid subtest fail (*ms) | 494 | 538 | 'test could not be started because its parent finished' |
|