| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
1 parent e2ba824 commit a21ae17
28 files changed
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -2780,6 +2780,38 @@ changes: | |||
| 2780 | 2780 | Configures the test runner to only execute top level tests that have the `only` | |
| 2781 | 2781 | option set. This flag is not necessary when test isolation is disabled. | |
| 2782 | 2782 | ||
| 2783 | + ### `--test-random-seed` | ||
| 2784 | + | ||
| 2785 | + <!-- YAML | ||
| 2786 | + added: REPLACEME | ||
| 2787 | + --> | ||
| 2788 | + | ||
| 2789 | + Set the seed used to randomize test execution order. This applies to both test | ||
| 2790 | + file execution order and queued tests within each file. Providing this flag | ||
| 2791 | + enables randomization implicitly, even without `--test-randomize`. | ||
| 2792 | + | ||
| 2793 | + The value must be an integer between `0` and `4294967295`. | ||
| 2794 | + | ||
| 2795 | + This flag cannot be used with `--watch` or `--test-rerun-failures`. | ||
| 2796 | + | ||
| 2797 | + ### `--test-randomize` | ||
| 2798 | + | ||
| 2799 | + <!-- YAML | ||
| 2800 | + added: REPLACEME | ||
| 2801 | + --> | ||
| 2802 | + | ||
| 2803 | + Randomize test execution order. This applies to both test file execution order | ||
| 2804 | + and queued tests within each file. This can help detect tests that rely on | ||
| 2805 | + shared state or execution order. | ||
| 2806 | + | ||
| 2807 | + The seed used for randomization is printed in the test summary and can be | ||
| 2808 | + reused with `--test-random-seed`. | ||
| 2809 | + | ||
| 2810 | + For detailed behavior and examples, see | ||
| 2811 | + [randomizing tests execution order][]. | ||
| 2812 | + | ||
| 2813 | + This flag cannot be used with `--watch` or `--test-rerun-failures`. | ||
| 2814 | + | ||
| 2783 | 2815 | ### `--test-reporter` | |
| 2784 | 2816 | ||
| 2785 | 2817 | <!-- YAML | |
@@ -3698,6 +3730,8 @@ one is included in the list below. | |||
| 3698 | 3730 | * `--test-isolation` | |
| 3699 | 3731 | * `--test-name-pattern` | |
| 3700 | 3732 | * `--test-only` | |
| 3733 | + * `--test-random-seed` | ||
| 3734 | + * `--test-randomize` | ||
| 3701 | 3735 | * `--test-reporter-destination` | |
| 3702 | 3736 | * `--test-reporter` | |
| 3703 | 3737 | * `--test-rerun-failures` | |
@@ -4282,6 +4316,7 @@ node --stack-trace-limit=12 -p -e "Error.stackTraceLimit" # prints 12 | |||
| 4282 | 4316 | [libuv threadpool documentation]: https://docs.libuv.org/en/latest/threadpool.html | |
| 4283 | 4317 | [module compile cache]: module.md#module-compile-cache | |
| 4284 | 4318 | [preloading asynchronous module customization hooks]: module.md#registration-of-asynchronous-customization-hooks | |
| 4319 | + [randomizing tests execution order]: test.md#randomizing-tests-execution-order | ||
| 4285 | 4320 | [remote code execution]: https://www.owasp.org/index.php/Code_Injection | |
| 4286 | 4321 | [running tests from the command line]: test.md#running-tests-from-the-command-line | |
| 4287 | 4322 | [scavenge garbage collector]: https://v8.dev/blog/orinoco-parallel-scavenger | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -627,6 +627,94 @@ prevent shell expansion, which can reduce portability across systems. | |||
| 627 | 627 | node --test "**/*.test.js" "**/*.spec.js" | |
| 628 | 628 | ``` | |
| 629 | 629 | ||
| 630 | + ### Randomizing tests execution order | ||
| 631 | + | ||
| 632 | + <!-- YAML | ||
| 633 | + added: REPLACEME | ||
| 634 | + --> | ||
| 635 | + | ||
| 636 | + > Stability: 1.0 - Early development | ||
| 637 | + | ||
| 638 | + The test runner can randomize execution order to help detect | ||
| 639 | + order-dependent tests. When enabled, the runner randomizes both discovered | ||
| 640 | + test files and queued tests within each file. Use `--test-randomize` to | ||
| 641 | + enable this mode. | ||
| 642 | + | ||
| 643 | + ```bash | ||
| 644 | + node --test --test-randomize | ||
| 645 | + ``` | ||
| 646 | + | ||
| 647 | + When randomization is enabled, the test runner prints the seed used for the run | ||
| 648 | + as a diagnostic message: | ||
| 649 | + | ||
| 650 | + ```text | ||
| 651 | + Randomized test order seed: 12345 | ||
| 652 | + ``` | ||
| 653 | + | ||
| 654 | + Use `--test-random-seed=<number>` to replay the same randomized order | ||
| 655 | + deterministically. Supplying `--test-random-seed` also enables randomization, | ||
| 656 | + so `--test-randomize` is optional when a seed is provided: | ||
| 657 | + | ||
| 658 | + ```bash | ||
| 659 | + node --test --test-random-seed=12345 | ||
| 660 | + ``` | ||
| 661 | + | ||
| 662 | + In most test files, randomization works automatically. One important exception | ||
| 663 | + is when subtests are awaited one by one. In that pattern, each subtest starts | ||
| 664 | + only after the previous one finishes, so the runner keeps declaration order | ||
| 665 | + instead of randomizing it. | ||
| 666 | + | ||
| 667 | + Example: this runs sequentially and is **not** randomized. | ||
| 668 | + | ||
| 669 | + ```mjs | ||
| 670 | + import test from 'node:test'; | ||
| 671 | + | ||
| 672 | + test('math', async (t) => { | ||
| 673 | + for (const name of ['adds', 'subtracts', 'multiplies']) { | ||
| 674 | + // Sequentially awaiting each subtest preserves declaration order. | ||
| 675 | + await t.test(name, async () => {}); | ||
| 676 | + } | ||
| 677 | + }); | ||
| 678 | + ``` | ||
| 679 | + | ||
| 680 | + ```cjs | ||
| 681 | + const test = require('node:test'); | ||
| 682 | + | ||
| 683 | + test('math', async (t) => { | ||
| 684 | + for (const name of ['adds', 'subtracts', 'multiplies']) { | ||
| 685 | + // Sequentially awaiting each subtest preserves declaration order. | ||
| 686 | + await t.test(name, async () => {}); | ||
| 687 | + } | ||
| 688 | + }); | ||
| 689 | + ``` | ||
| 690 | + | ||
| 691 | + Using suite-style APIs such as `describe()`/`it()` or `suite()`/`test()` | ||
| 692 | + still allows randomization, because sibling tests are enqueued together. | ||
| 693 | + | ||
| 694 | + Example: this remains eligible for randomization. | ||
| 695 | + | ||
| 696 | + ```mjs | ||
| 697 | + import { describe, it } from 'node:test'; | ||
| 698 | + | ||
| 699 | + describe('math', () => { | ||
| 700 | + it('adds', () => {}); | ||
| 701 | + it('subtracts', () => {}); | ||
| 702 | + it('multiplies', () => {}); | ||
| 703 | + }); | ||
| 704 | + ``` | ||
| 705 | + | ||
| 706 | + ```cjs | ||
| 707 | + const { describe, it } = require('node:test'); | ||
| 708 | + | ||
| 709 | + describe('math', () => { | ||
| 710 | + it('adds', () => {}); | ||
| 711 | + it('subtracts', () => {}); | ||
| 712 | + it('multiplies', () => {}); | ||
| 713 | + }); | ||
| 714 | + ``` | ||
| 715 | + | ||
| 716 | + `--test-randomize` and `--test-random-seed` are not supported with `--watch` mode. | ||
| 717 | + | ||
| 630 | 718 | Matching files are executed as test files. | |
| 631 | 719 | More information on the test file execution can be found | |
| 632 | 720 | in the [test runner execution model][] section. | |
@@ -667,6 +755,10 @@ test runner functionality: | |||
| 667 | 755 | * `--test-reporter` - Reporting is managed by the parent process | |
| 668 | 756 | * `--test-reporter-destination` - Output destinations are controlled by the parent | |
| 669 | 757 | * `--experimental-config-file` - Config file paths are managed by the parent | |
| 758 | + * `--test-randomize` - Randomization is managed by the parent process and | ||
| 759 | + propagated to child processes | ||
| 760 | + * `--test-random-seed` - Randomization seed is managed by the parent process and | ||
| 761 | + propagated to child processes | ||
| 670 | 762 | ||
| 671 | 763 | All other Node.js options from command line arguments, environment variables, | |
| 672 | 764 | and configuration files are inherited by the child processes. | |
@@ -1575,6 +1667,14 @@ changes: | |||
| 1575 | 1667 | that specifies the index of the shard to run. This option is _required_. | |
| 1576 | 1668 | * `total` {number} is a positive integer that specifies the total number | |
| 1577 | 1669 | of shards to split the test files to. This option is _required_. | |
| 1670 | + * `randomize` {boolean} Randomize execution order for test files and queued tests. | ||
| 1671 | + This option is not supported with `watch: true`. | ||
| 1672 | + **Default:** `false`. | ||
| 1673 | + * `randomSeed` {number} Seed used when randomizing execution order. If this | ||
| 1674 | + option is set, runs can replay the same randomized order deterministically, | ||
| 1675 | + and setting this option also enables randomization. The value must be an | ||
| 1676 | + integer between `0` and `4294967295`. | ||
| 1677 | + **Default:** `undefined`. | ||
| 1578 | 1678 | * `rerunFailuresFilePath` {string} A file path where the test runner will | |
| 1579 | 1679 | store the state of the tests to allow rerunning only the failed tests on a next run. | |
| 1580 | 1680 | see \[Rerunning failed tests]\[] for more information. | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -524,6 +524,14 @@ | |||
| 524 | 524 | "type": "boolean", | |
| 525 | 525 | "description": "run tests with 'only' option set" | |
| 526 | 526 | }, | |
| 527 | + "test-random-seed": { | ||
| 528 | + "type": "number", | ||
| 529 | + "description": "seed used to randomize test execution order" | ||
| 530 | + }, | ||
| 531 | + "test-randomize": { | ||
| 532 | + "type": "boolean", | ||
| 533 | + "description": "run tests in a random order" | ||
| 534 | + }, | ||
| 527 | 535 | "test-reporter": { | |
| 528 | 536 | "oneOf": [ | |
| 529 | 537 | { | |
@@ -906,6 +914,14 @@ | |||
| 906 | 914 | "type": "boolean", | |
| 907 | 915 | "description": "run tests with 'only' option set" | |
| 908 | 916 | }, | |
| 917 | + "test-random-seed": { | ||
| 918 | + "type": "number", | ||
| 919 | + "description": "seed used to randomize test execution order" | ||
| 920 | + }, | ||
| 921 | + "test-randomize": { | ||
| 922 | + "type": "boolean", | ||
| 923 | + "description": "run tests in a random order" | ||
| 924 | + }, | ||
| 909 | 925 | "test-reporter": { | |
| 910 | 926 | "oneOf": [ | |
| 911 | 927 | { | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -1363,6 +1363,22 @@ tests must satisfy \fBboth\fR requirements in order to be executed. | |||
| 1363 | 1363 | Configures the test runner to only execute top level tests that have the \fBonly\fR | |
| 1364 | 1364 | option set. This flag is not necessary when test isolation is disabled. | |
| 1365 | 1365 | . | |
| 1366 | + .It Fl -test-random-seed | ||
| 1367 | + Set the seed used to randomize test execution order. | ||
| 1368 | + This applies to both test file execution order and queued tests within each file. | ||
| 1369 | + Providing this flag enables randomization implicitly, even without | ||
| 1370 | + \fB--test-randomize\fR. | ||
| 1371 | + The value must be an integer between 0 and 4294967295. | ||
| 1372 | + This flag cannot be used with \fB--watch\fR or \fB--test-rerun-failures\fR. | ||
| 1373 | + . | ||
| 1374 | + .It Fl -test-randomize | ||
| 1375 | + Randomize test execution order. | ||
| 1376 | + This applies to both test file execution order and queued tests within each file. | ||
| 1377 | + This can help detect tests that rely on shared state or execution order. | ||
| 1378 | + The seed used for randomization is printed in the test summary and can be | ||
| 1379 | + reused with \fB--test-random-seed\fR. | ||
| 1380 | + This flag cannot be used with \fB--watch\fR or \fB--test-rerun-failures\fR. | ||
| 1381 | + . | ||
| 1366 | 1382 | .It Fl -test-reporter | |
| 1367 | 1383 | A test reporter to use when running tests. See the documentation on | |
| 1368 | 1384 | test reporters for more details. | |
@@ -2040,6 +2056,10 @@ one is included in the list below. | |||
| 2040 | 2056 | .It | |
| 2041 | 2057 | \fB--test-reporter-destination\fR | |
| 2042 | 2058 | .It | |
| 2059 | + \fB--test-randomize\fR | ||
| 2060 | + .It | ||
| 2061 | + \fB--test-random-seed\fR | ||
| 2062 | + .It | ||
| 2043 | 2063 | \fB--test-reporter\fR | |
| 2044 | 2064 | .It | |
| 2045 | 2065 | \fB--test-rerun-failures\fR | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -59,6 +59,7 @@ const { | |||
| 59 | 59 | validateObject, | |
| 60 | 60 | validateOneOf, | |
| 61 | 61 | validateInteger, | |
| 62 | + validateUint32, | ||
| 62 | 63 | validateString, | |
| 63 | 64 | validateStringArray, | |
| 64 | 65 | } = require('internal/validators'); | |
@@ -84,6 +85,7 @@ const { | |||
| 84 | 85 | const { FastBuffer } = require('internal/buffer'); | |
| 85 | 86 | ||
| 86 | 87 | const { | |
| 88 | + createRandomSeed, | ||
| 87 | 89 | convertStringToRegExp, | |
| 88 | 90 | countCompletedTest, | |
| 89 | 91 | kDefaultPattern, | |
@@ -105,12 +107,14 @@ const kIsolatedProcessName = Symbol('kIsolatedProcessName'); | |||
| 105 | 107 | const kFilterArgs = [ | |
| 106 | 108 | '--test', | |
| 107 | 109 | '--experimental-test-coverage', | |
| 110 | + '--test-randomize', | ||
| 108 | 111 | '--watch', | |
| 109 | 112 | '--experimental-default-config-file', | |
| 110 | 113 | ]; | |
| 111 | 114 | const kFilterArgValues = [ | |
| 112 | 115 | '--test-reporter', | |
| 113 | 116 | '--test-reporter-destination', | |
| 117 | + '--test-random-seed', | ||
| 114 | 118 | '--experimental-config-file', | |
| 115 | 119 | ]; | |
| 116 | 120 | const kDiagnosticsFilterArgs = ['tests', 'suites', 'pass', 'fail', 'cancelled', 'skipped', 'todo', 'duration_ms']; | |
@@ -168,6 +172,8 @@ function getRunArgs(path, { forceExit, | |||
| 168 | 172 | argv: suppliedArgs, | |
| 169 | 173 | execArgv, | |
| 170 | 174 | rerunFailuresFilePath, | |
| 175 | + randomize, | ||
| 176 | + randomSeed, | ||
| 171 | 177 | root: { timeout }, | |
| 172 | 178 | cwd }) { | |
| 173 | 179 | const processNodeOptions = getOptionsAsFlagsFromBinding(); | |
@@ -209,6 +215,12 @@ function getRunArgs(path, { forceExit, | |||
| 209 | 215 | if (rerunFailuresFilePath) { | |
| 210 | 216 | ArrayPrototypePush(runArgs, `--test-rerun-failures=${rerunFailuresFilePath}`); | |
| 211 | 217 | } | |
| 218 | + if (randomize) { | ||
| 219 | + ArrayPrototypePush(runArgs, '--test-randomize'); | ||
| 220 | + } | ||
| 221 | + if (randomSeed != null) { | ||
| 222 | + ArrayPrototypePush(runArgs, `--test-random-seed=${randomSeed}`); | ||
| 223 | + } | ||
| 212 | 224 | ||
| 213 | 225 | ArrayPrototypePushApply(runArgs, execArgv); | |
| 214 | 226 | ||
@@ -646,6 +658,8 @@ function run(options = kEmptyObject) { | |||
| 646 | 658 | lineCoverage = 0, | |
| 647 | 659 | branchCoverage = 0, | |
| 648 | 660 | functionCoverage = 0, | |
| 661 | + randomize: suppliedRandomize, | ||
| 662 | + randomSeed: suppliedRandomSeed, | ||
| 649 | 663 | execArgv = [], | |
| 650 | 664 | argv = [], | |
| 651 | 665 | cwd = process.cwd(), | |
@@ -674,6 +688,56 @@ function run(options = kEmptyObject) { | |||
| 674 | 688 | if (globPatterns != null) { | |
| 675 | 689 | validateArray(globPatterns, 'options.globPatterns'); | |
| 676 | 690 | } | |
| 691 | + if (suppliedRandomize != null) { | ||
| 692 | + validateBoolean(suppliedRandomize, 'options.randomize'); | ||
| 693 | + } | ||
| 694 | + if (suppliedRandomSeed != null) { | ||
| 695 | + validateUint32(suppliedRandomSeed, 'options.randomSeed'); | ||
| 696 | + } | ||
| 697 | + let randomize = suppliedRandomize; | ||
| 698 | + let randomSeed = suppliedRandomSeed; | ||
| 699 | + | ||
| 700 | + if (randomSeed != null) { | ||
| 701 | + randomize = true; | ||
| 702 | + } | ||
| 703 | + if (watch) { | ||
| 704 | + if (randomSeed != null) { | ||
| 705 | + throw new ERR_INVALID_ARG_VALUE( | ||
| 706 | + 'options.randomSeed', | ||
| 707 | + randomSeed, | ||
| 708 | + 'is not supported with watch mode', | ||
| 709 | + ); | ||
| 710 | + } | ||
| 711 | + if (randomize) { | ||
| 712 | + throw new ERR_INVALID_ARG_VALUE( | ||
| 713 | + 'options.randomize', | ||
| 714 | + randomize, | ||
| 715 | + 'is not supported with watch mode', | ||
| 716 | + ); | ||
| 717 | + } | ||
| 718 | + } | ||
| 719 | + if (rerunFailuresFilePath) { | ||
| 720 | + validatePath(rerunFailuresFilePath, 'options.rerunFailuresFilePath'); | ||
| 721 | + // TODO(pmarchini): Support rerun-failures with randomization by | ||
| 722 | + // persisting the randomization seed in the rerun state file. | ||
| 723 | + if (randomSeed != null) { | ||
| 724 | + throw new ERR_INVALID_ARG_VALUE( | ||
| 725 | + 'options.randomSeed', | ||
| 726 | + randomSeed, | ||
| 727 | + 'is not supported with rerun failures mode', | ||
| 728 | + ); | ||
| 729 | + } | ||
| 730 | + if (randomize) { | ||
| 731 | + throw new ERR_INVALID_ARG_VALUE( | ||
| 732 | + 'options.randomize', | ||
| 733 | + randomize, | ||
| 734 | + 'is not supported with rerun failures mode', | ||
| 735 | + ); | ||
| 736 | + } | ||
| 737 | + } | ||
| 738 | + if (randomize) { | ||
| 739 | + randomSeed ??= createRandomSeed(); | ||
| 740 | + } | ||
| 677 | 741 | ||
| 678 | 742 | validateString(cwd, 'options.cwd'); | |
| 679 | 743 | ||
@@ -683,10 +747,6 @@ function run(options = kEmptyObject) { | |||
| 683 | 747 | ); | |
| 684 | 748 | } | |
| 685 | 749 | ||
| 686 | - if (rerunFailuresFilePath) { | ||
| 687 | - validatePath(rerunFailuresFilePath, 'options.rerunFailuresFilePath'); | ||
| 688 | - } | ||
| 689 | - | ||
| 690 | 750 | if (shard != null) { | |
| 691 | 751 | validateObject(shard, 'options.shard'); | |
| 692 | 752 | // Avoid re-evaluating the shard object in case it's a getter | |
@@ -783,11 +843,18 @@ function run(options = kEmptyObject) { | |||
| 783 | 843 | functionCoverage: functionCoverage, | |
| 784 | 844 | cwd, | |
| 785 | 845 | globalSetupPath, | |
| 846 | + randomize, | ||
| 847 | + randomSeed, | ||
| 786 | 848 | }; | |
| 849 | + | ||
| 787 | 850 | const root = createTestTree(rootTestOptions, globalOptions); | |
| 788 | 851 | let testFiles = files ?? createTestFileList(globPatterns, cwd); | |
| 789 | 852 | const { isTestRunner } = globalOptions; | |
| 790 | 853 | ||
| 854 | + if (randomize) { | ||
| 855 | + root.diagnostic(`Randomized test order seed: ${randomSeed}`); | ||
| 856 | + } | ||
| 857 | + | ||
| 791 | 858 | if (shard) { | |
| 792 | 859 | testFiles = ArrayPrototypeFilter(testFiles, (_, index) => index % shard.total === shard.index - 1); | |
| 793 | 860 | } | |
@@ -833,6 +900,8 @@ function run(options = kEmptyObject) { | |||
| 833 | 900 | rerunFailuresFilePath, | |
| 834 | 901 | env, | |
| 835 | 902 | workerIdPool: isolation === 'process' ? workerIdPool : null, | |
| 903 | + randomize, | ||
| 904 | + randomSeed, | ||
| 836 | 905 | }; | |
| 837 | 906 | ||
| 838 | 907 | if (isolation === 'process') { | |
| Back | FazBrowse Home | New Git URL |
0 commit comments