Document how the cache behaves under parallel test runners - #82
Open
navidemad wants to merge 1 commit into
Open
Conversation
Issue Gusto#80 asks whether cache files generated by one test process can be reused by another, and the answer depends entirely on which runner is in use. Nothing in the reference said so. Rails `parallelize` forks its workers before Minitest runs any suite, and those workers run test methods rather than suites, so generation happens in the parent and workers only mount. One shared `cache_path` is right there, and there is no worker number to key it on: Rails names the per-worker databases itself and never sets `TEST_ENV_NUMBER`. `parallel_tests` is the opposite case. Every worker is a full process that calls `Runner#start`, so every worker clears the directory the others are using, and each one needs a `cache_path` of its own. Sharing a directory across processes is the third case, and the one with a trap in it: it requires `FIXTURE_KIT_PRESERVE_CACHE`, and preserving the cache makes invalidation the caller's problem, because `Cache#exists?` is `File.exist?`. A cache written under older definitions, factories or schema mounts without complaint and fails somewhere else entirely. The section shows the digest-in-`cache_path` pattern that bounds it, and what it costs.
This was referenced Aug 28, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes the question in #80 by writing down what the cache actually does under each kind of parallel runner. The answer is different for each one, and none of it was documented.
Rails
parallelizeActiveSupport::Testing::Parallelizationforks its workers inMinitest.run, before any suite runs, and each worker runs individual test methods (Minitest.run_one_method), neverrun_suite. Since FixtureKit generates fromFixtureKit::Minitest::ClassMethods#run_suite,Runner#startand everygeneratehappen in the parent, and the workers onlymountwhat the parent wrote.So one shared
cache_pathis correct here, and it is what the default already gives you. There is also nothing to key the path on: Rails names the per-worker databases itself and never setsTEST_ENV_NUMBER(no reference to it anywhere in activerecord, activesupport or railties). Keying on a worker number would point the workers at directories the parent never writes to.parallel_testsThe opposite case, and the one you suggested in #80. Each worker is a full process that boots the app and calls
Runner#start, so each one clears the directory the others are generating into or mounting from.config.cache_path = "tmp/cache/fixture_kit/#{ENV["TEST_ENV_NUMBER"]}"is the fix; the cost is that every worker generates its own copy of the fixtures its share of the suite needs.One directory shared between processes
The case with a trap in it, and where our own suite got burned. Reusing a directory across processes (a warm-up run, a CI cache restored between jobs) requires
FIXTURE_KIT_PRESERVE_CACHE, or the next process to start deletes it. And preserving the cache hands invalidation to the caller, becauseCache#exists?isFile.exist?: nothing compares that file against the definitions, the factories they call, or the schema. A cache written before a guard existed mounts without complaint, and the failure surfaces far from the fixture that produced it — for us, aPG::UniqueViolationonversions_pkeyfrom a cache written before a PaperTrail guard landed, with nothing pointing at the cache.The section documents the pattern that bounds it without asking the gem to do cache busting: a digest of the definitions, factories and schema, folded into
cache_path, so a cache written under any other state of the code is ignored and regenerated instead of read. It also names the two costs, obsolete digest directories that are not reclaimed and one changed file regenerating everything, so the tradeoff is visible before someone adopts it. This is the shape we run in CI on a large suite.That last part is the practical answer to #63, which you closed on the grounds that cache busting is too hard to get right in the gem. Agreed, and this documents how to do it in the application, where the list of inputs is knowable.
Notes
Docs only, no behavior change.
docs/reference.mdgains a## Parallel Test Suitessection after "Cache Identifiers and Paths", and theFIXTURE_KIT_PRESERVE_CACHEentry links to it.