1use crate::model::filesystem::{
9 DirEntry, FileMetadata, FilePermissions, FileReader, FileSystem, FileWriter,
10};
11use std::io;
12use std::path::{Path, PathBuf};
13use std::sync::atomic::{AtomicUsize, Ordering};
14use std::sync::Arc;
15use std::time::Duration;
16
17#[derive(Debug, Clone)]
19pub struct SlowFsConfig {
20 pub read_dir_delay: Duration,
22 pub metadata_delay: Duration,
24 pub read_file_delay: Duration,
26 pub write_file_delay: Duration,
28 pub other_delay: Duration,
30}
31
32impl SlowFsConfig {
33 pub fn uniform(delay: Duration) -> Self {
35 Self {
36 read_dir_delay: delay,
37 metadata_delay: delay,
38 read_file_delay: delay,
39 write_file_delay: delay,
40 other_delay: delay,
41 }
42 }
43
44 pub fn none() -> Self {
46 Self::uniform(Duration::ZERO)
47 }
48
49 pub fn slow_network() -> Self {
51 Self {
52 read_dir_delay: Duration::from_millis(500),
53 metadata_delay: Duration::from_millis(50),
54 read_file_delay: Duration::from_millis(200),
55 write_file_delay: Duration::from_millis(300),
56 other_delay: Duration::from_millis(30),
57 }
58 }
59
60 pub fn slow_disk() -> Self {
62 Self {
63 read_dir_delay: Duration::from_millis(200),
64 metadata_delay: Duration::from_millis(20),
65 read_file_delay: Duration::from_millis(100),
66 write_file_delay: Duration::from_millis(150),
67 other_delay: Duration::from_millis(10),
68 }
69 }
70}
71
72impl Default for SlowFsConfig {
73 fn default() -> Self {
74 Self::none()
75 }
76}
77
78#[derive(Debug, Default)]
80pub struct BackendMetrics {
81 pub read_dir_calls: AtomicUsize,
83 pub metadata_calls: AtomicUsize,
85 pub read_file_calls: AtomicUsize,
87 pub write_file_calls: AtomicUsize,
89 pub other_calls: AtomicUsize,
91}
92
93impl BackendMetrics {
94 pub fn new() -> Self {
96 Self::default()
97 }
98
99 pub fn reset(&self) {
101 self.read_dir_calls.store(0, Ordering::SeqCst);
102 self.metadata_calls.store(0, Ordering::SeqCst);
103 self.read_file_calls.store(0, Ordering::SeqCst);
104 self.write_file_calls.store(0, Ordering::SeqCst);
105 self.other_calls.store(0, Ordering::SeqCst);
106 }
107
108 pub fn total_calls(&self) -> usize {
110 self.read_dir_calls.load(Ordering::SeqCst)
111 + self.metadata_calls.load(Ordering::SeqCst)
112 + self.read_file_calls.load(Ordering::SeqCst)
113 + self.write_file_calls.load(Ordering::SeqCst)
114 + self.other_calls.load(Ordering::SeqCst)
115 }
116}
117
118pub struct SlowFileSystem {
123 inner: Arc<dyn FileSystem>,
125 config: SlowFsConfig,
127 metrics: Arc<BackendMetrics>,
129}
130
131impl SlowFileSystem {
132 pub fn new(inner: Arc<dyn FileSystem>, config: SlowFsConfig) -> Self {
134 Self {
135 inner,
136 config,
137 metrics: Arc::new(BackendMetrics::new()),
138 }
139 }
140
141 pub fn with_uniform_delay(inner: Arc<dyn FileSystem>, delay: Duration) -> Self {
143 Self::new(inner, SlowFsConfig::uniform(delay))
144 }
145
146 pub fn metrics(&self) -> &Arc<BackendMetrics> {
148 &self.metrics
149 }
150
151 pub fn reset_metrics(&self) {
153 self.metrics.reset();
154 }
155
156 fn add_delay(&self, delay: Duration) {
158 if !delay.is_zero() {
159 std::thread::sleep(delay);
160 }
161 }
162}
163
164impl FileSystem for SlowFileSystem {
165 fn read_file(&self, path: &Path) -> io::Result<Vec<u8>> {
166 self.add_delay(self.config.read_file_delay);
167 self.metrics.read_file_calls.fetch_add(1, Ordering::SeqCst);
168 self.inner.read_file(path)
169 }
170
171 fn read_range(&self, path: &Path, offset: u64, len: usize) -> io::Result<Vec<u8>> {
172 self.add_delay(self.config.read_file_delay);
173 self.metrics.read_file_calls.fetch_add(1, Ordering::SeqCst);
174 self.inner.read_range(path, offset, len)
175 }
176
177 fn write_file(&self, path: &Path, data: &[u8]) -> io::Result<()> {
178 self.add_delay(self.config.write_file_delay);
179 self.metrics.write_file_calls.fetch_add(1, Ordering::SeqCst);
180 self.inner.write_file(path, data)
181 }
182
183 fn create_file(&self, path: &Path) -> io::Result<Box<dyn FileWriter>> {
184 self.add_delay(self.config.write_file_delay);
185 self.metrics.write_file_calls.fetch_add(1, Ordering::SeqCst);
186 self.inner.create_file(path)
187 }
188
189 fn open_file(&self, path: &Path) -> io::Result<Box<dyn FileReader>> {
190 self.add_delay(self.config.read_file_delay);
191 self.metrics.read_file_calls.fetch_add(1, Ordering::SeqCst);
192 self.inner.open_file(path)
193 }
194
195 fn open_file_for_write(&self, path: &Path) -> io::Result<Box<dyn FileWriter>> {
196 self.add_delay(self.config.write_file_delay);
197 self.metrics.write_file_calls.fetch_add(1, Ordering::SeqCst);
198 self.inner.open_file_for_write(path)
199 }
200
201 fn open_file_for_append(&self, path: &Path) -> io::Result<Box<dyn FileWriter>> {
202 self.add_delay(self.config.write_file_delay);
203 self.metrics.write_file_calls.fetch_add(1, Ordering::SeqCst);
204 self.inner.open_file_for_append(path)
205 }
206
207 fn set_file_length(&self, path: &Path, len: u64) -> io::Result<()> {
208 self.add_delay(self.config.write_file_delay);
209 self.metrics.write_file_calls.fetch_add(1, Ordering::SeqCst);
210 self.inner.set_file_length(path, len)
211 }
212
213 fn rename(&self, from: &Path, to: &Path) -> io::Result<()> {
214 self.add_delay(self.config.other_delay);
215 self.metrics.other_calls.fetch_add(1, Ordering::SeqCst);
216 self.inner.rename(from, to)
217 }
218
219 fn copy(&self, from: &Path, to: &Path) -> io::Result<u64> {
220 self.add_delay(self.config.write_file_delay);
221 self.metrics.write_file_calls.fetch_add(1, Ordering::SeqCst);
222 self.inner.copy(from, to)
223 }
224
225 fn remove_file(&self, path: &Path) -> io::Result<()> {
226 self.add_delay(self.config.other_delay);
227 self.metrics.other_calls.fetch_add(1, Ordering::SeqCst);
228 self.inner.remove_file(path)
229 }
230
231 fn remove_dir(&self, path: &Path) -> io::Result<()> {
232 self.add_delay(self.config.other_delay);
233 self.metrics.other_calls.fetch_add(1, Ordering::SeqCst);
234 self.inner.remove_dir(path)
235 }
236
237 fn metadata(&self, path: &Path) -> io::Result<FileMetadata> {
238 self.add_delay(self.config.metadata_delay);
239 self.metrics.metadata_calls.fetch_add(1, Ordering::SeqCst);
240 self.inner.metadata(path)
241 }
242
243 fn symlink_metadata(&self, path: &Path) -> io::Result<FileMetadata> {
244 self.add_delay(self.config.metadata_delay);
245 self.metrics.metadata_calls.fetch_add(1, Ordering::SeqCst);
246 self.inner.symlink_metadata(path)
247 }
248
249 fn is_dir(&self, path: &Path) -> io::Result<bool> {
250 self.add_delay(self.config.other_delay);
251 self.metrics.other_calls.fetch_add(1, Ordering::SeqCst);
252 self.inner.is_dir(path)
253 }
254
255 fn is_file(&self, path: &Path) -> io::Result<bool> {
256 self.add_delay(self.config.other_delay);
257 self.metrics.other_calls.fetch_add(1, Ordering::SeqCst);
258 self.inner.is_file(path)
259 }
260
261 fn set_permissions(&self, path: &Path, permissions: &FilePermissions) -> io::Result<()> {
262 self.add_delay(self.config.other_delay);
263 self.metrics.other_calls.fetch_add(1, Ordering::SeqCst);
264 self.inner.set_permissions(path, permissions)
265 }
266
267 fn read_dir(&self, path: &Path) -> io::Result<Vec<DirEntry>> {
268 self.add_delay(self.config.read_dir_delay);
269 self.metrics.read_dir_calls.fetch_add(1, Ordering::SeqCst);
270 self.inner.read_dir(path)
271 }
272
273 fn create_dir(&self, path: &Path) -> io::Result<()> {
274 self.add_delay(self.config.other_delay);
275 self.metrics.other_calls.fetch_add(1, Ordering::SeqCst);
276 self.inner.create_dir(path)
277 }
278
279 fn create_dir_all(&self, path: &Path) -> io::Result<()> {
280 self.add_delay(self.config.other_delay);
281 self.metrics.other_calls.fetch_add(1, Ordering::SeqCst);
282 self.inner.create_dir_all(path)
283 }
284
285 fn canonicalize(&self, path: &Path) -> io::Result<PathBuf> {
286 self.add_delay(self.config.other_delay);
287 self.metrics.other_calls.fetch_add(1, Ordering::SeqCst);
288 self.inner.canonicalize(path)
289 }
290
291 fn current_uid(&self) -> u32 {
292 self.inner.current_uid()
293 }
294
295 fn sudo_write(
296 &self,
297 path: &Path,
298 data: &[u8],
299 mode: u32,
300 uid: u32,
301 gid: u32,
302 ) -> io::Result<()> {
303 self.add_delay(self.config.write_file_delay);
304 self.metrics.write_file_calls.fetch_add(1, Ordering::SeqCst);
305 self.inner.sudo_write(path, data, mode, uid, gid)
306 }
307}
308
309#[cfg(test)]
310mod tests {
311 use super::*;
312 use crate::model::filesystem::StdFileSystem;
313 use std::time::Instant;
314 use tempfile::TempDir;
315
316 #[test]
317 fn test_slow_fs_adds_delay() {
318 let temp_dir = TempDir::new().unwrap();
319 let temp_path = temp_dir.path();
320
321 let inner = Arc::new(StdFileSystem);
322 let slow_config = SlowFsConfig::uniform(Duration::from_millis(100));
323 let slow = SlowFileSystem::new(inner, slow_config);
324
325 let start = Instant::now();
326 drop(slow.read_dir(temp_path));
327 let elapsed = start.elapsed();
328
329 assert!(
331 elapsed >= Duration::from_millis(100),
332 "Expected at least 100ms delay, got {:?}",
333 elapsed
334 );
335
336 assert_eq!(slow.metrics().read_dir_calls.load(Ordering::SeqCst), 1);
338 }
339
340 #[test]
341 fn test_metrics_tracking() {
342 let temp_dir = TempDir::new().unwrap();
343 let temp_path = temp_dir.path();
344
345 let inner = Arc::new(StdFileSystem);
346 let slow = SlowFileSystem::new(inner, SlowFsConfig::none());
347
348 drop(slow.read_dir(temp_path));
350 drop(slow.metadata(temp_path));
351 drop(slow.is_dir(temp_path));
352
353 assert_eq!(slow.metrics().read_dir_calls.load(Ordering::SeqCst), 1);
354 assert_eq!(slow.metrics().metadata_calls.load(Ordering::SeqCst), 1);
355 assert_eq!(slow.metrics().other_calls.load(Ordering::SeqCst), 1);
356 assert_eq!(slow.metrics().total_calls(), 3);
357 }
358
359 #[test]
360 fn test_reset_metrics() {
361 let temp_dir = TempDir::new().unwrap();
362 let temp_path = temp_dir.path();
363
364 let inner = Arc::new(StdFileSystem);
365 let slow = SlowFileSystem::new(inner, SlowFsConfig::none());
366
367 drop(slow.read_dir(temp_path));
369 drop(slow.metadata(temp_path));
370
371 assert!(slow.metrics().total_calls() > 0);
373
374 slow.reset_metrics();
376
377 assert_eq!(slow.metrics().total_calls(), 0);
379 }
380
381 #[test]
382 fn test_preset_configs() {
383 let inner = Arc::new(StdFileSystem);
384
385 let network_config = SlowFsConfig::slow_network();
387 assert_eq!(network_config.read_dir_delay, Duration::from_millis(500));
388
389 let disk_config = SlowFsConfig::slow_disk();
391 assert_eq!(disk_config.read_dir_delay, Duration::from_millis(200));
392
393 let none_config = SlowFsConfig::none();
395 assert_eq!(none_config.read_dir_delay, Duration::ZERO);
396
397 let _slow_network = SlowFileSystem::new(inner.clone(), network_config);
399 let _slow_disk = SlowFileSystem::new(inner.clone(), disk_config);
400 let _no_delay = SlowFileSystem::new(inner, none_config);
401 }
402}