|
|
|
@@ -361,17 +361,35 @@ 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 racing a publisher's rotation of the source it is reading** — the race that actually truncates a tree |
|
|
|
|
|
| `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 |
|
|
|
|
|
| `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. |
|
|
|
|
|
|
|
|
|
|
Every suite runs the actual script, not a reimplementation of its logic, and
|
|
|
|
|
every fix scenario is paired with a control that reproduces the bug — a
|
|
|
|
|
scenario that passes either way proves nothing. The concurrency scenarios race
|
|
|
|
|
real processes rather than mocking the interleaving, and gate the interfering
|
|
|
|
|
step on *observed* progress of the step it interferes with, so the window is
|
|
|
|
|
hit deterministically instead of on a fast machine's coin flip.
|
|
|
|
|
scenario that passes either way proves nothing.
|
|
|
|
|
|
|
|
|
|
The concurrency scenarios run the real scripts as real concurrent processes,
|
|
|
|
|
but they never race for the interleaving. The interfering step is placed
|
|
|
|
|
inside the window by stubbing, on `PATH`, a command the code under test calls
|
|
|
|
|
at a known point: `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.
|
|
|
|
|
Placement is then a fact rather than a scheduling outcome — and each stub
|
|
|
|
|
asserts that it fired, because a scenario whose interference silently did not
|
|
|
|
|
happen passes for the wrong reason.
|
|
|
|
|
|
|
|
|
|
Gating the interfering step on *observed progress* of the step it interferes
|
|
|
|
|
with was the earlier answer here, and it is not one: seeing that a walk has
|
|
|
|
|
started says nothing about where it will be when the interference lands, so
|
|
|
|
|
the assertion downstream held only some of the time (issue #3).
|
|
|
|
|
|
|
|
|
|
Where more than one guard could catch a fault, the scenario asserts *which*
|
|
|
|
|
one did — otherwise deleting the guard under test leaves the suite green
|
|
|
|
|
because a sibling fires in its place.
|
|
|
|
|
|
|
|
|
|
The action YAML holds no logic beyond wiring; everything testable lives in
|
|
|
|
|
`scripts/`. A composite action needs `shell: bash` on every `run:` step, and
|
|
|
|
|