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