Monorepo for Tangled
tangled.org
1use std::num::NonZeroUsize;
2use std::time::Duration;
3
4use bobbin_sim::workloads::{
5 CancelMidHydration, CancelMidHydrationConfig, ColdStartUnderLiveLoad,
6 ColdStartUnderLiveLoadConfig, ConcurrentReadsDuringReplay, ConcurrentReadsDuringReplayConfig,
7 FrameBurst, HydrantDisconnectBarrage, HydrantDisconnectBarrageConfig, SlingshotFlap,
8 SlingshotFlapConfig,
9};
10use bobbin_sim::{LeakOutcome, LeakRunConfig, Workload, run_leak_check};
11
12const PARALLELISM_SWEEP: &[usize] = &[1, 4, 16, 64];
13const SEEDS: &[u64] = &[1, 7, 42, 1337];
14
15#[test]
16fn frame_burst_is_byte_deterministic_across_parallelism_sweep() {
17 for &par in PARALLELISM_SWEEP {
18 for &seed in SEEDS {
19 let parallelism = NonZeroUsize::new(par).unwrap();
20 let config = LeakRunConfig {
21 seed,
22 parallelism,
23 max_virtual_runtime: Duration::from_secs(30),
24 mem_ws_capacity: 4096,
25 warming_buffer_enabled: true,
26 };
27 let factory = || -> Box<dyn Workload> { Box::new(FrameBurst::new(64)) };
28 let result = run_leak_check(config.clone(), factory);
29 assert!(
30 result.passed(),
31 "frame-burst leak at par={par} seed={seed}: {:?}",
32 result.outcome,
33 );
34 match &result.outcome {
35 LeakOutcome::Match => {}
36 other => panic!("non-match outcome despite passed(): {other:?}"),
37 }
38 assert_eq!(
39 result.first_report.events_processed, 64,
40 "frame-burst should process all 64 frames at par={par} seed={seed}",
41 );
42 }
43 }
44}
45
46#[test]
47fn cancel_mid_hydration_is_byte_deterministic() {
48 for &par in PARALLELISM_SWEEP {
49 for &seed in SEEDS {
50 let parallelism = NonZeroUsize::new(par).unwrap();
51 let config = LeakRunConfig {
52 seed,
53 parallelism,
54 max_virtual_runtime: Duration::from_secs(60),
55 mem_ws_capacity: 4096,
56 warming_buffer_enabled: true,
57 };
58 let factory = || -> Box<dyn Workload> {
59 Box::new(CancelMidHydration::new(CancelMidHydrationConfig {
60 frames: 200,
61 cancel_at_events: 50,
62 slingshot_latency_ms: 50,
63 frame_pace_us: 100,
64 }))
65 };
66 let result = run_leak_check(config.clone(), factory);
67 assert!(
68 result.passed(),
69 "cancel-mid-hydration leak at par={par} seed={seed}: {:?}",
70 result.outcome,
71 );
72 match &result.outcome {
73 LeakOutcome::Match => {}
74 other => panic!("non-match outcome despite passed(): {other:?}"),
75 }
76 }
77 }
78}
79
80#[test]
81fn hydrant_disconnect_barrage_is_byte_deterministic() {
82 for &par in PARALLELISM_SWEEP {
83 for &seed in SEEDS {
84 let parallelism = NonZeroUsize::new(par).unwrap();
85 let config = LeakRunConfig {
86 seed,
87 parallelism,
88 max_virtual_runtime: Duration::from_secs(60),
89 mem_ws_capacity: 4096,
90 warming_buffer_enabled: true,
91 };
92 let factory = || -> Box<dyn Workload> {
93 Box::new(HydrantDisconnectBarrage::new(
94 HydrantDisconnectBarrageConfig {
95 frames: 256,
96 disconnect_after_frames_per_session: vec![50, 80, 60],
97 },
98 ))
99 };
100 let result = run_leak_check(config.clone(), factory);
101 assert!(
102 result.passed(),
103 "hydrant-disconnect-barrage leak at par={par} seed={seed}: {:?}",
104 result.outcome,
105 );
106 assert_eq!(
107 result.first_report.events_processed, 256,
108 "expected all 256 frames processed",
109 );
110 }
111 }
112}
113
114#[test]
115fn cold_start_under_live_load_is_byte_deterministic() {
116 for &par in PARALLELISM_SWEEP {
117 for &seed in SEEDS {
118 let parallelism = NonZeroUsize::new(par).unwrap();
119 let config = LeakRunConfig {
120 seed,
121 parallelism,
122 max_virtual_runtime: Duration::from_secs(60),
123 mem_ws_capacity: 4096,
124 warming_buffer_enabled: true,
125 };
126 let factory = || -> Box<dyn Workload> {
127 Box::new(ColdStartUnderLiveLoad::new(ColdStartUnderLiveLoadConfig {
128 replay_frames: 200,
129 live_frames: 50,
130 live_pace_us: 1_000,
131 replay_pace_us: 0,
132 }))
133 };
134 let result = run_leak_check(config.clone(), factory);
135 assert!(
136 result.passed(),
137 "cold-start-under-live-load leak at par={par} seed={seed}: {:?}",
138 result.outcome,
139 );
140 assert_eq!(
141 result.first_report.events_processed, 250,
142 "expected 250 (replay+live) frames",
143 );
144 assert_eq!(
145 result.first_report.edge_count, 250,
146 "diversified owners should yield 250 distinct edge keys",
147 );
148 }
149 }
150}
151
152#[test]
153fn concurrent_reads_during_replay_is_byte_deterministic() {
154 for &par in PARALLELISM_SWEEP {
155 for &seed in SEEDS {
156 let parallelism = NonZeroUsize::new(par).unwrap();
157 let config = LeakRunConfig {
158 seed,
159 parallelism,
160 max_virtual_runtime: Duration::from_secs(60),
161 mem_ws_capacity: 4096,
162 warming_buffer_enabled: true,
163 };
164 let factory = || -> Box<dyn Workload> {
165 Box::new(ConcurrentReadsDuringReplay::new(
166 ConcurrentReadsDuringReplayConfig {
167 follow_frames: 200,
168 frame_pace_us: 100,
169 read_pace_us: 1_000,
170 },
171 ))
172 };
173 let result = run_leak_check(config.clone(), factory);
174 assert!(
175 result.passed(),
176 "concurrent-reads-during-replay leak at par={par} seed={seed}: {:?}",
177 result.outcome,
178 );
179 assert_eq!(
180 result.first_report.events_processed, 200,
181 "expected 200 follow frames processed",
182 );
183 }
184 }
185}
186
187#[test]
188fn slingshot_flap_short_brownout_is_byte_deterministic() {
189 for &par in &[4usize, 16, 64] {
190 for &seed in &[7u64, 99] {
191 let parallelism = NonZeroUsize::new(par).unwrap();
192 let config = LeakRunConfig {
193 seed,
194 parallelism,
195 max_virtual_runtime: Duration::from_secs(30),
196 mem_ws_capacity: 4096,
197 warming_buffer_enabled: true,
198 };
199 let factory = || -> Box<dyn Workload> {
200 Box::new(SlingshotFlap::new(SlingshotFlapConfig {
201 cross_did_stars: 50,
202 normal_latency_ms: 2,
203 brownout_start_ms: 0,
204 brownout_duration_ms: 100,
205 brownout_latency_ms: 50,
206 brownout_enabled: true,
207 hydrant_send_timeout_ms: 30_000,
208 hydrant_frame_pace_us: 0,
209 omit_target_repos: false,
210 emit_live_promotion_frame: false,
211 }))
212 };
213 let result = run_leak_check(config.clone(), factory);
214 assert!(
215 result.passed(),
216 "slingshot-flap leak at par={par} seed={seed}: {:?}",
217 result.outcome,
218 );
219 assert_eq!(
220 result.first_report.events_processed, 100,
221 "expected 100 (50 stars + 50 repos) frames processed",
222 );
223 assert!(
224 result.first_report.warming_buffer.enqueued_total > 0,
225 "expected the buffer path to exercise during warming at par={par} seed={seed}, got snapshot={:?}",
226 result.first_report.warming_buffer,
227 );
228 assert_eq!(
229 result.first_report.warming_buffer.enqueued_total,
230 result.first_report.warming_buffer.drained_observe_total,
231 "every parked star must drain via observe at par={par} seed={seed}, got snapshot={:?}",
232 result.first_report.warming_buffer,
233 );
234 }
235 }
236}