Skip to content

ditto lock

Rebuild ditto.lock from a full run of the suite.

Runs the whole suite, creating missing snapshots but leaving existing values unchanged, then rewrites the lock from the snapshots the tests used. A run that is filtered (-k, -m, --lf, --ff), narrowed to paths or node ids, or failing is refused and leaves the lock unchanged. Set the suite's scope with testpaths in your pytest configuration instead.

Usage:

ditto lock [OPTIONS] [PYTEST_ARGS]...

Options:

  --help  Show this message and exit.

How it runs

Run ditto lock after adding, renaming or removing a snapshot, and after changing a test's node ID, so the lock records the identities your suite actually produces.

ditto lock re-runs your suite with --ditto-lock; extra arguments are passed through to pytest.

By default the suite runs in RECORD mode: snapshot calls reuse existing values and create missing snapshots. Existing values are not overwritten. Your test assertions compare those values with current expectations, and an assertion failure prevents the lock rebuild. Use ditto update to overwrite existing values; passing --ditto-update to this command also selects UPDATE mode.

After a successful full run, the command rebuilds the lock's snapshot identities. It does not check backend drift: an orphan can remain without failing this run. Use ditto verify for a read-only check of missing, orphan and unrecorded snapshots. The lock records identities, not expected snapshot values.

See The Lock File for the model behind the file.

Examples

# Rebuild the lock
ditto lock

A full, clean run only

ditto lock must collect the whole suite, and it must pass. A narrowed or failing run cannot rebuild the lock, because it cannot see the entries belonging to tests it did not collect — writing it anyway would silently drop them. So the run is refused when it used any of:

  • a filter: -k, -m, --lf, --ff
  • positional narrowing: a path or a ::nodeid argument
  • a failure, or any other non-zero exit status — a collection error counts, since a module that fails to import leaves no failed tests yet exits non-zero

The lock is left unchanged when a rebuild is refused. Snapshots created during test execution remain; refusal does not undo those writes. The reason is printed rather than warned, so a warning filter cannot hide why the run failed:

ditto: --ditto-lock requires a full run (no -k/-m/--lf, no path/nodeid args, and no failures); leaving ditto.lock unchanged.

To run a subset, set the scope in configuration rather than on the command line:

# pyproject.toml
[tool.pytest.ini_options]
testpaths = ["tests/ci"]

What it changes

ditto.lock is rewritten with the entries the run observed: one per snapshot, per target, with the test's node ID, the key, and the recorder. Entries for tests that no longer exist are dropped from the targets the run exercised, and new ones are added.

A test that was collected but didn't pass — skipped, xfailed, deselected — keeps the entries it had, as does a test pytest did not collect because an ignore path or a skipping collector above it applied. For tests still present, a passing run replaces their entries with the snapshots they actually accessed.

A target the run did not exercise is left as it was, so a suite that only reaches some of its backends does not lose the others. To remove a target no test uses any more, see Retire a target. An existing lock file that cannot be parsed is replaced rather than being treated as fatal: a rebuild is authoritative, and the entries for unexercised targets in a corrupt file are unrecoverable either way.

The lock is a committed file. Commit it alongside the snapshot changes that prompted the rebuild, so a reviewer sees which snapshots a change added.

When the run fails

A test or collection failure prevents the rebuild and fails the run. A lock that cannot be written also fails the run and prints the error. Backend drift is checked separately by ditto verify.

After a prune

Prune deletes orphans already absent from the lock, so deleting them does not itself require a lock rebuild. When removing or renaming tests or snapshot keys, first run ditto lock to drop obsolete identities from exercised targets, then run ditto prune to remove the newly orphaned files.

A prune run can also create new snapshots while running tests. These remain unsynced because prune does not append them to the lock. If prune warns about snapshots produced during that run, use ditto lock to record their identities.