1use std::collections::HashMap;
4use std::future::Future;
5use std::hash::Hash;
6use std::time::{Duration, SystemTime, UNIX_EPOCH};
7
8#[inline]
10pub fn bytes_to_human_readable(bytes: u64) -> String {
11 const UNITS: &[&str] = &["B", "KB", "MB", "GB", "TB", "PB"];
12
13 if bytes == 0 {
14 return "0 B".to_string();
15 }
16
17 let base = 1024_f64;
18 let exponent = (bytes as f64).log(base).floor() as usize;
19 let exponent = exponent.min(UNITS.len() - 1);
20
21 let value = bytes as f64 / base.powi(exponent as i32);
22
23 format!("{:.2} {}", value, UNITS[exponent])
24}
25
26#[inline]
28pub const fn kb_to_bytes(kb: u64) -> u64 {
29 kb * 1024
30}
31
32#[inline]
34pub const fn mb_to_bytes(mb: u64) -> u64 {
35 mb * 1024 * 1024
36}
37
38#[inline]
40pub const fn gb_to_bytes(gb: u64) -> u64 {
41 gb * 1024 * 1024 * 1024
42}
43
44#[inline]
46pub const fn tb_to_bytes(tb: u64) -> u64 {
47 tb * 1024 * 1024 * 1024 * 1024
48}
49
50#[inline]
52pub const fn bytes_to_kb(bytes: u64) -> u64 {
53 bytes / 1024
54}
55
56#[inline]
58pub const fn bytes_to_mb(bytes: u64) -> u64 {
59 bytes / (1024 * 1024)
60}
61
62#[inline]
64pub const fn bytes_to_gb(bytes: u64) -> u64 {
65 bytes / (1024 * 1024 * 1024)
66}
67
68#[inline]
70pub fn calculate_bandwidth_mbps(bytes: u64, duration: Duration) -> f64 {
71 if duration.is_zero() {
72 return 0.0;
73 }
74
75 let bits = bytes as f64 * 8.0;
76 let seconds = duration.as_secs_f64();
77
78 bits / seconds / 1_000_000.0
79}
80
81#[inline]
83pub fn calculate_bandwidth_gbps(bytes: u64, duration: Duration) -> f64 {
84 calculate_bandwidth_mbps(bytes, duration) / 1000.0
85}
86
87#[inline]
89pub const fn mbps_to_bytes_per_sec(mbps: u64) -> u64 {
90 mbps * 1_000_000 / 8
91}
92
93#[inline]
95pub const fn bytes_per_sec_to_mbps(bps: u64) -> u64 {
96 bps * 8 / 1_000_000
97}
98
99#[inline]
101pub fn calculate_percentage(part: u64, total: u64) -> f64 {
102 if total == 0 {
103 return 0.0;
104 }
105
106 (part as f64 / total as f64) * 100.0
107}
108
109#[inline]
111pub fn current_timestamp_ms() -> i64 {
112 SystemTime::now()
113 .duration_since(UNIX_EPOCH)
114 .unwrap_or(Duration::ZERO)
115 .as_millis() as i64
116}
117
118pub fn estimate_time_remaining(processed: u64, total: u64, elapsed: Duration) -> Option<Duration> {
120 if processed == 0 || total == 0 || processed >= total {
121 return None;
122 }
123
124 let rate = processed as f64 / elapsed.as_secs_f64();
125 let remaining = total - processed;
126 let seconds_remaining = remaining as f64 / rate;
127
128 Some(Duration::from_secs_f64(seconds_remaining))
129}
130
131#[inline]
133pub fn format_duration(duration: Duration) -> String {
134 let total_secs = duration.as_secs();
135
136 let hours = total_secs / 3600;
137 let minutes = (total_secs % 3600) / 60;
138 let seconds = total_secs % 60;
139
140 let mut parts = Vec::new();
141
142 if hours > 0 {
143 parts.push(format!("{}h", hours));
144 }
145 if minutes > 0 || hours > 0 {
146 parts.push(format!("{}m", minutes));
147 }
148 parts.push(format!("{}s", seconds));
149
150 parts.join(" ")
151}
152
153#[inline]
155pub const fn secs_to_duration(secs: u64) -> Duration {
156 Duration::from_secs(secs)
157}
158
159#[inline]
161pub const fn millis_to_duration(millis: u64) -> Duration {
162 Duration::from_millis(millis)
163}
164
165#[inline]
167pub const fn minutes_to_duration(minutes: u64) -> Duration {
168 Duration::from_secs(minutes * 60)
169}
170
171#[inline]
173pub const fn hours_to_duration(hours: u64) -> Duration {
174 Duration::from_secs(hours * 3600)
175}
176
177#[inline]
179pub const fn days_to_duration(days: u64) -> Duration {
180 Duration::from_secs(days * 86400)
181}
182
183#[inline]
185pub fn is_valid_peer_id(peer_id: &str) -> bool {
186 !peer_id.is_empty() && peer_id.len() <= 256 && peer_id.is_ascii()
187}
188
189#[inline]
191pub const fn chunk_size_with_overhead(data_size: usize) -> usize {
192 const ENCRYPTION_OVERHEAD: usize = 16;
194 data_size + ENCRYPTION_OVERHEAD
195}
196
197#[inline]
206pub const fn div_ceil(dividend: u64, divisor: u64) -> u64 {
207 if divisor == 0 {
208 return 0;
209 }
210 dividend.div_ceil(divisor)
211}
212
213#[inline]
222pub const fn is_power_of_two(n: u64) -> bool {
223 n != 0 && (n & (n - 1)) == 0
224}
225
226#[inline]
235pub const fn align_up(value: u64, alignment: u64) -> u64 {
236 if alignment == 0 {
237 return value;
238 }
239 let remainder = value % alignment;
240 if remainder == 0 {
241 value
242 } else {
243 value + (alignment - remainder)
244 }
245}
246
247#[inline]
256pub const fn align_down(value: u64, alignment: u64) -> u64 {
257 if alignment == 0 {
258 return value;
259 }
260 value - (value % alignment)
261}
262
263#[inline]
271pub const fn min_const(a: u64, b: u64) -> u64 {
272 if a < b { a } else { b }
273}
274
275#[inline]
283pub const fn max_const(a: u64, b: u64) -> u64 {
284 if a > b { a } else { b }
285}
286
287#[inline]
297pub const fn clamp_const(value: u64, min: u64, max: u64) -> u64 {
298 if value < min {
299 min
300 } else if value > max {
301 max
302 } else {
303 value
304 }
305}
306
307#[inline]
316pub const fn saturating_add_const(a: u64, b: u64) -> u64 {
317 a.saturating_add(b)
318}
319
320#[inline]
329pub const fn saturating_sub_const(a: u64, b: u64) -> u64 {
330 a.saturating_sub(b)
331}
332
333#[inline]
342pub const fn saturating_mul_const(a: u64, b: u64) -> u64 {
343 a.saturating_mul(b)
344}
345
346#[inline]
356pub const fn percentage_const(part: u64, total: u64) -> u64 {
357 if total == 0 { 0 } else { (part * 100) / total }
358}
359
360pub fn truncate_string(s: &str, max_len: usize) -> String {
362 if s.len() <= max_len {
363 s.to_string()
364 } else if max_len <= 3 {
365 s.chars().take(max_len).collect()
366 } else {
367 format!("{}...", &s[..max_len - 3])
368 }
369}
370
371pub fn exponential_backoff(
382 attempt: u32,
383 base_delay_ms: u64,
384 max_delay_ms: u64,
385 jitter: bool,
386) -> Duration {
387 let exp_delay = base_delay_ms.saturating_mul(2_u64.saturating_pow(attempt));
388 let delay = exp_delay.min(max_delay_ms);
389
390 if jitter {
391 use rand::Rng;
393 let jitter_range = delay;
394 let jitter_amount = rand::thread_rng().gen_range(0..=jitter_range);
395 Duration::from_millis(jitter_amount)
396 } else {
397 Duration::from_millis(delay)
398 }
399}
400
401#[derive(Debug, Clone)]
403pub struct RetryConfig {
404 pub max_attempts: u32,
406 pub base_delay_ms: u64,
408 pub max_delay_ms: u64,
410 pub jitter: bool,
412}
413
414impl Default for RetryConfig {
415 fn default() -> Self {
416 Self {
417 max_attempts: 3,
418 base_delay_ms: 100,
419 max_delay_ms: 30_000,
420 jitter: true,
421 }
422 }
423}
424
425impl RetryConfig {
426 #[must_use]
428 pub fn new(max_attempts: u32, base_delay_ms: u64, max_delay_ms: u64, jitter: bool) -> Self {
429 Self {
430 max_attempts,
431 base_delay_ms,
432 max_delay_ms,
433 jitter,
434 }
435 }
436
437 #[must_use]
439 pub fn builder() -> RetryConfigBuilder {
440 RetryConfigBuilder::default()
441 }
442
443 #[inline]
445 pub fn delay_for_attempt(&self, attempt: u32) -> Duration {
446 exponential_backoff(attempt, self.base_delay_ms, self.max_delay_ms, self.jitter)
447 }
448
449 #[must_use]
451 pub fn aggressive() -> Self {
452 Self {
453 max_attempts: 5,
454 base_delay_ms: 50,
455 max_delay_ms: 5_000,
456 jitter: true,
457 }
458 }
459
460 #[must_use]
462 pub fn conservative() -> Self {
463 Self {
464 max_attempts: 2,
465 base_delay_ms: 500,
466 max_delay_ms: 60_000,
467 jitter: true,
468 }
469 }
470
471 #[must_use]
473 pub fn none() -> Self {
474 Self {
475 max_attempts: 0,
476 base_delay_ms: 0,
477 max_delay_ms: 0,
478 jitter: false,
479 }
480 }
481}
482
483#[derive(Debug, Clone)]
485pub struct RetryConfigBuilder {
486 max_attempts: u32,
487 base_delay_ms: u64,
488 max_delay_ms: u64,
489 jitter: bool,
490}
491
492impl Default for RetryConfigBuilder {
493 fn default() -> Self {
494 let default_config = RetryConfig::default();
495 Self {
496 max_attempts: default_config.max_attempts,
497 base_delay_ms: default_config.base_delay_ms,
498 max_delay_ms: default_config.max_delay_ms,
499 jitter: default_config.jitter,
500 }
501 }
502}
503
504impl RetryConfigBuilder {
505 #[must_use]
507 pub fn max_attempts(mut self, max_attempts: u32) -> Self {
508 self.max_attempts = max_attempts;
509 self
510 }
511
512 #[must_use]
514 pub fn base_delay_ms(mut self, base_delay_ms: u64) -> Self {
515 self.base_delay_ms = base_delay_ms;
516 self
517 }
518
519 #[must_use]
521 pub fn max_delay_ms(mut self, max_delay_ms: u64) -> Self {
522 self.max_delay_ms = max_delay_ms;
523 self
524 }
525
526 #[must_use]
528 pub fn with_jitter(mut self, jitter: bool) -> Self {
529 self.jitter = jitter;
530 self
531 }
532
533 #[must_use]
535 pub fn build(self) -> RetryConfig {
536 RetryConfig {
537 max_attempts: self.max_attempts,
538 base_delay_ms: self.base_delay_ms,
539 max_delay_ms: self.max_delay_ms,
540 jitter: self.jitter,
541 }
542 }
543}
544
545#[derive(Debug)]
566pub struct LruCache<K, V>
567where
568 K: Eq + Hash + Clone,
569{
570 capacity: usize,
571 map: HashMap<K, V>,
572 order: Vec<K>,
573}
574
575impl<K, V> LruCache<K, V>
576where
577 K: Eq + Hash + Clone,
578{
579 #[must_use]
581 pub fn new(capacity: usize) -> Self {
582 Self {
583 capacity,
584 map: HashMap::new(),
585 order: Vec::new(),
586 }
587 }
588
589 pub fn get(&mut self, key: &K) -> Option<&V> {
593 if self.map.contains_key(key) {
594 if let Some(pos) = self.order.iter().position(|k| k == key) {
596 let k = self.order.remove(pos);
597 self.order.push(k);
598 }
599 self.map.get(key)
600 } else {
601 None
602 }
603 }
604
605 pub fn put(&mut self, key: K, value: V) {
609 if self.map.contains_key(&key) {
610 self.map.insert(key.clone(), value);
612 if let Some(pos) = self.order.iter().position(|k| k == &key) {
614 self.order.remove(pos);
615 self.order.push(key);
616 }
617 } else {
618 if self.map.len() >= self.capacity {
620 if let Some(lru_key) = self.order.first().cloned() {
622 self.map.remove(&lru_key);
623 self.order.remove(0);
624 }
625 }
626 self.map.insert(key.clone(), value);
627 self.order.push(key);
628 }
629 }
630
631 pub fn remove(&mut self, key: &K) -> Option<V> {
633 if let Some(value) = self.map.remove(key) {
634 if let Some(pos) = self.order.iter().position(|k| k == key) {
635 self.order.remove(pos);
636 }
637 Some(value)
638 } else {
639 None
640 }
641 }
642
643 #[inline]
645 #[must_use]
646 pub fn len(&self) -> usize {
647 self.map.len()
648 }
649
650 #[inline]
652 #[must_use]
653 pub fn is_empty(&self) -> bool {
654 self.map.is_empty()
655 }
656
657 pub fn clear(&mut self) {
659 self.map.clear();
660 self.order.clear();
661 }
662
663 #[inline]
665 pub fn capacity(&self) -> usize {
666 self.capacity
667 }
668
669 #[inline]
671 pub fn peek(&self, key: &K) -> Option<&V> {
672 self.map.get(key)
673 }
674
675 pub fn iter(&self) -> impl Iterator<Item = (&K, &V)> {
679 self.map.iter()
680 }
681}
682
683pub mod async_utils {
685 use super::*;
686
687 pub async fn timeout<F, T>(duration: Duration, future: F) -> Result<T, ()>
692 where
693 F: Future<Output = T>,
694 {
695 tokio::time::timeout(duration, future).await.map_err(|_| ())
696 }
697
698 pub async fn retry_async<F, Fut, T, E>(config: &RetryConfig, mut operation: F) -> Result<T, E>
707 where
708 F: FnMut() -> Fut,
709 Fut: Future<Output = Result<T, E>>,
710 {
711 let mut last_error = None;
712
713 for attempt in 0..=config.max_attempts {
714 match operation().await {
715 Ok(result) => return Ok(result),
716 Err(e) => {
717 last_error = Some(e);
718
719 if attempt < config.max_attempts {
720 let delay = config.delay_for_attempt(attempt);
721 tokio::time::sleep(delay).await;
722 }
723 }
724 }
725 }
726
727 Err(last_error.unwrap())
728 }
729
730 #[inline]
732 pub async fn sleep(duration: Duration) {
733 tokio::time::sleep(duration).await
734 }
735
736 #[inline]
738 pub async fn sleep_ms(millis: u64) {
739 tokio::time::sleep(Duration::from_millis(millis)).await
740 }
741
742 #[inline]
744 pub async fn sleep_secs(secs: u64) {
745 tokio::time::sleep(Duration::from_secs(secs)).await
746 }
747
748 pub struct Debouncer {
750 duration: Duration,
751 last_call: std::sync::Arc<tokio::sync::Mutex<Option<tokio::time::Instant>>>,
752 }
753
754 impl Debouncer {
755 pub fn new(duration: Duration) -> Self {
757 Self {
758 duration,
759 last_call: std::sync::Arc::new(tokio::sync::Mutex::new(None)),
760 }
761 }
762
763 pub async fn execute<F, Fut, T>(&self, operation: F) -> Option<T>
765 where
766 F: FnOnce() -> Fut,
767 Fut: Future<Output = T>,
768 {
769 {
770 let mut last = self.last_call.lock().await;
771 *last = Some(tokio::time::Instant::now());
772 }
773
774 tokio::time::sleep(self.duration).await;
775
776 let last = self.last_call.lock().await;
777 if let Some(last_time) = *last {
778 if last_time.elapsed() >= self.duration {
779 drop(last);
780 Some(operation().await)
781 } else {
782 None
783 }
784 } else {
785 None
786 }
787 }
788 }
789}
790
791pub type ValidationResult<T = ()> = Result<T, String>;
795
796pub type StorageResult<T> = Result<T, crate::storage::StorageError>;
798
799#[inline]
803#[must_use]
804pub const fn mb_to_kb(mb: u64) -> u64 {
805 mb * 1024
806}
807
808#[inline]
810#[must_use]
811pub const fn gb_to_mb(gb: u64) -> u64 {
812 gb * 1024
813}
814
815#[inline]
817#[must_use]
818pub const fn tb_to_gb(tb: u64) -> u64 {
819 tb * 1024
820}
821
822#[inline]
826#[must_use]
827pub const fn round_up_to_multiple(value: u64, multiple: u64) -> u64 {
828 if multiple == 0 {
829 return value;
830 }
831 let remainder = value % multiple;
832 if remainder == 0 {
833 value
834 } else {
835 value + (multiple - remainder)
836 }
837}
838
839#[inline]
841#[must_use]
842pub const fn round_down_to_multiple(value: u64, multiple: u64) -> u64 {
843 if multiple == 0 {
844 return value;
845 }
846 value - (value % multiple)
847}
848
849#[inline]
851#[must_use]
852pub const fn calculate_percentage_rounded(part: u64, total: u64) -> u8 {
853 if total == 0 {
854 return 0;
855 }
856 let result = (part * 100 + total / 2) / total; if result > 100 { 100 } else { result as u8 }
858}
859
860#[inline]
862#[must_use]
863pub const fn is_in_range(value: u64, min: u64, max: u64) -> bool {
864 value >= min && value <= max
865}
866
867#[inline]
869#[must_use]
870pub const fn average_u64(a: u64, b: u64) -> u64 {
871 (a / 2) + (b / 2) + ((a % 2) + (b % 2)) / 2
873}
874
875#[inline]
877#[must_use]
878pub const fn larger_of(a: u64, b: u64) -> u64 {
879 if a > b { a } else { b }
880}
881
882#[inline]
884#[must_use]
885pub const fn smaller_of(a: u64, b: u64) -> u64 {
886 if a < b { a } else { b }
887}
888
889#[inline]
893#[must_use]
894pub fn is_valid_identifier(s: &str) -> bool {
895 !s.is_empty()
896 && s.len() <= 256
897 && s.chars()
898 .all(|c| c.is_ascii_alphanumeric() || c == '_' || c == '-')
899}
900
901#[inline]
903#[must_use]
904pub fn truncate_with_ellipsis(s: &str, max_len: usize) -> String {
905 if s.len() <= max_len {
906 s.to_string()
907 } else if max_len <= 3 {
908 s.chars().take(max_len).collect()
909 } else {
910 let mut result: String = s.chars().take(max_len - 3).collect();
911 result.push_str("...");
912 result
913 }
914}
915
916#[inline]
918#[must_use]
919pub fn safe_slice(s: &str, start: usize, end: usize) -> &str {
920 let len = s.len();
921 let start = start.min(len);
922 let end = end.min(len).max(start);
923 &s[start..end]
924}
925
926#[must_use]
930pub fn format_number_with_commas(n: u64) -> String {
931 let s = n.to_string();
932 let mut result = String::with_capacity(s.len() + s.len() / 3);
933
934 for (i, c) in s.chars().rev().enumerate() {
935 if i > 0 && i % 3 == 0 {
936 result.push(',');
937 }
938 result.push(c);
939 }
940
941 result.chars().rev().collect()
942}
943
944#[must_use]
946pub fn format_duration_compact(duration: Duration) -> String {
947 let total_secs = duration.as_secs();
948
949 if total_secs >= 3600 {
950 let hours = total_secs / 3600;
951 let minutes = (total_secs % 3600) / 60;
952 if minutes > 0 {
953 format!("{}h{}m", hours, minutes)
954 } else {
955 format!("{}h", hours)
956 }
957 } else if total_secs >= 60 {
958 let minutes = total_secs / 60;
959 let seconds = total_secs % 60;
960 if seconds > 0 {
961 format!("{}m{}s", minutes, seconds)
962 } else {
963 format!("{}m", minutes)
964 }
965 } else {
966 format!("{}s", total_secs)
967 }
968}
969
970#[inline]
974#[must_use]
975pub const fn bytes_to_kb_f64(bytes: u64) -> f64 {
976 bytes as f64 / 1024.0
977}
978
979#[inline]
981#[must_use]
982pub const fn bytes_to_mb_f64(bytes: u64) -> f64 {
983 bytes as f64 / (1024.0 * 1024.0)
984}
985
986#[inline]
988#[must_use]
989pub const fn bytes_to_gb_f64(bytes: u64) -> f64 {
990 bytes as f64 / (1024.0 * 1024.0 * 1024.0)
991}
992
993#[inline]
995#[must_use]
996pub const fn bytes_to_tb_f64(bytes: u64) -> f64 {
997 bytes as f64 / (1024.0 * 1024.0 * 1024.0 * 1024.0)
998}
999
1000#[inline]
1004#[must_use]
1005pub fn normalize_cid(cid: &str) -> String {
1006 cid.trim()
1007 .chars()
1008 .filter(|c| c.is_alphanumeric() || *c == '-' || *c == '_')
1009 .collect()
1010}
1011
1012#[inline]
1016#[must_use]
1017pub fn cid_to_path_safe(cid: &str) -> std::path::PathBuf {
1018 use std::path::PathBuf;
1019 PathBuf::from(normalize_cid(cid))
1020}
1021
1022#[inline]
1026#[must_use]
1027pub fn is_valid_cid_format(cid: &str) -> bool {
1028 if cid.is_empty() || cid.len() < 10 {
1029 return false;
1030 }
1031
1032 if cid.starts_with("Qm") && cid.len() == 46 {
1034 return cid.chars().all(|c| c.is_alphanumeric());
1035 }
1036
1037 if cid.len() > 10 && (cid.starts_with('b') || cid.starts_with('z') || cid.starts_with('f')) {
1039 return cid.chars().all(|c| c.is_alphanumeric());
1040 }
1041
1042 false
1043}
1044
1045#[inline]
1049pub fn validate_peer_id(peer_id: &str) -> Result<(), String> {
1050 if peer_id.is_empty() {
1051 return Err("Peer ID cannot be empty".to_string());
1052 }
1053
1054 if peer_id.len() > 256 {
1055 return Err(format!("Peer ID too long: {} > 256", peer_id.len()));
1056 }
1057
1058 if !peer_id.is_ascii() {
1059 return Err("Peer ID must be ASCII".to_string());
1060 }
1061
1062 Ok(())
1063}
1064
1065#[inline]
1069#[must_use]
1070pub fn peer_id_hash(peer_id: &str) -> u64 {
1071 const FNV_OFFSET_BASIS: u64 = 0xcbf29ce484222325;
1072 const FNV_PRIME: u64 = 0x100000001b3;
1073
1074 let mut hash = FNV_OFFSET_BASIS;
1075 for byte in peer_id.bytes() {
1076 hash ^= byte as u64;
1077 hash = hash.wrapping_mul(FNV_PRIME);
1078 }
1079 hash
1080}
1081
1082#[inline]
1084#[must_use]
1085pub const fn timestamp_to_duration(timestamp_ms: i64) -> Duration {
1086 if timestamp_ms < 0 {
1087 Duration::ZERO
1088 } else {
1089 Duration::from_millis(timestamp_ms as u64)
1090 }
1091}
1092
1093#[inline]
1097#[must_use]
1098pub fn timestamp_age(timestamp_ms: i64) -> Duration {
1099 let now_ms = current_timestamp_ms();
1100 let age_ms = now_ms.saturating_sub(timestamp_ms);
1101
1102 if age_ms < 0 {
1103 Duration::ZERO
1104 } else {
1105 Duration::from_millis(age_ms as u64)
1106 }
1107}
1108
1109#[inline]
1113#[must_use]
1114pub fn is_timestamp_recent(timestamp_ms: i64, max_age_ms: u64) -> bool {
1115 let age = timestamp_age(timestamp_ms);
1116 age.as_millis() <= max_age_ms as u128
1117}
1118
1119#[inline]
1121#[must_use]
1122pub fn timestamp_to_systemtime(timestamp_ms: i64) -> SystemTime {
1123 if timestamp_ms < 0 {
1124 UNIX_EPOCH
1125 } else {
1126 UNIX_EPOCH + Duration::from_millis(timestamp_ms as u64)
1127 }
1128}
1129
1130#[cfg(test)]
1131mod tests {
1132 use super::*;
1133
1134 #[test]
1135 fn test_bytes_to_human_readable() {
1136 assert_eq!(bytes_to_human_readable(0), "0 B");
1137 assert_eq!(bytes_to_human_readable(512), "512.00 B");
1138 assert_eq!(bytes_to_human_readable(1024), "1.00 KB");
1139 assert_eq!(bytes_to_human_readable(1536), "1.50 KB");
1140 assert_eq!(bytes_to_human_readable(1024 * 1024), "1.00 MB");
1141 assert_eq!(bytes_to_human_readable(1024 * 1024 * 1024), "1.00 GB");
1142 assert_eq!(
1143 bytes_to_human_readable(1024 * 1024 * 1024 * 1024),
1144 "1.00 TB"
1145 );
1146 }
1147
1148 #[test]
1149 fn test_calculate_bandwidth_mbps() {
1150 let bytes = 1024 * 1024;
1152 let duration = Duration::from_secs(1);
1153 let bandwidth = calculate_bandwidth_mbps(bytes, duration);
1154 assert!((bandwidth - 8.388_608).abs() < 0.001);
1155
1156 assert_eq!(calculate_bandwidth_mbps(1024, Duration::ZERO), 0.0);
1158 }
1159
1160 #[test]
1161 fn test_calculate_percentage() {
1162 assert_eq!(calculate_percentage(50, 100), 50.0);
1163 assert_eq!(calculate_percentage(25, 100), 25.0);
1164 assert_eq!(calculate_percentage(100, 100), 100.0);
1165 assert_eq!(calculate_percentage(0, 100), 0.0);
1166 assert_eq!(calculate_percentage(50, 0), 0.0);
1167 }
1168
1169 #[test]
1170 fn test_current_timestamp_ms() {
1171 let ts = current_timestamp_ms();
1172 assert!(ts > 0);
1173 assert!(ts > 1_600_000_000_000); }
1175
1176 #[test]
1177 fn test_estimate_time_remaining() {
1178 let processed = 25;
1179 let total = 100;
1180 let elapsed = Duration::from_secs(10);
1181
1182 let remaining = estimate_time_remaining(processed, total, elapsed);
1183 assert!(remaining.is_some());
1184
1185 let remaining_secs = remaining.unwrap().as_secs();
1187 assert!((29..=31).contains(&remaining_secs));
1188
1189 assert!(estimate_time_remaining(0, 100, elapsed).is_none());
1191 assert!(estimate_time_remaining(100, 100, elapsed).is_none());
1192 }
1193
1194 #[test]
1195 fn test_format_duration() {
1196 assert_eq!(format_duration(Duration::from_secs(30)), "30s");
1197 assert_eq!(format_duration(Duration::from_secs(90)), "1m 30s");
1198 assert_eq!(format_duration(Duration::from_secs(3661)), "1h 1m 1s");
1199 assert_eq!(format_duration(Duration::from_secs(7200)), "2h 0m 0s");
1200 }
1201
1202 #[test]
1203 fn test_is_valid_peer_id() {
1204 assert!(is_valid_peer_id("peer-123"));
1205 assert!(is_valid_peer_id("abc123"));
1206 assert!(!is_valid_peer_id(""));
1207 assert!(!is_valid_peer_id("🦀")); let long_id = "a".repeat(257);
1211 assert!(!is_valid_peer_id(&long_id));
1212 }
1213
1214 #[test]
1215 fn test_chunk_size_with_overhead() {
1216 assert_eq!(chunk_size_with_overhead(1024), 1040);
1217 assert_eq!(chunk_size_with_overhead(0), 16);
1218 }
1219
1220 #[test]
1221 fn test_truncate_string() {
1222 assert_eq!(truncate_string("Hello, World!", 20), "Hello, World!");
1223 assert_eq!(truncate_string("Hello, World!", 10), "Hello, ...");
1224 assert_eq!(truncate_string("Hello, World!", 5), "He...");
1225 assert_eq!(truncate_string("Hi", 10), "Hi");
1226 assert_eq!(truncate_string("Hello", 3), "Hel");
1227 }
1228
1229 #[test]
1230 fn test_exponential_backoff() {
1231 let delay = exponential_backoff(0, 100, 10_000, false);
1233 assert_eq!(delay, Duration::from_millis(100));
1234
1235 let delay = exponential_backoff(1, 100, 10_000, false);
1236 assert_eq!(delay, Duration::from_millis(200));
1237
1238 let delay = exponential_backoff(2, 100, 10_000, false);
1239 assert_eq!(delay, Duration::from_millis(400));
1240
1241 let delay = exponential_backoff(3, 100, 10_000, false);
1242 assert_eq!(delay, Duration::from_millis(800));
1243
1244 let delay = exponential_backoff(10, 100, 5_000, false);
1246 assert_eq!(delay, Duration::from_millis(5_000));
1247
1248 let delay = exponential_backoff(2, 100, 10_000, true);
1250 assert!(delay <= Duration::from_millis(400));
1251 }
1252
1253 #[test]
1254 fn test_retry_config() {
1255 let config = RetryConfig::default();
1256 assert_eq!(config.max_attempts, 3);
1257 assert_eq!(config.base_delay_ms, 100);
1258 assert_eq!(config.max_delay_ms, 30_000);
1259 assert!(config.jitter);
1260
1261 let delay = config.delay_for_attempt(0);
1262 assert!(delay <= Duration::from_millis(100));
1263
1264 let custom = RetryConfig::new(5, 200, 60_000, false);
1265 assert_eq!(custom.max_attempts, 5);
1266 assert_eq!(custom.delay_for_attempt(1), Duration::from_millis(400));
1267 }
1268
1269 #[test]
1270 fn test_lru_cache_basic() {
1271 let mut cache = LruCache::new(2);
1272 cache.put("a", 1);
1273 cache.put("b", 2);
1274
1275 assert_eq!(cache.get(&"a"), Some(&1));
1276 assert_eq!(cache.get(&"b"), Some(&2));
1277 assert_eq!(cache.len(), 2);
1278 assert!(!cache.is_empty());
1279 }
1280
1281 #[test]
1282 fn test_lru_cache_eviction() {
1283 let mut cache = LruCache::new(2);
1284 cache.put("a", 1);
1285 cache.put("b", 2);
1286
1287 assert_eq!(cache.get(&"a"), Some(&1));
1289
1290 cache.put("c", 3);
1292
1293 assert_eq!(cache.get(&"a"), Some(&1));
1294 assert_eq!(cache.get(&"b"), None);
1295 assert_eq!(cache.get(&"c"), Some(&3));
1296 }
1297
1298 #[test]
1299 fn test_lru_cache_update() {
1300 let mut cache = LruCache::new(2);
1301 cache.put("a", 1);
1302 cache.put("a", 2); assert_eq!(cache.get(&"a"), Some(&2));
1305 assert_eq!(cache.len(), 1);
1306 }
1307
1308 #[test]
1309 fn test_lru_cache_remove() {
1310 let mut cache = LruCache::new(2);
1311 cache.put("a", 1);
1312 cache.put("b", 2);
1313
1314 assert_eq!(cache.remove(&"a"), Some(1));
1315 assert_eq!(cache.get(&"a"), None);
1316 assert_eq!(cache.len(), 1);
1317 }
1318
1319 #[test]
1320 fn test_lru_cache_clear() {
1321 let mut cache = LruCache::new(2);
1322 cache.put("a", 1);
1323 cache.put("b", 2);
1324
1325 cache.clear();
1326 assert_eq!(cache.len(), 0);
1327 assert!(cache.is_empty());
1328 }
1329
1330 #[test]
1331 fn test_lru_cache_capacity() {
1332 let cache = LruCache::<String, i32>::new(10);
1333 assert_eq!(cache.capacity(), 10);
1334 }
1335
1336 #[test]
1337 fn test_byte_conversions() {
1338 assert_eq!(kb_to_bytes(1), 1024);
1340 assert_eq!(kb_to_bytes(10), 10240);
1341 assert_eq!(bytes_to_kb(1024), 1);
1342 assert_eq!(bytes_to_kb(2048), 2);
1343
1344 assert_eq!(mb_to_bytes(1), 1024 * 1024);
1346 assert_eq!(mb_to_bytes(10), 10 * 1024 * 1024);
1347 assert_eq!(bytes_to_mb(1024 * 1024), 1);
1348 assert_eq!(bytes_to_mb(2 * 1024 * 1024), 2);
1349
1350 assert_eq!(gb_to_bytes(1), 1024 * 1024 * 1024);
1352 assert_eq!(gb_to_bytes(10), 10 * 1024 * 1024 * 1024);
1353 assert_eq!(bytes_to_gb(1024 * 1024 * 1024), 1);
1354
1355 assert_eq!(tb_to_bytes(1), 1024 * 1024 * 1024 * 1024);
1357 }
1358
1359 #[test]
1360 fn test_bandwidth_conversions() {
1361 let bytes = 1024 * 1024 * 125; let duration = Duration::from_secs(1);
1364 let gbps = calculate_bandwidth_gbps(bytes, duration);
1365 assert!((gbps - 1.0).abs() < 0.1);
1366
1367 assert_eq!(mbps_to_bytes_per_sec(8), 1_000_000);
1369 assert_eq!(mbps_to_bytes_per_sec(100), 12_500_000);
1370
1371 assert_eq!(bytes_per_sec_to_mbps(1_000_000), 8);
1373 assert_eq!(bytes_per_sec_to_mbps(12_500_000), 100);
1374 }
1375
1376 #[test]
1377 fn test_duration_conversions() {
1378 assert_eq!(secs_to_duration(60), Duration::from_secs(60));
1379 assert_eq!(millis_to_duration(1000), Duration::from_millis(1000));
1380 assert_eq!(minutes_to_duration(1), Duration::from_secs(60));
1381 assert_eq!(minutes_to_duration(5), Duration::from_secs(300));
1382 assert_eq!(hours_to_duration(1), Duration::from_secs(3600));
1383 assert_eq!(hours_to_duration(2), Duration::from_secs(7200));
1384 assert_eq!(days_to_duration(1), Duration::from_secs(86400));
1385 assert_eq!(days_to_duration(7), Duration::from_secs(604_800));
1386 }
1387
1388 #[test]
1389 fn test_retry_config_builder() {
1390 let config = RetryConfig::builder()
1391 .max_attempts(5)
1392 .base_delay_ms(200)
1393 .max_delay_ms(10_000)
1394 .with_jitter(false)
1395 .build();
1396
1397 assert_eq!(config.max_attempts, 5);
1398 assert_eq!(config.base_delay_ms, 200);
1399 assert_eq!(config.max_delay_ms, 10_000);
1400 assert!(!config.jitter);
1401 }
1402
1403 #[test]
1404 fn test_retry_config_presets() {
1405 let aggressive = RetryConfig::aggressive();
1407 assert_eq!(aggressive.max_attempts, 5);
1408 assert_eq!(aggressive.base_delay_ms, 50);
1409 assert_eq!(aggressive.max_delay_ms, 5_000);
1410
1411 let conservative = RetryConfig::conservative();
1413 assert_eq!(conservative.max_attempts, 2);
1414 assert_eq!(conservative.base_delay_ms, 500);
1415 assert_eq!(conservative.max_delay_ms, 60_000);
1416
1417 let none = RetryConfig::none();
1419 assert_eq!(none.max_attempts, 0);
1420 }
1421
1422 #[test]
1423 fn test_retry_config_builder_default() {
1424 let config = RetryConfig::builder().build();
1425 let default_config = RetryConfig::default();
1426
1427 assert_eq!(config.max_attempts, default_config.max_attempts);
1428 assert_eq!(config.base_delay_ms, default_config.base_delay_ms);
1429 assert_eq!(config.max_delay_ms, default_config.max_delay_ms);
1430 assert_eq!(config.jitter, default_config.jitter);
1431 }
1432
1433 #[test]
1434 fn test_div_ceil() {
1435 assert_eq!(div_ceil(10, 3), 4);
1436 assert_eq!(div_ceil(9, 3), 3);
1437 assert_eq!(div_ceil(0, 5), 0);
1438 assert_eq!(div_ceil(1, 1), 1);
1439 assert_eq!(div_ceil(100, 10), 10);
1440 assert_eq!(div_ceil(101, 10), 11);
1441 }
1442
1443 #[test]
1444 fn test_is_power_of_two() {
1445 assert!(is_power_of_two(1));
1446 assert!(is_power_of_two(2));
1447 assert!(is_power_of_two(4));
1448 assert!(is_power_of_two(8));
1449 assert!(is_power_of_two(1024));
1450 assert!(!is_power_of_two(0));
1451 assert!(!is_power_of_two(3));
1452 assert!(!is_power_of_two(5));
1453 assert!(!is_power_of_two(100));
1454 }
1455
1456 #[test]
1457 fn test_align_up() {
1458 assert_eq!(align_up(10, 8), 16);
1459 assert_eq!(align_up(16, 8), 16);
1460 assert_eq!(align_up(0, 8), 0);
1461 assert_eq!(align_up(1, 4), 4);
1462 assert_eq!(align_up(100, 64), 128);
1463 }
1464
1465 #[test]
1466 fn test_align_down() {
1467 assert_eq!(align_down(10, 8), 8);
1468 assert_eq!(align_down(16, 8), 16);
1469 assert_eq!(align_down(0, 8), 0);
1470 assert_eq!(align_down(7, 4), 4);
1471 assert_eq!(align_down(100, 64), 64);
1472 }
1473
1474 #[test]
1475 fn test_min_max_const() {
1476 assert_eq!(min_const(5, 10), 5);
1477 assert_eq!(min_const(10, 5), 5);
1478 assert_eq!(min_const(0, 0), 0);
1479
1480 assert_eq!(max_const(5, 10), 10);
1481 assert_eq!(max_const(10, 5), 10);
1482 assert_eq!(max_const(0, 0), 0);
1483 }
1484
1485 #[test]
1486 fn test_clamp_const() {
1487 assert_eq!(clamp_const(5, 0, 10), 5);
1488 assert_eq!(clamp_const(15, 0, 10), 10);
1489 assert_eq!(clamp_const(0, 5, 10), 5);
1490 assert_eq!(clamp_const(0, 0, 10), 0);
1491 assert_eq!(clamp_const(100, 50, 75), 75);
1492 assert_eq!(clamp_const(60, 50, 75), 60);
1493 }
1494
1495 #[test]
1496 fn test_saturating_ops_const() {
1497 assert_eq!(saturating_add_const(5, 10), 15);
1499 assert_eq!(saturating_add_const(u64::MAX, 1), u64::MAX);
1500 assert_eq!(saturating_add_const(u64::MAX - 10, 20), u64::MAX);
1501
1502 assert_eq!(saturating_sub_const(10, 5), 5);
1504 assert_eq!(saturating_sub_const(5, 10), 0);
1505 assert_eq!(saturating_sub_const(0, 1), 0);
1506
1507 assert_eq!(saturating_mul_const(5, 10), 50);
1509 assert_eq!(saturating_mul_const(u64::MAX, 2), u64::MAX);
1510 assert_eq!(saturating_mul_const(u64::MAX / 2 + 1, 2), u64::MAX);
1511 }
1512
1513 #[test]
1514 fn test_percentage_const() {
1515 assert_eq!(percentage_const(50, 100), 50);
1516 assert_eq!(percentage_const(1, 3), 33);
1517 assert_eq!(percentage_const(2, 3), 66);
1518 assert_eq!(percentage_const(10, 0), 0);
1519 assert_eq!(percentage_const(0, 100), 0);
1520 assert_eq!(percentage_const(100, 100), 100);
1521 assert_eq!(percentage_const(150, 100), 150); }
1523
1524 #[test]
1527 fn test_bytes_to_float_conversions() {
1528 assert!((bytes_to_kb_f64(1024) - 1.0).abs() < 0.001);
1530 assert!((bytes_to_kb_f64(2048) - 2.0).abs() < 0.001);
1531 assert!((bytes_to_kb_f64(1536) - 1.5).abs() < 0.001);
1532
1533 assert!((bytes_to_mb_f64(1024 * 1024) - 1.0).abs() < 0.001);
1535 assert!((bytes_to_mb_f64(1024 * 1024 * 5) - 5.0).abs() < 0.001);
1536
1537 assert!((bytes_to_gb_f64(1024 * 1024 * 1024) - 1.0).abs() < 0.001);
1539 assert!((bytes_to_gb_f64(1024 * 1024 * 1024 * 2) - 2.0).abs() < 0.001);
1540
1541 assert!((bytes_to_tb_f64(1024u64 * 1024 * 1024 * 1024) - 1.0).abs() < 0.001);
1543 }
1544
1545 #[test]
1546 fn test_normalize_cid() {
1547 assert_eq!(normalize_cid("QmTest123"), "QmTest123");
1548 assert_eq!(normalize_cid(" QmTest123 "), "QmTest123");
1549 assert_eq!(normalize_cid("Qm../../../etc/passwd"), "Qmetcpasswd");
1550 assert_eq!(normalize_cid("Qm Test@123!"), "QmTest123");
1551 assert_eq!(normalize_cid("valid-cid_123"), "valid-cid_123");
1552 assert_eq!(normalize_cid(""), "");
1553 }
1554
1555 #[test]
1556 fn test_cid_to_path_safe() {
1557 let path = cid_to_path_safe("QmTest123");
1558 assert_eq!(path.to_str().unwrap(), "QmTest123");
1559
1560 let path = cid_to_path_safe("Qm../../../etc/passwd");
1561 assert_eq!(path.to_str().unwrap(), "Qmetcpasswd");
1562 }
1563
1564 #[test]
1565 fn test_is_valid_cid_format() {
1566 assert!(is_valid_cid_format(
1568 "QmTest1234567890123456789012345678901234567890"
1569 ));
1570
1571 assert!(is_valid_cid_format(
1573 "bafybeigdyrzt5sfp7udm7hu76uh7y26nf3efuylqabf"
1574 ));
1575 assert!(is_valid_cid_format(
1576 "zb2rhe5P4gXftAwvA4eXQ5HJwsER2owDyS9sKaQRRVQPn93bA"
1577 ));
1578
1579 assert!(!is_valid_cid_format(""));
1581 assert!(!is_valid_cid_format("invalid"));
1582 assert!(!is_valid_cid_format("Qm"));
1583 assert!(!is_valid_cid_format("QmShort"));
1584 assert!(!is_valid_cid_format("QmTest12345")); }
1586
1587 #[test]
1588 fn test_validate_peer_id() {
1589 assert!(validate_peer_id("peer-123").is_ok());
1590 assert!(validate_peer_id("valid_peer_id").is_ok());
1591
1592 assert!(validate_peer_id("").is_err());
1593 assert!(validate_peer_id(&"x".repeat(300)).is_err());
1594 }
1595
1596 #[test]
1597 fn test_peer_id_hash() {
1598 let hash1 = peer_id_hash("peer-123");
1599 let hash2 = peer_id_hash("peer-456");
1600 let hash3 = peer_id_hash("peer-123");
1601
1602 assert_ne!(hash1, hash2);
1603 assert_eq!(hash1, hash3); assert_ne!(hash1, 0);
1605 }
1606
1607 #[test]
1608 fn test_timestamp_to_duration() {
1609 assert_eq!(timestamp_to_duration(0), Duration::ZERO);
1610 assert_eq!(timestamp_to_duration(1000), Duration::from_millis(1000));
1611 assert_eq!(timestamp_to_duration(-100), Duration::ZERO);
1612 }
1613
1614 #[test]
1615 fn test_timestamp_age() {
1616 let now = current_timestamp_ms();
1617 let old_timestamp = now - 5000; let age = timestamp_age(old_timestamp);
1620 assert!(age.as_millis() >= 5000);
1621 assert!(age.as_millis() < 6000); }
1623
1624 #[test]
1625 fn test_is_timestamp_recent() {
1626 let now = current_timestamp_ms();
1627 let recent = now - 1000; let old = now - 10000; assert!(is_timestamp_recent(recent, 5000));
1631 assert!(!is_timestamp_recent(old, 5000));
1632 assert!(is_timestamp_recent(now, 1000));
1633 }
1634
1635 #[test]
1636 fn test_timestamp_to_systemtime() {
1637 let timestamp_ms = 1609459200000i64; let system_time = timestamp_to_systemtime(timestamp_ms);
1639
1640 let duration = system_time.duration_since(UNIX_EPOCH).unwrap();
1641 assert_eq!(duration.as_millis(), timestamp_ms as u128);
1642
1643 let zero_time = timestamp_to_systemtime(0);
1644 assert_eq!(zero_time, UNIX_EPOCH);
1645
1646 let negative_time = timestamp_to_systemtime(-1000);
1647 assert_eq!(negative_time, UNIX_EPOCH);
1648 }
1649
1650 #[test]
1653 fn test_additional_unit_conversions() {
1654 assert_eq!(mb_to_kb(1), 1024);
1655 assert_eq!(mb_to_kb(10), 10240);
1656
1657 assert_eq!(gb_to_mb(1), 1024);
1658 assert_eq!(gb_to_mb(5), 5120);
1659
1660 assert_eq!(tb_to_gb(1), 1024);
1661 assert_eq!(tb_to_gb(2), 2048);
1662 }
1663
1664 #[test]
1665 fn test_round_to_multiple() {
1666 assert_eq!(round_up_to_multiple(10, 5), 10);
1668 assert_eq!(round_up_to_multiple(11, 5), 15);
1669 assert_eq!(round_up_to_multiple(14, 5), 15);
1670 assert_eq!(round_up_to_multiple(15, 5), 15);
1671 assert_eq!(round_up_to_multiple(0, 5), 0);
1672 assert_eq!(round_up_to_multiple(100, 0), 100); assert_eq!(round_down_to_multiple(10, 5), 10);
1676 assert_eq!(round_down_to_multiple(11, 5), 10);
1677 assert_eq!(round_down_to_multiple(14, 5), 10);
1678 assert_eq!(round_down_to_multiple(15, 5), 15);
1679 assert_eq!(round_down_to_multiple(0, 5), 0);
1680 assert_eq!(round_down_to_multiple(100, 0), 100); }
1682
1683 #[test]
1684 fn test_calculate_percentage_rounded() {
1685 assert_eq!(calculate_percentage_rounded(50, 100), 50);
1686 assert_eq!(calculate_percentage_rounded(1, 3), 33);
1687 assert_eq!(calculate_percentage_rounded(2, 3), 67);
1688 assert_eq!(calculate_percentage_rounded(0, 100), 0);
1689 assert_eq!(calculate_percentage_rounded(100, 0), 0); assert_eq!(calculate_percentage_rounded(100, 100), 100);
1691 assert_eq!(calculate_percentage_rounded(150, 100), 100); }
1693
1694 #[test]
1695 fn test_is_in_range() {
1696 assert!(is_in_range(5, 0, 10));
1697 assert!(is_in_range(0, 0, 10));
1698 assert!(is_in_range(10, 0, 10));
1699 assert!(!is_in_range(11, 0, 10));
1700 assert!(!is_in_range(15, 0, 10));
1701 }
1702
1703 #[test]
1704 fn test_average_u64() {
1705 assert_eq!(average_u64(10, 20), 15);
1706 assert_eq!(average_u64(0, 0), 0);
1707 assert_eq!(average_u64(100, 100), 100);
1708 assert_eq!(average_u64(1, 2), 1); assert_eq!(average_u64(u64::MAX, u64::MAX), u64::MAX);
1710 assert_eq!(average_u64(u64::MAX - 1, u64::MAX), u64::MAX - 1);
1711 }
1712
1713 #[test]
1714 fn test_larger_smaller_of() {
1715 assert_eq!(larger_of(10, 20), 20);
1716 assert_eq!(larger_of(20, 10), 20);
1717 assert_eq!(larger_of(15, 15), 15);
1718
1719 assert_eq!(smaller_of(10, 20), 10);
1720 assert_eq!(smaller_of(20, 10), 10);
1721 assert_eq!(smaller_of(15, 15), 15);
1722 }
1723
1724 #[test]
1725 fn test_is_valid_identifier() {
1726 assert!(is_valid_identifier("valid_id"));
1727 assert!(is_valid_identifier("valid-id"));
1728 assert!(is_valid_identifier("ValidId123"));
1729 assert!(is_valid_identifier("a"));
1730
1731 assert!(!is_valid_identifier(""));
1732 assert!(!is_valid_identifier("invalid id")); assert!(!is_valid_identifier("invalid@id")); assert!(!is_valid_identifier(&"x".repeat(257))); }
1736
1737 #[test]
1738 fn test_truncate_with_ellipsis() {
1739 assert_eq!(truncate_with_ellipsis("hello", 10), "hello");
1740 assert_eq!(truncate_with_ellipsis("hello world", 8), "hello...");
1741 assert_eq!(truncate_with_ellipsis("hello", 5), "hello");
1742 assert_eq!(truncate_with_ellipsis("hello", 4), "h...");
1743 assert_eq!(truncate_with_ellipsis("hello", 3), "hel"); assert_eq!(truncate_with_ellipsis("hello", 2), "he");
1745 assert_eq!(truncate_with_ellipsis("hello", 1), "h");
1746 }
1747
1748 #[test]
1749 fn test_safe_slice() {
1750 let s = "hello world";
1751 assert_eq!(safe_slice(s, 0, 5), "hello");
1752 assert_eq!(safe_slice(s, 6, 11), "world");
1753 assert_eq!(safe_slice(s, 0, 100), "hello world"); assert_eq!(safe_slice(s, 100, 200), ""); assert_eq!(safe_slice(s, 5, 3), ""); }
1757
1758 #[test]
1759 fn test_format_number_with_commas() {
1760 assert_eq!(format_number_with_commas(0), "0");
1761 assert_eq!(format_number_with_commas(123), "123");
1762 assert_eq!(format_number_with_commas(1234), "1,234");
1763 assert_eq!(format_number_with_commas(1234567), "1,234,567");
1764 assert_eq!(format_number_with_commas(1234567890), "1,234,567,890");
1765 }
1766
1767 #[test]
1768 fn test_format_duration_compact() {
1769 assert_eq!(format_duration_compact(Duration::from_secs(30)), "30s");
1770 assert_eq!(format_duration_compact(Duration::from_secs(60)), "1m");
1771 assert_eq!(format_duration_compact(Duration::from_secs(90)), "1m30s");
1772 assert_eq!(format_duration_compact(Duration::from_secs(3600)), "1h");
1773 assert_eq!(format_duration_compact(Duration::from_secs(3660)), "1h1m");
1774 assert_eq!(format_duration_compact(Duration::from_secs(7200)), "2h");
1775 assert_eq!(format_duration_compact(Duration::from_secs(7380)), "2h3m");
1776 }
1777}