Skip to main content

orion_accessor/
timeout.rs

1use serde::{Deserialize, Serialize};
2use std::time::Duration;
3
4/// 下载超时配置结构体
5#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
6#[serde(rename_all = "kebab-case")]
7pub struct TimeoutConfig {
8    /// 连接超时时间(秒)
9    pub connect_timeout: u64,
10    /// 读取/写入超时时间(秒)
11    pub read_timeout: u64,
12    /// 总操作超时时间(秒)
13    pub total_timeout: u64,
14}
15
16impl TimeoutConfig {
17    /// 创建默认配置
18    pub fn new() -> Self {
19        Self::default()
20    }
21
22    /// HTTP模式配置(适用于小文件)
23    pub fn http_simple() -> Self {
24        Self {
25            connect_timeout: 30,
26            read_timeout: 60,
27            total_timeout: 300,
28        }
29    }
30
31    /// HTTP大文件模式配置
32    pub fn http_large_file() -> Self {
33        Self {
34            connect_timeout: 60,
35            read_timeout: 300,
36            total_timeout: 3600,
37        }
38    }
39
40    /// Git操作配置
41    pub fn git_operation() -> Self {
42        Self {
43            connect_timeout: 120,
44            read_timeout: 180,
45            total_timeout: 1800,
46        }
47    }
48
49    /// 转为Duration格式
50    pub fn connect_duration(&self) -> Duration {
51        Duration::from_secs(self.connect_timeout)
52    }
53
54    pub fn read_duration(&self) -> Duration {
55        Duration::from_secs(self.read_timeout)
56    }
57
58    pub fn total_duration(&self) -> Duration {
59        Duration::from_secs(self.total_timeout)
60    }
61
62    /// 验证配置有效性
63    pub fn validate(&self) -> bool {
64        self.connect_timeout > 0 && self.read_timeout > 0 && self.total_timeout > 0
65    }
66}
67
68impl Default for TimeoutConfig {
69    fn default() -> Self {
70        Self {
71            connect_timeout: 30,
72            read_timeout: 60,
73            total_timeout: 300,
74        }
75    }
76}
77
78/// 下载进度监控器
79#[derive(Debug, Clone)]
80pub struct ProgressTracker {
81    last_activity: std::time::Instant,
82    timeout_duration: Duration,
83    total_downloaded: u64,
84    total_expected: Option<u64>,
85}
86
87impl ProgressTracker {
88    /// 创建新的进度跟踪器
89    pub fn new(timeout_duration: Duration) -> Self {
90        Self {
91            last_activity: std::time::Instant::now(),
92            timeout_duration,
93            total_downloaded: 0,
94            total_expected: None,
95        }
96    }
97
98    /// 更新进度信息
99    pub fn update(&mut self, bytes: u64, total: Option<u64>) {
100        self.last_activity = std::time::Instant::now();
101        self.total_downloaded = bytes;
102        self.total_expected = total;
103    }
104
105    pub fn reset(&mut self) {
106        self.last_activity = std::time::Instant::now();
107    }
108
109    /// 检查是否超时
110    pub fn has_timed_out(&self) -> bool {
111        self.last_activity.elapsed() > self.timeout_duration
112    }
113
114    /// 获取下载进度百分比
115    pub fn progress_percent(&self) -> Option<f64> {
116        match self.total_expected {
117            Some(total) if total > 0 => Some((self.total_downloaded as f64 / total as f64) * 100.0),
118            _ => None,
119        }
120    }
121
122    /// 获取已下载字节数
123    pub fn downloaded(&self) -> u64 {
124        self.total_downloaded
125    }
126
127    /// 获取总期望字节数
128    pub fn total_expected(&self) -> Option<u64> {
129        self.total_expected
130    }
131}
132
133#[cfg(test)]
134mod tests {
135    use super::*;
136    use std::time::Duration;
137
138    // TimeoutConfig 测试
139    mod timeout_config {
140        use super::*;
141
142        #[test]
143        fn test_default_config() {
144            let config = TimeoutConfig::default();
145            assert_eq!(config.connect_timeout, 30);
146            assert_eq!(config.read_timeout, 60);
147            assert_eq!(config.total_timeout, 300);
148        }
149
150        #[test]
151        fn test_new_config() {
152            let config = TimeoutConfig::new();
153            assert_eq!(config, TimeoutConfig::default());
154        }
155
156        #[test]
157        fn test_http_simple_config() {
158            let config = TimeoutConfig::http_simple();
159            assert_eq!(config.connect_timeout, 30);
160            assert_eq!(config.read_timeout, 60);
161            assert_eq!(config.total_timeout, 300);
162        }
163
164        #[test]
165        fn test_http_large_file_config() {
166            let config = TimeoutConfig::http_large_file();
167            assert_eq!(config.connect_timeout, 60);
168            assert_eq!(config.read_timeout, 300);
169            assert_eq!(config.total_timeout, 3600);
170        }
171
172        #[test]
173        fn test_git_operation_config() {
174            let config = TimeoutConfig::git_operation();
175            assert_eq!(config.connect_timeout, 120);
176            assert_eq!(config.read_timeout, 180);
177            assert_eq!(config.total_timeout, 1800);
178        }
179
180        #[test]
181        fn test_connect_duration() {
182            let config = TimeoutConfig {
183                connect_timeout: 45,
184                read_timeout: 90,
185                total_timeout: 180,
186            };
187            assert_eq!(config.connect_duration(), Duration::from_secs(45));
188        }
189
190        #[test]
191        fn test_read_duration() {
192            let config = TimeoutConfig {
193                connect_timeout: 30,
194                read_timeout: 120,
195                total_timeout: 300,
196            };
197            assert_eq!(config.read_duration(), Duration::from_secs(120));
198        }
199
200        #[test]
201        fn test_total_duration() {
202            let config = TimeoutConfig {
203                connect_timeout: 30,
204                read_timeout: 60,
205                total_timeout: 600,
206            };
207            assert_eq!(config.total_duration(), Duration::from_secs(600));
208        }
209
210        #[test]
211        fn test_duration_conversions_zero_values() {
212            let config = TimeoutConfig {
213                connect_timeout: 0,
214                read_timeout: 0,
215                total_timeout: 0,
216            };
217            assert_eq!(config.connect_duration(), Duration::from_secs(0));
218            assert_eq!(config.read_duration(), Duration::from_secs(0));
219            assert_eq!(config.total_duration(), Duration::from_secs(0));
220        }
221
222        #[test]
223        fn test_duration_conversions_large_values() {
224            let config = TimeoutConfig {
225                connect_timeout: u64::MAX,
226                read_timeout: u64::MAX,
227                total_timeout: u64::MAX,
228            };
229            assert_eq!(config.connect_duration(), Duration::from_secs(u64::MAX));
230            assert_eq!(config.read_duration(), Duration::from_secs(u64::MAX));
231            assert_eq!(config.total_duration(), Duration::from_secs(u64::MAX));
232        }
233
234        #[test]
235        fn test_validation_valid_config() {
236            let config = TimeoutConfig {
237                connect_timeout: 10,
238                read_timeout: 20,
239                total_timeout: 30,
240            };
241            assert!(config.validate());
242        }
243
244        #[test]
245        fn test_validation_zero_connect_timeout() {
246            let config = TimeoutConfig {
247                connect_timeout: 0,
248                read_timeout: 60,
249                total_timeout: 300,
250            };
251            assert!(!config.validate());
252        }
253
254        #[test]
255        fn test_validation_zero_read_timeout() {
256            let config = TimeoutConfig {
257                connect_timeout: 30,
258                read_timeout: 0,
259                total_timeout: 300,
260            };
261            assert!(!config.validate());
262        }
263
264        #[test]
265        fn test_validation_zero_total_timeout() {
266            let config = TimeoutConfig {
267                connect_timeout: 30,
268                read_timeout: 60,
269                total_timeout: 0,
270            };
271            assert!(!config.validate());
272        }
273
274        #[test]
275        fn test_validation_all_zero_values() {
276            let config = TimeoutConfig {
277                connect_timeout: 0,
278                read_timeout: 0,
279                total_timeout: 0,
280            };
281            assert!(!config.validate());
282        }
283
284        #[test]
285        fn test_validation_edge_case_one_second() {
286            let config = TimeoutConfig {
287                connect_timeout: 1,
288                read_timeout: 1,
289                total_timeout: 1,
290            };
291            assert!(config.validate());
292        }
293
294        #[test]
295        fn test_partial_eq() {
296            let config1 = TimeoutConfig {
297                connect_timeout: 30,
298                read_timeout: 60,
299                total_timeout: 300,
300            };
301            let config2 = TimeoutConfig {
302                connect_timeout: 30,
303                read_timeout: 60,
304                total_timeout: 300,
305            };
306            let config3 = TimeoutConfig {
307                connect_timeout: 31,
308                read_timeout: 60,
309                total_timeout: 300,
310            };
311
312            assert_eq!(config1, config2);
313            assert_ne!(config1, config3);
314        }
315
316        #[test]
317        fn test_clone() {
318            let config = TimeoutConfig {
319                connect_timeout: 45,
320                read_timeout: 90,
321                total_timeout: 180,
322            };
323            let cloned_config = config.clone();
324            assert_eq!(config, cloned_config);
325        }
326
327        #[test]
328        fn test_debug_format() {
329            let config = TimeoutConfig {
330                connect_timeout: 30,
331                read_timeout: 60,
332                total_timeout: 300,
333            };
334            let debug_str = format!("{config:?}");
335            assert!(debug_str.contains("connect_timeout: 30"));
336            assert!(debug_str.contains("read_timeout: 60"));
337            assert!(debug_str.contains("total_timeout: 300"));
338        }
339    }
340
341    // ProgressTracker 测试
342    mod progress_tracker {
343        use super::*;
344
345        #[test]
346        fn test_new_tracker() {
347            let duration = Duration::from_secs(10);
348            let tracker = ProgressTracker::new(duration);
349
350            assert_eq!(tracker.timeout_duration, duration);
351            assert_eq!(tracker.total_downloaded, 0);
352            assert_eq!(tracker.total_expected, None);
353            assert!(!tracker.has_timed_out());
354        }
355
356        #[test]
357        fn test_update_with_total() {
358            let mut tracker = ProgressTracker::new(Duration::from_secs(10));
359            tracker.update(50, Some(100));
360
361            assert_eq!(tracker.downloaded(), 50);
362            assert_eq!(tracker.total_expected(), Some(100));
363            assert_eq!(tracker.progress_percent(), Some(50.0));
364            assert!(!tracker.has_timed_out());
365        }
366
367        #[test]
368        fn test_update_without_total() {
369            let mut tracker = ProgressTracker::new(Duration::from_secs(10));
370            tracker.update(50, None);
371
372            assert_eq!(tracker.downloaded(), 50);
373            assert_eq!(tracker.total_expected(), None);
374            assert_eq!(tracker.progress_percent(), None);
375            assert!(!tracker.has_timed_out());
376        }
377
378        #[test]
379        fn test_progress_percent_zero_total() {
380            let mut tracker = ProgressTracker::new(Duration::from_secs(10));
381            tracker.update(50, Some(0));
382
383            assert_eq!(tracker.progress_percent(), None);
384        }
385
386        #[test]
387        fn test_progress_percent_complete() {
388            let mut tracker = ProgressTracker::new(Duration::from_secs(10));
389            tracker.update(100, Some(100));
390
391            assert_eq!(tracker.progress_percent(), Some(100.0));
392        }
393
394        #[test]
395        fn test_progress_percent_half() {
396            let mut tracker = ProgressTracker::new(Duration::from_secs(10));
397            tracker.update(50, Some(100));
398
399            assert_eq!(tracker.progress_percent(), Some(50.0));
400        }
401
402        #[test]
403        fn test_progress_percent_partial() {
404            let mut tracker = ProgressTracker::new(Duration::from_secs(10));
405            tracker.update(33, Some(100));
406
407            let percent = tracker.progress_percent().unwrap();
408            assert!((percent - 33.0).abs() < 0.01);
409        }
410
411        #[test]
412        fn test_progress_percent_large_numbers() {
413            let mut tracker = ProgressTracker::new(Duration::from_secs(10));
414            tracker.update(u64::MAX / 2, Some(u64::MAX));
415
416            let percent = tracker.progress_percent().unwrap();
417            assert!((percent - 50.0).abs() < 0.01);
418        }
419
420        #[test]
421        fn test_reset() {
422            let mut tracker = ProgressTracker::new(Duration::from_millis(100));
423
424            // 更新进度
425            tracker.update(50, Some(100));
426            assert_eq!(tracker.downloaded(), 50);
427
428            // 等待一段时间
429            std::thread::sleep(Duration::from_millis(50));
430            let old_time = tracker.last_activity;
431
432            // 重置
433            tracker.reset();
434            assert_eq!(tracker.downloaded(), 50); // 下载字节数不变
435            assert_eq!(tracker.total_expected(), Some(100)); // 总期望字节数不变
436            assert!(tracker.last_activity > old_time); // 时间被重置
437            assert!(!tracker.has_timed_out()); // 重置后不应该超时
438        }
439
440        #[test]
441        fn test_timeout_behavior() {
442            let mut tracker = ProgressTracker::new(Duration::from_millis(50));
443
444            // 初始状态不应该超时
445            assert!(!tracker.has_timed_out());
446
447            // 更新进度
448            tracker.update(10, Some(100));
449            assert!(!tracker.has_timed_out());
450
451            // 等待超时
452            std::thread::sleep(Duration::from_millis(60));
453            assert!(tracker.has_timed_out());
454
455            // 更新进度后应该重置超时状态
456            tracker.update(20, Some(100));
457            assert!(!tracker.has_timed_out());
458        }
459
460        #[test]
461        fn test_zero_timeout_duration() {
462            let tracker = ProgressTracker::new(Duration::from_millis(1));
463
464            // 立即检查,可能还没超时
465            let _timed_out = tracker.has_timed_out();
466            // 短暂等待后应该超时
467            std::thread::sleep(Duration::from_millis(2));
468            assert!(tracker.has_timed_out());
469        }
470
471        #[test]
472        fn test_large_timeout_duration() {
473            let tracker = ProgressTracker::new(Duration::from_secs(3600)); // 1小时
474            assert!(!tracker.has_timed_out());
475        }
476
477        #[test]
478        fn test_multiple_updates() {
479            let mut tracker = ProgressTracker::new(Duration::from_secs(10));
480
481            // 多次更新进度
482            tracker.update(10, Some(100));
483            assert_eq!(tracker.progress_percent(), Some(10.0));
484
485            tracker.update(25, Some(100));
486            assert_eq!(tracker.progress_percent(), Some(25.0));
487
488            tracker.update(75, Some(100));
489            assert_eq!(tracker.progress_percent(), Some(75.0));
490
491            tracker.update(100, Some(100));
492            assert_eq!(tracker.progress_percent(), Some(100.0));
493
494            assert!(!tracker.has_timed_out());
495        }
496
497        #[test]
498        fn test_update_changes_total_expected() {
499            let mut tracker = ProgressTracker::new(Duration::from_secs(10));
500
501            tracker.update(50, Some(100));
502            assert_eq!(tracker.total_expected(), Some(100));
503            assert_eq!(tracker.progress_percent(), Some(50.0));
504
505            // 更新时改变总期望大小
506            tracker.update(50, Some(200));
507            assert_eq!(tracker.total_expected(), Some(200));
508            assert_eq!(tracker.progress_percent(), Some(25.0));
509        }
510
511        #[test]
512        fn test_clone_tracker() {
513            let mut tracker = ProgressTracker::new(Duration::from_secs(10));
514            tracker.update(50, Some(100));
515
516            let cloned_tracker = tracker.clone();
517
518            assert_eq!(cloned_tracker.downloaded(), 50);
519            assert_eq!(cloned_tracker.total_expected(), Some(100));
520            assert_eq!(cloned_tracker.progress_percent(), Some(50.0));
521            assert!(!cloned_tracker.has_timed_out());
522        }
523
524        #[test]
525        fn test_debug_format_tracker() {
526            let mut tracker = ProgressTracker::new(Duration::from_secs(10));
527            tracker.update(50, Some(100));
528
529            let debug_str = format!("{tracker:?}");
530            assert!(debug_str.contains("last_activity"));
531            assert!(debug_str.contains("timeout_duration"));
532            assert!(debug_str.contains("total_downloaded"));
533            assert!(debug_str.contains("total_expected"));
534        }
535
536        #[test]
537        fn test_tracker_behavior_under_load() {
538            let mut tracker = ProgressTracker::new(Duration::from_millis(100));
539
540            // 模拟频繁更新
541            for i in 1..=100 {
542                tracker.update(i, Some(100));
543                assert_eq!(tracker.downloaded(), i);
544
545                // 使用近似比较来处理浮点数精度问题
546                let percent = tracker.progress_percent().unwrap();
547                assert!(
548                    (percent - i as f64).abs() < 0.0001,
549                    "Expected {}, got {} for i={}",
550                    i as f64,
551                    percent,
552                    i
553                );
554
555                assert!(!tracker.has_timed_out());
556
557                // 每次更新后短暂等待,确保不会超时
558                std::thread::sleep(Duration::from_millis(1));
559            }
560        }
561
562        #[test]
563        fn test_edge_case_max_values() {
564            let mut tracker = ProgressTracker::new(Duration::from_secs(1));
565
566            tracker.update(u64::MAX, Some(u64::MAX));
567            assert_eq!(tracker.downloaded(), u64::MAX);
568            assert_eq!(tracker.total_expected(), Some(u64::MAX));
569            assert_eq!(tracker.progress_percent(), Some(100.0));
570        }
571
572        #[test]
573        fn test_edge_case_zero_values() {
574            let mut tracker = ProgressTracker::new(Duration::from_secs(1));
575
576            tracker.update(0, Some(0));
577            assert_eq!(tracker.downloaded(), 0);
578            assert_eq!(tracker.total_expected(), Some(0));
579            assert_eq!(tracker.progress_percent(), None);
580
581            tracker.update(0, None);
582            assert_eq!(tracker.downloaded(), 0);
583            assert_eq!(tracker.total_expected(), None);
584            assert_eq!(tracker.progress_percent(), None);
585        }
586    }
587
588    // 序列化/反序列化测试
589    mod serde_tests {
590        use super::*;
591
592        #[test]
593        fn test_serialize_yaml() {
594            let config = TimeoutConfig {
595                connect_timeout: 30,
596                read_timeout: 60,
597                total_timeout: 300,
598            };
599
600            let yaml = serde_yaml::to_string(&config).unwrap();
601            assert!(yaml.contains("connect-timeout: 30"));
602            assert!(yaml.contains("read-timeout: 60"));
603            assert!(yaml.contains("total-timeout: 300"));
604        }
605
606        #[test]
607        fn test_deserialize_yaml() {
608            let yaml = r#"
609connect-timeout: 45
610read-timeout: 90
611total-timeout: 180
612"#;
613
614            let config: TimeoutConfig = serde_yaml::from_str(yaml).unwrap();
615            assert_eq!(config.connect_timeout, 45);
616            assert_eq!(config.read_timeout, 90);
617            assert_eq!(config.total_timeout, 180);
618        }
619
620        #[test]
621        fn test_serialize_json() {
622            let config = TimeoutConfig {
623                connect_timeout: 30,
624                read_timeout: 60,
625                total_timeout: 300,
626            };
627
628            let json = serde_json::to_string(&config).unwrap();
629            let parsed: serde_json::Value = serde_json::from_str(&json).unwrap();
630
631            assert_eq!(parsed["connect-timeout"], 30);
632            assert_eq!(parsed["read-timeout"], 60);
633            assert_eq!(parsed["total-timeout"], 300);
634        }
635
636        #[test]
637        fn test_deserialize_json() {
638            let json = r#"
639{
640    "connect-timeout": 45,
641    "read-timeout": 90,
642    "total-timeout": 180
643}
644"#;
645
646            let config: TimeoutConfig = serde_json::from_str(json).unwrap();
647            assert_eq!(config.connect_timeout, 45);
648            assert_eq!(config.read_timeout, 90);
649            assert_eq!(config.total_timeout, 180);
650        }
651
652        #[test]
653        fn test_serialize_roundtrip_yaml() {
654            let original = TimeoutConfig {
655                connect_timeout: 120,
656                read_timeout: 240,
657                total_timeout: 600,
658            };
659
660            let yaml = serde_yaml::to_string(&original).unwrap();
661            let deserialized: TimeoutConfig = serde_yaml::from_str(&yaml).unwrap();
662
663            assert_eq!(original, deserialized);
664        }
665
666        #[test]
667        fn test_serialize_roundtrip_json() {
668            let original = TimeoutConfig {
669                connect_timeout: 120,
670                read_timeout: 240,
671                total_timeout: 600,
672            };
673
674            let json = serde_json::to_string(&original).unwrap();
675            let deserialized: TimeoutConfig = serde_json::from_str(&json).unwrap();
676
677            assert_eq!(original, deserialized);
678        }
679
680        #[test]
681        fn test_deserialize_missing_field() {
682            let yaml = "connect-timeout: 30\nread-timeout: 60";
683            let result: Result<TimeoutConfig, _> = serde_yaml::from_str(yaml);
684            assert!(result.is_err());
685        }
686
687        #[test]
688        fn test_deserialize_extra_field() {
689            let yaml = r#"
690connect-timeout: 30
691read-timeout: 60
692total-timeout: 300
693extra-field: "ignored"
694"#;
695
696            let result: Result<TimeoutConfig, _> = serde_yaml::from_str(yaml);
697            // 这应该成功,因为serde默认忽略额外字段
698            assert!(result.is_ok());
699        }
700    }
701}