|
1 |
| -use std::collections::BTreeSet; |
2 | 1 |
|
3 |
| -use bstr::{BStr, ByteSlice}; |
4 |
| -use git_glob::{pattern, pattern::Case}; |
5 |
| - |
6 |
| -#[derive(Debug, Ord, PartialOrd, Eq, PartialEq, Copy, Clone)] |
7 |
| -pub struct GitMatch<'a> { |
8 |
| - pattern: &'a BStr, |
9 |
| - value: &'a BStr, |
10 |
| - /// True if git could match `value` with `pattern` |
11 |
| - is_match: bool, |
12 |
| -} |
13 |
| - |
14 |
| -pub struct Baseline<'a> { |
15 |
| - inner: bstr::Lines<'a>, |
16 |
| -} |
17 |
| - |
18 |
| -impl<'a> Iterator for Baseline<'a> { |
19 |
| - type Item = GitMatch<'a>; |
20 |
| - |
21 |
| - fn next(&mut self) -> Option<Self::Item> { |
22 |
| - let mut tokens = self.inner.next()?.splitn(2, |b| *b == b' '); |
23 |
| - let pattern = tokens.next().expect("pattern").as_bstr(); |
24 |
| - let value = tokens.next().expect("value").as_bstr().trim_start().as_bstr(); |
25 |
| - |
26 |
| - let git_match = self.inner.next()?; |
27 |
| - let is_match = !git_match.starts_with(b"::\t"); |
28 |
| - Some(GitMatch { |
29 |
| - pattern, |
30 |
| - value, |
31 |
| - is_match, |
32 |
| - }) |
33 |
| - } |
34 |
| -} |
35 |
| - |
36 |
| -impl<'a> Baseline<'a> { |
37 |
| - fn new(input: &'a [u8]) -> Self { |
38 |
| - Baseline { |
39 |
| - inner: input.as_bstr().lines(), |
40 |
| - } |
41 |
| - } |
42 |
| -} |
43 |
| - |
44 |
| -#[test] |
45 |
| -fn compare_baseline_with_ours() { |
46 |
| - let dir = git_testtools::scripted_fixture_repo_read_only("make_baseline.sh").unwrap(); |
47 |
| - let (mut total_matches, mut total_correct, mut panics) = (0, 0, 0); |
48 |
| - let mut mismatches = Vec::new(); |
49 |
| - for (input_file, expected_matches, case) in &[ |
50 |
| - ("git-baseline.match", true, pattern::Case::Sensitive), |
51 |
| - ("git-baseline.nmatch", false, pattern::Case::Sensitive), |
52 |
| - ("git-baseline.match-icase", true, pattern::Case::Fold), |
53 |
| - ] { |
54 |
| - let input = std::fs::read(dir.join(*input_file)).unwrap(); |
55 |
| - let mut seen = BTreeSet::default(); |
56 |
| - |
57 |
| - for m @ GitMatch { |
58 |
| - pattern, |
59 |
| - value, |
60 |
| - is_match, |
61 |
| - } in Baseline::new(&input) |
62 |
| - { |
63 |
| - total_matches += 1; |
64 |
| - assert!(seen.insert(m), "duplicate match entry: {:?}", m); |
65 |
| - assert_eq!( |
66 |
| - is_match, *expected_matches, |
67 |
| - "baseline for matches must be {} - check baseline and git version: {:?}", |
68 |
| - expected_matches, m |
69 |
| - ); |
70 |
| - match std::panic::catch_unwind(|| { |
71 |
| - let pattern = pat(pattern); |
72 |
| - pattern.matches_repo_relative_path(value, basename_start_pos(value), None, *case) |
73 |
| - }) { |
74 |
| - Ok(actual_match) => { |
75 |
| - if actual_match == is_match { |
76 |
| - total_correct += 1; |
77 |
| - } else { |
78 |
| - mismatches.push((pattern.to_owned(), value.to_owned(), is_match, expected_matches)); |
79 |
| - } |
80 |
| - } |
81 |
| - Err(_) => { |
82 |
| - panics += 1; |
83 |
| - continue; |
84 |
| - } |
85 |
| - }; |
86 |
| - } |
87 |
| - } |
88 |
| - |
89 |
| - dbg!(mismatches); |
90 |
| - assert_eq!( |
91 |
| - total_correct, |
92 |
| - total_matches - panics, |
93 |
| - "We perfectly agree with git here" |
94 |
| - ); |
95 |
| - assert_eq!(panics, 0); |
96 |
| -} |
97 |
| - |
98 |
| -#[test] |
99 |
| -fn non_dirs_for_must_be_dir_patterns_are_ignored() { |
100 |
| - let pattern = pat("hello/"); |
101 |
| - |
102 |
| - assert!(pattern.mode.contains(pattern::Mode::MUST_BE_DIR)); |
103 |
| - assert_eq!( |
104 |
| - pattern.text, "hello", |
105 |
| - "a dir pattern doesn't actually end with the trailing slash" |
106 |
| - ); |
107 |
| - let path = "hello"; |
108 |
| - assert!( |
109 |
| - !pattern.matches_repo_relative_path(path, None, false.into() /* is-dir */, Case::Sensitive), |
110 |
| - "non-dirs never match a dir pattern" |
111 |
| - ); |
112 |
| - assert!( |
113 |
| - pattern.matches_repo_relative_path(path, None, true.into() /* is-dir */, Case::Sensitive), |
114 |
| - "dirs can match a dir pattern with the normal rules" |
115 |
| - ); |
116 |
| -} |
117 |
| - |
118 |
| -#[test] |
119 |
| -fn matches_of_absolute_paths_work() { |
120 |
| - let pattern = "/hello/git"; |
121 |
| - assert!( |
122 |
| - git_glob::wildmatch(pattern.into(), pattern.into(), git_glob::wildmatch::Mode::empty()), |
123 |
| - "patterns always match themselves" |
124 |
| - ); |
125 |
| - assert!( |
126 |
| - git_glob::wildmatch( |
127 |
| - pattern.into(), |
128 |
| - pattern.into(), |
129 |
| - git_glob::wildmatch::Mode::NO_MATCH_SLASH_LITERAL |
130 |
| - ), |
131 |
| - "patterns always match themselves, path mode doesn't change that" |
132 |
| - ); |
133 |
| -} |
134 |
| - |
135 |
| -#[test] |
136 |
| -fn basename_matches_from_end() { |
137 |
| - let pat = &pat("foo"); |
138 |
| - assert!(match_file(pat, "FoO", Case::Fold)); |
139 |
| - assert!(!match_file(pat, "FoOo", Case::Fold)); |
140 |
| - assert!(!match_file(pat, "Foo", Case::Sensitive)); |
141 |
| - assert!(match_file(pat, "foo", Case::Sensitive)); |
142 |
| - assert!(!match_file(pat, "Foo", Case::Sensitive)); |
143 |
| - assert!(!match_file(pat, "barfoo", Case::Sensitive)); |
144 |
| -} |
145 |
| - |
146 |
| -#[test] |
147 |
| -fn absolute_basename_matches_only_from_beginning() { |
148 |
| - let pat = &pat("/foo"); |
149 |
| - assert!(match_file(pat, "FoO", Case::Fold)); |
150 |
| - assert!(!match_file(pat, "bar/Foo", Case::Fold)); |
151 |
| - assert!(match_file(pat, "foo", Case::Sensitive)); |
152 |
| - assert!(!match_file(pat, "Foo", Case::Sensitive)); |
153 |
| - assert!(!match_file(pat, "bar/foo", Case::Sensitive)); |
154 |
| -} |
155 |
| - |
156 |
| -#[test] |
157 |
| -fn absolute_path_matches_only_from_beginning() { |
158 |
| - let pat = &pat("/bar/foo"); |
159 |
| - assert!(!match_file(pat, "FoO", Case::Fold)); |
160 |
| - assert!(match_file(pat, "bar/Foo", Case::Fold)); |
161 |
| - assert!(!match_file(pat, "foo", Case::Sensitive)); |
162 |
| - assert!(match_file(pat, "bar/foo", Case::Sensitive)); |
163 |
| - assert!(!match_file(pat, "bar/Foo", Case::Sensitive)); |
164 |
| -} |
165 |
| - |
166 |
| -#[test] |
167 |
| -fn absolute_path_with_recursive_glob_detects_mismatches_quickly() { |
168 |
| - let pat = &pat("/bar/foo/**"); |
169 |
| - assert!(!match_file(pat, "FoO", Case::Fold)); |
170 |
| - assert!(!match_file(pat, "bar/Fooo", Case::Fold)); |
171 |
| - assert!(!match_file(pat, "baz/bar/Foo", Case::Fold)); |
172 |
| -} |
173 |
| - |
174 |
| -#[test] |
175 |
| -fn absolute_path_with_recursive_glob_can_do_case_insensitive_prefix_search() { |
176 |
| - let pat = &pat("/bar/foo/**"); |
177 |
| - assert!(!match_file(pat, "bar/Foo/match", Case::Sensitive)); |
178 |
| - assert!(match_file(pat, "bar/Foo/match", Case::Fold)); |
179 |
| -} |
180 |
| - |
181 |
| -#[test] |
182 |
| -fn relative_path_does_not_match_from_end() { |
183 |
| - for pattern in &["bar/foo", "/bar/foo"] { |
184 |
| - let pattern = &pat(*pattern); |
185 |
| - assert!(!match_file(pattern, "FoO", Case::Fold)); |
186 |
| - assert!(match_file(pattern, "bar/Foo", Case::Fold)); |
187 |
| - assert!(!match_file(pattern, "baz/bar/Foo", Case::Fold)); |
188 |
| - assert!(!match_file(pattern, "foo", Case::Sensitive)); |
189 |
| - assert!(match_file(pattern, "bar/foo", Case::Sensitive)); |
190 |
| - assert!(!match_file(pattern, "baz/bar/foo", Case::Sensitive)); |
191 |
| - assert!(!match_file(pattern, "Baz/bar/Foo", Case::Sensitive)); |
192 |
| - } |
193 |
| -} |
194 |
| - |
195 |
| -#[test] |
196 |
| -fn basename_glob_and_literal_is_ends_with() { |
197 |
| - let pattern = &pat("*foo"); |
198 |
| - assert!(match_file(pattern, "FoO", Case::Fold)); |
199 |
| - assert!(match_file(pattern, "BarFoO", Case::Fold)); |
200 |
| - assert!(!match_file(pattern, "BarFoOo", Case::Fold)); |
201 |
| - assert!(!match_file(pattern, "Foo", Case::Sensitive)); |
202 |
| - assert!(!match_file(pattern, "BarFoo", Case::Sensitive)); |
203 |
| - assert!(match_file(pattern, "barfoo", Case::Sensitive)); |
204 |
| - assert!(!match_file(pattern, "barfooo", Case::Sensitive)); |
205 |
| - |
206 |
| - assert!(match_file(pattern, "bar/foo", Case::Sensitive)); |
207 |
| - assert!(match_file(pattern, "bar/bazfoo", Case::Sensitive)); |
208 |
| -} |
209 |
| - |
210 |
| -#[test] |
211 |
| -fn special_cases_from_corpus() { |
212 |
| - let pattern = &pat("foo*bar"); |
213 |
| - assert!( |
214 |
| - !match_file(pattern, "foo/baz/bar", Case::Sensitive), |
215 |
| - "asterisk does not match path separators" |
216 |
| - ); |
217 |
| - let pattern = &pat("*some/path/to/hello.txt"); |
218 |
| - assert!( |
219 |
| - !match_file(pattern, "a/bigger/some/path/to/hello.txt", Case::Sensitive), |
220 |
| - "asterisk doesn't match path separators" |
221 |
| - ); |
222 |
| - |
223 |
| - let pattern = &pat("/*foo.txt"); |
224 |
| - assert!(match_file(pattern, "hello-foo.txt", Case::Sensitive)); |
225 |
| - assert!( |
226 |
| - !match_file(pattern, "hello/foo.txt", Case::Sensitive), |
227 |
| - "absolute single asterisk doesn't match paths" |
228 |
| - ); |
229 |
| -} |
230 |
| - |
231 |
| -#[test] |
232 |
| -fn absolute_basename_glob_and_literal_is_ends_with_in_basenames() { |
233 |
| - let pattern = &pat("/*foo"); |
234 |
| - |
235 |
| - assert!(match_file(pattern, "FoO", Case::Fold)); |
236 |
| - assert!(match_file(pattern, "BarFoO", Case::Fold)); |
237 |
| - assert!(!match_file(pattern, "BarFoOo", Case::Fold)); |
238 |
| - assert!(!match_file(pattern, "Foo", Case::Sensitive)); |
239 |
| - assert!(!match_file(pattern, "BarFoo", Case::Sensitive)); |
240 |
| - assert!(match_file(pattern, "barfoo", Case::Sensitive)); |
241 |
| - assert!(!match_file(pattern, "barfooo", Case::Sensitive)); |
242 |
| -} |
243 |
| - |
244 |
| -#[test] |
245 |
| -fn absolute_basename_glob_and_literal_is_glob_in_paths() { |
246 |
| - let pattern = &pat("/*foo"); |
247 |
| - |
248 |
| - assert!(!match_file(pattern, "bar/foo", Case::Sensitive), "* does not match /"); |
249 |
| - assert!(!match_file(pattern, "bar/bazfoo", Case::Sensitive)); |
250 |
| -} |
251 |
| - |
252 |
| -#[test] |
253 |
| -fn negated_patterns_are_handled_by_caller() { |
254 |
| - let pattern = &pat("!foo"); |
255 |
| - assert!( |
256 |
| - match_file(pattern, "foo", Case::Sensitive), |
257 |
| - "negative patterns match like any other" |
258 |
| - ); |
259 |
| - assert!( |
260 |
| - pattern.is_negative(), |
261 |
| - "the caller checks for the negative flag and acts accordingly" |
262 |
| - ); |
263 |
| -} |
264 |
| -#[test] |
265 |
| -fn names_do_not_automatically_match_entire_directories() { |
266 |
| - // this feature is implemented with the directory stack. |
267 |
| - let pattern = &pat("foo"); |
268 |
| - assert!(!match_file(pattern, "foobar", Case::Sensitive)); |
269 |
| - assert!(!match_file(pattern, "foo/bar", Case::Sensitive)); |
270 |
| - assert!(!match_file(pattern, "foo/bar/baz", Case::Sensitive)); |
271 |
| -} |
272 |
| - |
273 |
| -#[test] |
274 |
| -fn directory_patterns_do_not_match_files_within_a_directory_as_well_like_slash_star_star() { |
275 |
| - // this feature is implemented with the directory stack, which excludes entire directories |
276 |
| - let pattern = &pat("dir/"); |
277 |
| - assert!(!match_path(pattern, "dir/file", None, Case::Sensitive)); |
278 |
| - assert!(!match_path(pattern, "base/dir/file", None, Case::Sensitive)); |
279 |
| - assert!(!match_path(pattern, "base/ndir/file", None, Case::Sensitive)); |
280 |
| - assert!(!match_path(pattern, "Dir/File", None, Case::Fold)); |
281 |
| - assert!(!match_path(pattern, "Base/Dir/File", None, Case::Fold)); |
282 |
| - assert!(!match_path(pattern, "dir2/file", None, Case::Sensitive)); |
283 |
| - |
284 |
| - let pattern = &pat("dir/sub-dir/"); |
285 |
| - assert!(!match_path(pattern, "dir/sub-dir/file", None, Case::Sensitive)); |
286 |
| - assert!(!match_path(pattern, "dir/Sub-dir/File", None, Case::Fold)); |
287 |
| - assert!(!match_path(pattern, "dir/Sub-dir2/File", None, Case::Fold)); |
288 |
| -} |
289 |
| - |
290 |
| -#[test] |
291 |
| -fn single_paths_match_anywhere() { |
292 |
| - let pattern = &pat("target"); |
293 |
| - assert!(match_file(pattern, "dir/target", Case::Sensitive)); |
294 |
| - assert!(!match_file(pattern, "dir/atarget", Case::Sensitive)); |
295 |
| - assert!(!match_file(pattern, "dir/targeta", Case::Sensitive)); |
296 |
| - assert!(match_path(pattern, "dir/target", Some(true), Case::Sensitive)); |
297 |
| - |
298 |
| - let pattern = &pat("target/"); |
299 |
| - assert!(!match_file(pattern, "dir/target", Case::Sensitive)); |
300 |
| - assert!( |
301 |
| - !match_path(pattern, "dir/target", None, Case::Sensitive), |
302 |
| - "it assumes unknown to not be a directory" |
303 |
| - ); |
304 |
| - assert!(match_path(pattern, "dir/target", Some(true), Case::Sensitive)); |
305 |
| - assert!( |
306 |
| - !match_path(pattern, "dir/target/", Some(true), Case::Sensitive), |
307 |
| - "we need sanitized paths that don't have trailing slashes" |
308 |
| - ); |
309 |
| -} |
310 |
| - |
311 |
| -fn pat<'a>(pattern: impl Into<&'a BStr>) -> git_glob::Pattern { |
312 |
| - git_glob::Pattern::from_bytes(pattern.into()).expect("parsing works") |
313 |
| -} |
314 |
| - |
315 |
| -fn match_file<'a>(pattern: &git_glob::Pattern, path: impl Into<&'a BStr>, case: Case) -> bool { |
316 |
| - match_path(pattern, path, false.into(), case) |
317 |
| -} |
318 |
| - |
319 |
| -fn match_path<'a>(pattern: &git_glob::Pattern, path: impl Into<&'a BStr>, is_dir: Option<bool>, case: Case) -> bool { |
320 |
| - let path = path.into(); |
321 |
| - pattern.matches_repo_relative_path(path, basename_start_pos(path), is_dir, case) |
322 |
| -} |
323 |
| - |
324 |
| -fn basename_start_pos(value: &BStr) -> Option<usize> { |
325 |
| - value.rfind_byte(b'/').map(|pos| pos + 1) |
326 |
| -} |
0 commit comments