The supervisor must never call fork and exec directly, or nothing about it would be testable. Instead it talks to a process runtime through a small interface. Today you define that interface and a fake implementation that records what it was asked to do, so every later lesson can drive processes deterministically.
Define a Runtime interface and a fake that records started commands and delivered signals.
If the supervisor called the operating system to fork and exec real programs, you
could not test a single restart decision without spawning actual processes and
racing against wall-clock time. The fix is the oldest trick in testable design:
put a thin interface between the supervisor and the world. The supervisor only
ever asks a Runtime to Start a command (getting back an opaque handle) or to
send a Signal to a handle. It never knows or cares whether a real process exists.
The FakeRuntime implements that interface by simply writing down what it was told:
which command each handle was started with, and which signals each handle received.
That record is what your tests assert against. The real, fork-and-exec runtime is
the one piece that genuinely differs per operating system, so it lives outside the
graded core entirely - the supervisor you build is identical everywhere, and this
interface is the seam that makes that true. One thing is still missing: a way to say
“this process exited”. That is the reaping step, and it arrives with the lifecycle
next.
type Handle inttype Signal intconst ( SIGTERM Signal = iota; SIGKILL )type Runtime interface {Start(cmd string) HandleSignal(h Handle, sig Signal)}// FakeRuntime hands out handles 1,2,3... and records cmd + signals per handletype FakeRuntime struct { /* next Handle; started map; signals map */ }