Command line¶
voci runs the suite. With no arguments it collects from the configured testpaths; with none
configured, from tests/ if there is one and the current directory otherwise. It runs everything
it finds.
Each flag below has a matching key in the [tool.voci] table of pyproject.toml, and the two are
layered: a flag typed on the command line beats the config file, which beats the built-in default.
Exit codes¶
| Code | Meaning |
|---|---|
0 |
Every test that ran passed. |
1 |
A test failed, errored or timed out, or a file failed to collect. |
2 |
The run was interrupted with Ctrl-C. |
4 |
An argument was rejected — the message names which one. |
5 |
Nothing was collected. |
voci --version prints the installed version, the same string as voci.__version__.
The run cache¶
At the end of every run that executed tests, voci writes .voci_cache/lastfailed.json under the
rootdir: the ids of the tests that failed, errored or timed out, and the paths of the files that
failed to collect at all. --lf and --ff read it back. The rootdir is anchored on the nearest
pyproject.toml rather than on the arguments, so voci and voci tests/ share one cache —
see sharing code across test files. The directory
is the same one the
assertion rewriter keeps its bytecode in, and voci leaves a .gitignore inside it covering
everything, so a project picks up no diff for having run its suite.
A run overwrites only what it has an answer for. Tests it executed or skipped are settled by their
own outcome, as is a recorded test that no longer exists in a file the run collected; tests it
never reached — behind a --maxfail stop, a Ctrl-C, or a path argument naming somewhere else —
keep what the run before said about them, so iterating on one file leaves the failures found
elsewhere intact.
Because those are kept, a --lf whose selection holds none of them — pointed somewhere they do
not live, or run right after the failing test was renamed — selects nothing. It says so
(--lf: no recorded failure is in this run's selection) and exits 5, rather than leaving the
empty run to be read as a suite that collected nothing.
Deleting the directory is always safe. The next --lf finds nothing recorded and runs the whole
suite, which is also what it does on a first run and after one that went green.
$ voci --help
usage: voci [-h] [--version] [-k EXPR] [-m EXPR] [--lf] [--ff] [--watch]
[--assert {rewrite,plain}] [--rewrite-cache DIR] [--concurrency N]
[--timeout SECONDS] [--loop-watchdog SECONDS] [--capture no] [-s] [--serial]
[--maxfail N] [-x] [-v] [-q] [--durations N] [-W SPEC] [--collect-only]
[--co-json] [--report-json PATH] [--basetemp DIR]
[paths ...]
Positional arguments¶
paths¶
Files, directories, or test ids (path.py::test_name, path.py::TestGroup::test_name, path.py::test_name[case]) to run, each read relative to the current directory, or to the rootdir if it names nothing there. Defaults to the configured testpaths, else to 'tests' if there is one, else to the rootdir when [tool.voci] fixed it and the current directory otherwise.
Options¶
-h, --help¶
show this help message and exit
--version¶
show program's version number and exit
-k EXPR¶
Run only tests whose id satisfies this boolean expression, e.g. 'users and not slow'. Each term is matched as a case-insensitive substring of the whole id -- path, test name and [case] suffix alike -- so -k users selects every test in tests/test_users.py. A term that isn't a bare identifier (has a dash, a dot or brackets) must be quoted, e.g. "'test_create[admin]'". A test that doesn't match is deselected, not skipped.
-m EXPR¶
Run only tests whose @voci.tag(...) names satisfy this boolean expression, e.g. 'slow and not flaky'. A tag name that isn't a bare identifier (has a dash or a dot) must be quoted, e.g. "'smoke.fast'". Tags not mentioned in EXPR count as absent. A test that doesn't match is deselected, not skipped; a skip-marked test is always skipped, regardless of EXPR.
--lf, --last-failed¶
Run only the tests that failed, errored or timed out on the last run, plus every test in a file that failed to collect. Files holding none of them are not even imported. With nothing recorded -- a first run, or a run that went green -- the whole suite runs.
--ff, --failed-first¶
Run the whole suite, with the tests that failed on the last run first. Unlike --lf this changes only the order, so a run that is still red says so within the first few results.
--watch¶
Rerun after every change to a .py file under the selected paths, instead of exiting. The first run is whatever PATHS/-k/-m/--lf/--ff already say; every run after that applies --lf on top -- unless --lf or --ff was already given, which is left alone -- so a red run is what gets rerun until it's green, and a change with nothing left failing reruns the whole suite. Stops on Ctrl-C.
--assert {rewrite,plain}¶
Assertion introspection mode. 'plain' skips the import hook; PEP 657 carets still render. Default: rewrite.
--rewrite-cache DIR¶
Where rewritten .pyc files go. Defaults to a platform cache dir, or $VOCI_REWRITE_CACHE if set. If it is unwritable, voci warns and falls back to --assert=plain rather than silently paying the cold- import cost.
--concurrency N¶
Maximum number of tests running at once. Must be a positive integer; 1 means exactly serial. Default: 16, or [tool.voci] concurrency if set.
--timeout SECONDS¶
Per-test setup+call budget, in seconds, overridable per test with @voci.timeout(...). A test that exceeds its budget is reported as TIMEOUT rather than FAILED/ERROR. Must be positive and finite. Default: no limit, or [tool.voci] timeout if set.
--loop-watchdog SECONDS¶
Warn when the event loop has been blocked this long, naming the call holding it. A blocking call in an async test (or in a fixture) stalls every test at once. Pass 0 to switch it off. Default: 5.0, or [tool.voci] loop_watchdog if set.
--capture no¶
Set to 'no' (or pass -s) to pass captured stdout/stderr straight through to the real stream live, prefixed with the test id per line. Default: captured, and shown only for failing tests.
-s¶
Shorthand for --capture=no.
--serial¶
Shorthand for --concurrency=1: one test at a time, in collection order. The first step when a concurrent run behaves differently from a serial one.
--maxfail N¶
Stop the run once N tests have failed: tests that haven't started are dropped, and tests still in flight are cancelled and reported as CANCELLED. Default: run everything.
-x¶
Shorthand for --maxfail=1.
-v, --verbose¶
Print a line per test as it finishes, on top of the per-file blocks.
-q, --quiet¶
Print one character per file instead of a block, and drop the startup header. Failure detail, the summary and every end-of-run section are printed regardless.
--durations N¶
List the N slowest tests at the end of the run. Under concurrency the slowest test is what the wall clock can't drop below, so this is the list to read before tuning --concurrency. Default: 0, no such list.
-W SPEC¶
Add a warning filter, as action:message:category:module:lineno -- 'error', 'ignore::DeprecationWarning', 'error:.*legacy:UserWarning'. Repeatable; a later filter outranks an earlier one, and all of them outrank [tool.voci] filterwarnings. Every warning a run raises is reported either way; a filter decides which are silenced and which fail the test that raised them.
--collect-only¶
Print the id of every selected test, in the order they would run, and exit without running any of them.
--co-json¶
Like --collect-only (and implies it), but print one JSON object to stdout instead of the plain id list: every selected test's id, file and definition line, plus what else collection found -- skips, deselections, and any file that failed to import. For an editor integration that wants the result as data. Nothing else goes to stdout during this run, including the usual startup header and any import-time warnings, which are printed to stderr instead so the one line of JSON stays parseable on its own.
--report-json PATH¶
Write one JSON record of the run to PATH: an outcome, duration and failure reason per test, so a consumer reads the result as data instead of parsing this reporter's own output. Not written for --collect-only/--co-json, which never run anything to report on.
--basetemp DIR¶
Override where tmp_path/tmp_path_factory allocate. WARNING: this directory is cleared (removed and recreated) at the start of every run that uses it -- do not point it at anything you did not create for this purpose. Default: a fresh numbered directory under the platform temp dir, keeping the last 3 previous runs.