use super::*;
fn pattern(tokens: Vec<PatternToken>) -> NamePattern {
NamePattern::from_tokens(tokens)
}
fn literal(text: &str) -> PatternToken {
PatternToken::Literal(Wtf16String::from(text))
}
fn matches(pattern: &NamePattern, name: &str) -> bool {
pattern.matches(&Wtf16String::from(name), CaseSensitivity::Sensitive)
}
fn matches_ignoring_case(pattern: &NamePattern, name: &str) -> bool {
pattern.matches(&Wtf16String::from(name), CaseSensitivity::Insensitive)
}
#[test]
fn a_literal_pattern_matches_only_that_name() {
let subject = NamePattern::literal(&Wtf16String::from("readme.txt"));
assert!(matches(&subject, "readme.txt"));
assert!(!matches(&subject, "readme.txts"));
assert!(!matches(&subject, "readme.tx"));
assert!(!matches(&subject, "Readme.txt"));
}
#[test]
fn an_empty_pattern_matches_nothing_a_directory_can_contain() {
let subject = NamePattern::empty();
assert!(!matches(&subject, "a"));
assert!(subject.matches(&Wtf16String::new(), CaseSensitivity::Sensitive));
}
#[test]
fn any_run_matches_a_prefix_a_suffix_and_the_middle() {
let suffix = pattern(vec![PatternToken::AnyRun, literal(".log")]);
assert!(matches(&suffix, "server.log"));
assert!(matches(&suffix, ".log"));
assert!(!matches(&suffix, "server.log.gz"));
let contains = pattern(vec![
PatternToken::AnyRun,
literal("err"),
PatternToken::AnyRun,
]);
assert!(matches(&contains, "an-error-here"));
assert!(!matches(&contains, "all-fine"));
}
#[test]
fn any_run_matches_zero_code_points() {
let subject = pattern(vec![literal("a"), PatternToken::AnyRun, literal("b")]);
assert!(matches(&subject, "ab"));
assert!(matches(&subject, "axxxb"));
}
#[test]
fn any_one_matches_exactly_one_code_point() {
let subject = pattern(vec![literal("a"), PatternToken::AnyOne, literal("c")]);
assert!(matches(&subject, "abc"));
assert!(!matches(&subject, "ac"));
assert!(!matches(&subject, "abbc"));
}
#[test]
fn any_one_consumes_a_whole_surrogate_pair() {
let name: Vec<u16> = "a\u{1F600}c".encode_utf16().collect();
assert_eq!(name.len(), 4);
let subject = pattern(vec![literal("a"), PatternToken::AnyOne, literal("c")]);
assert!(subject.matches(&Wtf16String::from_units(&name), CaseSensitivity::Sensitive));
}
#[test]
fn any_one_matches_an_unpaired_surrogate_as_one_code_point() {
let name = [0x0061, 0xD800, 0x0063];
let subject = pattern(vec![literal("a"), PatternToken::AnyOne, literal("c")]);
assert!(subject.matches(&Wtf16String::from_units(&name), CaseSensitivity::Sensitive));
}
#[test]
fn any_run_does_not_split_a_surrogate_pair() {
let name: Vec<u16> = "\u{1F600}\u{1F600}z".encode_utf16().collect();
let subject = pattern(vec![PatternToken::AnyRun, literal("z")]);
assert!(subject.matches(&Wtf16String::from_units(&name), CaseSensitivity::Sensitive));
}
#[test]
fn alternation_tries_every_arm() {
let subject = pattern(vec![
PatternToken::AnyRun,
PatternToken::Alternation(vec![
NamePattern::literal(&Wtf16String::from(".log")),
NamePattern::literal(&Wtf16String::from(".txt")),
]),
]);
assert!(matches(&subject, "a.log"));
assert!(matches(&subject, "a.txt"));
assert!(!matches(&subject, "a.bin"));
}
#[test]
fn alternation_arms_compose_with_what_follows_them() {
let subject = pattern(vec![
PatternToken::Alternation(vec![
NamePattern::from_tokens(vec![literal("a"), PatternToken::AnyRun]),
NamePattern::literal(&Wtf16String::from("zzz")),
]),
literal("end"),
]);
assert!(matches(&subject, "aend"));
assert!(matches(&subject, "axxend"));
assert!(matches(&subject, "zzzend"));
assert!(!matches(&subject, "bend"));
}
#[test]
fn an_empty_alternation_matches_nothing() {
let subject = pattern(vec![PatternToken::Alternation(Vec::new())]);
assert!(!matches(&subject, ""));
assert!(!matches(&subject, "a"));
}
#[test]
fn insensitive_matching_uses_the_ordinal_uppercase_table() {
let subject = NamePattern::literal(&Wtf16String::from("ReadMe.TXT"));
assert!(matches_ignoring_case(&subject, "readme.txt"));
assert!(matches_ignoring_case(&subject, "README.TXT"));
assert!(!matches_ignoring_case(&subject, "readme.txtx"));
assert!(!matches(&subject, "readme.txt"));
}
#[test]
fn insensitive_matching_works_inside_a_wildcard_pattern() {
let subject = pattern(vec![PatternToken::AnyRun, literal(".LOG")]);
assert!(matches_ignoring_case(&subject, "server.log"));
assert!(!matches(&subject, "server.log"));
}
#[test]
fn insensitive_matching_folds_non_ascii_letters() {
let subject = NamePattern::literal(&Wtf16String::from("\u{00E9}t\u{00E9}"));
assert!(matches_ignoring_case(&subject, "\u{00C9}T\u{00C9}"));
}
#[test]
fn a_name_with_an_interior_nul_is_compared_as_content() {
let left = [0x0061, 0x0000, 0x0062];
let right = [0x0061, 0x0000, 0x0063];
let subject = NamePattern::literal(&Wtf16String::from_units(&left));
assert!(subject.matches(
&Wtf16String::from_units(&left),
CaseSensitivity::Insensitive
));
assert!(!subject.matches(
&Wtf16String::from_units(&right),
CaseSensitivity::Insensitive
));
}
#[test]
fn a_pattern_can_be_built_by_chaining() {
let subject = NamePattern::empty()
.with(PatternToken::AnyRun)
.with(literal(".rs"));
assert_eq!(subject.tokens().len(), 2);
assert!(matches(&subject, "lib.rs"));
}
#[test]
fn push_appends_in_order() {
let mut subject = NamePattern::empty();
subject.push(literal("a"));
subject.push(PatternToken::AnyRun);
assert!(matches(&subject, "abc"));
assert!(!matches(&subject, "bac"));
}
#[test]
fn only_whole_names_match() {
let subject = NamePattern::literal(&Wtf16String::from("log"));
assert!(!matches(&subject, "logs"));
assert!(!matches(&subject, "catalog"));
}
#[test]
fn sensitive_is_the_default_case_rule() {
assert_eq!(CaseSensitivity::default(), CaseSensitivity::Sensitive);
}