1use std::path::Path;
15
16use fallow_api::DuplicationGrouping;
17use fallow_types::duplicates::{CloneGroup, DuplicationReport};
18
19use super::grouping::OwnershipResolver;
20use super::relative_path;
21
22pub fn largest_owner(group: &CloneGroup, root: &Path, resolver: &OwnershipResolver) -> String {
29 fallow_api::largest_clone_group_owner_with(group, |path| {
30 resolver.resolve(relative_path(path, root))
31 })
32}
33
34pub fn build_duplication_grouping(
39 report: &DuplicationReport,
40 root: &Path,
41 resolver: &OwnershipResolver,
42) -> DuplicationGrouping {
43 fallow_api::build_duplication_grouping_with(report, resolver.mode_label(), |path| {
44 resolver.resolve(relative_path(path, root))
45 })
46}
47
48#[cfg(test)]
49mod tests {
50 use std::path::PathBuf;
51
52 use fallow_types::duplicates::{CloneInstance, DuplicationStats};
53
54 use super::*;
55 use crate::codeowners::{CodeOwners, UNOWNED_LABEL};
56
57 fn instance(path: &str, start: usize, end: usize) -> CloneInstance {
58 CloneInstance {
59 file: PathBuf::from(path),
60 start_line: start,
61 end_line: end,
62 start_col: 0,
63 end_col: 0,
64 fragment: String::new(),
65 }
66 }
67
68 fn group(instances: Vec<CloneInstance>) -> CloneGroup {
69 CloneGroup {
70 instances,
71 token_count: 50,
72 line_count: 10,
73 }
74 }
75
76 fn report(groups: Vec<CloneGroup>) -> DuplicationReport {
77 DuplicationReport {
78 clone_groups: groups,
79 clone_families: vec![],
80 mirrored_directories: vec![],
81 stats: DuplicationStats {
82 total_files: 10,
83 total_lines: 1000,
84 ..Default::default()
85 },
86 }
87 }
88
89 #[test]
90 fn largest_owner_majority_wins() {
91 let r = group(vec![
92 instance("/root/src/a.ts", 1, 10),
93 instance("/root/src/b.ts", 1, 10),
94 instance("/root/lib/c.ts", 1, 10),
95 ]);
96 let key = largest_owner(&r, Path::new("/root"), &OwnershipResolver::Directory);
97 assert_eq!(key, "src", "src has 2 instances vs lib's 1");
98 }
99
100 #[test]
101 fn largest_owner_alphabetical_tiebreak() {
102 let r = group(vec![
103 instance("/root/src/a.ts", 1, 10),
104 instance("/root/lib/b.ts", 1, 10),
105 ]);
106 let key = largest_owner(&r, Path::new("/root"), &OwnershipResolver::Directory);
107 assert_eq!(key, "lib");
108 }
109
110 #[test]
111 fn largest_owner_three_way_tie_alphabetical() {
112 let r = group(vec![
113 instance("/root/zeta/a.ts", 1, 10),
114 instance("/root/alpha/b.ts", 1, 10),
115 instance("/root/beta/c.ts", 1, 10),
116 ]);
117 let key = largest_owner(&r, Path::new("/root"), &OwnershipResolver::Directory);
118 assert_eq!(key, "alpha");
119 }
120
121 #[test]
122 fn build_grouping_partitions_clone_groups() {
123 let g1 = group(vec![
124 instance("/root/src/a.ts", 1, 10),
125 instance("/root/src/b.ts", 1, 10),
126 ]);
127 let g2 = group(vec![
128 instance("/root/lib/x.ts", 1, 10),
129 instance("/root/lib/y.ts", 1, 10),
130 ]);
131 let r = report(vec![g1, g2]);
132 let grouping =
133 build_duplication_grouping(&r, Path::new("/root"), &OwnershipResolver::Directory);
134 assert_eq!(grouping.groups.len(), 2);
135 let lib = grouping.groups.iter().find(|g| g.key == "lib").unwrap();
136 let src = grouping.groups.iter().find(|g| g.key == "src").unwrap();
137 assert_eq!(lib.clone_groups.len(), 1);
138 assert_eq!(src.clone_groups.len(), 1);
139 }
140
141 #[test]
142 fn build_grouping_unowned_pinned_last() {
143 let co = CodeOwners::parse("/src/ @frontend\n").unwrap();
144 let resolver = OwnershipResolver::Owner(co);
145 let g_src = group(vec![
146 instance("/root/src/a.ts", 1, 10),
147 instance("/root/src/b.ts", 1, 10),
148 ]);
149 let g_docs = group(vec![
150 instance("/root/docs/a.md", 1, 10),
151 instance("/root/docs/b.md", 1, 10),
152 ]);
153 let r = report(vec![g_src, g_docs]);
154 let grouping = build_duplication_grouping(&r, Path::new("/root"), &resolver);
155 assert_eq!(grouping.groups.len(), 2);
156 assert_eq!(grouping.groups.last().unwrap().key, UNOWNED_LABEL);
157 }
158
159 #[test]
160 fn build_grouping_per_instance_owner_inline() {
161 let g = group(vec![
162 instance("/root/src/a.ts", 1, 10),
163 instance("/root/src/b.ts", 1, 10),
164 instance("/root/lib/c.ts", 1, 10),
165 ]);
166 let r = report(vec![g]);
167 let grouping =
168 build_duplication_grouping(&r, Path::new("/root"), &OwnershipResolver::Directory);
169 assert_eq!(grouping.groups.len(), 1);
170 let bucket = &grouping.groups[0];
171 assert_eq!(bucket.key, "src");
172 assert_eq!(bucket.clone_groups.len(), 1);
173 let finding = &bucket.clone_groups[0];
174 let cg = &finding.group;
175 assert_eq!(cg.primary_owner, "src");
176 assert_eq!(cg.instances.len(), 3);
177 let owners: Vec<&str> = cg.instances.iter().map(|i| i.owner.as_str()).collect();
178 assert!(owners.contains(&"src"));
179 assert!(owners.contains(&"lib"));
180 assert_eq!(finding.actions.len(), 2);
181 }
182
183 #[test]
184 fn empty_report_produces_empty_grouping() {
185 let r = DuplicationReport::default();
186 let grouping =
187 build_duplication_grouping(&r, Path::new("/root"), &OwnershipResolver::Directory);
188 assert!(grouping.groups.is_empty());
189 }
190}