Audience: developers running latdx test repeatedly on the same org.
The test result cache skips Apex test methods whose source and dependencies have not changed since the last recorded run. Hits return the cached pass/fail in milliseconds instead of re-executing the test. The cache is on by default; bypass it for one run with --no-cache.
Why You Can Trust It
The cache is built correctness-first. It will only skip a test when it can prove that the cached outcome is identical to what a fresh run would produce; on any uncertainty, it falls through to the underlying SF API and runs the test. The day-1 promise is no slower than running without the cache, and never a false pass.
Trust is built into the design, not asserted:
- Phased rollout. The skip surface ships in phases. The first release ships with zero skips (Phase 0): the cache wires the infrastructure, records telemetry, and runs every test through the SF API. Later phases add skips only after a corpus of real-world failing-test PRs proves that the dependency graph catches the breaking diff in every fixture.
- Empirical gating. Each phase promotion requires a fixture corpus pass rate of 100% on correctness invariants. Engineering judgment alone does not authorize a skip rule; the corpus is the gate.
- Shadow runs. From Phase 1 onward, 1% of cache hits are shadow-run: the cache returns the cached outcome to you, and in parallel the test is also executed and the actual outcome compared. A single mismatch auto-disables the rule and triggers a rollback.
- Three-level kill switch.
LATDX_CACHE_DISABLE=1turns the cache fully off.LATDX_CACHE_PHASE=Npins it to a known-stable phase.LATDX_CACHE_RULE_<NAME>=offdisables a specific skip rule. None requires a redeploy. - Audit trail. Every cache decision is explainable via
latdx test cache explain <Class>.<method>, which prints the dependency closure, the hash, the rule that authorized the decision, and whether the entry is eligible for shadow validation.
Overview
The cache stores one outcome per (org, test class, test method) keyed by a SHA256 hash of:
- The test class source.
- The source of every class the test transitively references (AST-derived closure).
- An org-level schema revision digest (covers the deployed latdx-sf package and any org schema snapshot).
If any of those inputs change, the hash changes and the next run is a miss.
Storage: one JSON file per test class at:
~/.latdx/workspaces/<id>/cache/<orgId>/test-results/<testClass>.jsonPer-worktree daemons scope their cache under the workspace state dir; standalone runs fall back to ~/.latdx/cache/<orgId>/test-results/.
Disable
The cache is on by default. Skip it for a single run, a shell session, or a specific rule:
# One-shot bypass: run without the cache for this invocation.
latdx test run -f MyTest.cls -o my-org --no-cache
# Hard off for the shell session: every request runs through the SF API.
LATDX_CACHE_DISABLE=1 latdx test run -f MyTest.cls -o my-org
# Pin to a specific phase. Useful if a later phase has been rolled back.
LATDX_CACHE_PHASE=1 latdx test run -f MyTest.cls -o my-org
# Disable a single skip rule by name (rules are listed in `latdx test cache status`).
LATDX_CACHE_RULE_CLOSURE_INVARIANCE=off latdx test run -f MyTest.cls -o my-orgSwitches are documented in environment variables.
Run Summary
When the cache layer is active the run prints a trailing line summarising hits, misses, stale entries, and the active phase. Phase 0 (default) cannot serve hits but tracks would have hit counts so you can see what Phase 1+ would skip:
Cache: 0 hit / 18 miss / 0 stale (18 written) [phase 0, would have hit 12]At Phase >= 1 the line carries the active phase and shadow-run counters:
Cache: 12 hit / 3 miss / 1 stale (4 written) [phase 2, 1 shadow sample / 0 mismatches]In --json mode the same payload appears as result.cacheReport, including phase, killSwitch, wouldHaveHit, shadowSamples, and shadowMismatches. When --no-cache or LATDX_CACHE_DISABLE=1 is set the line is suppressed and the run header echoes Cache: off.
Shadow-run mismatches are written to <cacheHome>/cache/shadow-mismatches.ndjson, one JSON line per mismatch, recording the test class, method, cached outcome, engine outcome, phase, and rule that authorised the hit.
Invalidation
Entries expire automatically on any of these axes:
- Input hash change: editing the test class or any class in its dependency closure shifts the cached
inputHash, so the next run is a miss and writes a fresh entry. This is the primary invalidation path today. - TTL: 7 days since the entry was recorded.
- Schema bump: a
CACHE_VERSIONchange in a CLI upgrade drops every pre-bump entry. - Org mismatch: entries for one org are never served to another.
- Deployed package drift: redeploying latdx-sf changes the schema revision digest, invalidating every entry for the org.
- Explicit clear: see the commands below.
When the daemon is running its FileWatcher translates every .cls add / change / unlink into a cache wipe for the changed class and the test classes that depend on it (via the AST-derived closure). Dependent entries drop immediately on save, so the next test run is a miss without waiting for TTL or hash rolling. With the daemon stopped, file edits still invalidate via the input-hash path: the new run is a miss, the cache writes through, and the prior entry is collapsed by the per-method rotation.
LATDX_CACHE_DISABLE=1 suppresses the FileWatcher hook (no cache state to manage when fully off). The hook runs at every other phase, including Phase 0; the resulting stale counter on the run summary reflects cache entries whose input hash no longer matches.
Cache Management Commands
# Show entry count, disk usage, and age range for the current org.
latdx test cache status
latdx test cache status -o my-org
# Drop entries for the current org.
latdx test cache clear
latdx test cache clear -o my-org
# Drop entries for every org in this workspace.
latdx test cache clear --all-orgs
# Print the dependency closure used to compute the hash for one method.
# Useful when a hit or miss is unexpected.
latdx test cache explain MyTestClass.myTestMethod -o my-orglatdx cache clear (the existing top-level command) also drops test-results alongside other caches. Use latdx test cache clear when you only want to reset this tier.
Known Limitations
The dependency graph is AST-derived and therefore cannot follow dynamic Apex:
Type.forName("Foo").newInstance()touchesFooby string.Database.query(String dynamicSoql)can reference objects not named in static SOQL literals.- Reflection through
CallableorSObjectType.fromNamelookups.
If a test exercises dynamic dispatch and the referenced code changes, the cache may serve a stale pass/fail. Workarounds:
- Run with
--no-cachewhen iterating on dynamically dispatched code. - Use
latdx test cache clearafter the relevant change lands.
Report confirmed false hits: false-hit rate is one of the gates that controls how aggressively the cache is allowed to skip tests in future releases.
Troubleshooting
- Unexpected miss. Run
latdx test cache explain <Class>.<method>to see which dependencies contribute to the hash. A newly added dependency will change the hash. - Unexpected hit after editing code. Confirm the daemon is running (
latdx daemon status). The FileWatcher drops entries on.clssave by walking the dep closure and removing every test that references the changed class; without the daemon you fall back to the input-hash path (the new run is a miss, then writes through). - Disk growth.
latdx test cache statusreports disk usage. Clear the tier withlatdx test cache clearif it outgrows your budget.
See Also
- CLI reference for full flag semantics.
- Troubleshooting for daemon-level issues.
- Internal design:
docs/internal/adr/0010-test-result-caching.md.