| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
1 parent ec8c6b9 commit d14029b
28 files changed
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -2669,6 +2669,38 @@ changes: | |||
| 2669 | 2669 | Configures the test runner to only execute top level tests that have the `only` | |
| 2670 | 2670 | option set. This flag is not necessary when test isolation is disabled. | |
| 2671 | 2671 | ||
| 2672 | + ### `--test-random-seed` | ||
| 2673 | + | ||
| 2674 | + <!-- YAML | ||
| 2675 | + added: REPLACEME | ||
| 2676 | + --> | ||
| 2677 | + | ||
| 2678 | + Set the seed used to randomize test execution order. This applies to both test | ||
| 2679 | + file execution order and queued tests within each file. Providing this flag | ||
| 2680 | + enables randomization implicitly, even without `--test-randomize`. | ||
| 2681 | + | ||
| 2682 | + The value must be an integer between `0` and `4294967295`. | ||
| 2683 | + | ||
| 2684 | + This flag cannot be used with `--watch` or `--test-rerun-failures`. | ||
| 2685 | + | ||
| 2686 | + ### `--test-randomize` | ||
| 2687 | + | ||
| 2688 | + <!-- YAML | ||
| 2689 | + added: REPLACEME | ||
| 2690 | + --> | ||
| 2691 | + | ||
| 2692 | + Randomize test execution order. This applies to both test file execution order | ||
| 2693 | + and queued tests within each file. This can help detect tests that rely on | ||
| 2694 | + shared state or execution order. | ||
| 2695 | + | ||
| 2696 | + The seed used for randomization is printed in the test summary and can be | ||
| 2697 | + reused with `--test-random-seed`. | ||
| 2698 | + | ||
| 2699 | + For detailed behavior and examples, see | ||
| 2700 | + [randomizing tests execution order][]. | ||
| 2701 | + | ||
| 2702 | + This flag cannot be used with `--watch` or `--test-rerun-failures`. | ||
| 2703 | + | ||
| 2672 | 2704 | ### `--test-reporter` | |
| 2673 | 2705 | ||
| 2674 | 2706 | <!-- YAML | |
@@ -3582,6 +3614,8 @@ one is included in the list below. | |||
| 3582 | 3614 | * `--test-isolation` | |
| 3583 | 3615 | * `--test-name-pattern` | |
| 3584 | 3616 | * `--test-only` | |
| 3617 | + * `--test-random-seed` | ||
| 3618 | + * `--test-randomize` | ||
| 3585 | 3619 | * `--test-reporter-destination` | |
| 3586 | 3620 | * `--test-reporter` | |
| 3587 | 3621 | * `--test-rerun-failures` | |
@@ -4162,6 +4196,7 @@ node --stack-trace-limit=12 -p -e "Error.stackTraceLimit" # prints 12 | |||
| 4162 | 4196 | [libuv threadpool documentation]: https://docs.libuv.org/en/latest/threadpool.html | |
| 4163 | 4197 | [module compile cache]: module.md#module-compile-cache | |
| 4164 | 4198 | [preloading asynchronous module customization hooks]: module.md#registration-of-asynchronous-customization-hooks | |
| 4199 | + [randomizing tests execution order]: test.md#randomizing-tests-execution-order | ||
| 4165 | 4200 | [remote code execution]: https://www.owasp.org/index.php/Code_Injection | |
| 4166 | 4201 | [running tests from the command line]: test.md#running-tests-from-the-command-line | |
| 4167 | 4202 | [scavenge garbage collector]: https://v8.dev/blog/orinoco-parallel-scavenger | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -626,6 +626,94 @@ prevent shell expansion, which can reduce portability across systems. | |||
| 626 | 626 | node --test "**/*.test.js" "**/*.spec.js" | |
| 627 | 627 | ``` | |
| 628 | 628 | ||
| 629 | + ### Randomizing tests execution order | ||
| 630 | + | ||
| 631 | + <!-- YAML | ||
| 632 | + added: REPLACEME | ||
| 633 | + --> | ||
| 634 | + | ||
| 635 | + > Stability: 1.0 - Early development | ||
| 636 | + | ||
| 637 | + The test runner can randomize execution order to help detect | ||
| 638 | + order-dependent tests. When enabled, the runner randomizes both discovered | ||
| 639 | + test files and queued tests within each file. Use `--test-randomize` to | ||
| 640 | + enable this mode. | ||
| 641 | + | ||
| 642 | + ```bash | ||
| 643 | + node --test --test-randomize | ||
| 644 | + ``` | ||
| 645 | + | ||
| 646 | + When randomization is enabled, the test runner prints the seed used for the run | ||
| 647 | + as a diagnostic message: | ||
| 648 | + | ||
| 649 | + ```text | ||
| 650 | + Randomized test order seed: 12345 | ||
| 651 | + ``` | ||
| 652 | + | ||
| 653 | + Use `--test-random-seed=<number>` to replay the same randomized order | ||
| 654 | + deterministically. Supplying `--test-random-seed` also enables randomization, | ||
| 655 | + so `--test-randomize` is optional when a seed is provided: | ||
| 656 | + | ||
| 657 | + ```bash | ||
| 658 | + node --test --test-random-seed=12345 | ||
| 659 | + ``` | ||
| 660 | + | ||
| 661 | + In most test files, randomization works automatically. One important exception | ||
| 662 | + is when subtests are awaited one by one. In that pattern, each subtest starts | ||
| 663 | + only after the previous one finishes, so the runner keeps declaration order | ||
| 664 | + instead of randomizing it. | ||
| 665 | + | ||
| 666 | + Example: this runs sequentially and is **not** randomized. | ||
| 667 | + | ||
| 668 | + ```mjs | ||
| 669 | + import test from 'node:test'; | ||
| 670 | + | ||
| 671 | + test('math', async (t) => { | ||
| 672 | + for (const name of ['adds', 'subtracts', 'multiplies']) { | ||
| 673 | + // Sequentially awaiting each subtest preserves declaration order. | ||
| 674 | + await t.test(name, async () => {}); | ||
| 675 | + } | ||
| 676 | + }); | ||
| 677 | + ``` | ||
| 678 | + | ||
| 679 | + ```cjs | ||
| 680 | + const test = require('node:test'); | ||
| 681 | + | ||
| 682 | + test('math', async (t) => { | ||
| 683 | + for (const name of ['adds', 'subtracts', 'multiplies']) { | ||
| 684 | + // Sequentially awaiting each subtest preserves declaration order. | ||
| 685 | + await t.test(name, async () => {}); | ||
| 686 | + } | ||
| 687 | + }); | ||
| 688 | + ``` | ||
| 689 | + | ||
| 690 | + Using suite-style APIs such as `describe()`/`it()` or `suite()`/`test()` | ||
| 691 | + still allows randomization, because sibling tests are enqueued together. | ||
| 692 | + | ||
| 693 | + Example: this remains eligible for randomization. | ||
| 694 | + | ||
| 695 | + ```mjs | ||
| 696 | + import { describe, it } from 'node:test'; | ||
| 697 | + | ||
| 698 | + describe('math', () => { | ||
| 699 | + it('adds', () => {}); | ||
| 700 | + it('subtracts', () => {}); | ||
| 701 | + it('multiplies', () => {}); | ||
| 702 | + }); | ||
| 703 | + ``` | ||
| 704 | + | ||
| 705 | + ```cjs | ||
| 706 | + const { describe, it } = require('node:test'); | ||
| 707 | + | ||
| 708 | + describe('math', () => { | ||
| 709 | + it('adds', () => {}); | ||
| 710 | + it('subtracts', () => {}); | ||
| 711 | + it('multiplies', () => {}); | ||
| 712 | + }); | ||
| 713 | + ``` | ||
| 714 | + | ||
| 715 | + `--test-randomize` and `--test-random-seed` are not supported with `--watch` mode. | ||
| 716 | + | ||
| 629 | 717 | Matching files are executed as test files. | |
| 630 | 718 | More information on the test file execution can be found | |
| 631 | 719 | in the [test runner execution model][] section. | |
@@ -666,6 +754,10 @@ test runner functionality: | |||
| 666 | 754 | * `--test-reporter` - Reporting is managed by the parent process | |
| 667 | 755 | * `--test-reporter-destination` - Output destinations are controlled by the parent | |
| 668 | 756 | * `--experimental-config-file` - Config file paths are managed by the parent | |
| 757 | + * `--test-randomize` - Randomization is managed by the parent process and | ||
| 758 | + propagated to child processes | ||
| 759 | + * `--test-random-seed` - Randomization seed is managed by the parent process and | ||
| 760 | + propagated to child processes | ||
| 669 | 761 | ||
| 670 | 762 | All other Node.js options from command line arguments, environment variables, | |
| 671 | 763 | and configuration files are inherited by the child processes. | |
@@ -1572,6 +1664,14 @@ changes: | |||
| 1572 | 1664 | that specifies the index of the shard to run. This option is _required_. | |
| 1573 | 1665 | * `total` {number} is a positive integer that specifies the total number | |
| 1574 | 1666 | of shards to split the test files to. This option is _required_. | |
| 1667 | + * `randomize` {boolean} Randomize execution order for test files and queued tests. | ||
| 1668 | + This option is not supported with `watch: true`. | ||
| 1669 | + **Default:** `false`. | ||
| 1670 | + * `randomSeed` {number} Seed used when randomizing execution order. If this | ||
| 1671 | + option is set, runs can replay the same randomized order deterministically, | ||
| 1672 | + and setting this option also enables randomization. The value must be an | ||
| 1673 | + integer between `0` and `4294967295`. | ||
| 1674 | + **Default:** `undefined`. | ||
| 1575 | 1675 | * `rerunFailuresFilePath` {string} A file path where the test runner will | |
| 1576 | 1676 | store the state of the tests to allow rerunning only the failed tests on a next run. | |
| 1577 | 1677 | 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 | { | |
@@ -902,6 +910,14 @@ | |||
| 902 | 910 | "type": "boolean", | |
| 903 | 911 | "description": "run tests with 'only' option set" | |
| 904 | 912 | }, | |
| 913 | + "test-random-seed": { | ||
| 914 | + "type": "number", | ||
| 915 | + "description": "seed used to randomize test execution order" | ||
| 916 | + }, | ||
| 917 | + "test-randomize": { | ||
| 918 | + "type": "boolean", | ||
| 919 | + "description": "run tests in a random order" | ||
| 920 | + }, | ||
| 905 | 921 | "test-reporter": { | |
| 906 | 922 | "oneOf": [ | |
| 907 | 923 | { | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -498,6 +498,22 @@ Configures the type of test isolation used in the test runner. | |||
| 498 | 498 | A regular expression that configures the test runner to only execute tests | |
| 499 | 499 | whose name matches the provided pattern. | |
| 500 | 500 | . | |
| 501 | + .It Fl -test-random-seed | ||
| 502 | + Set the seed used to randomize test execution order. | ||
| 503 | + This applies to both test file execution order and queued tests within each file. | ||
| 504 | + Providing this flag enables randomization implicitly, even without | ||
| 505 | + \fB--test-randomize\fR. | ||
| 506 | + The value must be an integer between 0 and 4294967295. | ||
| 507 | + This flag cannot be used with \fB--watch\fR or \fB--test-rerun-failures\fR. | ||
| 508 | + . | ||
| 509 | + .It Fl -test-randomize | ||
| 510 | + Randomize test execution order. | ||
| 511 | + This applies to both test file execution order and queued tests within each file. | ||
| 512 | + This can help detect tests that rely on shared state or execution order. | ||
| 513 | + The seed used for randomization is printed in the test summary and can be | ||
| 514 | + reused with \fB--test-random-seed\fR. | ||
| 515 | + This flag cannot be used with \fB--watch\fR or \fB--test-rerun-failures\fR. | ||
| 516 | + . | ||
| 501 | 517 | .It Fl -test-reporter | |
| 502 | 518 | A test reporter to use when running tests. | |
| 503 | 519 | . | |
| 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