| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
1 parent 78be0d0 commit 84de97a
15 files changed
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -1872,6 +1872,15 @@ added: | |||
| 1872 | 1872 | The maximum number of test files that the test runner CLI will execute | |
| 1873 | 1873 | concurrently. The default value is `os.availableParallelism() - 1`. | |
| 1874 | 1874 | ||
| 1875 | + ### `--test-force-exit` | ||
| 1876 | + | ||
| 1877 | + <!-- YAML | ||
| 1878 | + added: REPLACEME | ||
| 1879 | + --> | ||
| 1880 | + | ||
| 1881 | + Configures the test runner to exit the process once all known tests have | ||
| 1882 | + finished executing even if the event loop would otherwise remain active. | ||
| 1883 | + | ||
| 1875 | 1884 | ### `--test-name-pattern` | |
| 1876 | 1885 | ||
| 1877 | 1886 | <!-- YAML | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -1119,6 +1119,9 @@ added: | |||
| 1119 | 1119 | - v18.9.0 | |
| 1120 | 1120 | - v16.19.0 | |
| 1121 | 1121 | changes: | |
| 1122 | + - version: REPLACEME | ||
| 1123 | + pr-url: https://github.com/nodejs/node/pull/52038 | ||
| 1124 | + description: Added the `forceExit` option. | ||
| 1122 | 1125 | - version: | |
| 1123 | 1126 | - v20.1.0 | |
| 1124 | 1127 | - v18.17.0 | |
@@ -1137,6 +1140,9 @@ changes: | |||
| 1137 | 1140 | **Default:** `false`. | |
| 1138 | 1141 | * `files`: {Array} An array containing the list of files to run. | |
| 1139 | 1142 | **Default** matching files from [test runner execution model][]. | |
| 1143 | + * `forceExit`: {boolean} Configures the test runner to exit the process once | ||
| 1144 | + all known tests have finished executing even if the event loop would | ||
| 1145 | + otherwise remain active. **Default:** `false`. | ||
| 1140 | 1146 | * `inspectPort` {number|Function} Sets inspector port of test child process. | |
| 1141 | 1147 | This can be a number, or a function that takes no arguments and returns a | |
| 1142 | 1148 | number. If a nullish value is provided, each process gets its own port, | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -422,6 +422,10 @@ Starts the Node.js command line test runner. | |||
| 422 | 422 | The maximum number of test files that the test runner CLI will execute | |
| 423 | 423 | concurrently. | |
| 424 | 424 | . | |
| 425 | + .It Fl -test-force-exit | ||
| 426 | + Configures the test runner to exit the process once all known tests have | ||
| 427 | + finished executing even if the event loop would otherwise remain active. | ||
| 428 | + . | ||
| 425 | 429 | .It Fl -test-name-pattern | |
| 426 | 430 | A regular expression that configures the test runner to only execute tests | |
| 427 | 431 | whose name matches the provided pattern. | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -195,6 +195,7 @@ function setup(root) { | |||
| 195 | 195 | suites: 0, | |
| 196 | 196 | }, | |
| 197 | 197 | shouldColorizeTestFiles: false, | |
| 198 | + teardown: exitHandler, | ||
| 198 | 199 | }; | |
| 199 | 200 | root.startTime = hrtime(); | |
| 200 | 201 | return root; | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -112,8 +112,11 @@ function filterExecArgv(arg, i, arr) { | |||
| 112 | 112 | !ArrayPrototypeSome(kFilterArgValues, (p) => arg === p || (i > 0 && arr[i - 1] === p) || StringPrototypeStartsWith(arg, `${p}=`)); | |
| 113 | 113 | } | |
| 114 | 114 | ||
| 115 | - function getRunArgs(path, { inspectPort, testNamePatterns, only }) { | ||
| 115 | + function getRunArgs(path, { forceExit, inspectPort, testNamePatterns, only }) { | ||
| 116 | 116 | const argv = ArrayPrototypeFilter(process.execArgv, filterExecArgv); | |
| 117 | + if (forceExit === true) { | ||
| 118 | + ArrayPrototypePush(argv, '--test-force-exit'); | ||
| 119 | + } | ||
| 117 | 120 | if (isUsingInspector()) { | |
| 118 | 121 | ArrayPrototypePush(argv, `--inspect-port=${getInspectPort(inspectPort)}`); | |
| 119 | 122 | } | |
@@ -440,14 +443,33 @@ function run(options = kEmptyObject) { | |||
| 440 | 443 | validateObject(options, 'options'); | |
| 441 | 444 | ||
| 442 | 445 | let { testNamePatterns, shard } = options; | |
| 443 | - const { concurrency, timeout, signal, files, inspectPort, watch, setup, only } = options; | ||
| 446 | + const { | ||
| 447 | + concurrency, | ||
| 448 | + timeout, | ||
| 449 | + signal, | ||
| 450 | + files, | ||
| 451 | + forceExit, | ||
| 452 | + inspectPort, | ||
| 453 | + watch, | ||
| 454 | + setup, | ||
| 455 | + only, | ||
| 456 | + } = options; | ||
| 444 | 457 | ||
| 445 | 458 | if (files != null) { | |
| 446 | 459 | validateArray(files, 'options.files'); | |
| 447 | 460 | } | |
| 448 | 461 | if (watch != null) { | |
| 449 | 462 | validateBoolean(watch, 'options.watch'); | |
| 450 | 463 | } | |
| 464 | + if (forceExit != null) { | ||
| 465 | + validateBoolean(forceExit, 'options.forceExit'); | ||
| 466 | + | ||
| 467 | + if (forceExit && watch) { | ||
| 468 | + throw new ERR_INVALID_ARG_VALUE( | ||
| 469 | + 'options.forceExit', watch, 'is not supported with watch mode', | ||
| 470 | + ); | ||
| 471 | + } | ||
| 472 | + } | ||
| 451 | 473 | if (only != null) { | |
| 452 | 474 | validateBoolean(only, 'options.only'); | |
| 453 | 475 | } | |
@@ -501,7 +523,15 @@ function run(options = kEmptyObject) { | |||
| 501 | 523 | ||
| 502 | 524 | let postRun = () => root.postRun(); | |
| 503 | 525 | let filesWatcher; | |
| 504 | - const opts = { __proto__: null, root, signal, inspectPort, testNamePatterns, only }; | ||
| 526 | + const opts = { | ||
| 527 | + __proto__: null, | ||
| 528 | + root, | ||
| 529 | + signal, | ||
| 530 | + inspectPort, | ||
| 531 | + testNamePatterns, | ||
| 532 | + only, | ||
| 533 | + forceExit, | ||
| 534 | + }; | ||
| 505 | 535 | if (watch) { | |
| 506 | 536 | filesWatcher = watchFiles(testFiles, opts); | |
| 507 | 537 | postRun = undefined; | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -78,7 +78,12 @@ const kHookNames = ObjectSeal(['before', 'after', 'beforeEach', 'afterEach']); | |||
| 78 | 78 | const kUnwrapErrors = new SafeSet() | |
| 79 | 79 | .add(kTestCodeFailure).add(kHookFailure) | |
| 80 | 80 | .add('uncaughtException').add('unhandledRejection'); | |
| 81 | - const { sourceMaps, testNamePatterns, testOnlyFlag } = parseCommandLine(); | ||
| 81 | + const { | ||
| 82 | + forceExit, | ||
| 83 | + sourceMaps, | ||
| 84 | + testNamePatterns, | ||
| 85 | + testOnlyFlag, | ||
| 86 | + } = parseCommandLine(); | ||
| 82 | 87 | let kResistStopPropagation; | |
| 83 | 88 | let findSourceMap; | |
| 84 | 89 | ||
@@ -748,6 +753,16 @@ class Test extends AsyncResource { | |||
| 748 | 753 | // This helps catch any asynchronous activity that occurs after the tests | |
| 749 | 754 | // have finished executing. | |
| 750 | 755 | this.postRun(); | |
| 756 | + } else if (forceExit) { | ||
| 757 | + // This is the root test, and all known tests and hooks have finished | ||
| 758 | + // executing. If the user wants to force exit the process regardless of | ||
| 759 | + // any remaining ref'ed handles, then do that now. It is theoretically | ||
| 760 | + // possible that a ref'ed handle could asynchronously create more tests, | ||
| 761 | + // but the user opted into this behavior. | ||
| 762 | + this.reporter.once('close', () => { | ||
| 763 | + process.exit(); | ||
| 764 | + }); | ||
| 765 | + this.harness.teardown(); | ||
| 751 | 766 | } | |
| 752 | 767 | } | |
| 753 | 768 | ||
@@ -798,12 +813,11 @@ class Test extends AsyncResource { | |||
| 798 | 813 | if (this.parent === this.root && | |
| 799 | 814 | this.root.activeSubtests === 0 && | |
| 800 | 815 | this.root.pendingSubtests.length === 0 && | |
| 801 | - this.root.readySubtests.size === 0 && | ||
| 802 | - this.root.hooks.after.length > 0) { | ||
| 803 | - // This is done so that any global after() hooks are run. At this point | ||
| 804 | - // all of the tests have finished running. However, there might be | ||
| 805 | - // ref'ed handles keeping the event loop alive. This gives the global | ||
| 806 | - // after() hook a chance to clean them up. | ||
| 816 | + this.root.readySubtests.size === 0) { | ||
| 817 | + // At this point all of the tests have finished running. However, there | ||
| 818 | + // might be ref'ed handles keeping the event loop alive. This gives the | ||
| 819 | + // global after() hook a chance to clean them up. The user may also | ||
| 820 | + // want to force the test runner to exit despite ref'ed handles. | ||
| 807 | 821 | this.root.run(); | |
| 808 | 822 | } | |
| 809 | 823 | } else if (!this.reported) { | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -193,6 +193,7 @@ function parseCommandLine() { | |||
| 193 | 193 | ||
| 194 | 194 | const isTestRunner = getOptionValue('--test'); | |
| 195 | 195 | const coverage = getOptionValue('--experimental-test-coverage'); | |
| 196 | + const forceExit = getOptionValue('--test-force-exit'); | ||
| 196 | 197 | const sourceMaps = getOptionValue('--enable-source-maps'); | |
| 197 | 198 | const isChildProcess = process.env.NODE_TEST_CONTEXT === 'child'; | |
| 198 | 199 | const isChildProcessV8 = process.env.NODE_TEST_CONTEXT === 'child-v8'; | |
@@ -245,6 +246,7 @@ function parseCommandLine() { | |||
| 245 | 246 | __proto__: null, | |
| 246 | 247 | isTestRunner, | |
| 247 | 248 | coverage, | |
| 249 | + forceExit, | ||
| 248 | 250 | sourceMaps, | |
| 249 | 251 | testOnlyFlag, | |
| 250 | 252 | testNamePatterns, | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -179,6 +179,9 @@ void EnvironmentOptions::CheckOptions(std::vector<std::string>* errors, | |||
| 179 | 179 | } else if (force_repl) { | |
| 180 | 180 | errors->push_back("either --watch or --interactive " | |
| 181 | 181 | "can be used, not both"); | |
| 182 | + } else if (test_runner_force_exit) { | ||
| 183 | + errors->push_back("either --watch or --test-force-exit " | ||
| 184 | + "can be used, not both"); | ||
| 182 | 185 | } else if (!test_runner && (argv->size() < 1 || (*argv)[1].empty())) { | |
| 183 | 186 | errors->push_back("--watch requires specifying a file"); | |
| 184 | 187 | } | |
@@ -616,6 +619,9 @@ EnvironmentOptionsParser::EnvironmentOptionsParser() { | |||
| 616 | 619 | AddOption("--test-concurrency", | |
| 617 | 620 | "specify test runner concurrency", | |
| 618 | 621 | &EnvironmentOptions::test_runner_concurrency); | |
| 622 | + AddOption("--test-force-exit", | ||
| 623 | + "force test runner to exit upon completion", | ||
| 624 | + &EnvironmentOptions::test_runner_force_exit); | ||
| 619 | 625 | AddOption("--test-timeout", | |
| 620 | 626 | "specify test runner timeout", | |
| 621 | 627 | &EnvironmentOptions::test_runner_timeout); | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -166,6 +166,7 @@ class EnvironmentOptions : public Options { | |||
| 166 | 166 | uint64_t test_runner_concurrency = 0; | |
| 167 | 167 | uint64_t test_runner_timeout = 0; | |
| 168 | 168 | bool test_runner_coverage = false; | |
| 169 | + bool test_runner_force_exit = false; | ||
| 169 | 170 | std::vector<std::string> test_name_pattern; | |
| 170 | 171 | std::vector<std::string> test_reporter; | |
| 171 | 172 | std::vector<std::string> test_reporter_destination; | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -0,0 +1,27 @@ | |||
| 1 | + // Flags: --test-force-exit --test-reporter=spec | ||
| 2 | + 'use strict'; | ||
| 3 | + const { after, afterEach, before, beforeEach, test } = require('node:test'); | ||
| 4 | + | ||
| 5 | + before(() => { | ||
| 6 | + console.log('BEFORE'); | ||
| 7 | + }); | ||
| 8 | + | ||
| 9 | + beforeEach(() => { | ||
| 10 | + console.log('BEFORE EACH'); | ||
| 11 | + }); | ||
| 12 | + | ||
| 13 | + after(() => { | ||
| 14 | + console.log('AFTER'); | ||
| 15 | + }); | ||
| 16 | + | ||
| 17 | + afterEach(() => { | ||
| 18 | + console.log('AFTER EACH'); | ||
| 19 | + }); | ||
| 20 | + | ||
| 21 | + test('passes but oops', () => { | ||
| 22 | + setTimeout(() => { | ||
| 23 | + throw new Error('this should not have a chance to be thrown'); | ||
| 24 | + }, 1000); | ||
| 25 | + }); | ||
| 26 | + | ||
| 27 | + test('also passes'); | ||
| Back | FazBrowse Home | New Git URL |
0 commit comments