Tests cannot pin the dice a subsystem rolls for itself (the cause behind #119) #141
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?
Contract.Skilling.dice/0is the production source of randomness and there is no way for a test to supply its own. That makes a whole class of test either flaky or impossible, and it has now bitten twice in different subsystems — #119 is one of them, and this is the shared cause rather than a note on that one test.World.RunecraftWorldTestmined the essence rock and waited 12s for the yield. That deadline is not seconds, it is rolls: 12,000ms ÷ 600ms is 20 ticks, and a pickaxe legal at Mining 1 is bronze (period 8) or iron (7), so the wait bought two rolls against alow: 129-out-of-256 rock. One required success, 24.6% failure — measured, it failed once in three runs of the file alone. It surfaced as a Postgres-leg failure while SQLite was green, which reads as adapter-dependent and is only the two legs landing on different sides of the same flip.That one was fixed by moving the branch it was actually asserting (
Mine.product/2, pure and public) into deterministic unit tests, and leaving the world test the single claim only the world can make. Sizing the remaining wait at eight rolls puts a miss under 1%.What could not be done in either case is pin the roll.
Contract.Skilling.dice/0is&(:rand.uniform(&1) - 1), and:randreads its state from the process dictionary of whatever process calls it — here the world's or the player's, never the test's. So a test cannot seed the randomness it depends on, which is exactly why this issue's failure attaches to whichever file was added to the selection last: the only lever a test has is the global run order.The fix that closes both shapes is a seam — a way for a test to supply the
dice()a subsystem uses, rather than each test either rolling the dice again itself or buying more attempts.Contract.Skilling.dice/0is the single place it would go, and most callers already take adiceargument and only fall back to it;Light.run/2andWorld.Content.Mineare the ones that call it for themselves.Until that exists, the workaround for any new test is mechanical: count the rolls the deadline buys (
patience ÷ 600ms ÷ period), and if the answer is small, assert the pure function instead.Fixed on main:
Contract.Skilling.pin/1lets a test pin the dice a subsystem rolls for itself, anddice/0reads the pin from the process dictionary. Not carried over to forge.home.arpa.