Filtering tests
Use -E / --filter to select tests by name or tag. Combine predicates
with boolean operators, using syntax inspired by nextest's filtersets:
| Bash | |
|---|---|
1 2 3 | |
Adding custom tags
Any attribute under karva.tags that is not a built-in tag creates a custom
tag. Tags are permissive by default:
| test_checkout.py | |
|---|---|
1 2 3 4 5 6 | |
Pytest marks are also recognized as custom tags, so
@pytest.mark.integration matches the same tag(integration) predicate.
Arguments attached to custom tags do not affect filtering; filters match the
tag name.
Set karva_tag at module scope to apply one Karva tag or several tags to every
test in that module:
| test_checkout.py | |
|---|---|
1 2 3 | |
Module tags support built-in tags, custom tags, and parametrization. Karva
composes tags in this order: function decorators, karva_tag, then
pytestmark. Scalar policies such as timeout use the first applicable value;
skip tags accumulate, and parametrization dimensions compose. An empty list
applies no tags.
Projects can register supported names once and enable strict validation in any profile:
| karva.toml | |
|---|---|
1 2 3 4 5 6 | |
With strict validation, unregistered karva.tags.* decorators, karva_tag
values, and pytest.mark.* marks fail collection. Karva validates tags on
tests, fixtures, and individual parameter sets. Clear misspellings include a
suggested registered name. Built-in tags and marks do not need registration.
When -E is passed more than once, a test runs if it matches any of
the expressions (OR across flags):
| Bash | |
|---|---|
1 | |
Tests that do not match are skipped.
Predicates
A filter expression is built from predicates combined with boolean operators. Karva currently supports two predicates:
test(<matcher>)— evaluated against the fully qualified test name, e.g.mod::sub::test_login.tag(<matcher>)— evaluated against each tag on the test; matches if any tag matches. Built-in and customkarva.tags.*decorators andpytest.mark.*decorators all contribute tags.
Unknown predicates produce a parse error listing the supported names.
Operators
Combine predicates with these operators:
&orand— logical AND, e.g.tag(slow) & test(~login).|oror— logical OR, e.g.tag(slow) or tag(fast).notor!— logical NOT, e.g.not tag(flaky).-— difference (and-not), e.g.tag(slow) - tag(flaky)is shorthand fortag(slow) & not tag(flaky).( … )— grouping, e.g.(tag(a) or tag(b)) and tag(c).
Operator precedence
From tightest-binding to loosest:
- Grouping with parentheses
not/!&/andand-|/or
A few worked examples:
| Text Only | |
|---|---|
1 2 3 4 | |
When in doubt, parenthesize.
Matchers
Prefixes control how test and tag names are matched:
=foo— exact: the value must equal the pattern exactly.~foo— substring: the pattern must appear anywhere in the value./foo/— regex: the value must match the Rust regex. Regex uses partial matching — anchor with^and$for a full match.#foo— glob: the value must match the glob pattern.*matches any run of characters,?matches a single character, and[...]is a character class.- No prefix — default: see below.
Example expressions:
| Text Only | |
|---|---|
1 2 3 4 5 6 7 8 | |
Default matchers
When you omit the prefix, the matcher kind depends on the predicate:
test(foo)defaults to substring.test(login)is the same astest(~login).tag(foo)defaults to exact.tag(slow)is the same astag(=slow).
Use explicit prefixes when generating expressions in tooling.
Matcher bodies
A matcher body is either a bare identifier, a quoted string, or a delimited regex:
- Bare identifiers may contain letters, digits,
_,.,:, and the glob metacharacters*,?,[,],{,},^,$. Most test names and tag names fit without quoting:test(=mod::sub::test_login)works as-is because:is permitted inside an identifier. - Quoted strings (
"...") allow any character, including spaces and operator characters, so use them when a tag or test name contains something like a hyphen, a space, or a parenthesis:tag(="my-nightly tag"). - Regex literals (
/.../) accept the full Rust regex syntax. Note that/is the delimiter, so a literal/inside a regex must be escaped as\/.
The keywords test, tag, and, or, and not are not reserved
inside a matcher body — tag(test) correctly matches a tag literally
named test, and tag(and) matches a tag named and. The outer parser
only treats them as keywords at the top level of an expression.
Escape sequences
Karva uses a deliberately minimal escape scheme so that regex metacharacters round-trip without double-backslashing:
- Inside a regex literal
/ … /, only\/is processed (to embed a literal/). All other backslash sequences are passed through to the regex engine unchanged, so you can writetest(/\d+/)without doubling the backslash. - Inside a quoted string
" … ", only\"is processed (to embed a literal"). Again, other backslashes are preserved verbatim. - Bare identifiers have no escape syntax at all — if your tag or test name needs characters the identifier rules don't allow, quote it.
- For literal glob metacharacters, use the bracket escape from
globset —
#[*]matches a literal*character,#[?]matches a literal?, and so on.
Migration from -t and -m
Older releases of karva exposed separate -t / --tag and -m /
--match flags. Both have been removed and replaced by -E /
--filter. The new syntax is a strict superset — every old invocation
has a direct translation:
-t slowbecomes-E 'tag(slow)'-t 'not slow'becomes-E 'not tag(slow)'-t 'slow and integration'becomes-E 'tag(slow) & tag(integration)'-t 'slow or integration'becomes-E 'tag(slow) or tag(integration)'-t '(slow or fast) and not flaky'becomes-E '(tag(slow) or tag(fast)) - tag(flaky)'-m authbecomes-E 'test(/auth/)'-m '^test::test_login'becomes-E 'test(/^test::test_login/)'-m 'slow|fast'becomes-E 'test(/slow|fast/)'-t slow -m authbecomes-E 'tag(slow) & test(/auth/)'
Multiple -E flags are combined with OR, as multiple -t or -m flags
were. For example, -t a -t b becomes -E 'tag(a)' -E 'tag(b)'.
When nothing matches
With the default --no-tests=auto, collecting no tests fails the run when no
filter was supplied, but a valid filter that matches nothing exits
successfully. This makes optional CI shards and local filters easy to compose
without hiding an empty suite.
Use --no-tests=pass, --no-tests=warn, or --no-tests=fail to choose the
behavior explicitly. The same setting is available as no-tests in the
profile's test configuration.
Grammar
For reference, the full grammar:
| Text Only | |
|---|---|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | |