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