World.Content.LightTest is seed-flaky: it needs a 65/256 roll to fail #119
Loading…
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
World.Content.LightTest— "a failed attempt is retried on the very nexttick" (
test/revenant/world/content/light_test.exs:270) fails on roughly onerun in five, and it is not flaky in the "sometimes the machine is slow"
sense: it is deterministic per ExUnit seed.
Reproduction
3 of the first 20 integer seeds fail. The file alone is enough — no other test
file, no world, no database.
The mechanism
The test drives
World.Content.Light.run/2, which callsContract.Skilling.dice()for itself. That is&(:rand.uniform(&1) - 1)onthe calling process, and ExUnit seeds
:randfrom the run seed, so theroll is a pure function of
--seed.The test needs the roll to fail in order to observe the retry. At
Firemaking 1 a log succeeds
65/256of the time — about 25% — so about aquarter of seeds light the fire instead, the loop ends, and the timer the
assertion looks for is gone. The measured 3/20 is that number with sampling
noise.
The file's own moduledoc says it is "driven with the dice handed in so every
branch is reachable without waiting on chance", and every other test in it
is. This one is the exception, because it is the only one exercising
run/2rather than
attempt/5.Why this is worth writing down
It presents as "passes alone, fails in the suite", which is the exact
signature of the
World.FiremakingWorldTesthardcoded-tick trap CLAUDE.mddocuments — and it is nothing to do with it. Adding any test file to a
selection reshuffles the order and therefore re-rolls the seed lottery, so the
failure appears to be caused by whatever was added last. It was blamed on the
fishing branch on that basis and is not fishing's.
The fix
Light.attempt/5already takes the dice as an argument for exactly thisreason.
run/2does not, and it is the only entry point that rolls. Eitherthread a dice through the timer args the way
factsis threaded throughattempt/5, or rewrite this one test to driveattempt/5and assert thereschedule separately.
Every other skilling loop has the same shape (
Chop,Mine,Fishall rollinside
run/2withSkilling.dice()), so whoever fixes this should checkwhether their own tests avoid
run/2by accident or by design.