GeezerVison is showing github.com — test_runner: add shards support · nodejs/node@b55eb2a
File treeExpand file treeCollapse file tree Open diff view settings fixtures/test-runner/shards
Expand file treeCollapse file tree Open diff view settings Collapse file Copy file name to clipboardExpand all lines: doc/api/cli.md +22Lines changed: 22 additions & 0 deletions - Display the source diff
- Display the rich diff
| Original file line number | Diff line number | Diff line change |
|---|
@@ -1289,6 +1289,27 @@ added: v18.0.0 | 1289 | 1289 | Configures the test runner to only execute top level tests that have the `only` | 1290 | 1290 | option set. | 1291 | 1291 |
| | 1292 | +### `--test-shard` | | 1293 | + | | 1294 | +<!-- YAML | | 1295 | +added: REPLACEME | | 1296 | +--> | | 1297 | + | | 1298 | +Test suite shard to execute in a format of `<index>/<total>`, where | | 1299 | + | | 1300 | +`index` is a positive integer, index of divided parts | | 1301 | +`total` is a positive integer, total of divided part | | 1302 | +This command will divide all tests files into `total` equal parts, | | 1303 | +and will run only those that happen to be in an `index` part. | | 1304 | + | | 1305 | +For example, to split your tests suite into three parts, use this: | | 1306 | + | | 1307 | +```bash | | 1308 | +node --test --test-shard=1/3 | | 1309 | +node --test --test-shard=2/3 | | 1310 | +node --test --test-shard=3/3 | | 1311 | +``` | | 1312 | + | 1292 | 1313 | ### `--throw-deprecation` | 1293 | 1314 |
| 1294 | 1315 | <!-- YAML | @@ -1952,6 +1973,7 @@ Node.js options that are allowed are: | 1952 | 1973 | * `--test-only` | 1953 | 1974 | * `--test-reporter-destination` | 1954 | 1975 | * `--test-reporter` | | 1976 | +* `--test-shard` | 1955 | 1977 | * `--throw-deprecation` | 1956 | 1978 | * `--title` | 1957 | 1979 | * `--tls-cipher-list` | |
Collapse file Copy file name to clipboardExpand all lines: doc/api/test.md +5Lines changed: 5 additions & 0 deletions - Display the source diff
- Display the rich diff
| Original file line number | Diff line number | Diff line change |
|---|
@@ -901,6 +901,11 @@ changes: | 901 | 901 | If unspecified, subtests inherit this value from their parent. | 902 | 902 | **Default:** `Infinity`. | 903 | 903 | * `watch` {boolean} Whether to run in watch mode or not. **Default:** `false`. | | 904 | + * `shard` {Object} Running tests in a specific shard. **Default:** `undefined`. | | 905 | + * `index` {number} is a positive integer between 1 and `<total>` | | 906 | + that specifies the index of the shard to run. This option is _required_. | | 907 | + * `total` {number} is a positive integer that specifies the total number | | 908 | + of shards to split the test files to. This option is _required_. | 904 | 909 | * Returns: {TestsStream} | 905 | 910 |
| 906 | 911 | ```mjs | |
Collapse file Copy file name to clipboardExpand all lines: doc/node.1 +3Lines changed: 3 additions & 0 deletions | Original file line number | Diff line number | Diff line change |
|---|
@@ -409,6 +409,9 @@ The destination for the corresponding test reporter. | 409 | 409 | Configures the test runner to only execute top level tests that have the `only` | 410 | 410 | option set. | 411 | 411 | . | | 412 | +.It Fl -test-shard | | 413 | +Test suite shard to execute in a format of <index>/<total>. | | 414 | +. | 412 | 415 | .It Fl -throw-deprecation | 413 | 416 | Throw errors for deprecations. | 414 | 417 | . | |
Collapse file Copy file name to clipboardExpand all lines: lib/internal/main/test_runner.js +36-1Lines changed: 36 additions & 1 deletion | Original file line number | Diff line number | Diff line change |
|---|
@@ -7,6 +7,16 @@ const { getOptionValue } = require('internal/options'); | 7 | 7 | const { isUsingInspector } = require('internal/util/inspector'); | 8 | 8 | const { run } = require('internal/test_runner/runner'); | 9 | 9 | const { setupTestReporters } = require('internal/test_runner/utils'); | | 10 | +const { | | 11 | + codes: { | | 12 | + ERR_INVALID_ARG_VALUE, | | 13 | + }, | | 14 | +} = require('internal/errors'); | | 15 | +const { | | 16 | + NumberParseInt, | | 17 | + RegExpPrototypeExec, | | 18 | + StringPrototypeSplit, | | 19 | +} = primordials; | 10 | 20 |
| 11 | 21 | prepareMainThreadExecution(false); | 12 | 22 | markBootstrapComplete(); | @@ -21,7 +31,32 @@ if (isUsingInspector()) { | 21 | 31 | inspectPort = process.debugPort; | 22 | 32 | } | 23 | 33 |
| 24 | | -run({ concurrency, inspectPort, watch: getOptionValue('--watch'), setup: setupTestReporters }) | | 34 | +let shard; | | 35 | +const shardOption = getOptionValue('--test-shard'); | | 36 | +if (shardOption) { | | 37 | + if (!RegExpPrototypeExec(/^\d+\/\d+$/, shardOption)) { | | 38 | + process.exitCode = 1; | | 39 | + | | 40 | + throw new ERR_INVALID_ARG_VALUE( | | 41 | + '--test-shard', | | 42 | + shardOption, | | 43 | + 'must be in the form of <index>/<total>', | | 44 | + ); | | 45 | + } | | 46 | + | | 47 | + const { 0: indexStr, 1: totalStr } = StringPrototypeSplit(shardOption, '/'); | | 48 | + | | 49 | + const index = NumberParseInt(indexStr, 10); | | 50 | + const total = NumberParseInt(totalStr, 10); | | 51 | + | | 52 | + shard = { | | 53 | + __proto__: null, | | 54 | + index, | | 55 | + total, | | 56 | + }; | | 57 | +} | | 58 | + | | 59 | +run({ concurrency, inspectPort, watch: getOptionValue('--watch'), setup: setupTestReporters, shard }) | 25 | 60 | .once('test:fail', () => { | 26 | 61 | process.exitCode = 1; | 27 | 62 | }); |
Collapse file Copy file name to clipboardExpand all lines: lib/internal/test_runner/runner.js +31-3Lines changed: 31 additions & 3 deletions | Original file line number | Diff line number | Diff line change |
|---|
@@ -39,10 +39,18 @@ const console = require('internal/console/global'); | 39 | 39 | const { | 40 | 40 | codes: { | 41 | 41 | ERR_INVALID_ARG_TYPE, | | 42 | + ERR_INVALID_ARG_VALUE, | 42 | 43 | ERR_TEST_FAILURE, | | 44 | + ERR_OUT_OF_RANGE, | 43 | 45 | }, | 44 | 46 | } = require('internal/errors'); | 45 | | -const { validateArray, validateBoolean, validateFunction } = require('internal/validators'); | | 47 | +const { | | 48 | + validateArray, | | 49 | + validateBoolean, | | 50 | + validateFunction, | | 51 | + validateObject, | | 52 | + validateInteger, | | 53 | +} = require('internal/validators'); | 46 | 54 | const { getInspectPort, isUsingInspector, isInspectorMessage } = require('internal/util/inspector'); | 47 | 55 | const { isRegExp } = require('internal/util/types'); | 48 | 56 | const { kEmptyObject } = require('internal/util'); | @@ -459,7 +467,7 @@ function run(options) { | 459 | 467 | if (options === null || typeof options !== 'object') { | 460 | 468 | options = kEmptyObject; | 461 | 469 | } | 462 | | - let { testNamePatterns } = options; | | 470 | + let { testNamePatterns, shard } = options; | 463 | 471 | const { concurrency, timeout, signal, files, inspectPort, watch, setup } = options; | 464 | 472 |
| 465 | 473 | if (files != null) { | @@ -468,6 +476,22 @@ function run(options) { | 468 | 476 | if (watch != null) { | 469 | 477 | validateBoolean(watch, 'options.watch'); | 470 | 478 | } | | 479 | + if (shard != null) { | | 480 | + validateObject(shard, 'options.shard'); | | 481 | + // Avoid re-evaluating the shard object in case it's a getter | | 482 | + shard = { __proto__: null, index: shard.index, total: shard.total }; | | 483 | + | | 484 | + validateInteger(shard.total, 'options.shard.total', 1); | | 485 | + validateInteger(shard.index, 'options.shard.index'); | | 486 | + | | 487 | + if (shard.index <= 0 || shard.total < shard.index) { | | 488 | + throw new ERR_OUT_OF_RANGE('options.shard.index', `>= 1 && <= ${shard.total} ("options.shard.total")`, shard.index); | | 489 | + } | | 490 | + | | 491 | + if (watch) { | | 492 | + throw new ERR_INVALID_ARG_VALUE('options.shard', watch, 'shards not supported with watch mode'); | | 493 | + } | | 494 | + } | 471 | 495 | if (setup != null) { | 472 | 496 | validateFunction(setup, 'options.setup'); | 473 | 497 | } | @@ -489,7 +513,11 @@ function run(options) { | 489 | 513 | } | 490 | 514 |
| 491 | 515 | const root = createTestTree({ concurrency, timeout, signal }); | 492 | | - const testFiles = files ?? createTestFileList(); | | 516 | + let testFiles = files ?? createTestFileList(); | | 517 | + | | 518 | + if (shard) { | | 519 | + testFiles = ArrayPrototypeFilter(testFiles, (_, index) => index % shard.total === shard.index - 1); | | 520 | + } | 493 | 521 |
| 494 | 522 | let postRun = () => root.postRun(); | 495 | 523 | let filesWatcher; | |
Collapse file Copy file name to clipboardExpand all lines: src/node_options.cc +4Lines changed: 4 additions & 0 deletions | Original file line number | Diff line number | Diff line change |
|---|
@@ -581,6 +581,10 @@ EnvironmentOptionsParser::EnvironmentOptionsParser() { | 581 | 581 | "run tests with 'only' option set", | 582 | 582 | &EnvironmentOptions::test_only, | 583 | 583 | kAllowedInEnvvar); | | 584 | + AddOption("--test-shard", | | 585 | + "run test at specific shard", | | 586 | + &EnvironmentOptions::test_shard, | | 587 | + kAllowedInEnvvar); | 584 | 588 | AddOption("--test-udp-no-try-send", "", // For testing only. | 585 | 589 | &EnvironmentOptions::test_udp_no_try_send); | 586 | 590 | AddOption("--throw-deprecation", | |
Collapse file Copy file name to clipboardExpand all lines: src/node_options.h +1Lines changed: 1 addition & 0 deletions | Original file line number | Diff line number | Diff line change |
|---|
@@ -161,6 +161,7 @@ class EnvironmentOptions : public Options { | 161 | 161 | std::vector<std::string> test_reporter_destination; | 162 | 162 | bool test_only = false; | 163 | 163 | bool test_udp_no_try_send = false; | | 164 | + std::string test_shard; | 164 | 165 | bool throw_deprecation = false; | 165 | 166 | bool trace_atomics_wait = false; | 166 | 167 | bool trace_deprecation = false; | |
Collapse file Copy file name to clipboard +4Lines changed: 4 additions & 0 deletions | Original file line number | Diff line number | Diff line change |
|---|
| | 1 | +'use strict'; | | 2 | +const test = require('node:test'); | | 3 | + | | 4 | +test('a.cjs this should pass'); |
Collapse file Copy file name to clipboard +4Lines changed: 4 additions & 0 deletions | Original file line number | Diff line number | Diff line change |
|---|
| | 1 | +'use strict'; | | 2 | +const test = require('node:test'); | | 3 | + | | 4 | +test('b.cjs this should pass'); |
Collapse file Copy file name to clipboard +4Lines changed: 4 additions & 0 deletions | Original file line number | Diff line number | Diff line change |
|---|
| | 1 | +'use strict'; | | 2 | +const test = require('node:test'); | | 3 | + | | 4 | +test('c.cjs this should pass'); |
|