mirror of
https://seed.radicle.dev/rad:z3gqcJUoA1n9HaHKufZs5FCSGazv5.git
synced 2026-11-15 15:50:38 +08:00
node/test: Make simulations reproducible
A simulation that fails under a random seed can only be investigated if it can be run again with the same seed. Replaying from a seed is also a first step towards the deterministic simulation testing proposed [on Zulip][dst], and one that works with the simulator as it is. The simulator seeds its generator randomly and only logs the seed, so there is no way to run a simulation with it again. Even with a fixed seed, runs would not reliably replay, because partitions were decided on the absolute time, and simulations start at `LocalTime::now()`. Seed simulations from `RAD_SIM_SEED`, mention it next to the logged seed, and decide partitions by the elapsed time. A fixed seed in every test would also make runs reproducible, but would give up the variation that random seeds provide. Message timestamps still differ between runs of tests that start at `LocalTime::now()`, and tests that draw from their own generator, such as `announcement_message_amplification`, are not reproducible from the seed alone yet. [dst]: <https://radicle.zulipchat.com/#narrow/channel/369277-heartwood/topic/DST.3A.20Deterministic.20Simulation.20Testing/near/567515594>
This commit is contained in:
committed by
Lorenz Leutgeb
parent
9417a20fcd
commit
ad1699b025
@@ -6,7 +6,7 @@ use std::marker::PhantomData;
|
||||
use std::ops::Range;
|
||||
use std::rc::Rc;
|
||||
use std::sync::Arc;
|
||||
use std::{fmt, io, net};
|
||||
use std::{env, fmt, io, net};
|
||||
|
||||
use localtime::{LocalDuration, LocalTime};
|
||||
use log::*;
|
||||
@@ -190,13 +190,29 @@ impl<S> Simulation<S>
|
||||
where
|
||||
S: WriteStorage + 'static,
|
||||
{
|
||||
/// Create a new simulation.
|
||||
/// Environment variable that sets the seed of new simulations.
|
||||
const SEED_VAR: &str = "RAD_SIM_SEED";
|
||||
|
||||
/// Create a new simulation, seeded from [`Self::SEED_VAR`] if it is set,
|
||||
/// and randomly otherwise.
|
||||
pub fn new(time: LocalTime, opts: Options) -> Self {
|
||||
let rng = fastrand::Rng::new();
|
||||
let seed = env::var(Self::SEED_VAR)
|
||||
.ok()
|
||||
.and_then(|s| s.parse().ok())
|
||||
.unwrap_or_else(|| fastrand::u64(..));
|
||||
|
||||
// Log the seed so that we can reproduce the simulation if needed.
|
||||
log::info!(target: "sim", "Seed: {}", rng.get_seed());
|
||||
log::info!(
|
||||
target: "sim",
|
||||
"Simulation seed is {seed}, set {}={seed} to reproduce.",
|
||||
Self::SEED_VAR
|
||||
);
|
||||
|
||||
Self::with_seed(time, seed, opts)
|
||||
}
|
||||
|
||||
/// Create a new simulation with the given seed.
|
||||
pub fn with_seed(time: LocalTime, seed: u64, opts: Options) -> Self {
|
||||
Self {
|
||||
inbox: Inbox {
|
||||
messages: BTreeMap::new(),
|
||||
@@ -211,7 +227,7 @@ where
|
||||
opts,
|
||||
start_time: time,
|
||||
time,
|
||||
rng: RefCell::new(rng),
|
||||
rng: RefCell::new(fastrand::Rng::with_seed(seed)),
|
||||
storage: PhantomData,
|
||||
}
|
||||
}
|
||||
@@ -222,7 +238,6 @@ where
|
||||
}
|
||||
|
||||
/// Total amount of simulated time elapsed.
|
||||
#[allow(dead_code)]
|
||||
pub fn elapsed(&self) -> LocalDuration {
|
||||
self.time - self.start_time
|
||||
}
|
||||
@@ -292,7 +307,7 @@ where
|
||||
// between individual nodes. We need to think about more realistic
|
||||
// scenarios. We should also think about creating various network
|
||||
// topologies.
|
||||
if self.time.as_secs().is_multiple_of(10) {
|
||||
if self.elapsed().as_secs().is_multiple_of(10) {
|
||||
for (i, x) in nodes.keys().enumerate() {
|
||||
for y in nodes.keys().skip(i + 1) {
|
||||
if self.is_fallible() {
|
||||
|
||||
Reference in New Issue
Block a user