ravel
Deterministic simulation testing for C++. Seed a bug, replay it exact.
Loading...
Searching...
No Matches
simulation.hpp
1#pragma once
2
3#include <cstdint>
4#include <deque>
5#include <filesystem>
6#include <functional>
7#include <iosfwd>
8#include <limits>
9#include <memory>
10#include <optional>
11#include <string>
12#include <utility>
13#include <vector>
14
15#include "ravel/clock.hpp"
16#include "ravel/disk.hpp"
17#include "ravel/network.hpp"
18#include "ravel/rng.hpp"
19#include "ravel/scheduler.hpp"
20#include "ravel/trace.hpp"
21
22namespace ravel {
23
24class Simulation;
25
27using InvariantFn = std::function<bool()>;
28
33using SimulationSetup = std::function<void(Simulation&)>;
34
39 std::uint64_t max_steps = 1'000'000;
40
44 VirtualClock::Tick time_limit = std::numeric_limits<VirtualClock::Tick>::max();
45
48 std::filesystem::path trace_dir;
49
52 std::optional<std::vector<VirtualRng::Choice>> replay_choices;
53};
54
56struct Result {
58 bool ok = false;
60 std::uint64_t seed = 0;
61 std::string failure;
62 std::uint64_t steps = 0;
63 std::uint64_t trace_digest = 0;
64 std::string trace_path;
65};
66
71 public:
73 explicit Simulation(std::uint64_t seed, SimulationOptions options = {});
74
76 Simulation(const Simulation&) = delete;
77 Simulation& operator=(const Simulation&) = delete;
78
80 VirtualClock& clock() noexcept { return clock_; }
82 VirtualRng& rng() noexcept { return rng_; }
84 Scheduler& scheduler() noexcept { return scheduler_; }
86 const Trace& trace() const noexcept { return trace_; }
87
90 const std::vector<VirtualRng::Choice>& choices() const noexcept { return rng_.choices(); }
91
93 Channel& add_channel(std::string from, std::string to, FaultSpec fault);
95 Disk& add_disk(std::string name, DiskFaultSpec fault = {});
96
102 template <typename T, typename... Args>
103 T& make_state(Args&&... args) {
104 auto state = std::make_shared<T>(std::forward<Args>(args)...);
105 T& reference = *state;
106 owned_state_.push_back(std::move(state));
107 return reference;
108 }
109
112 void add_invariant(std::string name, InvariantFn invariant);
113
117
121 void write_trace(std::ostream& out) const;
122
124 std::string describe(const TraceEvent& event) const;
125
126 private:
127 struct NamedInvariant {
128 std::string name;
129 InvariantFn check;
130 };
131
133 std::string first_failed_invariant() const;
134
137 std::string dump_trace() const;
138
140 std::string subject_name(const TraceEvent& event) const;
141
144 std::vector<std::shared_ptr<void>> owned_state_;
145
147 std::uint64_t seed_;
148 SimulationOptions options_;
149 VirtualClock clock_;
150 VirtualRng rng_;
151 Trace trace_;
152 Scheduler scheduler_;
153
155 std::deque<Channel> channels_;
156 std::deque<Disk> disks_;
157 std::vector<NamedInvariant> invariants_;
158};
159
160} // namespace ravel
A virtual, one-way, in-process channel between two named endpoints.
Definition network.hpp:43
A virtual disk holding named files, with the failure behavior that makes storage code hard to get rig...
Definition disk.hpp:99
Cooperative, single-threaded task scheduler.
Definition scheduler.hpp:65
Top-level harness: owns the seed, virtual clock, RNG, trace and scheduler for one deterministic run.
Definition simulation.hpp:70
const Trace & trace() const noexcept
Everything that has happened so far.
Definition simulation.hpp:86
void add_invariant(std::string name, InvariantFn invariant)
Invariants are checked once, after the scheduler has run to quiescence.
T & make_state(Args &&... args)
Creates an object owned by the simulation and returns a reference to it.
Definition simulation.hpp:103
Scheduler & scheduler() noexcept
The scheduler: spawn tasks through it.
Definition simulation.hpp:84
Disk & add_disk(std::string name, DiskFaultSpec fault={})
Adds a virtual disk.
void write_trace(std::ostream &out) const
Writes the trace as JSON Lines: a header line (format, versions, seed), then one line per event with ...
const std::vector< VirtualRng::Choice > & choices() const noexcept
Every random choice made so far.
Definition simulation.hpp:90
VirtualClock & clock() noexcept
The virtual clock.
Definition simulation.hpp:80
std::string describe(const TraceEvent &event) const
One event in words, for messages: ‘TaskResumed 'client’ at t=40`.
VirtualRng & rng() noexcept
The run's one source of randomness. Everything random in the code under test must come from here.
Definition simulation.hpp:82
Result run_until_quiescent()
Runs until nothing is left to happen (or a task throws, the step limit is hit, or the time limit pass...
Simulation(const Simulation &)=delete
The scheduler refers to the members below, so a Simulation cannot move.
Channel & add_channel(std::string from, std::string to, FaultSpec fault)
Adds a one-way message channel from endpoint from to endpoint to.
Simulation(std::uint64_t seed, SimulationOptions options={})
A run for seed. Then add channels and disks, spawn tasks, add invariants, and run.
Ordered record of everything that happened during a run (scheduling decisions and message fates),...
Definition trace.hpp:48
Monotonic virtual time.
Definition clock.hpp:12
std::uint64_t Tick
Virtual time, in ticks. A tick has no fixed real-world length.
Definition clock.hpp:15
The one source of randomness a Simulation may use.
Definition rng.hpp:28
const std::vector< Choice > & choices() const noexcept
Every choice made so far, in order: what was actually used, after clamping.
Definition rng.hpp:59
Injectable disk behavior.
Definition disk.hpp:49
Injectable fault behavior for a Channel.
Definition network.hpp:23
How one run ended.
Definition simulation.hpp:56
std::uint64_t trace_digest
Equal digests mean identical runs.
Definition simulation.hpp:63
std::string trace_path
The dumped trace; empty if none was written.
Definition simulation.hpp:64
bool ok
True if the run passed every check.
Definition simulation.hpp:58
std::string failure
What went wrong; empty when ok.
Definition simulation.hpp:61
std::uint64_t steps
Scheduler steps taken.
Definition simulation.hpp:62
std::uint64_t seed
The seed the run was made from.
Definition simulation.hpp:60
Options for one run. Every field has a sensible default.
Definition simulation.hpp:36
std::uint64_t max_steps
Upper bound on scheduler steps, so a livelocked system fails the run instead of hanging it.
Definition simulation.hpp:39
std::optional< std::vector< VirtualRng::Choice > > replay_choices
When set, the run answers every random draw from this list instead of the seed (see VirtualRng::repla...
Definition simulation.hpp:52
std::filesystem::path trace_dir
When set, a failed run writes its trace here as ravel-seed-<seed>.trace.jsonl.
Definition simulation.hpp:48
VirtualClock::Tick time_limit
Virtual time after which the run stops, without failing.
Definition simulation.hpp:44
One fact about the run.
Definition trace.hpp:33