Overview
Marionette is a deterministic I/O and simulation testing library for Zig.
The long-term target is to become the deterministic std.Io implementation
for Zig.
The core promise: write production-shaped code against std.Io and narrow
Marionette handles such as Recorder or Endpoint(Message). In production,
those handles route to direct operations. In simulation, they route through a
controlled world that can replay the same execution from the same seed.
Marionette is still experimental, but the core replay loop is real: seeded
randomness, simulated time, trace logging, twice-and-compare replay, trace
summaries, named post-scenario checks, deterministic disk/network authorities
with replayable faults, and a deterministic std.Io backend for the current
file and network subset. A real scheduler and richer replay tooling are
planned.
For the precise correctness model, see Architecture. For
the replay artifact bytes, see Trace Format. For
simulation-only fault hooks, see BUGGIFY. For storage faults,
see Disk Fault Model. For the long-term std.Io
direction, see std.Io Direction.
What This Solves
Distributed systems bugs often depend on timing, ordering, and failure:
- A timeout fires just before a response arrives.
- A write succeeds locally but is lost before replication.
- A node observes messages in an unlucky order.
- A retry races with leader election.
Normal tests rarely explore those interleavings systematically. When they do find one, the failure can be hard to reproduce.
DST makes the test environment deterministic. A seed controls simulated choices. The world records a trace. When a bug appears, the seed and trace become the starting point for debugging.
What Marionette Is Not
Marionette is not:
- A general-purpose testing framework. Use
std.testingfor unit tests. - A fuzzer for pure functions.
- A consensus library.
- A production runtime.
- A syscall interception layer.
- A tool that makes arbitrary non-deterministic code deterministic.
Users must route time, randomness, disk, and network through Marionette-owned
authorities. For storage code, the preferred authority is std.Io; Env is
the harness-owned bundle that supplies that I/O backend plus recorders and
remaining Marionette capabilities. That discipline is the product.
Marionette does not auto-detect whether code is running in production or
simulation. The composition root passes explicit handles into the application;
simulation harnesses build those handles with world.simulate.
Why Zig
Marionette fits Zig because Zig already pushes code in the right direction:
- Explicit allocator passing.
- No hidden runtime.
- Comptime specialization.
- Interface-passing by convention.
- Systems programmers who care about reproducibility and control.
The design goal is for production builds to pay nothing for simulation code they do not use, while tests get a world that can control and replay every interesting source of non-determinism.
Audience
Marionette is aimed at people building reliability-critical systems:
- Databases.
- Queues.
- Storage engines.
- Consensus systems.
- Replicated services.
- Distributed schedulers.
- Infrastructure control planes.
It is probably overkill for ordinary CRUD applications.