-
-
Notifications
You must be signed in to change notification settings - Fork 35.6k
test_runner: extend tag filter with boolean expression DSL #63054
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -1413,22 +1413,28 @@ Enable module mocking in the test runner. | |
|
|
||
| This feature requires `--allow-worker` if used with the [Permission Model][]. | ||
|
|
||
| ### `--experimental-test-tag-filter=<tag>` | ||
| ### `--experimental-test-tag-filter='<expr>'` | ||
|
|
||
| <!-- YAML | ||
| added: REPLACEME | ||
| --> | ||
|
|
||
| > Stability: 1.0 - Early development | ||
|
|
||
| Run only tests whose tag set contains `<tag>`. Tests declare tags via the | ||
| `tags` option on `test()`, `it()`, `suite()`, or `describe()`; tags | ||
| inherit from suites to nested tests by union. Filtering is | ||
| case-insensitive. | ||
| Run only tests that match the provided boolean tag-filter expression. Tests | ||
| declare tags via the `tags` option on `test()`, `it()`, `suite()`, or | ||
| `describe()`. Tags inherit from suites to nested tests by union. | ||
|
|
||
| The flag may be specified more than once; tests must contain **every** | ||
| filter value to run. See [Test tags][] for details on declaring and | ||
| inheriting tags. | ||
| The expression supports boolean operators (`and`/`&&`, `or`/`||`, | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. isnt there a better way then this tag filtering syntax?.
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. that is more consistent to our name filter flag
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Without the boolean syntax this feature mostly collapses into name-pattern... The goal (I had in mind) of this feature is to introduce a easy (and common, see prior art) way to filter without the need for a programmatic API. Sure, you could already achieve complex filtering via Vitest, mocha-tags, and jest-runner-groups all expose such a boolean composition syntax.
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Before I even saw Moshe's comment, I was thinking the same thing. But after a minute, I can see the use of it. I know --experimental-test-tag-filter=foo&bar&qux|zedIs that "foo and bar and (qux or zed)" or "(foo and bar and qux) or zed"? For some reason, my eyes see the former, but syntactically, it's usually the latter. IMO the original spec for syntax made a huge mistake not requiring parentheses when combining operators. If we support combining operators, I think parentheses should be required. --experimental-test-tag-filter=(foo&bar&qux)|zed--experimental-test-tag-filter=foo&bar&(qux|zed)
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @JakobJingleheimer I feel that
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I considered that before posting: I think nobody would be confused here because bitwise is not appropriate.
My bigger concern would be parens for combined operators.
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. thats why I think we should release a initial version that does not include this new langauge
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @MoLow @JakobJingleheimer I opened #63221 to separate these concerns.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Does it really make sense to have this in core? It adds a lot of complexity, I wonder if having it as a plugin maintained separately would make more sense
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @aduh95 IMHO it makes sense because it would add a more complex mechanism for filtering tests, which we currently don't have. I spoke with @MoLow about maybe simplifying by allowing to pass a
I am happy to do that, but still - I am not sure if/how that would be achievable |
||
| `not`/`!`), parentheses for grouping, and `*` wildcards inside identifiers. | ||
| Standard precedence applies: `not` binds tighter than `and`, which binds | ||
| tighter than `or`. See [Test tags][] for the full grammar and behavior. | ||
|
|
||
| The flag may be specified more than once; multiple expressions are combined | ||
| with AND, so a test must satisfy every expression to run. | ||
|
|
||
| A malformed expression causes the test runner to exit with a non-zero status | ||
| before running any tests. | ||
|
|
||
| ### `--experimental-vm-modules` | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -489,8 +489,8 @@ added: REPLACEME | |
|
|
||
| Tags annotate tests and suites with arbitrary string labels. The | ||
| [`--experimental-test-tag-filter`][] CLI flag (or the `testTagFilters` | ||
| option on [`run()`][]) selects tests whose tag set contains every | ||
| provided filter value. | ||
| option on [`run()`][]) selects tests by a boolean expression over those | ||
| labels. | ||
|
|
||
| Tags are an alternative to encoding metadata into test names. They are | ||
| useful for cross-cutting axes such as subsystem, speed bucket, flakiness, | ||
|
|
@@ -523,37 +523,89 @@ describe('database', { tags: ['db'] }, () => { | |
| }); | ||
| ``` | ||
|
|
||
| Tag values must be non-empty strings. Tags are matched case-insensitively; | ||
| the canonical form is lowercase. Duplicates within a single `tags` array | ||
| are collapsed on the lowercased form, preserving the first-seen | ||
| declaration order. | ||
| Tag values must be non-empty strings that contain no whitespace, no | ||
| operator characters (`& | ! ( ) *`), and are not the reserved words | ||
| `'and'`, `'or'`, or `'not'` in any casing. Tags are matched | ||
| case-insensitively; the canonical form is lowercase. Duplicates within a | ||
| single `tags` array are collapsed on the lowercased form, preserving the | ||
| first-seen declaration order. | ||
|
|
||
| Hooks (`before`, `after`, `beforeEach`, `afterEach`) do not declare their | ||
| own tags. They run as part of their owning suite, which carries the | ||
| suite's tags. | ||
|
|
||
| ### Filtering by tag | ||
| ### Filtering syntax | ||
|
|
||
| Each [`--experimental-test-tag-filter`][] value is a literal tag name. A | ||
| test runs only when its tag set contains that name. The flag may be | ||
| specified more than once; tests must match **every** filter to run. The | ||
| same applies to the `testTagFilters` array on [`run()`][]. Filters are | ||
| case-insensitive and AND'd with [`--test-name-pattern`][], | ||
| [`--test-skip-pattern`][], and `.only` filtering. | ||
| The filter expression supports: | ||
|
|
||
| Untagged tests are excluded under any non-empty filter, since the filter | ||
| requires the tag to be present. | ||
| * Identifiers—any non-whitespace, non-operator characters. A literal | ||
| identifier matches a tag of the same value (case-insensitive). | ||
| * `*` wildcards inside an identifier match any sequence of characters. | ||
| A bare `*` matches any tagged test. | ||
| * Boolean operators with two equivalent forms: | ||
| * `and` / `&&` | ||
| * `or` / `||` | ||
| * `not` / `!` | ||
| * Parentheses for grouping. | ||
|
Comment on lines
+541
to
+549
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm not too keen on creating a new language for that. Let's either use JS (e.g.
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Probably better if we have this conversation at: #63054 (comment)? Having it in JS means we have a problem (can't serialize functions, so |
||
|
|
||
| ### Reading tags from inside a test | ||
| The word forms (`and`, `or`, `not`) require whitespace separation; the | ||
| punctuation forms do not. | ||
|
|
||
| #### Operator precedence | ||
|
|
||
| The expression is evaluated with the standard precedence | ||
| `not > and > or`. Binary operators are left-associative. | ||
|
|
||
| | Expression | Equivalent grouping | | ||
| | -------------- | ------------------- | | ||
| | `a or b and c` | `a or (b and c)` | | ||
| | `not a and b` | `(not a) and b` | | ||
|
|
||
| Use parentheses to override: | ||
|
|
||
| | Expression | Selects | | ||
| | ------------------------------ | ------------------------------------------ | | ||
| | `(unit or smoke) and not slow` | unit-or-smoke tests that are not also slow | | ||
| | `db && !flaky` | db tests that are not flaky | | ||
| | `*` | every tagged test | | ||
|
|
||
| #### Untagged tests | ||
|
|
||
| Untagged tests behave as if they have an empty tag set. As a result: | ||
|
|
||
| | Filter expression | Untagged test | Why | | ||
| | ------------------------ | ------------- | ------------------------------------------------ | | ||
| | `db` | excluded | Positive match against an empty tag set is false | | ||
| | `*` | excluded | The bare wildcard requires at least one tag | | ||
| | `db or unit` | excluded | Both branches are false against an empty tag set | | ||
| | `not flaky` | included | Negation against an empty tag set is true | | ||
| | `not flaky and not slow` | included | Both negations are true against an empty tag set | | ||
| | `db or not flaky` | included | The negated branch is true | | ||
|
|
||
| For example, `--experimental-test-tag-filter='not flaky'` runs every test | ||
| that is not tagged `flaky`, including all untagged tests. | ||
|
|
||
| #### Composing multiple filters | ||
|
|
||
| [`--experimental-test-tag-filter`][] may be specified more than once on the | ||
| command line. Multiple expressions compose by AND—a test must satisfy | ||
| every expression to run. The same applies to passing an array to | ||
| `testTagFilters` on [`run()`][]. The tag filter is also AND'd with | ||
| [`--test-name-pattern`][], [`--test-skip-pattern`][], and `.only` | ||
| filtering. | ||
|
|
||
| #### Reading tags from inside a test | ||
|
|
||
| The [`TestContext`][] object exposes the test's tags as a frozen array | ||
| through [`context.tags`][], so tests can branch on their own metadata. | ||
|
|
||
| ### Errors | ||
| #### Errors | ||
|
|
||
| A tag value that violates the validation rules above throws | ||
| `ERR_INVALID_ARG_VALUE` at the registration site, before any test runs. | ||
| A non-array `tags` value throws `ERR_INVALID_ARG_TYPE`. | ||
| A non-array `tags` value throws `ERR_INVALID_ARG_TYPE`. A malformed | ||
| filter expression on the CLI causes the test runner to exit with a | ||
| non-zero status before running any test files. | ||
|
|
||
| ## Extraneous asynchronous activity | ||
|
|
||
|
|
@@ -826,7 +878,7 @@ test runner functionality: | |
|
|
||
| * `--test` - Prevented to avoid recursive test execution | ||
| * `--experimental-test-coverage` - Managed by the test runner | ||
| * `--experimental-test-tag-filter` - Filter values are validated by the parent | ||
| * `--experimental-test-tag-filter` - Filter expressions are validated by the parent | ||
| process and re-emitted to child processes | ||
| * `--watch` - Watch mode is handled at the parent level | ||
| * `--experimental-default-config-file` - Config file loading is handled by the parent | ||
|
|
@@ -1737,10 +1789,11 @@ changes: | |
| For each test that is executed, any corresponding test hooks, such as | ||
| `beforeEach()`, are also run. | ||
| **Default:** `undefined`. | ||
| * `testTagFilters` {string|string\[]} A tag name, or an array of tag names, | ||
| used to filter tests by their declared tags. Tests must contain every | ||
| listed tag to run. Equivalent to passing [`--experimental-test-tag-filter`][] | ||
| on the command line. See [Test tags][]. **Default:** `undefined`. | ||
| * `testTagFilters` {string|string\[]} A boolean expression, or an array of | ||
| boolean expressions, used to filter tests by their declared tags. | ||
| Multiple expressions compose by AND. Equivalent to passing | ||
| [`--experimental-test-tag-filter`][] on the command line. See | ||
| [Test tags][]. **Default:** `undefined`. | ||
| * `timeout` {number} A number of milliseconds the test execution will | ||
| fail after. | ||
| If unspecified, subtests inherit this value from their parent. | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure adding quotes helps here, to me at least that seems more confusing (e.g. git docs do not do that: https://git-scm.com/docs/git-log)