1use std::cmp::Reverse;
2
3use crate::model::{SortBy, Stat};
4
5pub fn sort_stats(stats: &mut [Stat], sort: SortBy, reverse: bool) {
9 match sort {
10 SortBy::Author => stats.sort_by(|a, b| b.author.cmp(&a.author)),
11 SortBy::Commits => stats.sort_by_key(|s| Reverse(s.commits)),
12 SortBy::Files => stats.sort_by_key(|s| Reverse(s.num_files)),
13 SortBy::Insertions => stats.sort_by_key(|s| Reverse(s.insertions)),
14 SortBy::Deletions => stats.sort_by_key(|s| Reverse(s.deletions)),
15 SortBy::Net => stats.sort_by_key(|s| Reverse(s.net)),
16 }
17 if reverse {
18 stats.reverse();
19 }
20}
21
22#[cfg(test)]
23mod tests {
24 use super::*;
25 use hegel::generators::{self, Generator};
26
27 #[hegel::composite]
28 fn stat_list(tc: hegel::TestCase) -> Vec<Stat> {
29 let n = tc.draw(generators::integers::<usize>().max_value(100));
30 let mut stats = Vec::with_capacity(n);
31 for _ in 0..n {
32 let who = tc.draw(generators::integers::<u8>().max_value(8));
33 stats.push(Stat {
34 author: format!("Author {who}"),
36 commits: u64::from(tc.draw(generators::integers::<u16>())),
37 num_files: u64::from(tc.draw(generators::integers::<u16>())),
38 insertions: u64::from(tc.draw(generators::integers::<u16>())),
39 deletions: u64::from(tc.draw(generators::integers::<u16>())),
40 net: i64::from(tc.draw(generators::integers::<i16>())),
41 });
42 }
43 stats
44 }
45
46 fn any_sort_by() -> impl Generator<SortBy> {
47 generators::sampled_from(vec![
48 SortBy::Author,
49 SortBy::Commits,
50 SortBy::Files,
51 SortBy::Insertions,
52 SortBy::Deletions,
53 SortBy::Net,
54 ])
55 }
56
57 fn canonical(stats: &[Stat]) -> Vec<(String, u64, u64, u64, u64, i64)> {
58 let mut key: Vec<_> = stats
59 .iter()
60 .map(|s| {
61 (
62 s.author.clone(),
63 s.commits,
64 s.num_files,
65 s.insertions,
66 s.deletions,
67 s.net,
68 )
69 })
70 .collect();
71 key.sort();
72 key
73 }
74
75 #[hegel::test]
76 fn sort_preserves_the_multiset(tc: hegel::TestCase) {
77 let original = tc.draw(stat_list());
78 let by = tc.draw(any_sort_by());
79 let reverse = tc.draw(generators::booleans());
80 let mut sorted = original.clone();
81 sort_stats(&mut sorted, by, reverse);
82 assert_eq!(canonical(&original), canonical(&sorted));
83 }
84
85 #[hegel::test]
87 fn default_order_is_descending(tc: hegel::TestCase) {
88 let mut stats = tc.draw(stat_list());
89 let by = tc.draw(any_sort_by());
90 sort_stats(&mut stats, by, false);
91 for w in stats.windows(2) {
92 let (a, b) = (&w[0], &w[1]);
93 let descending = match by {
94 SortBy::Author => a.author >= b.author,
95 SortBy::Commits => a.commits >= b.commits,
96 SortBy::Files => a.num_files >= b.num_files,
97 SortBy::Insertions => a.insertions >= b.insertions,
98 SortBy::Deletions => a.deletions >= b.deletions,
99 SortBy::Net => a.net >= b.net,
100 };
101 assert!(descending, "not descending for {by:?}: {a:?} then {b:?}");
102 }
103 }
104}