use std::{ fmt::{Debug, Display, Formatter}, panic::catch_unwind,}; use bstr::ByteSlice;use git_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 += if actual.any_panicked() { 1 } else { 0 }; } } 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 = git_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(|| git_glob::wildmatch(pattern.text.as_bstr(), text.into(), wildmatch::Mode::empty())).into(); let actual_glob_imatch: MatchResult = catch_unwind(|| git_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: &git_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)}
use std::{
fmt::{Debug, Display, Formatter},
panic::catch_unwind,
};
use bstr::ByteSlice;
use git_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 += if actual.any_panicked() { 1 } else { 0 };
}
}
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 = git_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(|| git_glob::wildmatch(pattern.text.as_bstr(), text.into(), wildmatch::Mode::empty())).into();
let actual_glob_imatch: MatchResult =
catch_unwind(|| git_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: &git_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)
}