Skip to content

Commit f294298

Browse files
iduartgomezclaude
andauthored
feat(simulation): prepare DTS integration (2/N) (#2602)
Co-authored-by: Claude <noreply@anthropic.com>
1 parent 7a4bb93 commit f294298

27 files changed

+2031
-543
lines changed

AGENTS.md

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,120 @@ Run these in any worktree before pushing a branch or opening a PR.
136136
- The repository uses the special `TODO-` `MUST-FIX` marker to block commits that temporarily disable tests. If a test must be skipped, leave a `// TODO-` `MUST-FIX:` comment explaining why and create a follow-up issue.
137137
- Never remove or ignore failing tests without understanding the root cause.
138138

139+
### Deterministic Simulation Testing (DST) Guidelines
140+
141+
The codebase uses deterministic simulation testing to ensure reproducible test results. When working with simulation code or tests in `crates/core`, follow these guidelines:
142+
143+
#### ❌ DON'T: Use Non-Deterministic Time Sources
144+
145+
```rust
146+
// BAD - Uses real time, non-deterministic
147+
use std::time::Instant;
148+
let start = Instant::now();
149+
150+
// BAD - Uses tokio's real time
151+
tokio::time::sleep(Duration::from_secs(1)).await;
152+
153+
// BAD - tokio::time::timeout depends on real time
154+
tokio::time::timeout(Duration::from_secs(5), async { ... }).await;
155+
```
156+
157+
#### ✅ DO: Use VirtualTime and TimeSource
158+
159+
```rust
160+
// GOOD - Use TimeSource trait for time operations
161+
use crate::transport::TimeSource;
162+
163+
// For sleeping in simulation tests, advance VirtualTime explicitly:
164+
for _ in 0..10 {
165+
sim.advance_time(Duration::from_millis(100));
166+
tokio::task::yield_now().await;
167+
}
168+
169+
// For components that need time, inject TimeSource:
170+
fn new_with_time_source<T: TimeSource>(time_source: T) -> Self { ... }
171+
```
172+
173+
#### ❌ DON'T: Use Non-Deterministic Random Sources
174+
175+
```rust
176+
// BAD - Uses system entropy, non-deterministic
177+
let value: u64 = rand::random();
178+
179+
// BAD - Creates unseeded RNG
180+
let mut rng = rand::thread_rng();
181+
```
182+
183+
#### ✅ DO: Use GlobalRng
184+
185+
```rust
186+
// GOOD - Uses seeded RNG in simulation mode
187+
use crate::config::GlobalRng;
188+
189+
// Generate random values
190+
let value = GlobalRng::random_u64();
191+
let in_range = GlobalRng::random_range(0u8..100);
192+
193+
// Fill byte arrays
194+
let mut bytes = [0u8; 32];
195+
GlobalRng::fill_bytes(&mut bytes);
196+
197+
// For complex RNG operations
198+
let result = GlobalRng::with_rng(|rng| {
199+
use rand::Rng;
200+
rng.random::<f64>()
201+
});
202+
```
203+
204+
#### ❌ DON'T: Use Real Network I/O in Simulation
205+
206+
```rust
207+
// BAD - Real UDP socket in simulation code
208+
use tokio::net::UdpSocket;
209+
let socket = UdpSocket::bind("0.0.0.0:0").await?;
210+
```
211+
212+
#### ✅ DO: Use SimulationSocket
213+
214+
```rust
215+
// GOOD - In-memory socket with VirtualTime
216+
use crate::transport::SimulationSocket;
217+
let socket = SimulationSocket::bind(addr).await?;
218+
```
219+
220+
#### Key Abstractions
221+
222+
| Concern | Abstraction | Location |
223+
|---------|-------------|----------|
224+
| Time | `TimeSource` trait, `VirtualTime`, `RealTime` | `crates/core/src/transport/` |
225+
| RNG | `GlobalRng` | `crates/core/src/config.rs` |
226+
| Sockets | `Socket` trait, `SimulationSocket` | `crates/core/src/transport/` |
227+
| Async execution | `GlobalExecutor` | `crates/core/src/config.rs` |
228+
229+
#### Test Pattern: Advancing Virtual Time
230+
231+
When migrating tests from `start_paused = true` to explicit VirtualTime:
232+
233+
```rust
234+
// OLD PATTERN (implicit tokio paused time):
235+
#[tokio::test(flavor = "current_thread", start_paused = true)]
236+
async fn test_foo() {
237+
tokio::time::sleep(Duration::from_secs(3)).await;
238+
}
239+
240+
// NEW PATTERN (explicit VirtualTime):
241+
#[tokio::test(flavor = "current_thread")]
242+
async fn test_foo() {
243+
// Advance time explicitly, yielding to let tasks process
244+
for _ in 0..30 {
245+
sim.advance_time(Duration::from_millis(100));
246+
tokio::task::yield_now().await;
247+
}
248+
}
249+
```
250+
251+
This makes time progression explicit and deterministic, independent of tokio's internal time handling.
252+
139253
### Integration Testing with `freenet-test-network`
140254
- Use the `freenet-test-network` crate from https://github.com/freenet/freenet-test-network to spin up gateways and peers for integration tests.
141255
- Add it as a dev-dependency using either a path (if cloned locally) or git dependency, and construct networks with the builder API.

Cargo.lock

Lines changed: 33 additions & 11 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/core/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,7 @@ freenet-stdlib = { features = ["net"], workspace = true }
9696
console-subscriber = { version = "0.5.0", optional = true }
9797
tokio-stream = "0.1.17"
9898
freenet-test-network = { version = "0.1.20", optional = true }
99+
turmoil = "0.6" # Deterministic simulation scheduling (always enabled for SimNetwork)
99100

100101
[target.'cfg(unix)'.dependencies]
101102
libc = "0.2" # For sendmmsg syscall batching on Linux

crates/core/src/lib.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -73,10 +73,10 @@ pub mod dev_tool {
7373
pub use message::Transaction;
7474
pub use node::{
7575
testing_impl::{
76-
ContractDistribution, ConvergedContract, ConvergenceResult, DivergedContract,
77-
EventChain, EventSummary, NetworkPeer, NodeLabel, OperationStats, OperationSummary,
78-
PeerMessage, PeerStatus, PutOperationStats, RunningNode, SimNetwork,
79-
UpdateOperationStats,
76+
run_turmoil_simulation, ContractDistribution, ConvergedContract, ConvergenceResult,
77+
DivergedContract, EventChain, EventSummary, NetworkPeer, NodeLabel, OperationStats,
78+
OperationSummary, PeerMessage, PeerStatus, PutOperationStats, RunningNode, SimNetwork,
79+
TurmoilConfig, TurmoilResult, UpdateOperationStats,
8080
},
8181
InitPeerNode, NetworkStats, NodeConfig, PeerId,
8282
};

0 commit comments

Comments
 (0)