GeezerVison is showing github.com — test_runner: automatically wait for subtests to finish · nodejs/node@aa3523e
File treeExpand file treeCollapse file tree Open diff view settings fixtures/test-runner/output
Expand file treeCollapse file tree Open diff view settings Collapse file Copy file name to clipboardExpand all lines: lib/internal/test_runner/harness.js +14-3Lines changed: 14 additions & 3 deletions | Original file line number | Diff line number | Diff line change |
|---|
@@ -27,6 +27,8 @@ const { | 27 | 27 | shouldColorizeTestFiles, | 28 | 28 | } = require('internal/test_runner/utils'); | 29 | 29 | const { queueMicrotask } = require('internal/process/task_queues'); | | 30 | +const { TIMEOUT_MAX } = require('internal/timers'); | | 31 | +const { clearInterval, setInterval } = require('timers'); | 30 | 32 | const { bigint: hrtime } = process.hrtime; | 31 | 33 | const resolvedPromise = PromiseResolve(); | 32 | 34 | const testResources = new SafeMap(); | @@ -212,11 +214,20 @@ function setupProcessState(root, globalOptions) { | 212 | 214 | const rejectionHandler = | 213 | 215 | createProcessEventHandler('unhandledRejection', root); | 214 | 216 | const coverage = configureCoverage(root, globalOptions); | 215 | | - const exitHandler = async () => { | | 217 | + const exitHandler = async (kill) => { | 216 | 218 | if (root.subtests.length === 0 && (root.hooks.before.length > 0 || root.hooks.after.length > 0)) { | 217 | 219 | // Run global before/after hooks in case there are no tests | 218 | 220 | await root.run(); | 219 | 221 | } | | 222 | + | | 223 | + if (kill !== true && root.subtestsPromise !== null) { | | 224 | + // Wait for all subtests to finish, but keep the process alive in case | | 225 | + // there are no ref'ed handles left. | | 226 | + const keepAlive = setInterval(() => {}, TIMEOUT_MAX); | | 227 | + await root.subtestsPromise.promise; | | 228 | + clearInterval(keepAlive); | | 229 | + } | | 230 | + | 220 | 231 | root.postRun(new ERR_TEST_FAILURE( | 221 | 232 | 'Promise resolution is still pending but the event loop has already resolved', | 222 | 233 | kCancelledByParent)); | @@ -231,8 +242,8 @@ function setupProcessState(root, globalOptions) { | 231 | 242 | } | 232 | 243 | }; | 233 | 244 |
| 234 | | - const terminationHandler = () => { | 235 | | - exitHandler(); | | 245 | + const terminationHandler = async () => { | | 246 | + await exitHandler(true); | 236 | 247 | process.exit(); | 237 | 248 | }; | 238 | 249 |
| |
Collapse file Copy file name to clipboardExpand all lines: lib/internal/test_runner/test.js +14Lines changed: 14 additions & 0 deletions | Original file line number | Diff line number | Diff line change |
|---|
@@ -579,6 +579,8 @@ class Test extends AsyncResource { | 579 | 579 | this.activeSubtests = 0; | 580 | 580 | this.pendingSubtests = []; | 581 | 581 | this.readySubtests = new SafeMap(); | | 582 | + this.unfinishedSubtests = new SafeSet(); | | 583 | + this.subtestsPromise = null; | 582 | 584 | this.subtests = []; | 583 | 585 | this.waitingOn = 0; | 584 | 586 | this.finished = false; | @@ -682,6 +684,11 @@ class Test extends AsyncResource { | 682 | 684 |
| 683 | 685 | addReadySubtest(subtest) { | 684 | 686 | this.readySubtests.set(subtest.childNumber, subtest); | | 687 | + | | 688 | + if (this.unfinishedSubtests.delete(subtest) && | | 689 | + this.unfinishedSubtests.size === 0) { | | 690 | + this.subtestsPromise.resolve(); | | 691 | + } | 685 | 692 | } | 686 | 693 |
| 687 | 694 | processReadySubtestRange(canSend) { | @@ -743,6 +750,7 @@ class Test extends AsyncResource { | 743 | 750 |
| 744 | 751 | if (parent.waitingOn === 0) { | 745 | 752 | parent.waitingOn = test.childNumber; | | 753 | + parent.subtestsPromise = PromiseWithResolvers(); | 746 | 754 | } | 747 | 755 |
| 748 | 756 | if (preventAddingSubtests) { | @@ -865,6 +873,7 @@ class Test extends AsyncResource { | 865 | 873 | // If there is enough available concurrency to run the test now, then do | 866 | 874 | // it. Otherwise, return a Promise to the caller and mark the test as | 867 | 875 | // pending for later execution. | | 876 | + this.parent.unfinishedSubtests.add(this); | 868 | 877 | this.reporter.enqueue(this.nesting, this.loc, this.name, this.reportedType); | 869 | 878 | if (this.root.harness.buildPromise || !this.parent.hasConcurrency()) { | 870 | 879 | const deferred = PromiseWithResolvers(); | @@ -982,6 +991,11 @@ class Test extends AsyncResource { | 982 | 991 | } | 983 | 992 |
| 984 | 993 | this[kShouldAbort](); | | 994 | + | | 995 | + if (this.subtestsPromise !== null) { | | 996 | + await SafePromiseRace([this.subtestsPromise.promise, stopPromise]); | | 997 | + } | | 998 | + | 985 | 999 | this.plan?.check(); | 986 | 1000 | this.pass(); | 987 | 1001 | await afterEach(); | |
Collapse file Copy file name to clipboardExpand all lines: test/fixtures/test-runner/output/default_output.snapshot +3-7Lines changed: 3 additions & 7 deletions | Original file line number | Diff line number | Diff line change |
|---|
| 3 | 3 | [90m﹣ should skip [90m(*ms)[39m # SKIP[39m | 4 | 4 | ▶ parent | 5 | 5 | [31m✖ should fail [90m(*ms)[39m[39m | 6 | | - [31m✖ should pass but parent fail [90m(*ms)[39m[39m | | 6 | + [32m✔ should pass but parent fail [90m(*ms)[39m[39m | 7 | 7 | [31m✖ parent [90m(*ms)[39m[39m | 8 | 8 | [34mℹ tests 6[39m | 9 | 9 | [34mℹ suites 0[39m | 10 | | -[34mℹ pass 1[39m | | 10 | +[34mℹ pass 2[39m | 11 | 11 | [34mℹ fail 3[39m | 12 | | -[34mℹ cancelled 1[39m | | 12 | +[34mℹ cancelled 0[39m | 13 | 13 | [34mℹ skipped 1[39m | 14 | 14 | [34mℹ todo 0[39m | 15 | 15 | [34mℹ duration_ms *[39m | | 40 | 40 | *[39m | 41 | 41 | *[39m | 42 | 42 | *[39m | 43 | | - | 44 | | -* | 45 | | -[31m✖ should pass but parent fail [90m(*ms)[39m[39m | 46 | | - [32m'test did not finish before its parent and was cancelled'[39m |
Collapse file Copy file name to clipboardExpand all lines: test/fixtures/test-runner/output/dot_reporter.snapshot +1-5Lines changed: 1 addition & 5 deletions | Original file line number | Diff line number | Diff line change |
|---|
| 1 | 1 | ..XX...X..XXX.X..... | 2 | | -XXX.....X..X...X.... | | 2 | +XXX............X.... | 3 | 3 | .....X...XXX.XX..... | 4 | 4 | XXXXXXX...XXXXX | 5 | 5 |
| @@ -93,10 +93,6 @@ Failed tests: | 93 | 93 | '1 subtest failed' | 94 | 94 | ✖ sync throw non-error fail (*ms) | 95 | 95 | Symbol(thrown symbol from sync throw non-error fail) | 96 | | -✖ +long running (*ms) | 97 | | - 'test did not finish before its parent and was cancelled' | 98 | | -✖ top level (*ms) | 99 | | - '1 subtest failed' | 100 | 96 | ✖ sync skip option is false fail (*ms) | 101 | 97 | Error: this should be executed | 102 | 98 | * | |
Collapse file Copy file name to clipboardExpand all lines: test/fixtures/test-runner/output/junit_reporter.snapshot +5-9Lines changed: 5 additions & 9 deletions | Original file line number | Diff line number | Diff line change |
|---|
@@ -186,12 +186,8 @@ Error [ERR_TEST_FAILURE]: thrown from subtest sync throw fail | 186 | 186 | <testcase name="level 1c" time="*" classname="test"/> | 187 | 187 | <testcase name="level 1d" time="*" classname="test"/> | 188 | 188 | </testsuite> | 189 | | - <testsuite name="top level" time="*" disabled="0" errors="0" tests="2" failures="1" skipped="0" hostname="HOSTNAME"> | 190 | | - <testcase name="+long running" time="*" classname="test" failure="test did not finish before its parent and was cancelled"> | 191 | | - <failure type="cancelledByParent" message="test did not finish before its parent and was cancelled"> | 192 | | -[Error [ERR_TEST_FAILURE]: test did not finish before its parent and was cancelled] { code: 'ERR_TEST_FAILURE', failureType: 'cancelledByParent', cause: 'test did not finish before its parent and was cancelled' } | 193 | | - </failure> | 194 | | - </testcase> | | 189 | + <testsuite name="top level" time="*" disabled="0" errors="0" tests="2" failures="0" skipped="0" hostname="HOSTNAME"> | | 190 | + <testcase name="+long running" time="*" classname="test"/> | 195 | 191 | <testsuite name="+short running" time="*" disabled="0" errors="0" tests="1" failures="0" skipped="0" hostname="HOSTNAME"> | 196 | 192 | <testcase name="++short running" time="*" classname="test"/> | 197 | 193 | </testsuite> | @@ -519,9 +515,9 @@ Error [ERR_TEST_FAILURE]: test could not be started because its parent finished | 519 | 515 | <!-- Error: Test "callback async throw after done" at test/fixtures/test-runner/output/output.js:269:1 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. --> | 520 | 516 | <!-- tests 75 --> | 521 | 517 | <!-- suites 0 --> | 522 | | - <!-- pass 34 --> | 523 | | - <!-- fail 25 --> | 524 | | - <!-- cancelled 3 --> | | 518 | + <!-- pass 36 --> | | 519 | + <!-- fail 24 --> | | 520 | + <!-- cancelled 2 --> | 525 | 521 | <!-- skipped 9 --> | 526 | 522 | <!-- todo 4 --> | 527 | 523 | <!-- duration_ms * --> | |
Collapse file Copy file name to clipboard +4-16Lines changed: 4 additions & 16 deletions | Original file line number | Diff line number | Diff line change |
|---|
| 1 | 1 | TAP version 13 | 2 | 2 | # Subtest: does not keep event loop alive | 3 | 3 | # Subtest: +does not keep event loop alive | 4 | | - not ok 1 - +does not keep event loop alive | | 4 | + ok 1 - +does not keep event loop alive | 5 | 5 | --- | 6 | 6 | duration_ms: * | 7 | 7 | type: 'test' | 8 | | - location: '/test/fixtures/test-runner/output/no_refs.js:(LINE):11' | 9 | | - failureType: 'cancelledByParent' | 10 | | - error: 'Promise resolution is still pending but the event loop has already resolved' | 11 | | - code: 'ERR_TEST_FAILURE' | 12 | | - stack: |- | 13 | | - * | 14 | 8 | ... | 15 | 9 | 1..1 | 16 | | -not ok 1 - does not keep event loop alive | | 10 | +ok 1 - does not keep event loop alive | 17 | 11 | --- | 18 | 12 | duration_ms: * | 19 | 13 | type: 'test' | 20 | | - location: '/test/fixtures/test-runner/output/no_refs.js:(LINE):1' | 21 | | - failureType: 'cancelledByParent' | 22 | | - error: 'Promise resolution is still pending but the event loop has already resolved' | 23 | | - code: 'ERR_TEST_FAILURE' | 24 | | - stack: |- | 25 | | - * | 26 | 14 | ... | 27 | 15 | 1..1 | 28 | 16 | # tests 2 | 29 | 17 | # suites 0 | 30 | | -# pass 0 | | 18 | +# pass 2 | 31 | 19 | # fail 0 | 32 | | -# cancelled 2 | | 20 | +# cancelled 0 | 33 | 21 | # skipped 0 | 34 | 22 | # todo 0 | 35 | 23 | # duration_ms * |
Collapse file Copy file name to clipboardExpand all lines: test/fixtures/test-runner/output/output.snapshot +5-13Lines changed: 5 additions & 13 deletions | Original file line number | Diff line number | Diff line change |
|---|
@@ -288,14 +288,10 @@ ok 23 - level 0a | 288 | 288 | ... | 289 | 289 | # Subtest: top level | 290 | 290 | # Subtest: +long running | 291 | | - not ok 1 - +long running | | 291 | + ok 1 - +long running | 292 | 292 | --- | 293 | 293 | duration_ms: * | 294 | 294 | type: 'test' | 295 | | - location: '/test/fixtures/test-runner/output/output.js:(LINE):5' | 296 | | - failureType: 'cancelledByParent' | 297 | | - error: 'test did not finish before its parent and was cancelled' | 298 | | - code: 'ERR_TEST_FAILURE' | 299 | 295 | ... | 300 | 296 | # Subtest: +short running | 301 | 297 | # Subtest: ++short running | @@ -311,14 +307,10 @@ ok 23 - level 0a | 311 | 307 | type: 'test' | 312 | 308 | ... | 313 | 309 | 1..2 | 314 | | -not ok 24 - top level | | 310 | +ok 24 - top level | 315 | 311 | --- | 316 | 312 | duration_ms: * | 317 | 313 | type: 'test' | 318 | | - location: '/test/fixtures/test-runner/output/output.js:(LINE):1' | 319 | | - failureType: 'subtestsFailed' | 320 | | - error: '1 subtest failed' | 321 | | - code: 'ERR_TEST_FAILURE' | 322 | 314 | ... | 323 | 315 | # Subtest: invalid subtest - pass but subtest fails | 324 | 316 | ok 25 - invalid subtest - pass but subtest fails | @@ -787,9 +779,9 @@ not ok 62 - invalid subtest fail | 787 | 779 | # Error: Test "callback async throw after done" at test/fixtures/test-runner/output/output.js:(LINE):1 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. | 788 | 780 | # tests 75 | 789 | 781 | # suites 0 | 790 | | -# pass 34 | 791 | | -# fail 25 | 792 | | -# cancelled 3 | | 782 | +# pass 36 | | 783 | +# fail 24 | | 784 | +# cancelled 2 | 793 | 785 | # skipped 9 | 794 | 786 | # todo 4 | 795 | 787 | # duration_ms * |
Collapse file Copy file name to clipboardExpand all lines: test/fixtures/test-runner/output/output_cli.snapshot +5-13Lines changed: 5 additions & 13 deletions | Original file line number | Diff line number | Diff line change |
|---|
@@ -288,14 +288,10 @@ ok 23 - level 0a | 288 | 288 | ... | 289 | 289 | # Subtest: top level | 290 | 290 | # Subtest: +long running | 291 | | - not ok 1 - +long running | | 291 | + ok 1 - +long running | 292 | 292 | --- | 293 | 293 | duration_ms: * | 294 | 294 | type: 'test' | 295 | | - location: '/test/fixtures/test-runner/output/output.js:(LINE):5' | 296 | | - failureType: 'cancelledByParent' | 297 | | - error: 'test did not finish before its parent and was cancelled' | 298 | | - code: 'ERR_TEST_FAILURE' | 299 | 295 | ... | 300 | 296 | # Subtest: +short running | 301 | 297 | # Subtest: ++short running | @@ -311,14 +307,10 @@ ok 23 - level 0a | 311 | 307 | type: 'test' | 312 | 308 | ... | 313 | 309 | 1..2 | 314 | | -not ok 24 - top level | | 310 | +ok 24 - top level | 315 | 311 | --- | 316 | 312 | duration_ms: * | 317 | 313 | type: 'test' | 318 | | - location: '/test/fixtures/test-runner/output/output.js:(LINE):1' | 319 | | - failureType: 'subtestsFailed' | 320 | | - error: '1 subtest failed' | 321 | | - code: 'ERR_TEST_FAILURE' | 322 | 314 | ... | 323 | 315 | # Subtest: invalid subtest - pass but subtest fails | 324 | 316 | ok 25 - invalid subtest - pass but subtest fails | @@ -801,9 +793,9 @@ ok 63 - last test | 801 | 793 | 1..63 | 802 | 794 | # tests 77 | 803 | 795 | # suites 0 | 804 | | -# pass 36 | 805 | | -# fail 25 | 806 | | -# cancelled 3 | | 796 | +# pass 38 | | 797 | +# fail 24 | | 798 | +# cancelled 2 | 807 | 799 | # skipped 9 | 808 | 800 | # todo 4 | 809 | 801 | # duration_ms * |
Collapse file Copy file name to clipboardExpand all lines: test/fixtures/test-runner/output/spec_reporter.snapshot +3-7Lines changed: 3 additions & 7 deletions | Original file line number | Diff line number | Diff line change |
|---|
| 90 | 90 | Error: Test "callback async throw after done" at test/fixtures/test-runner/output/output.js:269:1 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. | 91 | 91 | tests 75 | 92 | 92 | suites 0 | 93 | | - pass 34 | 94 | | - fail 25 | 95 | | - cancelled 3 | | 93 | + pass 36 | | 94 | + fail 24 | | 95 | + cancelled 2 | 96 | 96 | skipped 9 | 97 | 97 | todo 4 | 98 | 98 | duration_ms * | | 203 | 203 | sync throw non-error fail (*ms) | 204 | 204 | Symbol(thrown symbol from sync throw non-error fail) | 205 | 205 |
| 206 | | -* | 207 | | - +long running (*ms) | 208 | | - 'test did not finish before its parent and was cancelled' | 209 | | - | 210 | 206 | * | 211 | 207 | sync skip option is false fail (*ms) | 212 | 208 | Error: this should be executed | |
Collapse file Copy file name to clipboardExpand all lines: test/fixtures/test-runner/output/spec_reporter_cli.snapshot +3-7Lines changed: 3 additions & 7 deletions | Original file line number | Diff line number | Diff line change |
|---|
| 93 | 93 | Error: Test "callback async throw after done" at test/fixtures/test-runner/output/output.js:269:1 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. | 94 | 94 | tests 76 | 95 | 95 | suites 0 | 96 | | - pass 35 | 97 | | - fail 25 | 98 | | - cancelled 3 | | 96 | + pass 37 | | 97 | + fail 24 | | 98 | + cancelled 2 | 99 | 99 | skipped 9 | 100 | 100 | todo 4 | 101 | 101 | duration_ms * | | 206 | 206 | sync throw non-error fail (*ms) | 207 | 207 | Symbol(thrown symbol from sync throw non-error fail) | 208 | 208 |
| 209 | | -* | 210 | | - +long running (*ms) | 211 | | - 'test did not finish before its parent and was cancelled' | 212 | | - | 213 | 209 | * | 214 | 210 | sync skip option is false fail (*ms) | 215 | 211 | Error: this should be executed | |
|