// fnmatchtest — exercises lib/fnmatch. Run with // `out/bin/ww run lib/fnmatch/fnmatchtest.ww`. // // Table-driven via the [[check]] helper: every row is the uniform // 4-tuple `(pattern, string, expected, flags)`. Cases follow Hare's // ref/hare/fnmatch/+test.ha (the de-facto spec for this port); // multibyte UTF-8 rows (わたし, ε) are skipped — our matcher is // byte-wise and those would match byte-identically but obscure the // ASCII semantics the test is asserting. // // Failure path: a miscompare aborts via the assert/abort builtin // (task #5 @test conversion). package fnmatch_test; import fnmatch; // check — pin one row. `flags` is taken as i32 and cast to // fnmatch.flag so the test rows can read as integer bitmasks // without dragging enum literals into every line. fn check(pat: str, s: str, expected: bool, flags: i32) void = { let f: fnmatch.flag = flags as fnmatch.flag; assert(!(fnmatch.fnmatch(pat, s, f) != expected)); }; // ---- basic literal / wildcard cases --------------------------------- @test fn basic() void = { check("a", "a", true, 0); check("b", "b", true, 0); check("\0", "\0", true, 0); check("abcde", "abcde", true, 0); check("aaa", "bbb", false, 0); check("ab*cde", "abcde", true, 0); check("g*a", "gordana", true, 0); check("ab*cde*foo*bar", "abcdefooba", false, 0); check("*", "foo", true, 0); check("aa*", "aafoo", true, 0); check("bb*", "foo", false, 0); check("*cc", "foocc", true, 0); check("*dd", "foo", false, 0); check("ra**ra", "rarara", true, 0); check("x*yy*x", "xxyyyyyxxx", true, 0); check("*", "*", true, 0); check("*", "", true, 0); check("****", "a", true, 0); check("**a**", "a", true, 0); check("****", "", true, 0); check("?", "*", true, 0); check("?", "", false, 0); check("??", "a", false, 0); check("??", "abc", false, 0); check("?aa", "bbb", false, 0); check("**?**", "", false, 0); check("*?*?*?*?", "abcd", true, 0); check("*?*?*?*?", "abc", false, 0); }; // ---- bracket expressions: literal / range / negation ---------------- @test fn brackets() void = { check("[b]", "b", true, 0); check("a[b]c", "abc", true, 0); check("a[b]c", "axc", false, 0); check("[a-c]", "b", true, 0); check("[a-z]", "a", true, 0); check("[c-a]", "b", false, 0); check("x[a-c]y", "xay", true, 0); check("x[a-c]y", "xby", true, 0); check("x[a-c]y", "xcy", true, 0); check("x[a-c]y", "xzy", false, 0); check("x[a-c]y", "xy", false, 0); check("[a-c]*[a-c]", "axxxb", true, 0); // Special members: '-' at start/end, ']' as first member. check("[-]", "-", true, 0); check("[.]", ".", true, 0); check("[:ias]", ":", true, 0); check("[-]", "a", false, 0); check("[-ac]", "a", true, 0); check("[-ac]", "-", true, 0); check("[-ac]", "b", false, 0); check("[ac-]", "a", true, 0); check("[ac-]", "-", true, 0); check("[ac-]", "b", false, 0); // Equivalence-class-like syntax: invalid in Hare/POSIX. check("[.a.]", "a", false, 0); // Negation with '!'. check("[!b]", "b", false, 0); check("a[!b]c", "abc", false, 0); check("a[!b]c", "axc", true, 0); check("[!a-c]", "b", false, 0); check("[!c-a]", "b", true, 0); check("x[!a-c]y", "xay", false, 0); check("x[!a-c]y", "xby", false, 0); check("x[!a-c]y", "xcy", false, 0); check("x[!a-c]y", "xzy", true, 0); check("x[!a-c]y", "xy", false, 0); check("[!a-c]*[!a-c]", "axxxb", false, 0); check("[!-]", "-", false, 0); check("[!-]", "a", true, 0); check("[!-ac]", "a", false, 0); check("[!-ac]", "-", false, 0); check("[!-ac]", "b", true, 0); }; // ---- POSIX character classes: [[:alnum:]] / [[:alpha:]] / ... ------- @test fn ctype() void = { check("[[:alnum:]]", "7", true, 0); check("[[:alpha:]]", "[", false, 0); check("[[:alpha:]]", "[[", false, 0); check("[[alpha:]]", "a]", true, 0); check("[[alpha:]]", ":]", true, 0); check("[[:alpha:]]", "a", true, 0); check("[[:blank:]]", " ", true, 0); // pin the ascii.is{blank,space,print,graph} predicates: their // char-literal bounds are reachable only through these classes. check("[[:blank:]]", "\t", true, 0); check("[[:space:]]", " ", true, 0); check("[[:space:]]", "\t", true, 0); check("[[:space:]]", "x", false, 0); check("[[:print:]]", "~", true, 0); check("[[:print:]]", "\t", false, 0); check("[[:graph:]]", "a", true, 0); check("[[:graph:]]", " ", false, 0); check("[[:alnum:]]a", "a]a", false, 0); check("[[:alnum:][[:digit:]]", "a", true, 0); check("[![:alnum:]]", "a", false, 0); check("[![:alpha:]]", "[", true, 0); check("[![:alnum:]]a", "a]a", false, 0); check("[![:alpha:]]a", "[a", true, 0); check("[![:alnum:][:digit:]]", "a", false, 0); }; // ---- flag.PERIOD: leading '.' must be literal in the pattern -------- @test fn period() void = { let fp: i32 = 4; // flag.PERIOD check(".", ".", true, fp); check("*", ".", false, fp); check("?", ".", false, fp); check("[.]", ".", false, fp); check(".*", ".asdf", true, fp); check(".*", "asdf", false, fp); }; // ---- flag.NOESCAPE: '\\' loses its escape meaning ------------------- @test fn noescape() void = { let nesc: i32 = 2; // flag.NOESCAPE check("\\", "\\", true, nesc); check("\\*", "\\asdf", true, nesc); }; // ---- musl-adapted cases (no flags) --------------------------------- @test fn musl_basic() void = { check("*.c", "foo.c", true, 0); check("*.c", ".c", true, 0); check("*.a", "foo.c", false, 0); check("*.c", ".foo.c", true, 0); check("a\\*.c", "ax.c", false, 0); check("a[xy].c", "ax.c", true, 0); check("a[!y].c", "ax.c", true, 0); check("-O[01]", "-O1", true, 0); check("[[?*\\]", "\\", true, 0); check("[]?*\\]", "]", true, 0); check("[!]a-]", "b", true, 0); check("[]-_]", "^", true, 0); check("[!]-_]", "X", true, 0); check("??", "-", false, 0); // Without PATHNAME, `[...]` and `?` happily eat '/'. check("[*]/b", "a/b", false, 0); check("[*]/b", "*/b", true, 0); check("[?]/b", "a/b", false, 0); check("[?]/b", "?/b", true, 0); check("[[a]/b", "a/b", true, 0); check("[[a]/b", "[/b", true, 0); check("\\*/b", "a/b", false, 0); check("\\*/b", "*/b", true, 0); check("\\?/b", "a/b", false, 0); check("\\?/b", "?/b", true, 0); check("[/b", "[/b", false, 0); check("\\[/b", "[/b", true, 0); check("a[/]b", "a/b", true, 0); // "[![:d-d]" — bracket carrying a class-name start without // proper close-bracket is treated as a non-class literal set. // Hare's table asserts false in all three rows. check("[![:d-d]", "b", false, 0); check("[[:d-d]", "[", false, 0); check("[![:d-d]", "[", false, 0); }; // ---- flag.PATHNAME: '/' must be matched by literal '/' -------------- @test fn pathname() void = { let fp: i32 = 1; // flag.PATHNAME check("[a-z]/[a-z]", "a/b", true, fp); check("a[/]b", "a/b", false, fp); check("*", "a/b", false, fp); check("*[/]b", "a/b", false, fp); check("*[b]", "a/b", false, fp); check("a[a/z]*.c", "a/x.c", false, fp); check("a/*.c", "a/x.c", true, fp); check("a*.c", "a/x.c", false, fp); check("*/foo", "/foo", true, fp); check("???b", "aa/b", false, fp); }; // ---- flag.PERIOD + flag.PATHNAME combined --------------------------- @test fn combined() void = { let pp: i32 = 1; // PATHNAME let fp: i32 = 4; // PERIOD let pf: i32 = pp | fp; check("?a/b", ".a/b", false, pf); check("a/?b", "a/.b", false, pf); check("*a/b", ".a/b", false, pf); check("a/*b", "a/.b", false, pf); check("[.]a/b", ".a/b", false, pf); check("a/[.]b", "a/.b", false, pf); check("*/?", "a/b", true, pf); check("?/*", "a/b", true, pf); check(".*/?", ".a/b", true, pf); check("*/.?", "a/.b", true, pf); check("*/*", "a/.b", false, pf); check("*?*/*", "a/.b", true, fp); check("*[.]/b", "a./b", true, pf); check("*[[:alpha:]]/*[[:alnum:]]", "a/b", true, pp); check("a?b", "a.b", true, pf); check("a*b", "a.b", true, pf); check("a[.]b", "a.b", true, pf); check("*.c", ".foo.c", false, fp); check("*.c", "foo.c", true, fp); let nesc: i32 = 2; // NOESCAPE check("a\\*.c", "a*.c", false, nesc); };