Skip to content

test_runner: extend tag filter with boolean expression DSL#63054

Open
atlowChemi wants to merge 1 commit into
nodejs:mainfrom
atlowChemi:test_runner-tag-filter
Open

test_runner: extend tag filter with boolean expression DSL#63054
atlowChemi wants to merge 1 commit into
nodejs:mainfrom
atlowChemi:test_runner-tag-filter

Conversation

@atlowChemi
Copy link
Copy Markdown
Member

@atlowChemi atlowChemi commented Apr 30, 2026

Summary

Builds on #63221, which landed the tags option, inheritance, reporter
event payloads, and a literal-tag-name filter. This PR upgrades the
filter to a boolean expression DSL:

  • Grammar: and/&&, or/||, not/!, parentheses for grouping,
    and * wildcards inside identifiers (bare * matches any tagged
    test). Standard precedence (not > and > or); binary operators are
    left-associative. Word forms (and/or/not) require whitespace
    separation; punctuation forms do not.
  • --experimental-test-tag-filter and the testTagFilters run()
    option now accept an expression rather than a literal tag name. The
    flag is still repeatable; multiple expressions still AND together.
  • Untagged tests: any include expression evaluates false against an
    empty tag set; not X evaluates true. So not flaky keeps every
    untagged test, while db excludes them.
  • Tag value validation tightens: tag names may no longer contain
    whitespace, operator characters (& | ! ( ) *), or the reserved
    words and/or/not in any casing. This is a breaking change
    relative to test_runner: add tags option and tag-name filter #63221 - acceptable since the feature is at Stability 1.0
    (Early development).
  • Malformed expressions fail fast at parent-process startup before any
    test file is spawned.

Prior art

Tag filtering with boolean composition is well-trodden in the JS
testing ecosystem. The expression syntax here is closest to Vitest's:

Vitest additionally lets a tag carry per-test config overrides such as
timeout or retry (with priority resolution between overlapping
tags). That's intentionally out of scope for this PR - landing it
would commit to a separate tags: [{ name, timeout, retry, priority }]
config surface that's orthogonal to filtering. It can be layered on
later without breaking the union-inheritance semantics established in
#63221.

@nodejs-github-bot
Copy link
Copy Markdown
Collaborator

Review requested:

  • @nodejs/config
  • @nodejs/test_runner

@nodejs-github-bot nodejs-github-bot added lib / src Issues and PRs related to general changes in the lib or src directory. needs-ci PRs that need a full CI run. labels Apr 30, 2026
@atlowChemi atlowChemi added the test_runner Issues and PRs related to the test runner subsystem. label Apr 30, 2026
Comment thread doc/api/cli.md
declare tags via the `tags` option on `test()`, `it()`, `suite()`, or
`describe()`. Tags inherit from suites to nested tests by union.

The expression supports boolean operators (`and`/`&&`, `or`/`||`,
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

isnt there a better way then this tag filtering syntax?.
i.e accept in the run method a string[] or function(string) => boolean and in case you want some complex logic - use the run api without the node cli - That makes much more sense to me

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

that is more consistent to our name filter flag

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The 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 run() API, but the goal here is to add a built-in logic for this.

Vitest, mocha-tags, and jest-runner-groups all expose such a boolean composition syntax.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The 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 && and || align with javascript, but it seems a little long, and the simpler & and | look pretty straightforward to me (and aligns with others, like query params and old skool forum query syntax). What happens when you combine them though?

--experimental-test-tag-filter=foo&bar&qux|zed

Is 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)

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@JakobJingleheimer I feel that & vs && is not really "a little long", and I feel using & and | could be confusing with bitwise operators, while removing just a single character...

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The 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.

&& vs & is a thought musing.

My bigger concern would be parens for combined operators.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The 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

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@MoLow @JakobJingleheimer I opened #63221 to separate these concerns.

@codecov
Copy link
Copy Markdown

codecov Bot commented Apr 30, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 90.07%. Comparing base (b06aee0) to head (3f583b5).
⚠️ Report is 8 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff             @@
##             main   #63054      +/-   ##
==========================================
+ Coverage   90.04%   90.07%   +0.02%     
==========================================
  Files         714      714              
  Lines      225245   225641     +396     
  Branches    42571    42668      +97     
