2117 assert_eq!( |
2122 assert_eq!( |
2118 matcher.visit_children_set(HgPath::new(b"dir/subdir/x")), |
2123 matcher.visit_children_set(HgPath::new(b"dir/subdir/x")), |
2119 VisitChildrenSet::This |
2124 VisitChildrenSet::This |
2120 ); |
2125 ); |
2121 } |
2126 } |
2122 } |
2127 |
|
2128 mod invariants { |
|
2129 pub mod visit_children_set { |
|
2130 |
|
2131 use crate::{ |
|
2132 matchers::{tests::Tree, Matcher, VisitChildrenSet}, |
|
2133 utils::hg_path::HgPath, |
|
2134 }; |
|
2135 |
|
2136 #[allow(dead_code)] |
|
2137 #[derive(Debug)] |
|
2138 struct Error<'a, M> { |
|
2139 matcher: &'a M, |
|
2140 path: &'a HgPath, |
|
2141 matching: &'a Tree, |
|
2142 visit_children_set: &'a VisitChildrenSet, |
|
2143 } |
|
2144 |
|
2145 fn holds(matching: &Tree, vcs: &VisitChildrenSet) -> bool { |
|
2146 match vcs { |
|
2147 VisitChildrenSet::Empty => matching.is_empty(), |
|
2148 VisitChildrenSet::This => { |
|
2149 // `This` does not come with any obligations. |
|
2150 true |
|
2151 } |
|
2152 VisitChildrenSet::Recursive => { |
|
2153 // `Recursive` does not come with any correctness |
|
2154 // obligations. |
|
2155 // It instructs the caller to stop calling |
|
2156 // `visit_children_set` for all |
|
2157 // descendants, so may have negative performance |
|
2158 // implications, but we're not testing against that |
|
2159 // here. |
|
2160 true |
|
2161 } |
|
2162 VisitChildrenSet::Set(allowed_children) => { |
|
2163 // `allowed_children` does not distinguish between |
|
2164 // files and directories: if it's not included, it |
|
2165 // must not be matched. |
|
2166 for k in matching.dirs.keys() { |
|
2167 if !(allowed_children.contains(k)) { |
|
2168 return false; |
|
2169 } |
|
2170 } |
|
2171 for k in matching.files.iter() { |
|
2172 if !(allowed_children.contains(k)) { |
|
2173 return false; |
|
2174 } |
|
2175 } |
|
2176 true |
|
2177 } |
|
2178 } |
|
2179 } |
|
2180 |
|
2181 pub fn check<M: Matcher + std::fmt::Debug>( |
|
2182 matcher: &M, |
|
2183 path: &HgPath, |
|
2184 matching: &Tree, |
|
2185 visit_children_set: &VisitChildrenSet, |
|
2186 ) { |
|
2187 if !holds(matching, visit_children_set) { |
|
2188 panic!( |
|
2189 "{:#?}", |
|
2190 Error { |
|
2191 matcher, |
|
2192 path, |
|
2193 visit_children_set, |
|
2194 matching |
|
2195 } |
|
2196 ) |
|
2197 } |
|
2198 } |
|
2199 } |
|
2200 } |
|
2201 |
|
2202 #[derive(Debug, Clone)] |
|
2203 pub struct Tree { |
|
2204 files: BTreeSet<HgPathBuf>, |
|
2205 dirs: BTreeMap<HgPathBuf, Tree>, |
|
2206 } |
|
2207 |
|
2208 impl Tree { |
|
2209 fn len(&self) -> usize { |
|
2210 let mut n = 0; |
|
2211 n += self.files.len(); |
|
2212 for d in self.dirs.values() { |
|
2213 n += d.len(); |
|
2214 } |
|
2215 n |
|
2216 } |
|
2217 |
|
2218 fn is_empty(&self) -> bool { |
|
2219 self.files.is_empty() && self.dirs.is_empty() |
|
2220 } |
|
2221 |
|
2222 fn filter_and_check<M: Matcher + Debug>( |
|
2223 &self, |
|
2224 m: &M, |
|
2225 path: &HgPath, |
|
2226 ) -> Self { |
|
2227 let files: BTreeSet<HgPathBuf> = self |
|
2228 .files |
|
2229 .iter() |
|
2230 .filter(|v| m.matches(&path.join(v))) |
|
2231 .map(|f| f.to_owned()) |
|
2232 .collect(); |
|
2233 let dirs: BTreeMap<HgPathBuf, Tree> = self |
|
2234 .dirs |
|
2235 .iter() |
|
2236 .filter_map(|(k, v)| { |
|
2237 let path = path.join(k); |
|
2238 let v = v.filter_and_check(m, &path); |
|
2239 if v.is_empty() { |
|
2240 None |
|
2241 } else { |
|
2242 Some((k.to_owned(), v)) |
|
2243 } |
|
2244 }) |
|
2245 .collect(); |
|
2246 let matching = Self { files, dirs }; |
|
2247 let vcs = m.visit_children_set(path); |
|
2248 invariants::visit_children_set::check(m, path, &matching, &vcs); |
|
2249 matching |
|
2250 } |
|
2251 |
|
2252 fn check_matcher<M: Matcher + Debug>( |
|
2253 &self, |
|
2254 m: &M, |
|
2255 expect_count: usize, |
|
2256 ) { |
|
2257 let res = self.filter_and_check(m, &HgPathBuf::new()); |
|
2258 if expect_count != res.len() { |
|
2259 eprintln!( |
|
2260 "warning: expected {} matches, got {} for {:#?}", |
|
2261 expect_count, |
|
2262 res.len(), |
|
2263 m |
|
2264 ); |
|
2265 } |
|
2266 } |
|
2267 } |
|
2268 |
|
2269 fn mkdir(children: &[(&[u8], &Tree)]) -> Tree { |
|
2270 let p = HgPathBuf::from_bytes; |
|
2271 let names = [ |
|
2272 p(b"a"), |
|
2273 p(b"b.txt"), |
|
2274 p(b"file.txt"), |
|
2275 p(b"c.c"), |
|
2276 p(b"c.h"), |
|
2277 p(b"dir1"), |
|
2278 p(b"dir2"), |
|
2279 p(b"subdir"), |
|
2280 ]; |
|
2281 let files: BTreeSet<HgPathBuf> = BTreeSet::from(names); |
|
2282 let dirs = children |
|
2283 .iter() |
|
2284 .map(|(name, t)| (p(name), (*t).clone())) |
|
2285 .collect(); |
|
2286 Tree { files, dirs } |
|
2287 } |
|
2288 |
|
2289 fn make_example_tree() -> Tree { |
|
2290 let leaf = mkdir(&[]); |
|
2291 let abc = mkdir(&[(b"d", &leaf)]); |
|
2292 let ab = mkdir(&[(b"c", &abc)]); |
|
2293 let a = mkdir(&[(b"b", &ab)]); |
|
2294 let dir = mkdir(&[(b"subdir", &leaf), (b"subdir.c", &leaf)]); |
|
2295 mkdir(&[(b"dir", &dir), (b"dir1", &dir), (b"dir2", &dir), (b"a", &a)]) |
|
2296 } |
|
2297 |
|
2298 #[test] |
|
2299 fn test_pattern_matcher_visit_children_set() { |
|
2300 let tree = make_example_tree(); |
|
2301 let _pattern_dir1_glob_c = |
|
2302 PatternMatcher::new(vec![IgnorePattern::new( |
|
2303 PatternSyntax::Glob, |
|
2304 b"dir1/*.c", |
|
2305 Path::new(""), |
|
2306 )]) |
|
2307 .unwrap(); |
|
2308 let pattern_dir1 = || { |
|
2309 PatternMatcher::new(vec![IgnorePattern::new( |
|
2310 PatternSyntax::Path, |
|
2311 b"dir1", |
|
2312 Path::new(""), |
|
2313 )]) |
|
2314 .unwrap() |
|
2315 }; |
|
2316 let pattern_dir1_a = PatternMatcher::new(vec![IgnorePattern::new( |
|
2317 PatternSyntax::Glob, |
|
2318 b"dir1/a", |
|
2319 Path::new(""), |
|
2320 )]) |
|
2321 .unwrap(); |
|
2322 let pattern_relglob_c = || { |
|
2323 PatternMatcher::new(vec![IgnorePattern::new( |
|
2324 PatternSyntax::RelGlob, |
|
2325 b"*.c", |
|
2326 Path::new(""), |
|
2327 )]) |
|
2328 .unwrap() |
|
2329 }; |
|
2330 // // TODO: re-enable this test when the corresponding bug is |
|
2331 // fixed if false { |
|
2332 // tree.check_matcher(&pattern_dir1_glob_c); |
|
2333 // } |
|
2334 let files = vec![HgPathBuf::from_bytes(b"dir/subdir/b.txt")]; |
|
2335 let file_dir_subdir_b = FileMatcher::new(files).unwrap(); |
|
2336 |
|
2337 let files = vec![ |
|
2338 HgPathBuf::from_bytes(b"file.txt"), |
|
2339 HgPathBuf::from_bytes(b"a/file.txt"), |
|
2340 HgPathBuf::from_bytes(b"a/b/file.txt"), |
|
2341 // No file in a/b/c |
|
2342 HgPathBuf::from_bytes(b"a/b/c/d/file.txt"), |
|
2343 ]; |
|
2344 let file_abcdfile = FileMatcher::new(files).unwrap(); |
|
2345 let _rootfilesin_dir = PatternMatcher::new(vec![IgnorePattern::new( |
|
2346 PatternSyntax::RootFiles, |
|
2347 b"dir", |
|
2348 Path::new(""), |
|
2349 )]) |
|
2350 .unwrap(); |
|
2351 |
|
2352 let pattern_filepath_dir_subdir = |
|
2353 PatternMatcher::new(vec![IgnorePattern::new( |
|
2354 PatternSyntax::FilePath, |
|
2355 b"dir/subdir", |
|
2356 Path::new(""), |
|
2357 )]) |
|
2358 .unwrap(); |
|
2359 |
|
2360 let include_dir_subdir = |
|
2361 IncludeMatcher::new(vec![IgnorePattern::new( |
|
2362 PatternSyntax::RelPath, |
|
2363 b"dir/subdir", |
|
2364 Path::new(""), |
|
2365 )]) |
|
2366 .unwrap(); |
|
2367 |
|
2368 let more_includematchers = [ |
|
2369 IncludeMatcher::new(vec![IgnorePattern::new( |
|
2370 PatternSyntax::Glob, |
|
2371 b"dir/s*", |
|
2372 Path::new(""), |
|
2373 )]) |
|
2374 .unwrap(), |
|
2375 // Test multiple patterns |
|
2376 IncludeMatcher::new(vec![ |
|
2377 IgnorePattern::new( |
|
2378 PatternSyntax::RelPath, |
|
2379 b"dir", |
|
2380 Path::new(""), |
|
2381 ), |
|
2382 IgnorePattern::new(PatternSyntax::Glob, b"s*", Path::new("")), |
|
2383 ]) |
|
2384 .unwrap(), |
|
2385 // Test multiple patterns |
|
2386 IncludeMatcher::new(vec![IgnorePattern::new( |
|
2387 PatternSyntax::Glob, |
|
2388 b"**/*.c", |
|
2389 Path::new(""), |
|
2390 )]) |
|
2391 .unwrap(), |
|
2392 ]; |
|
2393 |
|
2394 tree.check_matcher(&pattern_dir1(), 25); |
|
2395 tree.check_matcher(&pattern_dir1_a, 1); |
|
2396 tree.check_matcher(&pattern_relglob_c(), 14); |
|
2397 tree.check_matcher(&AlwaysMatcher, 112); |
|
2398 tree.check_matcher(&NeverMatcher, 0); |
|
2399 tree.check_matcher( |
|
2400 &IntersectionMatcher::new( |
|
2401 Box::new(pattern_relglob_c()), |
|
2402 Box::new(pattern_dir1()), |
|
2403 ), |
|
2404 3, |
|
2405 ); |
|
2406 tree.check_matcher( |
|
2407 &UnionMatcher::new(vec![ |
|
2408 Box::new(pattern_relglob_c()), |
|
2409 Box::new(pattern_dir1()), |
|
2410 ]), |
|
2411 36, |
|
2412 ); |
|
2413 tree.check_matcher( |
|
2414 &DifferenceMatcher::new( |
|
2415 Box::new(pattern_relglob_c()), |
|
2416 Box::new(pattern_dir1()), |
|
2417 ), |
|
2418 11, |
|
2419 ); |
|
2420 tree.check_matcher(&file_dir_subdir_b, 1); |
|
2421 tree.check_matcher(&file_abcdfile, 4); |
|
2422 // // TODO: re-enable this test when the corresponding bug is |
|
2423 // fixed |
|
2424 // |
|
2425 // if false { |
|
2426 // tree.check_matcher(&rootfilesin_dir, 6); |
|
2427 // } |
|
2428 tree.check_matcher(&pattern_filepath_dir_subdir, 1); |
|
2429 tree.check_matcher(&include_dir_subdir, 9); |
|
2430 tree.check_matcher(&more_includematchers[0], 17); |
|
2431 tree.check_matcher(&more_includematchers[1], 25); |
|
2432 tree.check_matcher(&more_includematchers[2], 35); |
|
2433 } |
|
2434 } |