View as Markdown, for AI agents Edit on GitHub

Testing seedfall itself

This page is for people who work on seedfall itself, or who want to know why its results can be trusted. It lists the tests that check seedfall's model of crashes, faults and replay, and how to run them. You do not need it to use seedfall.

seedfall is only useful if its own model of crashes, faults and replay is right, so that model has its own tests. This page lists what they check and how to run them.

What the tests check

Area Test file What it proves
Disk durability engine/memfs_test.go Synced writes survive a crash, unsynced writes and never-synced files do not, O_SYNC makes every write durable, an unsynced truncate is undone
Torn writes engine/memfs_test.go A torn crash keeps an in-order prefix of the pending writes, the last one possibly cut mid-way, and records the tear
Out-of-order writeback engine/memfs_test.go Writes split at 4 KiB page boundaries, pages persist independently (a later page can survive without an earlier one), and writes to one page persist in order
fsync EIO engine/memfs_test.go A failed fsync drops the pending writes while they stay readable, and a retried fsync does not bring them back
Disk faults engine/diskfaults_test.go disk_bytes cuts a write short and then refuses it, flagged so the guest gets ENOSPC, while overwrites still fit; a failed write writes nothing but counts as a disk operation; a corrupted read flips the returned buffer and never the stored data; a lost write keeps the old page on disk (zeros for a new page) and a misdirected write puts the page at another offset, while the page cache keeps the new content, and rewriting the page later writes it in full; crash corruption flips exactly one bit inside the bytes that were being written; bit rot flips one stored bit; fsync_corrupts flips only the durable copy; a misdirected write is chosen from the seed and operation number and replays from its recorded fault
File system calls engine/memfs_test.go OpenFile flags, Mkdir, Rename, Unlink, Rmdir and Readdir return the POSIX errors and results
strict_dirs engine/namespace_test.go New entries need the parent directory fsynced to survive, a rename reverts without a directory fsync, and a torn crash recovers an in-order prefix of directory operations
Fault schedules engine/faults_test.go Schedules are identical for the same seed and differ across seeds, stay inside the time horizon and in order, never crash the workload unless asked, and never plan runtime-only faults; swarm rates stay in [0, 2 x rate), swarm mixes are deterministic per seed, and partition sides are never empty
Network faults engine/netfaults_test.go Every partition topology has the promised shape (a ring gives each node a distinct majority), the default half topology keeps the old schedule, a one-way clog holds one direction only, jam accepts late without an error, slow adds its delay, per-link latency overrides apply one way, a reset breaks both ends, including connections waiting to be accepted, while the listener stays open, a slow node stretches its latency and timers, sim.QuietFor heals clogs, swizzles clog one node at a time and unclog in random order, and bad settings, including network faults on a single-node config, are rejected
Scheduler engine/sched_test.go The next step is the earliest one; at the same time a planned fault goes before a restart and a restart before a resume; every resume due at that time is a candidate for the seeded scheduler; a paused node waits for its pause to end
Shrinking engine/shrink_test.go, cmd/seedfall/shrink_test.go Against a fake runner, Shrink keeps the original failure, ignores a different failure, and returns a 1-minimal schedule; delta debugging finds one needed fault among 64 in fewer runs than removing one at a time; downtimes, clock jumps, partition sides and fault times shrink to just what the failure needs, while faults recorded at run time keep their operation; the duration and a sim.Budget workload are trimmed, and a workload without sim.Budget is left alone; the result and run count are the same for 1, 3 and 8 jobs; a run or time budget stops it with the failure intact; failure matching ignores numbers only. Schedule files round-trip, the older fault arrays still read, and a passing replay gets a note only when a shortened duration ended it before the workload finished
Network engine/net_test.go Messages on one connection arrive in order under random latency, reads wait for arrival, a partition holds messages until it heals, close delivers EOF after data, and a crash resets every connection
Linearizability engine/history_test.go Every model (kv, cas, append, queue, set, counter) accepts and rejects known histories, including unknown, pending and failed operations; operations a model does not have, and inputs or outputs of the wrong type, are rejected; a history survives the JSONL round trip and re-checks the same; a custom Porcupine model checks a history through PorcupineOperations
Transactional anomalies engine/elle_test.go The list-append checker finds G0, G1a, G1b, G1c, G-single, G2, internal inconsistency, incompatible orders, duplicate elements and appends, and garbage reads in minimal histories, passes serializable ones and reads of unknown transactions, explains the cycle, and rejects malformed transactions
End to end engine/guest_test.go A small guest in engine/testdata/guest runs under crashes, partitions, torn writes, out-of-order writeback, crashes during disk operations and pauses: two runs of a seed give byte-identical traces, -check passes, a sweep finds the same failing seeds with 1 or 4 jobs, a failure replays exactly from its saved fault schedule, and a shrunk schedule keeps the failure, replays exactly and is 1-minimal. sim.Budget caps the client's writes when a schedule sets ops, the trace shows the cap, the run is shorter and replays exactly. Under clogs, swizzles, resets, slow nodes and partition topologies, two runs of a seed and a replay of its fault schedule give byte-identical traces. Under every disk fault the same holds, a full disk and write_enospc reach the guest as syscall.ENOSPC, and a failure caused by a lost write shrinks to a schedule that keeps the lost write and replays exactly
Liveness and workload faults engine/liveness_test.go Against the guest: with liveness, a workload that never finishes fails as liveness at duration + within and without it only hits the horizon; the recovery phase restarts a node that would stay down and heals a partition, so the workload finishes; a finally check starts only after the workload, with no fault after it, and its sim.Eventually counts as a property; an Eventually that never holds fails as liveness with its message; include_workload crashes and pauses the client, open operations are reported and stay sound for linearizability, and a replay of the recorded faults is byte-identical; every one of several workloads must finish. Without the guest: supervise never restarts a workload that exits with an error, "faults": false keeps a node out of every planned fault and every partition, the workload is a fault target only when asked, configs with workloads[], finally and liveness are validated, and sim.Eventually is cataloged
Assertions and events engine/assertions_test.go, sim/sim_test.go The catalog finds numeric and grouped assertions with their message at the right argument; Antithesis guidance records keep the smallest margin for always and the largest for sometimes, and guidance without an assertion creates nothing; lifecycle.SendEvent becomes a trace event; margins aggregate across seeds; outside the simulator the numeric and grouped forms fail with JSON details and sim.Event does nothing
Determinism hash engine/hostcalls_test.go The host-call hash is stable, checkpointed every 4,096 calls and sensitive to field boundaries, and a divergence report names the first differing call with the call before it, including a run that ends early
Mutants engine/mutation_test.go rename_not_atomic loses both names in a crash before the next fsync in loose and strict mode, but not a rename to a new name or one a later fsync closed; truncate_not_durable brings back the old tail; net_drop_after_close drops only an unread last chunk; net_dup_chunk duplicates a seeded share of writes and replays them identically; network mutants skip nodes that take no faults; monotonic_jump moves the monotonic clock only when active; -only parses and rejects typos. engine/appmutant_test.go: source mutants find comparisons, early error returns and Sync calls and still parse, the selection is seeded and ordered, and a mutant's overlay merges with the net_overlay one
Preemption engine/preempt_test.go Strategy validation; loop counting adds the countdown after every loop header while decoding LEB immediates, br_table and 0xfc opcodes, and rejects unsupported opcodes; the loop-counted guest compiles; with preempt.loops a call-free loop of three time slices takes at least 30ms of virtual time and without it almost none; pct finds a goroutine race, records its change points as preempt faults that replay exactly, an empty schedule fires none, shrinking keeps one, every change point fires at its counted call across time-slice parks and sleeps and replays the same, and a preempt fault is rejected unless the strategy is pct; calls preempts deterministically
Confidence math report/report_test.go SeedsToConfirm and the detectable failure rate against known values, findings moving through new, ongoing, not seen, resolved and regressed across runs, and the JSON report against a golden file
Proof rendering verify/verify_test.go How many seeds a proof needs, and the text and Markdown proof blocks against golden files

