pacenotes_installer\engine/
overlap.rs1#[derive(Clone, Debug, PartialEq, Eq)]
4pub struct Overlap {
5 pub path: String,
7 pub packages: Vec<String>,
9}
10
11impl Overlap {
12 pub fn refs(&self) -> Vec<String> {
16 self.packages
17 .iter()
18 .filter_map(|key| {
19 let (_, directory, name) = pacenotes_shared::split_download_path(key)?;
20 Some(format!("{directory}/{name}"))
21 })
22 .collect()
23 }
24}
25
26pub fn overlaps(
29 install_order: &[String],
30 files: &std::collections::BTreeMap<String, Vec<String>>,
31) -> Vec<Overlap> {
32 let mut writers: std::collections::BTreeMap<String, (String, Vec<String>)> =
33 std::collections::BTreeMap::new();
34 for package in install_order {
35 for path in files.get(package).into_iter().flatten() {
36 let (_, packages) = writers
37 .entry(crate::ledger::fold(path))
38 .or_insert_with(|| (path.clone(), Vec::new()));
39 packages.push(package.clone());
40 }
41 }
42 writers
43 .into_values()
44 .filter(|(_, packages)| packages.len() > 1)
45 .map(|(path, packages)| Overlap { path, packages })
46 .collect()
47}
48
49#[cfg(test)]
50mod tests {
51 const KAMIL: &str = "rbr/public/codriver/polish/male/Kamil";
52 const POLISH: &str = "rbr/public/language/polish";
53
54 fn files_of(entries: &[(&str, &[&str])]) -> std::collections::BTreeMap<String, Vec<String>> {
55 entries
56 .iter()
57 .map(|(package, paths)| {
58 (
59 package.to_string(),
60 paths.iter().map(|path| path.to_string()).collect(),
61 )
62 })
63 .collect()
64 }
65
66 fn install_order() -> [String; 2] {
67 [POLISH.to_string(), KAMIL.to_string()]
68 }
69
70 #[test]
71 fn overlapping_writers_are_reported_in_install_order() {
72 let files = files_of(&[
73 (
74 POLISH,
75 &[
76 "Plugins/Pacenote/PaceNote.ini",
77 "Plugins/Pacenote/language/polish/strings.ini",
78 ],
79 ),
80 (
81 KAMIL,
82 &[
83 "Plugins/Pacenote/PaceNote.ini",
84 "Plugins/Pacenote/sounds/polish/male/Kamil/1-1.ogg",
85 ],
86 ),
87 ]);
88 let found = super::overlaps(&install_order(), &files);
89 assert_eq!(found.len(), 1);
90 assert_eq!(found[0].path, "Plugins/Pacenote/PaceNote.ini");
91 assert_eq!(found[0].packages, install_order().to_vec());
92 }
93
94 #[test]
95 fn an_overlap_is_found_across_spellings() {
96 let files = files_of(&[
97 (POLISH, &["Plugins/Pacenote/X.ini"]),
98 (KAMIL, &["plugins/pacenote/x.INI"]),
99 ]);
100 assert_eq!(super::overlaps(&install_order(), &files).len(), 1);
101 }
102
103 #[test]
104 fn packages_that_share_nothing_report_nothing() {
105 let files = files_of(&[(POLISH, &["Plugins/a.ini"]), (KAMIL, &["Plugins/b.ini"])]);
106 assert!(super::overlaps(&install_order(), &files).is_empty());
107 }
108
109 fn overlap_of(packages: &[&str]) -> super::Overlap {
110 super::Overlap {
111 path: "Plugins/Pacenote/PaceNote.ini".into(),
112 packages: packages.iter().map(|key| key.to_string()).collect(),
113 }
114 }
115
116 #[test]
117 fn an_overlap_names_its_writers_the_way_a_report_does() {
118 let refs = overlap_of(&[POLISH, KAMIL]).refs();
119 assert_eq!(
120 refs,
121 vec![
122 "public/language/polish",
123 "public/codriver/polish/male/Kamil"
124 ]
125 );
126 for reference in refs {
127 assert!(
128 pacenotes_shared::PackageRef::parse(&reference).is_some(),
129 "{reference}"
130 );
131 }
132 }
133
134 #[test]
135 fn an_unsplittable_key_is_left_out_of_the_refs() {
136 assert_eq!(
137 overlap_of(&["nonsense", KAMIL]).refs(),
138 vec!["public/codriver/polish/male/Kamil"]
139 );
140 }
141}