Solutions: 43 - Tests are systems
Exercise 1 - A test as a system
Add no_creature_moves_too_far to your simulator’s DAG behind a --test flag:
#![allow(unused)]
fn main() {
if cfg.test_mode {
let suspicious = no_creature_moves_too_far(
&world.px_before, &world.py_before,
&world.creatures.px, &world.creatures.py, MAX_STEP);
assert!(suspicious.is_empty(), "{:?}", suspicious);
}
}
In live mode, the system is absent. In test mode, it runs every tick. Same code path; different schedule.
Exercise 2 - Property test
#![allow(unused)]
fn main() {
let mut world = init_world(0xCAFE);
let initial = world.creatures.len();
for _ in 0..1000 {
tick(&mut world, 0.033);
assert!(world.creatures.len() <= 2 * initial);
}
}
Run twice. Both runs report identical assertion outcomes (because of §16). If the property fails, both runs fail at the same tick.
Exercise 3 - Replay test
#![allow(unused)]
fn main() {
let recording = run_and_record(&mut world1, 100);
let mut world2 = init_world(seed);
for inputs in &recording {
world2.in_queue.extend(inputs.iter().cloned());
tick(&mut world2, /* recorded current_time */);
}
assert_eq!(hash_world(&world1), hash_world(&world2));
}
Replay and live run produce bit-identical states. The test is assert_eq!; the test fixture is the recorded queue.
Exercise 4 - TDD a new system
Test first:
#![allow(unused)]
fn main() {
fn test_growth_slows_at_high_energy() {
let mut world = init_one_creature_with_energy(100.0);
let initial = world.creatures.size[0];
for _ in 0..10 { tick(&mut world, 0.033); }
let final_size = world.creatures.size[0];
assert!(
final_size - initial < HIGH_ENERGY_GROWTH_RATE * 10.0,
"growth too fast at high energy"
);
}
}
The test states what the system should do. Then write the system. Then watch the test pass. The order matters: writing the test first forces you to specify the behaviour before implementing it.
Exercise 5 - InspectionSystem connection
Both:
- Read all relevant tables (
&borrows everywhere) - Have empty (or report-only) write-sets
- Run last in the DAG (after all mutations have settled)
- Produce reports for consumption outside the simulator
The only difference: an InspectionSystem reports state to a debug consumer (pptop, an IDE, a log). A test reports assertion results to a test runner. Same shape; different consumer.
Exercise 6 - Test runner = simulator scheduler
The simulator’s main:
fn main() {
let mut world = init_world(seed);
let scheduler = build_schedule(&[
food_spawn,
motion,
next_event,
apply_eat, apply_reproduce, apply_starve,
cleanup,
// inspect: present in --debug only
]);
loop { scheduler.tick(&mut world); }
}
The test runner:
#![allow(unused)]
fn main() {
fn test_main() {
let mut world = init_world(seed);
let scheduler = build_schedule(&[
food_spawn,
motion,
next_event,
apply_eat, apply_reproduce, apply_starve,
cleanup,
check_no_creature_moves_too_far, // assertion system
check_population_bounded, // assertion system
inspect, // and inspect
]);
for _ in 0..1000 { scheduler.tick(&mut world); }
}
}
The two binaries differ in which systems they include. The scheduler, the world, and every system itself is the same code. Most of the binary is shared.
Exercise 7 - The scale sweep (a test for cost)
The minimum of repetitions, not the mean: interference (a scheduler tick, a migration, a thermal blip) only ever adds time, so the smallest sample is the closest you get to the machine’s intrinsic cost. The mean drags toward whatever else the box was doing; the minimum is the one statistic stable enough to compare across runs and machines.
Laying the budget across the curve, the crossing scale is the system’s ceiling. It is a curve to read, not a threshold to pass: the only one-sided, falsifiable claim a wall-clock number supports is that even the unimpeded minimum is over budget - then it is definitively too slow. Anything where the minimum is under and the mean is over is a measurement under variance, not a failure.
Making it lie is the lesson. Hold foragers fixed and grow only targets, and forager density stays constant, so the binned neighbourhood stays small and the curve looks linear - while the real system, growing both, is quadratic. The axis a sweep must grow on is the one production grows on; a sweep on any other axis reports a confident, precise, wrong number, and you believe it because it came with a chart. A benchmark that does not scale the way the system scales is worse than none.