scripts/regress.sh then runs every example except badgerkv against its expected pass or fail result, replays saved fault schedules, and checks that replays are identical.

Running them

go test ./...                 # everything, including the wasip1 guest build
go test -short ./...          # skip the tests that build and run a wasip1 guest
go test -race ./...           # what CI runs; needs cgo
go test ./report ./verify -update   # rewrite golden files after an intended change, then review the diff
go run honnef.co/go/tools/cmd/staticcheck@latest ./...
go run golang.org/x/vuln/cmd/govulncheck@latest ./...
bash scripts/regress.sh       # every example; takes hours

govulncheck also reports standard library vulnerabilities, so run it with the latest Go patch release, as CI does with go-version: stable.

CI

Workflow When What
ci.yml every push to main and every pull request gofmt, go vet, staticcheck, govulncheck, the site checks (scripts/site-check.sh and an up-to-date site/llms.txt and site/llms-full.txt); go test -race ./... on Ubuntu, macOS and Windows; and two example sweeps through the GitHub Action with -check
regress.yml nightly, or by hand bash scripts/regress.sh
codeql.yml every push to main, every pull request, weekly CodeQL analysis of the Go code

Dependabot opens weekly updates for the root module and the GitHub Actions. The examples are left alone on purpose: each pins the library version its findings were made against.