fast_pull/base/
progress.rs1use core::ops::Range;
4
5pub type ProgressEntry = Range<u64>;
9
10pub trait Total {
12 fn total(&self) -> u64;
14}
15
16impl Total for ProgressEntry {
17 #[allow(clippy::inline_always)]
18 #[inline(always)]
19 fn total(&self) -> u64 {
20 self.end.saturating_sub(self.start)
21 }
22}
23
24impl Total for Vec<ProgressEntry> {
34 fn total(&self) -> u64 {
35 self.iter()
36 .fold(0, |total, entry| total.saturating_add(entry.total()))
37 }
38}
39
40#[cfg(test)]
41mod tests {
42 use super::*;
43
44 #[test]
45 fn progress_entry_total() {
46 assert_eq!((0..0).total(), 0);
47 assert_eq!((5..10).total(), 5);
48 assert_eq!((3..3).total(), 0);
49 let reversed = core::ops::Range { start: 10, end: 5 };
51 assert_eq!(reversed.total(), 0);
52 }
53
54 #[test]
55 fn vec_progress_total() {
56 #![allow(clippy::single_range_in_vec_init)]
57 let v: Vec<ProgressEntry> = vec![1..5, 8..10, 12..15];
58 assert_eq!(v.total(), 9);
59 let empty: Vec<ProgressEntry> = vec![];
60 assert_eq!(empty.total(), 0);
61 }
62
63 #[test]
64 fn progress_entry_total_reversed_ranges_are_safe() {
65 assert_eq!((core::ops::Range { start: 10, end: 5 }).total(), 0);
67 assert_eq!(
68 (core::ops::Range {
69 start: u64::MAX,
70 end: 0
71 })
72 .total(),
73 0
74 );
75 assert_eq!(
76 (core::ops::Range {
77 start: 100,
78 end: 99
79 })
80 .total(),
81 0
82 );
83 assert_eq!((core::ops::Range { start: 1, end: 0 }).total(), 0);
84 }
85
86 #[test]
87 fn progress_entry_total_extreme_and_degenerate() {
88 assert_eq!((0..u64::MAX).total(), u64::MAX);
90 assert_eq!((u64::MAX..u64::MAX).total(), 0);
91 assert_eq!((42..42).total(), 0);
92 assert_eq!((0..0).total(), 0);
93 }
94
95 #[test]
96 fn vec_progress_total_counts_overlap_twice() {
97 #![allow(clippy::single_range_in_vec_init)]
98 let v: Vec<ProgressEntry> = vec![0..5, 3..10];
101 assert_eq!(v.total(), 12);
103 }
104
105 #[test]
106 fn vec_progress_total_overflow_saturates() {
107 let v: Vec<ProgressEntry> = vec![0..u64::MAX, 0..1];
108 assert_eq!(v.total(), u64::MAX);
109 }
110}