| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
1 parent 575260d commit e3fda69
11 files changed
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -1480,22 +1480,28 @@ Enable module mocking in the test runner. | |||
| 1480 | 1480 | ||
| 1481 | 1481 | This feature requires `--allow-worker` if used with the [Permission Model][]. | |
| 1482 | 1482 | ||
| 1483 | - ### `--experimental-test-tag-filter=<tag>` | ||
| 1483 | + ### `--experimental-test-tag-filter='<expr>'` | ||
| 1484 | 1484 | ||
| 1485 | 1485 | <!-- YAML | |
| 1486 | 1486 | added: v26.2.0 | |
| 1487 | 1487 | --> | |
| 1488 | 1488 | ||
| 1489 | 1489 | > Stability: 1.0 - Early development | |
| 1490 | 1490 | ||
| 1491 | - Run only tests whose tag set contains `<tag>`. Tests declare tags via the | ||
| 1492 | - `tags` option on `test()`, `it()`, `suite()`, or `describe()`; tags | ||
| 1493 | - inherit from suites to nested tests by union. Filtering is | ||
| 1494 | - case-insensitive. | ||
| 1491 | + Run only tests that match the provided boolean tag-filter expression. Tests | ||
| 1492 | + declare tags via the `tags` option on `test()`, `it()`, `suite()`, or | ||
| 1493 | + `describe()`. Tags inherit from suites to nested tests by union. | ||
| 1495 | 1494 | ||
| 1496 | - The flag may be specified more than once; tests must contain **every** | ||
| 1497 | - filter value to run. See [Test tags][] for details on declaring and | ||
| 1498 | - inheriting tags. | ||
| 1495 | + The expression supports boolean operators (`and`/`&&`, `or`/`||`, | ||
| 1496 | + `not`/`!`), parentheses for grouping, and `*` wildcards inside identifiers. | ||
| 1497 | + Standard precedence applies: `not` binds tighter than `and`, which binds | ||
| 1498 | + tighter than `or`. See [Test tags][] for the full grammar and behavior. | ||
| 1499 | + | ||
| 1500 | + The flag may be specified more than once; multiple expressions are combined | ||
| 1501 | + with AND, so a test must satisfy every expression to run. | ||
| 1502 | + | ||
| 1503 | + A malformed expression causes the test runner to exit with a non-zero status | ||
| 1504 | + before running any tests. | ||
| 1499 | 1505 | ||
| 1500 | 1506 | ### `--experimental-vfs` | |
| 1501 | 1507 | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -489,8 +489,8 @@ added: v26.2.0 | |||
| 489 | 489 | ||
| 490 | 490 | Tags annotate tests and suites with arbitrary string labels. The | |
| 491 | 491 | [`--experimental-test-tag-filter`][] CLI flag (or the `testTagFilters` | |
| 492 | - option on [`run()`][]) selects tests whose tag set contains every | ||
| 493 | - provided filter value. | ||
| 492 | + option on [`run()`][]) selects tests by a boolean expression over those | ||
| 493 | + labels. | ||
| 494 | 494 | ||
| 495 | 495 | Tags are an alternative to encoding metadata into test names. They are | |
| 496 | 496 | useful for cross-cutting axes such as subsystem, speed bucket, flakiness, | |
@@ -523,37 +523,89 @@ describe('database', { tags: ['db'] }, () => { | |||
| 523 | 523 | }); | |
| 524 | 524 | ``` | |
| 525 | 525 | ||
| 526 | - Tag values must be non-empty strings. Tags are matched case-insensitively; | ||
| 527 | - the canonical form is lowercase. Duplicates within a single `tags` array | ||
| 528 | - are collapsed on the lowercased form, preserving the first-seen | ||
| 529 | - declaration order. | ||
| 526 | + Tag values must be non-empty strings that contain no whitespace, no | ||
| 527 | + operator characters (`& | ! ( ) *`), and are not the reserved words | ||
| 528 | + `'and'`, `'or'`, or `'not'` in any casing. Tags are matched | ||
| 529 | + case-insensitively; the canonical form is lowercase. Duplicates within a | ||
| 530 | + single `tags` array are collapsed on the lowercased form, preserving the | ||
| 531 | + first-seen declaration order. | ||
| 530 | 532 | ||
| 531 | 533 | Hooks (`before`, `after`, `beforeEach`, `afterEach`) do not declare their | |
| 532 | 534 | own tags. They run as part of their owning suite, which carries the | |
| 533 | 535 | suite's tags. | |
| 534 | 536 | ||
| 535 | - ### Filtering by tag | ||
| 537 | + ### Filtering syntax | ||
| 536 | 538 | ||
| 537 | - Each [`--experimental-test-tag-filter`][] value is a literal tag name. A | ||
| 538 | - test runs only when its tag set contains that name. The flag may be | ||
| 539 | - specified more than once; tests must match **every** filter to run. The | ||
| 540 | - same applies to the `testTagFilters` array on [`run()`][]. Filters are | ||
| 541 | - case-insensitive and AND'd with [`--test-name-pattern`][], | ||
| 542 | - [`--test-skip-pattern`][], and `.only` filtering. | ||
| 539 | + The filter expression supports: | ||
| 543 | 540 | ||
| 544 | - Untagged tests are excluded under any non-empty filter, since the filter | ||
| 545 | - requires the tag to be present. | ||
| 541 | + * Identifiers—any non-whitespace, non-operator characters. A literal | ||
| 542 | + identifier matches a tag of the same value (case-insensitive). | ||
| 543 | + * `*` wildcards inside an identifier match any sequence of characters. | ||
| 544 | + A bare `*` matches any tagged test. | ||
| 545 | + * Boolean operators with two equivalent forms: | ||
| 546 | + * `and` / `&&` | ||
| 547 | + * `or` / `||` | ||
| 548 | + * `not` / `!` | ||
| 549 | + * Parentheses for grouping. | ||
| 546 | 550 | ||
| 547 | - ### Reading tags from inside a test | ||
| 551 | + The word forms (`and`, `or`, `not`) require whitespace separation; the | ||
| 552 | + punctuation forms do not. | ||
| 553 | + | ||
| 554 | + #### Operator precedence | ||
| 555 | + | ||
| 556 | + The expression is evaluated with the standard precedence | ||
| 557 | + `not > and > or`. Binary operators are left-associative. | ||
| 558 | + | ||
| 559 | + | Expression | Equivalent grouping | | ||
| 560 | + | -------------- | ------------------- | | ||
| 561 | + | `a or b and c` | `a or (b and c)` | | ||
| 562 | + | `not a and b` | `(not a) and b` | | ||
| 563 | + | ||
| 564 | + Use parentheses to override: | ||
| 565 | + | ||
| 566 | + | Expression | Selects | | ||
| 567 | + | ------------------------------ | ------------------------------------------ | | ||
| 568 | + | `(unit or smoke) and not slow` | unit-or-smoke tests that are not also slow | | ||
| 569 | + | `db && !flaky` | db tests that are not flaky | | ||
| 570 | + | `*` | every tagged test | | ||
| 571 | + | ||
| 572 | + #### Untagged tests | ||
| 573 | + | ||
| 574 | + Untagged tests behave as if they have an empty tag set. As a result: | ||
| 575 | + | ||
| 576 | + | Filter expression | Untagged test | Why | | ||
| 577 | + | ------------------------ | ------------- | ------------------------------------------------ | | ||
| 578 | + | `db` | excluded | Positive match against an empty tag set is false | | ||
| 579 | + | `*` | excluded | The bare wildcard requires at least one tag | | ||
| 580 | + | `db or unit` | excluded | Both branches are false against an empty tag set | | ||
| 581 | + | `not flaky` | included | Negation against an empty tag set is true | | ||
| 582 | + | `not flaky and not slow` | included | Both negations are true against an empty tag set | | ||
| 583 | + | `db or not flaky` | included | The negated branch is true | | ||
| 584 | + | ||
| 585 | + For example, `--experimental-test-tag-filter='not flaky'` runs every test | ||
| 586 | + that is not tagged `flaky`, including all untagged tests. | ||
| 587 | + | ||
| 588 | + #### Composing multiple filters | ||
| 589 | + | ||
| 590 | + [`--experimental-test-tag-filter`][] may be specified more than once on the | ||
| 591 | + command line. Multiple expressions compose by AND—a test must satisfy | ||
| 592 | + every expression to run. The same applies to passing an array to | ||
| 593 | + `testTagFilters` on [`run()`][]. The tag filter is also AND'd with | ||
| 594 | + [`--test-name-pattern`][], [`--test-skip-pattern`][], and `.only` | ||
| 595 | + filtering. | ||
| 596 | + | ||
| 597 | + #### Reading tags from inside a test | ||
| 548 | 598 | ||
| 549 | 599 | The [`TestContext`][] object exposes the test's tags as a frozen array | |
| 550 | 600 | through [`context.tags`][], so tests can branch on their own metadata. | |
| 551 | 601 | ||
| 552 | - ### Errors | ||
| 602 | + #### Errors | ||
| 553 | 603 | ||
| 554 | 604 | A tag value that violates the validation rules above throws | |
| 555 | 605 | `ERR_INVALID_ARG_VALUE` at the registration site, before any test runs. | |
| 556 | - A non-array `tags` value throws `ERR_INVALID_ARG_TYPE`. | ||
| 606 | + A non-array `tags` value throws `ERR_INVALID_ARG_TYPE`. A malformed | ||
| 607 | + filter expression on the CLI causes the test runner to exit with a | ||
| 608 | + non-zero status before running any test files. | ||
| 557 | 609 | ||
| 558 | 610 | ## Extraneous asynchronous activity | |
| 559 | 611 | ||
@@ -826,7 +878,7 @@ test runner functionality: | |||
| 826 | 878 | ||
| 827 | 879 | * `--test` - Prevented to avoid recursive test execution | |
| 828 | 880 | * `--experimental-test-coverage` - Managed by the test runner | |
| 829 | - * `--experimental-test-tag-filter` - Filter values are validated by the parent | ||
| 881 | + * `--experimental-test-tag-filter` - Filter expressions are validated by the parent | ||
| 830 | 882 | process and re-emitted to child processes | |
| 831 | 883 | * `--watch` - Watch mode is handled at the parent level | |
| 832 | 884 | * `--experimental-default-config-file` - Config file loading is handled by the parent | |
@@ -1740,10 +1792,11 @@ changes: | |||
| 1740 | 1792 | For each test that is executed, any corresponding test hooks, such as | |
| 1741 | 1793 | `beforeEach()`, are also run. | |
| 1742 | 1794 | **Default:** `undefined`. | |
| 1743 | - * `testTagFilters` {string|string\[]} A tag name, or an array of tag names, | ||
| 1744 | - used to filter tests by their declared tags. Tests must contain every | ||
| 1745 | - listed tag to run. Equivalent to passing [`--experimental-test-tag-filter`][] | ||
| 1746 | - on the command line. See [Test tags][]. **Default:** `undefined`. | ||
| 1795 | + * `testTagFilters` {string|string\[]} A boolean expression, or an array of | ||
| 1796 | + boolean expressions, used to filter tests by their declared tags. | ||
| 1797 | + Multiple expressions compose by AND. Equivalent to passing | ||
| 1798 | + [`--experimental-test-tag-filter`][] on the command line. See | ||
| 1799 | + [Test tags][]. **Default:** `undefined`. | ||
| 1747 | 1800 | * `timeout` {number} A number of milliseconds the test execution will | |
| 1748 | 1801 | fail after. | |
| 1749 | 1802 | If unspecified, subtests inherit this value from their parent. | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -810,14 +810,18 @@ collecting code coverage from tests for more details. | |||
| 810 | 810 | Enable module mocking in the test runner. | |
| 811 | 811 | This feature requires \fB--allow-worker\fR if used with the Permission Model. | |
| 812 | 812 | . | |
| 813 | - .It Fl -experimental-test-tag-filter Ns = Ns Ar <tag> | ||
| 814 | - Run only tests whose tag set contains \fB<tag>\fR. Tests declare tags via the | ||
| 815 | - \fBtags\fR option on \fBtest()\fR, \fBit()\fR, \fBsuite()\fR, or \fBdescribe()\fR; tags | ||
| 816 | - inherit from suites to nested tests by union. Filtering is | ||
| 817 | - case-insensitive. | ||
| 818 | - The flag may be specified more than once; tests must contain \fBevery\fR | ||
| 819 | - filter value to run. See Test tags for details on declaring and | ||
| 820 | - inheriting tags. | ||
| 813 | + .It Fl -experimental-test-tag-filter Ns = Ns Ar '<expr>' | ||
| 814 | + Run only tests that match the provided boolean tag-filter expression. Tests | ||
| 815 | + declare tags via the \fBtags\fR option on \fBtest()\fR, \fBit()\fR, \fBsuite()\fR, or | ||
| 816 | + \fBdescribe()\fR. Tags inherit from suites to nested tests by union. | ||
| 817 | + The expression supports boolean operators (\fBand\fR/\fB&&\fR, \fBor\fR/\fB||\fR, | ||
| 818 | + \fBnot\fR/\fB!\fR), parentheses for grouping, and \fB*\fR wildcards inside identifiers. | ||
| 819 | + Standard precedence applies: \fBnot\fR binds tighter than \fBand\fR, which binds | ||
| 820 | + tighter than \fBor\fR. See Test tags for the full grammar and behavior. | ||
| 821 | + The flag may be specified more than once; multiple expressions are combined | ||
| 822 | + with AND, so a test must satisfy every expression to run. | ||
| 823 | + A malformed expression causes the test runner to exit with a non-zero status | ||
| 824 | + before running any tests. | ||
| 821 | 825 | . | |
| 822 | 826 | .It Fl -experimental-vfs | |
| 823 | 827 | Enable the experimental \fBnode:vfs\fR module. | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -12,6 +12,7 @@ const { | |||
| 12 | 12 | ArrayPrototypePush, | |
| 13 | 13 | ArrayPrototypePushApply, | |
| 14 | 14 | ArrayPrototypeShift, | |
| 15 | + ArrayPrototypeSlice, | ||
| 15 | 16 | ArrayPrototypeSome, | |
| 16 | 17 | ArrayPrototypeSort, | |
| 17 | 18 | MathMax, | |
@@ -93,7 +94,7 @@ const { | |||
| 93 | 94 | parseCommandLine, | |
| 94 | 95 | } = require('internal/test_runner/utils'); | |
| 95 | 96 | const { | |
| 96 | - validateAndCanonicalizeTagFilter, | ||
| 97 | + parseTagFilterExpression, | ||
| 97 | 98 | } = require('internal/test_runner/tag_filter'); | |
| 98 | 99 | const { Glob } = require('internal/fs/glob'); | |
| 99 | 100 | const { once } = require('events'); | |
@@ -182,7 +183,7 @@ function getRunArgs(path, { forceExit, | |||
| 182 | 183 | inspectPort, | |
| 183 | 184 | testNamePatterns, | |
| 184 | 185 | testSkipPatterns, | |
| 185 | - testTagFilters, | ||
| 186 | + testTagFilterExpressions, | ||
| 186 | 187 | only, | |
| 187 | 188 | hasFiles, | |
| 188 | 189 | testFiles, | |
@@ -224,8 +225,8 @@ function getRunArgs(path, { forceExit, | |||
| 224 | 225 | if (testSkipPatterns != null) { | |
| 225 | 226 | ArrayPrototypeForEach(testSkipPatterns, (pattern) => ArrayPrototypePush(runArgs, `--test-skip-pattern=${pattern}`)); | |
| 226 | 227 | } | |
| 227 | - if (testTagFilters != null) { | ||
| 228 | - ArrayPrototypeForEach(testTagFilters, (value) => ArrayPrototypePush(runArgs, `--experimental-test-tag-filter=${value}`)); | ||
| 228 | + if (testTagFilterExpressions != null) { | ||
| 229 | + ArrayPrototypeForEach(testTagFilterExpressions, (expr) => ArrayPrototypePush(runArgs, `--experimental-test-tag-filter=${expr}`)); | ||
| 229 | 230 | } | |
| 230 | 231 | if (only === true) { | |
| 231 | 232 | ArrayPrototypePush(runArgs, '--test-only'); | |
@@ -872,19 +873,37 @@ function run(options = kEmptyObject) { | |||
| 872 | 873 | }); | |
| 873 | 874 | } | |
| 874 | 875 | ||
| 876 | + // The public contract of testTagFilters is `string | string[]`. The | ||
| 877 | + // parseCommandLine bootstrap path piggybacks the already-parsed AST array | ||
| 878 | + // on the same field, identifiable by the sibling testTagFilterExpressions | ||
| 879 | + // field which only that path sets. When that marker is present and the | ||
| 880 | + // first element isn't a string, treat the array as ASTs and skip the | ||
| 881 | + // public validation loop. Otherwise validate every element as a string, | ||
| 882 | + // so any non-string input throws ERR_INVALID_ARG_TYPE with the offending | ||
| 883 | + // index regardless of position. | ||
| 884 | + let testTagFilterExpressions = null; | ||
| 875 | 885 | if (testTagFilters != null) { | |
| 876 | 886 | if (!ArrayIsArray(testTagFilters)) { | |
| 877 | 887 | testTagFilters = [testTagFilters]; | |
| 878 | 888 | } | |
| 879 | 889 | if (testTagFilters.length === 0) { | |
| 880 | 890 | testTagFilters = null; | |
| 891 | + } else if (options.testTagFilterExpressions != null && | ||
| 892 | + typeof testTagFilters[0] !== 'string') { | ||
| 893 | + // Internal bootstrap: trust the AST array as already-parsed. | ||
| 881 | 894 | } else { | |
| 882 | 895 | emitExperimentalWarning('Test tags'); | |
| 883 | - testTagFilters = ArrayPrototypeMap(testTagFilters, (value, i) => ( | ||
| 884 | - validateAndCanonicalizeTagFilter(value, `options.testTagFilters[${i}]`) | ||
| 885 | - )); | ||
| 896 | + testTagFilterExpressions = ArrayPrototypeSlice(testTagFilters); | ||
| 897 | + testTagFilters = ArrayPrototypeMap(testTagFilters, (value, i) => { | ||
| 898 | + const name = `options.testTagFilters[${i}]`; | ||
| 899 | + if (typeof value !== 'string') { | ||
| 900 | + throw new ERR_INVALID_ARG_TYPE(name, 'string', value); | ||
| 901 | + } | ||
| 902 | + return parseTagFilterExpression(value, name); | ||
| 903 | + }); | ||
| 886 | 904 | } | |
| 887 | 905 | } | |
| 906 | + testTagFilterExpressions ??= options.testTagFilterExpressions; | ||
| 888 | 907 | ||
| 889 | 908 | validateOneOf(isolation, 'options.isolation', ['process', 'none']); | |
| 890 | 909 | validateBoolean(coverage, 'options.coverage'); | |
@@ -986,7 +1005,7 @@ function run(options = kEmptyObject) { | |||
| 986 | 1005 | inspectPort, | |
| 987 | 1006 | testNamePatterns, | |
| 988 | 1007 | testSkipPatterns, | |
| 989 | - testTagFilters, | ||
| 1008 | + testTagFilterExpressions, | ||
| 990 | 1009 | hasFiles: files != null, | |
| 991 | 1010 | globPatterns, | |
| 992 | 1011 | only, | |
| Back | FazBrowse Home | New Git URL |
0 commit comments