Planning¶
diffcone plan compares two snapshots and reports which targets the
changes between them can affect. It reads files and git objects only: it
doesn't check anything out, import your code, or run tests.
diffcone plan --base main --head HEAD --discover pytest \
--source-root src --source-root . --format text
Choose what to compare¶
--base and --head each take a git revision, INDEX or WORKTREE
(snapshots). Common combinations:
| You want to check | Use |
|---|---|
| Uncommitted work, before you commit | --base HEAD --head WORKTREE |
| A branch, before you open a pull request | --base main --head WORKTREE |
| A pull request in CI | --base origin/main --head HEAD |
| Only what you have staged | --base HEAD --head INDEX |
Source roots¶
A source root is a directory that diffcone treats as an import root, the
way Python's sys.path does. Module names come from the closest root that
contains a file:
| Roots | src/calc/ops.py |
tests/test_calc.py |
|---|---|---|
. (the default) |
src.calc.ops |
tests.test_calc |
src and . |
calc.ops |
tests.test_calc |
src and tests |
calc.ops |
test_calc |
Use the roots your code is actually imported from. For a src layout,
that's usually --source-root src --source-root .; for a flat layout, the
default is fine. Files outside every root are not analysed, so code there
is treated like a third-party library.
If one pytest session collects several test directories whose files share
names (common in monorepos that use --import-mode=importlib), give each
directory its own namespace with DIR=PREFIX:
diffcone plan --base main --head HEAD --discover pytest \
--source-root packages/api/src --source-root packages/api/tests=api_tests
Find the targets¶
Discover them¶
--discover pytest and --discover asv find targets by reading your
configuration and test files, the way the runner would collect them,
without importing anything. To see what discovery finds, write it to a
file:
For pytest, discovery follows each test's fixtures, so a change to a
fixture or a conftest.py selects the tests that use it. Fixtures from
well-known plugins (mocker, httpx_mock, freezer, benchmark and
others) are recognised automatically. If your tests use fixtures from
another installed plugin, name them with --assume-external-fixture NAME;
otherwise diffcone can't see what they depend on and selects their tests
every time.
When the plan exits with code 3
Exit code 3 means your runner may collect tests that diffcone can't
see: for example a pytest plugin that collects classes by its own
naming rules, or a test base class outside your source roots. The
report's discovery section lists each case. Running only the selected
tests could skip some, so diffcone run refuses unless you pass
--allow-incomplete-discovery. See
static discovery.
List them in a manifest¶
For a runner without discovery, or to adjust what discovery finds, pass a
target manifest with --targets targets.json.
You can combine it with --discover; manifest entries replace discovered
targets with the same ID.
Declare dependencies diffcone can't see¶
Some dependencies aren't visible in the code: a plugin registered through
entry points, a handler named in a configuration file. Declare them in a
diffcone.toml at the repository root:
[[edges]]
from = "pkg.registry.dispatch"
to = "pkg.handlers.json_handler"
why = "handlers register themselves through entry points"
Now a change to json_handler selects the tests that reach dispatch. See
the diffcone.toml reference.
Speed¶
diffcone caches its analysis in .diffcone/cache/ (add .diffcone/ to
your .gitignore). Committed snapshots are analysed once, and every file
is cached by its content, so planning your working tree again only re-reads
the files you changed. Cached and uncached plans are identical.
--no-cacheturns the cache off;--cache-dirmoves it.diffcone prune --keep HEADremoves everything the cache doesn't need for planning from a commit, which keeps a cache saved between CI runs small.