==========================================
+ Hits       202821   203242     +421     
- Misses      14186    14192       +6     
+ Partials     8238     8207      -31     
Files with missing lines Coverage Δ
lib/internal/test_runner/runner.js 93.93% <100.00%> (+0.35%) ⬆️
lib/internal/test_runner/tag_filter.js 100.00% <100.00%> (ø)
lib/internal/test_runner/utils.js 65.19% <100.00%> (-0.05%) ⬇️

... and 41 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@JakobJingleheimer
Copy link
Copy Markdown
Member

Sweet! I'll try to carve out time on Saturday to reciew

Comment thread test/parallel/test-runner-tag-filter-cli.mjs Outdated
@atlowChemi atlowChemi force-pushed the test_runner-tag-filter branch 3 times, most recently from 3591324 to 31a7737 Compare May 5, 2026 16:47
Copy link
Copy Markdown
Member

@JakobJingleheimer JakobJingleheimer left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry for the delay. GitHub is having a bit of a stroke. Every time I try to add a comment to the review "Something went wrong" and I have to copy it, reload, paste, and try a couple more times.

I'll try to finish tomorrow (I got through 10 of 18 files, which doesn't include tag_filter.js).

Comment thread doc/api/cli.md
declare tags via the `tags` option on `test()`, `it()`, `suite()`, or
`describe()`. Tags inherit from suites to nested tests by union.

The expression supports boolean operators (`and`/`&&`, `or`/`||`,
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The 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 && and || align with javascript, but it seems a little long, and the simpler & and | look pretty straightforward to me (and aligns with others, like query params and old skool forum query syntax). What happens when you combine them though?

--experimental-test-tag-filter=foo&bar&qux|zed

Is 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)

Comment thread doc/api/test.md Outdated
Comment thread doc/api/test.md Outdated
Comment thread doc/api/test.md Outdated
Comment on lines +576 to +580
* Any include expression (a tag, wildcard, `and`, or `or`) is **false**
for an untagged test, so untagged tests are excluded under any positive
filter.
* `not X` is **true** for an untagged test, so excluding tags does not
accidentally remove untagged tests.
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is generally difficult to follow. I think a table of examples would be more straightforward.

Comment thread lib/internal/test_runner/test.js Outdated
Comment on lines +37 to +38
fail(nesting, loc, testNumber, name, details, directive, testId) {
fail(nesting, loc, testNumber, name, details, directive, testId, tags) {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

General comment: this is getting to be a bit of a junk-drawer of arguments 😬

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree, perhaps we could open a good first issue Issues that are suitable for first-time contributors. issue to handle this

@atlowChemi
Copy link
Copy Markdown
Member Author

GitHub is having a bit of a stroke.

@JakobJingleheimer github having issues? no way 😂

Upgrades the experimental tag filter introduced by stage 1 to accept a
boolean expression instead of a literal tag name.

Grammar: `and`/`&&`, `or`/`||`, `not`/`!`, parentheses for grouping,
and `*` wildcards inside identifiers. Standard precedence
(`not > and > or`); binary operators are left-associative. Word forms
require whitespace separation; punctuation forms do not. Untagged
tests evaluate `false` for any include expression and `true` for
`not X`, so excluding tags does not accidentally remove untagged
tests.

The flag and `testTagFilters` option are still repeatable; multiple
expressions still AND together. Malformed expressions fail fast at
the parent process at startup.

Tag value validation tightens to reject whitespace, operator
characters (`& | ! ( ) *`), and the reserved words `and`/`or`/`not`
in any casing - a breaking change relative to the stage 1 ship,
acceptable at Stability 1.0 (Early development).

Signed-off-by: atlowChemi <chemi@atlow.co.il>
@atlowChemi atlowChemi force-pushed the test_runner-tag-filter branch from 131d524 to 3f583b5 Compare May 12, 2026 17:51
@atlowChemi atlowChemi changed the title test_runner: add experimental tag-based test filtering test_runner: extend tag filter with boolean expression DSL May 12, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

lib / src Issues and PRs related to general changes in the lib or src directory. needs-ci PRs that need a full CI run. test_runner Issues and PRs related to the test runner subsystem.

Projects

Status: No status

Development

Successfully merging this pull request may close these issues.

5 participants