diff options
Diffstat (limited to 'vendor/gix-glob/tests')
-rw-r--r-- | vendor/gix-glob/tests/fixtures/generated-archives/make_baseline.tar.xz | bin | 0 -> 10428 bytes | |||
-rw-r--r-- | vendor/gix-glob/tests/fixtures/make_baseline.sh | 158 | ||||
-rw-r--r-- | vendor/gix-glob/tests/glob.rs | 3 | ||||
-rw-r--r-- | vendor/gix-glob/tests/parse/mod.rs | 155 | ||||
-rw-r--r-- | vendor/gix-glob/tests/pattern/matching.rs | 325 | ||||
-rw-r--r-- | vendor/gix-glob/tests/pattern/mod.rs | 18 | ||||
-rw-r--r-- | vendor/gix-glob/tests/wildmatch/mod.rs | 374 |
7 files changed, 1033 insertions, 0 deletions
diff --git a/vendor/gix-glob/tests/fixtures/generated-archives/make_baseline.tar.xz b/vendor/gix-glob/tests/fixtures/generated-archives/make_baseline.tar.xz Binary files differnew file mode 100644 index 000000000..5fc9dfdf1 --- /dev/null +++ b/vendor/gix-glob/tests/fixtures/generated-archives/make_baseline.tar.xz diff --git a/vendor/gix-glob/tests/fixtures/make_baseline.sh b/vendor/gix-glob/tests/fixtures/make_baseline.sh new file mode 100644 index 000000000..5787ff64c --- /dev/null +++ b/vendor/gix-glob/tests/fixtures/make_baseline.sh @@ -0,0 +1,158 @@ +#!/bin/bash +set -eu -o pipefail + +git init -q +git config core.autocrlf false +git config core.ignorecase false + +while read -r pattern value; do + echo "$pattern" "$value" + echo "$pattern" > .gitignore + echo "$value" | git check-ignore -vn --stdin 2>&1 || : +done <<EOF >git-baseline.nmatch +/*foo bam/barfoo/baz/bam +/*foo bar/bam/barfoo/baz/bam +foo foobaz +*/\' XXX/\' +/*foo bar/foo +/*foo bar/bazfoo +foo*bar foo/baz/bar +/*foo.txt hello/foo.txt +bar/foo baz/bar/foo +*hello.txt hello.txt-and-then-some +*hello.txt goodbye.txt +*some/path/to/hello.txt some/path/to/hello.txt-and-then-some +*some/path/to/hello.txt some/other/path/to/hello.txt +*some/path/to/hello.txt a/bigger/some/path/to/hello.txt +abc?def abc/def +a*b*c abcd +abc*abc*abc abcabcabcabcabcabcabca +a[0-9]b a_b +a[!0-9]b a0b +a[!0-9]b a9b +[!-] - +a[^0-9]b a0b +a[^0-9]b a9b +[^-] - +{a,b} a +{a,b} b +{[}],foo} } +{foo} foo +{*.foo,*.bar,*.wat} test.foo +{*.foo,*.bar,*.wat} test.bar +{*.foo,*.bar,*.wat} test.wat +abc*def abc/def +aBcDeFg abcdefg +aBcDeFg ABCDEFG +aBcDeFg AbCdEfG +some/**/needle.txt some/other/notthis.txt +some/**/**/needle.txt some/other/notthis.txt +/**/test one/notthis +/**/test notthis +**/.* ab.c +**/.* abc/ab.c +.*/** a.bc +.*/** abc/a.bc +./foo foo +**/foo foofoo +**/foo/bar foofoo/bar +/*.c mozilla-sha1/sha1.c +**/m4/ltoptions.m4 csharp/src/packages/repositories.config +some/*/needle.txt some/needle.txt +some/*/needle.txt some/one/two/needle.txt +some/*/needle.txt some/one/two/three/needle.txt +.*/** .abc +foo/** foo +{**/src/**,foo} abc/src/bar +{**/src/**,foo} foo +abc[/]def abc/def +EOF + +while read -r pattern value; do + echo "$pattern" "$value" + echo "$pattern" > .gitignore + echo "$value" | git check-ignore -vn --stdin 2>&1 || : +done <<EOF >git-baseline.match +*/' XXX/' +\a a +\\\[a-z] \a +\\\? \a +\\\* \\ +/*foo.txt barfoo.txt +*foo.txt bar/foo.txt +*.c mozilla-sha1/sha1.c +*.rs .rs +*hello.txt hello.txt +*hello.txt gareth_says_hello.txt +*hello.txt some/path/to/hello.txt +/*foo.txt foo.txt +*hello.txt some\path\to\hello.txt +*hello.txt an/absolute/path/to/hello.txt +*some/path/to/hello.txt some/path/to/hello.txt +a foo/a +a a +a*b a_b +a*b*c abc +a*b*c a_b_c +a*b*c a___b___c +abc*abc*abc abcabcabcabcabcabcabc +a*a*a*a*a*a*a*a*a aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa +a*b[xyz]c*d abxcdbxcddd +☃ ☃ +** abcde +** .asdf +** x/.asdf +a[0-9]b a0b +a[0-9]b a9b +a[!0-9]b a_b +[a-z123] 1 +[1a-z23] 1 +[123a-z] 1 +[abc-] - +[-abc] - +[-a-c] b +[a-c-] b +[-] - +a[^0-9]b a_b +_[[]_[]]_[?]_[*]_!_ _[_]_?_*_!_ +a,b a,b +\[ [ +\? ? +\* * +aBcDeFg aBcDeFg +some/**/needle.txt some/needle.txt +some/**/needle.txt some/one/needle.txt +some/**/needle.txt some/one/two/needle.txt +some/**/needle.txt some/other/needle.txt +some/**/**/needle.txt some/needle.txt +some/**/**/needle.txt some/one/needle.txt +some/**/**/needle.txt some/one/two/needle.txt +some/**/**/needle.txt some/other/needle.txt +**/test one/two/test +**/test one/test +**/test test +/**/test one/two/test +/**/test one/test +/**/test test +**/.* .abc +**/.* abc/.abc +**/foo/bar foo/bar +.*/** .abc/abc +test/** test/ +test/** test/one +test/** test/one/two +some/*/needle.txt some/one/needle.txt +abc/def abc/def +EOF + +git config core.ignorecase true +while read -r pattern value; do + echo "$pattern" "$value" + echo "$pattern" > .gitignore + echo "$value" | git check-ignore -vn --stdin 2>&1 || : +done <<EOF >git-baseline.match-icase +aBcDeFg aBcDeFg +aBcDeFg abcdefg +aBcDeFg ABCDEFG +aBcDeFg AbCdEfG +EOF diff --git a/vendor/gix-glob/tests/glob.rs b/vendor/gix-glob/tests/glob.rs new file mode 100644 index 000000000..3a90f1d51 --- /dev/null +++ b/vendor/gix-glob/tests/glob.rs @@ -0,0 +1,3 @@ +mod parse; +mod pattern; +mod wildmatch; diff --git a/vendor/gix-glob/tests/parse/mod.rs b/vendor/gix-glob/tests/parse/mod.rs new file mode 100644 index 000000000..d6be0df24 --- /dev/null +++ b/vendor/gix-glob/tests/parse/mod.rs @@ -0,0 +1,155 @@ +use gix_glob::{pattern::Mode, Pattern}; + +#[test] +fn mark_ends_with_pattern_specifically() { + assert_eq!( + gix_glob::parse(br"*literal"), + pat(r"*literal", Mode::NO_SUB_DIR | Mode::ENDS_WITH, Some(0)) + ); + assert_eq!( + gix_glob::parse(br"**literal"), + pat(r"**literal", Mode::NO_SUB_DIR, Some(0)), + "double-asterisk won't allow for fast comparisons" + ); + assert_eq!( + gix_glob::parse(br"*litera[l]"), + pat(r"*litera[l]", Mode::NO_SUB_DIR, Some(0)) + ); + assert_eq!( + gix_glob::parse(br"*litera?"), + pat(r"*litera?", Mode::NO_SUB_DIR, Some(0)) + ); + assert_eq!( + gix_glob::parse(br"*litera\?"), + pat(r"*litera\?", Mode::NO_SUB_DIR, Some(0)), + "for now we don't handle escapes properly like git seems to do" + ); +} + +fn pat(pattern: &str, mode: Mode, first_glob_char_pos: Option<usize>) -> Option<Pattern> { + Some(Pattern { + text: pattern.into(), + mode, + first_wildcard_pos: first_glob_char_pos, + }) +} + +#[test] +fn whitespace_only_is_ignored() { + assert!(gix_glob::parse(b"\n\r\n\t\t \n").is_none()); +} + +#[test] +fn hash_symbols_are_not_special() { + assert_eq!( + gix_glob::parse(b"# hello world"), + pat("# hello world", Mode::NO_SUB_DIR, None) + ); +} + +#[test] +fn backslashes_before_hashes_are_considered_an_escape_sequence() { + assert_eq!(gix_glob::parse(br"\#hello"), pat(r"#hello", Mode::NO_SUB_DIR, None)); +} + +#[test] +fn backslashes_are_part_of_the_pattern_if_not_in_specific_positions() { + assert_eq!( + gix_glob::parse(br"\hello\world"), + pat(r"\hello\world", Mode::NO_SUB_DIR, Some(0)) + ); +} + +#[test] +fn leading_exclamation_mark_negates_pattern() { + assert_eq!( + gix_glob::parse(b"!hello"), + pat("hello", Mode::NEGATIVE | Mode::NO_SUB_DIR, None) + ); +} + +#[test] +fn leading_exclamation_marks_can_be_escaped_with_backslash() { + assert_eq!(gix_glob::parse(br"\!hello"), pat("!hello", Mode::NO_SUB_DIR, None)); +} + +#[test] +fn leading_slashes_mark_patterns_as_absolute() { + assert_eq!( + gix_glob::parse(br"/absolute"), + pat("absolute", Mode::NO_SUB_DIR | Mode::ABSOLUTE, None) + ); + + assert_eq!( + gix_glob::parse(br"/absolute/path"), + pat("absolute/path", Mode::ABSOLUTE, None) + ); +} + +#[test] +fn absence_of_sub_directories_are_marked() { + assert_eq!(gix_glob::parse(br"a/b"), pat("a/b", Mode::empty(), None)); + assert_eq!(gix_glob::parse(br"ab"), pat("ab", Mode::NO_SUB_DIR, None)); +} + +#[test] +fn trailing_slashes_are_marked_and_removed() { + assert_eq!( + gix_glob::parse(b"dir/"), + pat("dir", Mode::MUST_BE_DIR | Mode::NO_SUB_DIR, None) + ); + assert_eq!( + gix_glob::parse(b"dir///"), + pat("dir//", Mode::MUST_BE_DIR, None), + "but only the last slash is removed" + ); +} + +#[test] +fn trailing_spaces_are_ignored() { + assert_eq!(gix_glob::parse(br"a "), pat("a", Mode::NO_SUB_DIR, None)); + assert_eq!( + gix_glob::parse(b"a\t\t "), + pat("a\t\t", Mode::NO_SUB_DIR, None), + "trailing tabs are not ignored" + ); +} + +#[test] +fn trailing_spaces_can_be_escaped_to_be_literal() { + assert_eq!( + gix_glob::parse(br"a \ "), + pat("a ", Mode::NO_SUB_DIR, None), + "a single escape in front of the last desired space is enough" + ); + assert_eq!( + gix_glob::parse(br"a b c "), + pat("a b c", Mode::NO_SUB_DIR, None), + "spaces in the middle are fine" + ); + assert_eq!( + gix_glob::parse(br"a\ \ \ "), + pat("a ", Mode::NO_SUB_DIR, None), + "one can also escape every single one" + ); + assert_eq!( + gix_glob::parse(br"a \ "), + pat("a ", Mode::NO_SUB_DIR, None), + "or just the one in the middle, losing the last actual space" + ); + assert_eq!( + gix_glob::parse(br"a \"), + pat("a ", Mode::NO_SUB_DIR, None), + "escaping nothing also works as a whitespace protection" + ); + assert_eq!( + gix_glob::parse(br"a \\\ "), + pat(r"a ", Mode::NO_SUB_DIR, None), + "strange things like these work too" + ); + assert_eq!( + gix_glob::parse(br"a \\ "), + pat(r"a ", Mode::NO_SUB_DIR, None), + "strange things like these work as well" + ); +} diff --git a/vendor/gix-glob/tests/pattern/matching.rs b/vendor/gix-glob/tests/pattern/matching.rs new file mode 100644 index 000000000..3e757f8d6 --- /dev/null +++ b/vendor/gix-glob/tests/pattern/matching.rs @@ -0,0 +1,325 @@ +use std::collections::BTreeSet; + +use bstr::{BStr, ByteSlice}; +use gix_glob::{pattern, pattern::Case}; + +#[derive(Debug, Ord, PartialOrd, Eq, PartialEq, Copy, Clone)] +pub struct GitMatch<'a> { + pattern: &'a BStr, + value: &'a BStr, + /// True if git could match `value` with `pattern` + is_match: bool, +} + +pub struct Baseline<'a> { + inner: bstr::Lines<'a>, +} + +impl<'a> Iterator for Baseline<'a> { + type Item = GitMatch<'a>; + + fn next(&mut self) -> Option<Self::Item> { + let mut tokens = self.inner.next()?.splitn(2, |b| *b == b' '); + let pattern = tokens.next().expect("pattern").as_bstr(); + let value = tokens.next().expect("value").as_bstr().trim_start().as_bstr(); + + let git_match = self.inner.next()?; + let is_match = !git_match.starts_with(b"::\t"); + Some(GitMatch { + pattern, + value, + is_match, + }) + } +} + +impl<'a> Baseline<'a> { + fn new(input: &'a [u8]) -> Self { + Baseline { + inner: input.as_bstr().lines(), + } + } +} + +#[test] +fn compare_baseline_with_ours() { + let dir = gix_testtools::scripted_fixture_read_only("make_baseline.sh").unwrap(); + let (mut total_matches, mut total_correct, mut panics) = (0, 0, 0); + let mut mismatches = Vec::new(); + for (input_file, expected_matches, case) in &[ + ("git-baseline.match", true, pattern::Case::Sensitive), + ("git-baseline.nmatch", false, pattern::Case::Sensitive), + ("git-baseline.match-icase", true, pattern::Case::Fold), + ] { + let input = std::fs::read(dir.join(*input_file)).unwrap(); + let mut seen = BTreeSet::default(); + + for m @ GitMatch { + pattern, + value, + is_match, + } in Baseline::new(&input) + { + total_matches += 1; + assert!(seen.insert(m), "duplicate match entry: {m:?}"); + assert_eq!( + is_match, *expected_matches, + "baseline for matches must be {expected_matches} - check baseline and git version: {m:?}" + ); + match std::panic::catch_unwind(|| { + let pattern = pat(pattern); + pattern.matches_repo_relative_path(value, basename_start_pos(value), None, *case) + }) { + Ok(actual_match) => { + if actual_match == is_match { + total_correct += 1; + } else { + mismatches.push((pattern.to_owned(), value.to_owned(), is_match, expected_matches)); + } + } + Err(_) => { + panics += 1; + continue; + } + }; + } + } + + dbg!(mismatches); + assert_eq!( + total_correct, + total_matches - panics, + "We perfectly agree with git here" + ); + assert_eq!(panics, 0); +} + +#[test] +fn non_dirs_for_must_be_dir_patterns_are_ignored() { + let pattern = pat("hello/"); + + assert!(pattern.mode.contains(pattern::Mode::MUST_BE_DIR)); + assert_eq!( + pattern.text, "hello", + "a dir pattern doesn't actually end with the trailing slash" + ); + let path = "hello"; + assert!( + !pattern.matches_repo_relative_path(path, None, false.into() /* is-dir */, Case::Sensitive), + "non-dirs never match a dir pattern" + ); + assert!( + pattern.matches_repo_relative_path(path, None, true.into() /* is-dir */, Case::Sensitive), + "dirs can match a dir pattern with the normal rules" + ); +} + +#[test] +fn matches_of_absolute_paths_work() { + let pattern = "/hello/git"; + assert!( + gix_glob::wildmatch(pattern.into(), pattern.into(), gix_glob::wildmatch::Mode::empty()), + "patterns always match themselves" + ); + assert!( + gix_glob::wildmatch( + pattern.into(), + pattern.into(), + gix_glob::wildmatch::Mode::NO_MATCH_SLASH_LITERAL + ), + "patterns always match themselves, path mode doesn't change that" + ); +} + +#[test] +fn basename_matches_from_end() { + let pat = &pat("foo"); + assert!(match_file(pat, "FoO", Case::Fold)); + assert!(!match_file(pat, "FoOo", Case::Fold)); + assert!(!match_file(pat, "Foo", Case::Sensitive)); + assert!(match_file(pat, "foo", Case::Sensitive)); + assert!(!match_file(pat, "Foo", Case::Sensitive)); + assert!(!match_file(pat, "barfoo", Case::Sensitive)); +} + +#[test] +fn absolute_basename_matches_only_from_beginning() { + let pat = &pat("/foo"); + assert!(match_file(pat, "FoO", Case::Fold)); + assert!(!match_file(pat, "bar/Foo", Case::Fold)); + assert!(match_file(pat, "foo", Case::Sensitive)); + assert!(!match_file(pat, "Foo", Case::Sensitive)); + assert!(!match_file(pat, "bar/foo", Case::Sensitive)); +} + +#[test] +fn absolute_path_matches_only_from_beginning() { + let pat = &pat("/bar/foo"); + assert!(!match_file(pat, "FoO", Case::Fold)); + assert!(match_file(pat, "bar/Foo", Case::Fold)); + assert!(!match_file(pat, "foo", Case::Sensitive)); + assert!(match_file(pat, "bar/foo", Case::Sensitive)); + assert!(!match_file(pat, "bar/Foo", Case::Sensitive)); +} + +#[test] +fn absolute_path_with_recursive_glob_detects_mismatches_quickly() { + let pat = &pat("/bar/foo/**"); + assert!(!match_file(pat, "FoO", Case::Fold)); + assert!(!match_file(pat, "bar/Fooo", Case::Fold)); + assert!(!match_file(pat, "baz/bar/Foo", Case::Fold)); +} + +#[test] +fn absolute_path_with_recursive_glob_can_do_case_insensitive_prefix_search() { + let pat = &pat("/bar/foo/**"); + assert!(!match_file(pat, "bar/Foo/match", Case::Sensitive)); + assert!(match_file(pat, "bar/Foo/match", Case::Fold)); +} + +#[test] +fn relative_path_does_not_match_from_end() { + for pattern in &["bar/foo", "/bar/foo"] { + let pattern = &pat(*pattern); + assert!(!match_file(pattern, "FoO", Case::Fold)); + assert!(match_file(pattern, "bar/Foo", Case::Fold)); + assert!(!match_file(pattern, "baz/bar/Foo", Case::Fold)); + assert!(!match_file(pattern, "foo", Case::Sensitive)); + assert!(match_file(pattern, "bar/foo", Case::Sensitive)); + assert!(!match_file(pattern, "baz/bar/foo", Case::Sensitive)); + assert!(!match_file(pattern, "Baz/bar/Foo", Case::Sensitive)); + } +} + +#[test] +fn basename_glob_and_literal_is_ends_with() { + let pattern = &pat("*foo"); + assert!(match_file(pattern, "FoO", Case::Fold)); + assert!(match_file(pattern, "BarFoO", Case::Fold)); + assert!(!match_file(pattern, "BarFoOo", Case::Fold)); + assert!(!match_file(pattern, "Foo", Case::Sensitive)); + assert!(!match_file(pattern, "BarFoo", Case::Sensitive)); + assert!(match_file(pattern, "barfoo", Case::Sensitive)); + assert!(!match_file(pattern, "barfooo", Case::Sensitive)); + + assert!(match_file(pattern, "bar/foo", Case::Sensitive)); + assert!(match_file(pattern, "bar/bazfoo", Case::Sensitive)); +} + +#[test] +fn special_cases_from_corpus() { + let pattern = &pat("foo*bar"); + assert!( + !match_file(pattern, "foo/baz/bar", Case::Sensitive), + "asterisk does not match path separators" + ); + let pattern = &pat("*some/path/to/hello.txt"); + assert!( + !match_file(pattern, "a/bigger/some/path/to/hello.txt", Case::Sensitive), + "asterisk doesn't match path separators" + ); + + let pattern = &pat("/*foo.txt"); + assert!(match_file(pattern, "hello-foo.txt", Case::Sensitive)); + assert!( + !match_file(pattern, "hello/foo.txt", Case::Sensitive), + "absolute single asterisk doesn't match paths" + ); +} + +#[test] +fn absolute_basename_glob_and_literal_is_ends_with_in_basenames() { + let pattern = &pat("/*foo"); + + assert!(match_file(pattern, "FoO", Case::Fold)); + assert!(match_file(pattern, "BarFoO", Case::Fold)); + assert!(!match_file(pattern, "BarFoOo", Case::Fold)); + assert!(!match_file(pattern, "Foo", Case::Sensitive)); + assert!(!match_file(pattern, "BarFoo", Case::Sensitive)); + assert!(match_file(pattern, "barfoo", Case::Sensitive)); + assert!(!match_file(pattern, "barfooo", Case::Sensitive)); +} + +#[test] +fn absolute_basename_glob_and_literal_is_glob_in_paths() { + let pattern = &pat("/*foo"); + + assert!(!match_file(pattern, "bar/foo", Case::Sensitive), "* does not match /"); + assert!(!match_file(pattern, "bar/bazfoo", Case::Sensitive)); +} + +#[test] +fn negated_patterns_are_handled_by_caller() { + let pattern = &pat("!foo"); + assert!( + match_file(pattern, "foo", Case::Sensitive), + "negative patterns match like any other" + ); + assert!( + pattern.is_negative(), + "the caller checks for the negative flag and acts accordingly" + ); +} +#[test] +fn names_do_not_automatically_match_entire_directories() { + // this feature is implemented with the directory stack. + let pattern = &pat("foo"); + assert!(!match_file(pattern, "foobar", Case::Sensitive)); + assert!(!match_file(pattern, "foo/bar", Case::Sensitive)); + assert!(!match_file(pattern, "foo/bar/baz", Case::Sensitive)); +} + +#[test] +fn directory_patterns_do_not_match_files_within_a_directory_as_well_like_slash_star_star() { + // this feature is implemented with the directory stack, which excludes entire directories + let pattern = &pat("dir/"); + assert!(!match_path(pattern, "dir/file", None, Case::Sensitive)); + assert!(!match_path(pattern, "base/dir/file", None, Case::Sensitive)); + assert!(!match_path(pattern, "base/ndir/file", None, Case::Sensitive)); + assert!(!match_path(pattern, "Dir/File", None, Case::Fold)); + assert!(!match_path(pattern, "Base/Dir/File", None, Case::Fold)); + assert!(!match_path(pattern, "dir2/file", None, Case::Sensitive)); + + let pattern = &pat("dir/sub-dir/"); + assert!(!match_path(pattern, "dir/sub-dir/file", None, Case::Sensitive)); + assert!(!match_path(pattern, "dir/Sub-dir/File", None, Case::Fold)); + assert!(!match_path(pattern, "dir/Sub-dir2/File", None, Case::Fold)); +} + +#[test] +fn single_paths_match_anywhere() { + let pattern = &pat("target"); + assert!(match_file(pattern, "dir/target", Case::Sensitive)); + assert!(!match_file(pattern, "dir/atarget", Case::Sensitive)); + assert!(!match_file(pattern, "dir/targeta", Case::Sensitive)); + assert!(match_path(pattern, "dir/target", Some(true), Case::Sensitive)); + + let pattern = &pat("target/"); + assert!(!match_file(pattern, "dir/target", Case::Sensitive)); + assert!( + !match_path(pattern, "dir/target", None, Case::Sensitive), + "it assumes unknown to not be a directory" + ); + assert!(match_path(pattern, "dir/target", Some(true), Case::Sensitive)); + assert!( + !match_path(pattern, "dir/target/", Some(true), Case::Sensitive), + "we need sanitized paths that don't have trailing slashes" + ); +} + +fn pat<'a>(pattern: impl Into<&'a BStr>) -> gix_glob::Pattern { + gix_glob::Pattern::from_bytes(pattern.into()).expect("parsing works") +} + +fn match_file<'a>(pattern: &gix_glob::Pattern, path: impl Into<&'a BStr>, case: Case) -> bool { + match_path(pattern, path, false.into(), case) +} + +fn match_path<'a>(pattern: &gix_glob::Pattern, path: impl Into<&'a BStr>, is_dir: Option<bool>, case: Case) -> bool { + let path = path.into(); + pattern.matches_repo_relative_path(path, basename_start_pos(path), is_dir, case) +} + +fn basename_start_pos(value: &BStr) -> Option<usize> { + value.rfind_byte(b'/').map(|pos| pos + 1) +} diff --git a/vendor/gix-glob/tests/pattern/mod.rs b/vendor/gix-glob/tests/pattern/mod.rs new file mode 100644 index 000000000..ca5d8cbad --- /dev/null +++ b/vendor/gix-glob/tests/pattern/mod.rs @@ -0,0 +1,18 @@ +use gix_glob::{pattern::Mode, Pattern}; + +#[test] +fn display() { + fn pat(text: &str, mode: Mode) -> String { + Pattern { + text: text.into(), + mode, + first_wildcard_pos: None, + } + .to_string() + } + assert_eq!(pat("a", Mode::ABSOLUTE), "/a"); + assert_eq!(pat("a", Mode::MUST_BE_DIR), "a/"); + assert_eq!(pat("a", Mode::NEGATIVE), "!a"); + assert_eq!(pat("a", Mode::ABSOLUTE | Mode::NEGATIVE | Mode::MUST_BE_DIR), "!/a/"); +} +mod matching; diff --git a/vendor/gix-glob/tests/wildmatch/mod.rs b/vendor/gix-glob/tests/wildmatch/mod.rs new file mode 100644 index 000000000..2e74dabf3 --- /dev/null +++ b/vendor/gix-glob/tests/wildmatch/mod.rs @@ -0,0 +1,374 @@ +use std::{ + fmt::{Debug, Display, Formatter}, + panic::catch_unwind, +}; + +use bstr::ByteSlice; +use gix_glob::{pattern::Case, wildmatch, Pattern}; + +#[test] +fn corpus() { + // based on git/t/t3070.sh + let tests = [ + (1u8,1u8,1u8,1u8, "foo", "foo"), + (0,0,0,0, "foo", "bar"), + (1,1,1,1, "foo", "???"), + (0,0,0,0, "foo", "??"), + (1,1,1,1, "foo", "*"), + (1,1,1,1, "foo", "f*"), + (0,0,0,0, "foo", "*f"), + (1,1,1,1, "foo", "*foo*"), + (1,1,1,1, "foobar", "*ob*a*r*"), + (1,1,1,1, "aaaaaaabababab", "*ab"), + (1,1,1,1, "foo*", r"foo\*"), + (0,0,0,0, "foobar", r"foo\*bar"), + (1,1,1,1, r"f\oo", r"f\\oo"), + (1,1,1,1, "ball", "*[al]?"), + (0,0,0,0, "ten", "[ten]"), + (1,1,1,1, "ten", "**[!te]"), + (0,0,0,0, "ten", "**[!ten]"), + (1,1,1,1, "ten", "t[a-g]n"), + (0,0,0,0, "ten", "t[!a-g]n"), + (1,1,1,1, "ton", "t[!a-g]n"), + (1,1,1,1, "ton", "t[^a-g]n"), + (1,1,1,1, "a]b", "a[]]b"), + (1,1,1,1, "a-b", "a[]-]b"), + (1,1,1,1, "a]b", "a[]-]b"), + (0,0,0,0, "aab", "a[]-]b"), + (1,1,1,1, "aab", "a[]a-]b"), + (1,1,1,1, "]", "]"), + // Extended slash-matching features + (0,0,1,1, "foo/baz/bar", "foo*bar"), + (0,0,1,1, "foo/baz/bar", "foo**bar"), + (1,1,1,1, "foobazbar", "foo**bar"), + (1,1,1,1, "foo/baz/bar", "foo/**/bar"), + (1,1,0,0, "foo/baz/bar", "foo/**/**/bar"), + (1,1,1,1, "foo/b/a/z/bar", "foo/**/bar"), + (1,1,1,1, "foo/b/a/z/bar", "foo/**/**/bar"), + (1,1,0,0, "foo/bar", "foo/**/bar"), + (1,1,0,0, "foo/bar", "foo/**/**/bar"), + (0,0,1,1, "foo/bar", "foo?bar"), + (0,0,1,1, "foo/bar", "foo[/]bar"), + (0,0,1,1, "foo/bar", "foo[^a-z]bar"), + (0,0,1,1, "foo/bar", "f[^eiu][^eiu][^eiu][^eiu][^eiu]r"), + (1,1,1,1, "foo-bar", "f[^eiu][^eiu][^eiu][^eiu][^eiu]r"), + (1,1,0,0, "foo", "**/foo"), + (1,1,1,1, "XXX/foo", "**/foo"), + (1,1,1,1, "bar/baz/foo", "**/foo"), + (0,0,1,1, "bar/baz/foo", "*/foo"), + (0,0,1,1, "foo/bar/baz", "**/bar*"), + (1,1,1,1, "deep/foo/bar/baz", "**/bar/*"), + (0,0,1,1, "deep/foo/bar/baz/", "**/bar/*"), + (1,1,1,1, "deep/foo/bar/baz/", "**/bar/**"), + (0,0,0,0, "deep/foo/bar", "**/bar/*"), + (1,1,1,1, "deep/foo/bar/", "**/bar/**"), + (0,0,1,1, "foo/bar/baz", "**/bar**"), + (1,1,1,1, "foo/bar/baz/x", "*/bar/**"), + (0,0,1,1, "deep/foo/bar/baz/x", "*/bar/**"), + (1,1,1,1, "deep/foo/bar/baz/x", "**/bar/*/*"), + + // Various additional tests + (0,0,0,0, "acrt", "a[c-c]st"), + (1,1,1,1, "acrt", "a[c-c]rt"), + (0,0,0,0, "]", "[!]-]"), + (1,1,1,1, "a", "[!]-]"), + (0,0,0,0, "", r"\"), + (0,0,1,1, r"XXX/\", r"*/\"), + (0,0,1,1, r"XXX/\", r"*/\\"), + (1,1,1,1, "foo", "foo"), + (1,1,1,1, "@foo", "@foo"), + (0,0,0,0, "foo", "@foo"), + (1,1,1,1, "[ab]", r"\[ab]"), + (1,1,1,1, "[ab]", "[[]ab]"), + (1,1,1,1, "[ab]", "[[:]ab]"), + (0,0,0,0, "[ab]", "[[::]ab]"), + (1,1,1,1, "[ab]", "[[:digit]ab]"), + (1,1,1,1, "[ab]", r"[\[:]ab]"), + (1,1,1,1, "?a?b", r"\??\?b"), + (1,1,1,1, "abc", r"\a\b\c"), + (1,1,1,1, "foo/bar/baz/to", "**/t[o]"), + + // Character class tests + (1,1,1,1, "a1B", "[[:alpha:]][[:digit:]][[:upper:]]"), + (0,1,0,1, "a", "[[:digit:][:upper:][:space:]]"), + (1,1,1,1, "A", "[[:digit:][:upper:][:space:]]"), + (1,1,1,1, "1", "[[:digit:][:upper:][:space:]]"), + (0,0,0,0, "1", "[[:digit:][:upper:][:spaci:]]"), + (1,1,1,1, " ", "[[:digit:][:upper:][:space:]]"), + (0,0,0,0, ".", "[[:digit:][:upper:][:space:]]"), + (1,1,1,1, ".", "[[:digit:][:punct:][:space:]]"), + (1,1,1,1, "5", "[[:xdigit:]]"), + (1,1,1,1, "f", "[[:xdigit:]]"), + (1,1,1,1, "D", "[[:xdigit:]]"), + (1,1,1,1, "_", "[[:alnum:][:alpha:][:blank:][:cntrl:][:digit:][:graph:][:lower:][:print:][:punct:][:space:][:upper:][:xdigit:]]"), + (1,1,1,1, ".", "[^[:alnum:][:alpha:][:blank:][:cntrl:][:digit:][:lower:][:space:][:upper:][:xdigit:]]"), + (1,1,1,1, "5", "[a-c[:digit:]x-z]"), + (1,1,1,1, "b", "[a-c[:digit:]x-z]"), + (1,1,1,1, "y", "[a-c[:digit:]x-z]"), + (0,0,0,0, "q", "[a-c[:digit:]x-z]"), + + // Additional tests, including some malformed wild(patterns + (1,1,1,1, "]", r"[\\-^]"), + (0,0,0,0, "[", r"[\\-^]"), + (1,1,1,1, "-", r"[\-_]"), + (1,1,1,1, "]", r"[\]]"), + (0,0,0,0, r"\]", r"[\]]"), + (0,0,0,0, r"\", r"[\]]"), + (0,0,0,0, "ab", "a[]b"), + (0,0,0,0, "ab", "[!"), + (0,0,0,0, "ab", "[-"), + (1,1,1,1, "-", "[-]"), + (0,0,0,0, "-", "[a-"), + (0,0,0,0, "-", "[!a-"), + (1,1,1,1, "-", "[--A]"), + (1,1,1,1, "5", "[--A]"), + (1,1,1,1, " ", "[ --]"), + (1,1,1,1, "$", "[ --]"), + (1,1,1,1, "-", "[ --]"), + (0,0,0,0, "0", "[ --]"), + (1,1,1,1, "-", "[---]"), + (1,1,1,1, "-", "[------]"), + (0,0,0,0, "j", "[a-e-n]"), + (1,1,1,1, "-", "[a-e-n]"), + (1,1,1,1, "a", "[!------]"), + (0,0,0,0, "[", "[]-a]"), + (1,1,1,1, "^", "[]-a]"), + (0,0,0,0, "^", "[!]-a]"), + (1,1,1,1, "[", "[!]-a]"), + (1,1,1,1, "^", "[a^bc]"), + (1,1,1,1, "-b]", "[a-]b]"), + (0,0,0,0, r"\", r"[\]"), + (1,1,1,1, r"\", r"[\\]"), + (0,0,0,0, r"\", r"[!\\]"), + (1,1,1,1, "G", r"[A-\\]"), + (0,0,0,0, "aaabbb", "b*a"), + (0,0,0,0, "aabcaa", "*ba*"), + (1,1,1,1, ",", "[,]"), + (1,1,1,1, ",", r"[\\,]"), + (1,1,1,1, r"\", r"[\\,]"), + (1,1,1,1, "-", "[,-.]"), + (0,0,0,0, "+", "[,-.]"), + (0,0,0,0, "-.]", "[,-.]"), + (1,1,1,1, "2", r"[\1-\3]"), + (1,1,1,1, "3", r"[\1-\3]"), + (0,0,0,0, "4", r"[\1-\3]"), + (1,1,1,1, r"\", r"[[-\]]"), + (1,1,1,1, "[", r"[[-\]]"), + (1,1,1,1, "]", r"[[-\]]"), + (0,0,0,0, "-", r"[[-\]]"), + + // Test recursion + (1,1,1,1, "-adobe-courier-bold-o-normal--12-120-75-75-m-70-iso8859-1", "-*-*-*-*-*-*-12-*-*-*-m-*-*-*"), + (0,0,0,0, "-adobe-courier-bold-o-normal--12-120-75-75-X-70-iso8859-1", "-*-*-*-*-*-*-12-*-*-*-m-*-*-*"), + (0,0,0,0, "-adobe-courier-bold-o-normal--12-120-75-75-/-70-iso8859-1", "-*-*-*-*-*-*-12-*-*-*-m-*-*-*"), + (1,1,1,1, "XXX/adobe/courier/bold/o/normal//12/120/75/75/m/70/iso8859/1", "XXX/*/*/*/*/*/*/12/*/*/*/m/*/*/*"), + (0,0,0,0, "XXX/adobe/courier/bold/o/normal//12/120/75/75/X/70/iso8859/1", "XXX/*/*/*/*/*/*/12/*/*/*/m/*/*/*"), + (1,1,1,1, "abcd/abcdefg/abcdefghijk/abcdefghijklmnop.txt", "**/*a*b*g*n*t"), + (0,0,0,0, "abcd/abcdefg/abcdefghijk/abcdefghijklmnop.txtz", "**/*a*b*g*n*t"), + (0,0,0,0, "foo", "*/*/*"), + (0,0,0,0, "foo/bar", "*/*/*"), + (1,1,1,1, "foo/bba/arr", "*/*/*"), + (0,0,1,1, "foo/bb/aa/rr", "*/*/*"), + (1,1,1,1, "foo/bb/aa/rr", "**/**/**"), + (1,1,1,1, "abcXdefXghi", "*X*i"), + (0,0,1,1, "ab/cXd/efXg/hi", "*X*i"), + (1,1,1,1, "ab/cXd/efXg/hi", "*/*X*/*/*i"), + (1,1,1,1, "ab/cXd/efXg/hi", "**/*X*/**/*i"), + + // Extra path(tests + (0,0,0,0, "foo", "fo"), + (1,1,1,1,"foo/bar", "foo/bar"), + (1,1,1,1, "foo/bar", "foo/*"), + (0,0,1,1, "foo/bba/arr", "foo/*"), + (1,1,1,1, "foo/bba/arr", "foo/**"), + (0,0,1,1, "foo/bba/arr", "foo*"), + (0,0,1,1, "foo/bba/arr", "foo/*arr"), + (0,0,1,1, "foo/bba/arr", "foo/**arr"), + (0,0,0,0, "foo/bba/arr", "foo/*z"), + (0,0,0,0, "foo/bba/arr", "foo/**z"), + (0,0,1,1, "foo/bar", "foo?bar"), + (0,0,1,1, "foo/bar", "foo[/]bar"), + (0,0,1,1, "foo/bar", "foo[^a-z]bar"), + (0,0,1,1, "ab/cXd/efXg/hi", "*Xg*i"), + + // Extra case-sensitivity tests + (0,1,0,1, "a", "[A-Z]"), + (1,1,1,1, "A", "[A-Z]"), + (0,1,0,1, "A", "[a-z]"), + (1,1,1,1, "a", "[a-z]"), + (0,1,0,1, "a", "[[:upper:]]"), + (1,1,1,1, "A", "[[:upper:]]"), + (0,1,0,1, "A", "[[:lower:]]"), + (1,1,1,1, "a", "[[:lower:]]"), + (0,1,0,1, "A", "[B-Za]"), + (1,1,1,1, "a", "[B-Za]"), + (0,1,0,1, "A", "[B-a]"), + (1,1,1,1, "a", "[B-a]"), + (0,1,0,1, "z", "[Z-y]"), + (1,1,1,1, "Z", "[Z-y]"), + ]; + + let mut failures = Vec::new(); + let mut at_least_one_panic = 0; + for (path_match, path_imatch, glob_match, glob_imatch, text, pattern_text) in tests { + let (pattern, actual) = multi_match(pattern_text, text); + let expected = expect_multi(path_match, path_imatch, glob_match, glob_imatch); + + if actual.all_panicked() { + at_least_one_panic += 1; + } else if actual != expected { + failures.push((pattern, pattern_text, text, actual, expected)); + } else { + at_least_one_panic += i32::from(actual.any_panicked()); + } + } + + dbg!(&failures); + assert_eq!(failures.len(), 0); + assert_eq!(at_least_one_panic, 0, "not a single panic in any invocation"); + + // TODO: reproduce these + // (0 0 0 0 \ + // 1 1 1 1 '\' '\' + // (0 0 0 0 \ + // E E E E 'foo' '' + // (0 0 0 0 \ + // 1 1 1 1 'a[]b' 'a[]b' + // (0 0 0 0 \ + // 1 1 1 1 'ab[' 'ab[' + // (0 0 1 1 \ + // 1 1 1 1 foo/bba/arr 'foo**' +} + +#[test] +fn brackets() { + let (_pattern, actual) = multi_match(r"[B-a]", "A"); + assert!(!actual.any_panicked()); + assert_eq!(actual, expect_multi(0, 1, 0, 1)); +} + +fn multi_match(pattern_text: &str, text: &str) -> (Pattern, MultiMatch) { + let pattern = gix_glob::Pattern::from_bytes(pattern_text.as_bytes()).expect("valid (enough) pattern"); + let actual_path_match: MatchResult = catch_unwind(|| match_file_path(&pattern, text, Case::Sensitive)).into(); + let actual_path_imatch: MatchResult = catch_unwind(|| match_file_path(&pattern, text, Case::Fold)).into(); + let actual_glob_match: MatchResult = + catch_unwind(|| gix_glob::wildmatch(pattern.text.as_bstr(), text.into(), wildmatch::Mode::empty())).into(); + let actual_glob_imatch: MatchResult = + catch_unwind(|| gix_glob::wildmatch(pattern.text.as_bstr(), text.into(), wildmatch::Mode::IGNORE_CASE)).into(); + let actual = MultiMatch { + path_match: actual_path_match, + path_imatch: actual_path_imatch, + glob_match: actual_glob_match, + glob_imatch: actual_glob_imatch, + }; + (pattern, actual) +} + +fn expect_multi(path_match: u8, path_imatch: u8, glob_match: u8, glob_imatch: u8) -> MultiMatch { + (path_match, path_imatch, glob_match, glob_imatch).into() +} + +#[derive(Eq, PartialEq)] +struct MultiMatch { + path_match: MatchResult, + path_imatch: MatchResult, + glob_match: MatchResult, + glob_imatch: MatchResult, +} + +impl MultiMatch { + fn all_panicked(&self) -> bool { + use MatchResult::Panic; + matches!(self.path_match, Panic) + && matches!(self.path_imatch, Panic) + && matches!(self.glob_match, Panic) + && matches!(self.glob_imatch, Panic) + } + fn any_panicked(&self) -> bool { + use MatchResult::Panic; + matches!(self.path_match, Panic) + || matches!(self.path_imatch, Panic) + || matches!(self.glob_match, Panic) + || matches!(self.glob_imatch, Panic) + } +} + +impl From<(u8, u8, u8, u8)> for MultiMatch { + fn from(t: (u8, u8, u8, u8)) -> Self { + MultiMatch { + path_match: t.0.into(), + path_imatch: t.1.into(), + glob_match: t.2.into(), + glob_imatch: t.3.into(), + } + } +} + +impl Debug for MultiMatch { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!( + f, + "({} {} {} {})", + self.path_match, self.path_imatch, self.glob_match, self.glob_imatch + ) + } +} + +enum MatchResult { + Match, + NoMatch, + Panic, +} + +impl PartialEq<Self> for MatchResult { + fn eq(&self, other: &Self) -> bool { + use MatchResult::*; + match (self, other) { + (Panic, _) | (_, Panic) => true, + (Match, NoMatch) | (NoMatch, Match) => false, + (Match, Match) | (NoMatch, NoMatch) => true, + } + } +} + +impl std::cmp::Eq for MatchResult {} + +impl From<std::thread::Result<bool>> for MatchResult { + fn from(v: std::thread::Result<bool>) -> Self { + use MatchResult::*; + match v { + Ok(v) if v => Match, + Ok(_) => NoMatch, + Err(_) => Panic, + } + } +} + +impl From<u8> for MatchResult { + fn from(v: u8) -> Self { + use MatchResult::*; + match v { + 1 => Match, + 0 => NoMatch, + _ => unreachable!("BUG: only use 0 or 1 for expected values"), + } + } +} + +impl Display for MatchResult { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + use MatchResult::*; + f.write_str(match self { + Match => "✔️", + NoMatch => "⨯", + Panic => "E", + }) + } +} + +fn match_file_path(pattern: &gix_glob::Pattern, path: &str, case: Case) -> bool { + pattern.matches_repo_relative_path(path, basename_of(path), false.into() /* is_dir */, case) +} +fn basename_of(path: &str) -> Option<usize> { + path.rfind('/').map(|pos| pos + 1) +} |