Watch mode¶
Watch mode monitors your project for file changes and reruns only the affected tests.
Basic usage¶
On startup Tryke pre-warms the worker pool and runs discovery to
build the import graph (so it can answer "which tests are affected?"
on the first save). It then enters an idle state — no tests are
executed until you save a file. Pass --now to also run the full
test set on startup. Either way, once a file changes, Tryke:
- Identifies which modules were modified
- Walks the import graph to find all tests that depend on the changed modules
- Restarts the worker subprocesses so the next run loads the updated code in a fresh Python interpreter
- Reruns only the affected tests
This gives you fast feedback without rerunning the entire suite. Restarting the workers (rather than calling importlib.reload in-process) avoids the classic reload pitfalls — stale class objects, captured closures, and decorator-bound state from the old definitions are all dropped because the interpreter itself is gone.
Use tryke test --watch ... whenever you need to pass test flags such as
paths, filters, -j, --all, or --now. The bare tryke form is only a
shortcut for the no-argument watch loop.
Keyboard shortcuts¶
Watch mode listens for keypresses while it waits between runs:
| Key | Action |
|---|---|
q / esc |
Quit |
enter |
Run all discovered tests against fresh workers (ignores affected-only). |
c |
Clear the current test results and keep watching. |
How affected tests are determined¶
Tryke builds a static import graph at startup (see test discovery). When a file changes, it traces the graph forward to find every test file that transitively imports the changed module, then reruns the tests in those files.
Files with dynamic imports (importlib.import_module(), __import__()) are always included in every rerun. See discovery for details.
Filtering¶
All the standard filtering flags work in watch mode:
# Only watch tests matching a name pattern
tryke test --watch -k "math"
# Only watch tests with specific tags
tryke test --watch -m "fast"
# Combine filters
tryke test --watch -k "parse" -m "not slow"
Options¶
Reporter¶
Choose an output format:
See reporters for all formats.
Fail fast¶
Stop a run on the first failure:
Or after N failures:
Workers¶
Override the number of parallel workers (defaults to CPU count):
See concurrency for details on the worker pool.
Run tests immediately on startup¶
By default watch mode does not run any tests until the first file change.
Pass --now to run the full test set as soon as the watcher comes up:
This is useful when you want the watcher to behave like a continuous test runner that always gives you a baseline result without waiting for a save.
Run all tests on every change¶
By default, watch mode reruns only the tests affected by the changed files
(via the import graph). Pass --all (short: -a) to rerun the full
discovered test set on every change instead:
This is useful when:
- The import graph misses a dependency the test relies on (dynamic imports, plugin registries, fixtures wired up at runtime, string-referenced modules).
- An external resource (schema, fixture file, environment variable) changed in a way the import graph cannot see.
- You are debugging test ordering or flake issues and want a full run on every save.
Worker subprocesses are still restarted on every change so Python picks up the new code from a fresh interpreter; only the test selection is broadened.
Debouncing and change dedup¶
File system events are debounced with a 50ms quiet window — just long enough to coalesce the burst of inotify events the kernel emits for a single write syscall.
On top of the debouncer, watch mode tracks each file's (mtime, size) signature and skips events that don't actually move it. Editor tail activity (atomic-rename metadata writes, swap-file cleanup, format-on-save that produces identical output, LSP re-saves) often produces a second batch of events outside the debounce window; without the signature check, that second batch would trigger a redundant restart for a single user save. With it, only batches that represent a real content change reach the worker pool — so we can keep the debounce tight without paying for it in spurious restarts.