// Two cohorts, grouped Hare-style — one @test fn per cohort, table- // driven inside via per-arity helpers: // // • split rows go through [[check1]] / [[check2]] / [[check3]] / // [[checkerr]]. Inputs are lifted from ref/hare/shlex/+test.ha // @test fn split() (the de-facto spec for this port). Per-row // arity is fixed (1, 2, or 3 expected tokens across the Hare // cases), so we ship per-arity helpers rather than a full // variadic check that would obscure the row data. // // • quote rows go through [[checkquote]] — uniform (input, expected) // shape against a memio.dynamic sink, mirroring Hare's escape.ha // testquote table. // // Failure path: a miscompare aborts via the assert/abort builtin // (task #5 @test conversion). package shlex_test; import shlex; import io; import memio; fn streq(a: str, b: str) bool = { if (a.len != b.len) { return false; }; let i: i32 = 0; for (i < a.len) { if (a[i] != b[i]) { return false; }; i += 1; }; return true; }; // checkN — split `in` and assert the result is a slice of length N // matching the named expected tokens. Leaks the result (test process // is short-lived; same precedent as fnmatchtest). fn check1(in: str, e0: str) void = { let r = shlex.split(in); match (r) { case shlex.syntaxerr => { abort(); }; case let s: []str => { assert(!(s.len != 1)); assert(!(!streq(s[0], e0))); }; }; }; fn check2(in: str, e0: str, e1: str) void = { let r = shlex.split(in); match (r) { case shlex.syntaxerr => { abort(); }; case let s: []str => { assert(!(s.len != 2)); assert(!(!streq(s[0], e0))); assert(!(!streq(s[1], e1))); }; }; }; fn check3(in: str, e0: str, e1: str, e2: str) void = { let r = shlex.split(in); match (r) { case shlex.syntaxerr => { abort(); }; case let s: []str => { assert(!(s.len != 3)); assert(!(!streq(s[0], e0))); assert(!(!streq(s[1], e1))); assert(!(!streq(s[2], e2))); }; }; }; fn checkerr(in: str) void = { let r = shlex.split(in); match (r) { case shlex.syntaxerr => {}; case let s: []str => { abort(); }; }; }; fn checkempty(in: str) void = { let r = shlex.split(in); match (r) { case shlex.syntaxerr => { abort(); }; case let s: []str => { assert(!(s.len != 0)); }; }; }; fn checkquote(in: str, expected: str) void = { let st: memio.stream = memio.dynamic(); let snk: io.stream = &st.vt; let r = shlex.quote(snk, in); let n: size = 0; match (r) { case let v: size => { n = v; }; case let _e: io.error => { abort(); }; }; assert(!(n != expected.len: size)); let view: str = memio.string(&st); assert(!(!streq(view, expected))); let _c: (void | io.error) = io.close(snk); }; // 9 success rows + 3 syntaxerr rows ported VERBATIM from // ref/hare/shlex/+test.ha; plus one ww-specific edge (empty input → // empty []str) confirmed by drew. // // Local @test fns are `test_*`-prefixed because `use shlex;` flat- // concats shlex's exported names (split / quote / quotestr / strerror) // into the fixture's namespace, and bare `fn split() ...` would // duplicate-define them. Retires when task #17 (cgen mod-mangles fn // labels) lands. @test fn test_split() void = { check1("hello\\ world", "hello world"); check1("'hello\\ world'", "hello\\ world"); check1("\"hello\\\\world\"", "hello\\world"); // "hello "'"'"world"'"' → hello "world" check1("\"hello \"'\"'\"world\"'\"'", "hello \"world\""); check3("hello '' world", "hello", "", "world"); check2("Empty ''", "Empty", ""); check2(" Leading spaces", "Leading", "spaces"); check3("with\\ backslashes 'single quoted' \"double quoted\"", "with backslashes", "single quoted", "double quoted"); check2("'multiple spaces' 42", "multiple spaces", "42"); // pin the '\t'/'\n' arms of split's whitespace test. check3("a\tb\nc", "a", "b", "c"); // Invalid checkerr("\"dangling double quote"); checkerr("'dangling single quote"); checkerr("unterminated\\ backslash \\"); // Empty input → empty []str (ww edge confirmed by drew). checkempty(""); }; // 4 rows from ref/hare/shlex/+test.ha @test fn quote(). The empty- // input row (→ `''`) is implementation-specific (Hare's testquote // doesn't cover it) but is documented behaviour per shlex.ww's // quote() header — exercised here so the contract is load-bearing. @test fn test_quote() void = { checkquote("hello", "hello"); checkquote("hello world", "'hello world'"); checkquote("'hello' \"world\"", "''\"'\"'hello'\"'\"' \"world\"'"); checkquote("hello\\world", "'hello\\world'"); checkquote("", "''"); // pin issafe's special-char set: all safe → emitted raw, unquoted. checkquote("@%+=:,./-", "@%+=:,./-"); }; @test fn test_quotestr() void = { let r: str = shlex.quotestr("hello world"); assert(!(!streq(r, "'hello world'"))); // leak r — short-lived test process, same precedent as fnmatchtest. }; @test fn test_strerror() void = { let e: shlex.syntaxerr; let s: str = shlex.strerror(e); assert(!(!streq(s, "Invalid shell syntax"))); };