Merge pull request 'test(seed): pin the two unguarded terms of the torn-clone condition' (#9) from test/pin-clone-guards into main
This commit was merged in pull request #9.
This commit is contained in:
@@ -422,7 +422,7 @@ bash scripts/selftest.sh --fast # fixture-only suites, no compiler
|
||||
| suite | covers |
|
||||
|---|---|
|
||||
| `hardlink-clone-selftest.sh` | that a build in a clone cannot mutate its source — with a control proving a raw `cp -al` does. Needs a real compiler. |
|
||||
| `seed-target-dir-selftest.sh` | seed-source preference, lock-file stripping, two jobs racing on one cache key, **and a seed whose source is rotated — or silently loses a subtree — underneath its clone**: the two ways a hardlink clone tears |
|
||||
| `seed-target-dir-selftest.sh` | seed-source preference, lock-file stripping, two jobs racing on one cache key, **and one scenario per check a hardlink clone is validated against**: a source rotated wholesale, a subtree silently lost from the walk, a copy that reports failure over a tree both other checks read as whole, and a source identity that resolved at neither end — plus a staging tree that could not be privately owned being discarded rather than published |
|
||||
| `publish-snapshot-selftest.sh` | the atomic swap, that a live consumer survives a republish, and the publisher's side of the rotation race: deferred reclamation under a live reader, and its sweep once the reader is gone |
|
||||
| `prune-cache-selftest.sh` | liveness, protection, locking, eviction order, self-clear, **and that a cache a job claims *inside* the check-to-unlink window survives it** — against a real scratch `origin` |
|
||||
| `restore-mtimes-selftest.sh` | the merge hazard and the watermark that closes it, including the two-jobs-one-namespace case. Needs a real compiler. |
|
||||
@@ -442,13 +442,20 @@ key and asserts only what must be true whichever of them wins the rename.
|
||||
which places the interference inside the window rather than hoping it lands
|
||||
there. `prune-cache-selftest.sh` scenario 12 stubs `du`, so the pass's own
|
||||
measurement publishes a reader marker strictly between its check and its
|
||||
unlink; `seed-target-dir-selftest.sh` scenarios 8a and 8b stub `cp`, so the
|
||||
consumer's own clone is what rotates the snapshot underneath it, or what
|
||||
loses a subtree of its own source, strictly inside the identity window. 8a
|
||||
does start a second real process — the actual `publish-snapshot.sh` — but the
|
||||
stub is what fixes where its swap lands; the concurrency is incidental to the
|
||||
determinism. Every stub asserts that it fired, because a scenario whose
|
||||
interference silently did not happen passes for the wrong reason.
|
||||
unlink. `seed-target-dir-selftest.sh` uses the shape four times over, on two
|
||||
different commands. Scenarios 8a, 8b and 8c stub `cp`, so the consumer's own
|
||||
clone is what rotates the snapshot underneath it, loses a subtree of its own
|
||||
source, or reports a failure over a tree that is in fact whole — each strictly
|
||||
inside the window that clone's checks cover; scenario 10 stubs `cp` one level
|
||||
down instead, refusing the per-file copies that unshare the mutable paths.
|
||||
Scenario 8d stubs `stat`, because the check it pins fires on an identity that
|
||||
could not be READ rather than on one that changed, and the only way to make
|
||||
that the sole witness is to fail the identity reads while the copy between
|
||||
them succeeds. 8a also starts a second real process — the actual
|
||||
`publish-snapshot.sh` — but the stub is what fixes where its swap lands; the
|
||||
concurrency is incidental to the determinism. Every stub asserts that it
|
||||
fired, because a scenario whose interference silently did not happen passes
|
||||
for the wrong reason.
|
||||
|
||||
**A synthetic stand-in for the other side, where that artefact *is* the
|
||||
contract.** `publish-snapshot-selftest.sh` scenarios 6 to 8 hold a
|
||||
@@ -465,9 +472,20 @@ does it any more.
|
||||
|
||||
Where more than one guard could catch a fault, a scenario should assert
|
||||
*which* one did — otherwise deleting the guard under test leaves the suite
|
||||
green because a sibling fires in its place. Scenarios 8a and 8b of the seed
|
||||
suite do; scenario 9 of the same suite does not yet, which is why a mutation
|
||||
survives it (issue #5).
|
||||
green because a sibling fires in its place. Scenarios 8a to 8d and 10 of the
|
||||
seed suite do, and each is reddened by exactly one mutation of the clone's
|
||||
checks. Scenario 9 does not, and a mutation still survives it: with its source
|
||||
unreadable, `cp -al` leaves the staging directory at mode `000`, so deleting
|
||||
the exit-status check does not change the outcome — the unshare pass aborts
|
||||
the clone instead, and the assertion is satisfied down a path it was not
|
||||
written for.
|
||||
|
||||
That is the standing hazard here, and it is not hypothetical. Two guards that
|
||||
can each catch the same fault mask each other, so **neither** is individually
|
||||
necessary and no fixture built around that fault can pin either one — which is
|
||||
how both `[ "$cp_rc" -eq 0 ]` and `[ "$i_before" != missing ]` sat unpinned
|
||||
(issue #5) while looking well covered. Isolating a check means constructing
|
||||
the state only it can see, not the state that trips several at once.
|
||||
|
||||
The action YAML holds no logic beyond wiring; everything testable lives in
|
||||
`scripts/`. A composite action needs `shell: bash` on every `run:` step, and
|
||||
|
||||
Reference in New Issue
Block a user