// pathtest — exercises lib/path (c2-stack subset). Run with // `out/bin/ww run lib/path/path_test.ww`. A failing row aborts via the // assert/abort builtin (task #5 @test conversion). // // Vectors mirror Hare's @test fns in ref/hare/path/stack.ha:77-119 // and buffer.ha. The absolute rows (Hare's `local("/")`-seeded) DEFER // to c3 (abs/local unimplemented); only the relative rows run here. // // Parallel `[N]str` row arrays (rather than `[N]struct{...}`) sidestep // the cstage cgen chained `arr[i].field` store gap (getopt_test.ww:6). // push is stateful, so each row is applied in sequence to one buffer; // the table holds {segment, expected-string} pairs (stack.ha:36-42 is // the normalization spec these rows encode). package path_test; import path; // ---- push() + appendnorm normalization -------------------------------- // ref/hare/path/stack.ha:77-119 (relative rows only; absolute local() // rows defer to c3). The dot/dotdot handling exercised here IS the // appendnorm normalization spec (stack.ha:36-42), so no separate // appendnorm @test is needed. @test fn push_cases() void = { let buf = buffer { ... }; assert(!(string(&buf) != ".")); // current-dir + parent-dir invariants (stack.ha:82-88). Single // segment per row → table-driven sequential apply. let segs: [5]str; let wants: [5]str; segs[0]=""; wants[0]="."; segs[1]="."; wants[1]="."; segs[2]=".."; wants[2]=".."; segs[3]=""; wants[3]=".."; segs[4]="."; wants[4]=".."; let i: i32 = 0; for (i < 5) { assert(!(push(&buf, segs[i])! != wants[i])); i += 1; }; // set(&buf) resets to "." (stack.ha:91). set forwards an EMPTY // variadic into push (KEN-oracle flag 1: zero-arg gather+forward). assert(!(set(&buf)! != ".")); // regular path + parent (stack.ha:101-104, minus the local() row). let segs2: [3]str; let wants2: [3]str; segs2[0]="foo"; wants2[0]="foo"; segs2[1]="."; wants2[1]="foo"; segs2[2]=".."; wants2[2]="."; let k: i32 = 0; for (k < 3) { assert(!(push(&buf, segs2[k])! != wants2[k])); k += 1; }; // multi-segment (stack.ha:107-111). Variadic arity varies (1 or 2) // → inline. assert(!(push(&buf, "a", "b")! != "a/b")); assert(!(push(&buf, "..", "c")! != "a/c")); assert(!(push(&buf, "..")! != "a")); // stack.ha:110; local()→literal, SEP='/' on Linux, proper local() rides c3 assert(!(push(&buf, "/d")! != "a/d")); assert(!(push(&buf, "..", "..")! != ".")); // stack.ha:111 // leading-SEP segment into an EMPTY buffer exercises push's // `j==0 && buf.end==0` root-seed arm (stack.ha:18-20), unreached // by the relative rows above. Hare covers it via local("/")-seeded // rows that defer to c3; literal "/foo" stands in (SEP='/' on Linux). assert(!(set(&buf)! != ".")); assert(!(push(&buf, "/foo")! != "/foo")); }; // ---- abs() ------------------------------------------------------------ // ref/hare/path/buffer.ha:34-41. str literals widen into the (*buffer|str) // union arg (str-arm = hasprefix vs sepstr); buffer-arm = byte. Parallel // [N]str inputs + [N]i32 wantbool (no [N]bool in the stack; cgen-field gap). @test fn abs_cases() void = { let ins: [4]str; let want: [4]i32; ins[0]="/"; want[0]=1; ins[1]="/foo"; want[1]=1; ins[2]="foo"; want[2]=0; ins[3]=""; want[3]=0; let i: i32 = 0; for (i < 4) { let got: i32 = 0; if (abs(ins[i])) { got = 1; }; assert(!(got != want[i])); i += 1; }; // buffer-arm: &buf ptr-ident-widens into the union. let buf = buffer { ... }; assert(!(abs(&buf))); // empty → false assert(!(push(&buf, "/foo")! != "/foo")); assert(!(!abs(&buf))); // "/foo" → true buf.end = 0; assert(!(push(&buf, "foo")! != "foo")); assert(!(abs(&buf))); // "foo" → false }; // ---- isroot() --------------------------------------------------------- // ref/hare/path/buffer.ha:44-51. Both arms: str (== sepstr) and *buffer // (byte+int). Heterogeneous buffer seeds (manual root seed vs push) → // inline; str-arm rows table-driven. @test fn isroot_cases() void = { let buf = buffer { ... }; // empty buffer (end 0) assert(!(isroot(&buf))); // "/" — root. local() is c3, so seed the SEP byte directly. buf.buf[0] = SEP; buf.end = 1; assert(!(!isroot(&buf))); assert(!(string(&buf) != "/")); // "foo" — relative, not root. buf.end = 0; assert(!(push(&buf, "foo")! != "foo")); assert(!(isroot(&buf))); // "/foo" — absolute but not root. buf.buf[0] = SEP; buf.end = 1; assert(!(push(&buf, "foo")! != "/foo")); assert(!(isroot(&buf))); // str-arm (buffer.ha:47): only the bare separator is root. let ins: [4]str; let want: [4]i32; ins[0]="/"; want[0]=1; ins[1]="/foo"; want[1]=0; ins[2]="foo"; want[2]=0; ins[3]=""; want[3]=0; let i: i32 = 0; for (i < 4) { let got: i32 = 0; if (isroot(ins[i])) { got = 1; }; assert(!(got != want[i])); i += 1; }; }; // ---- string() --------------------------------------------------------- // ref/hare/path/buffer.ha:28-31. Empty buffer views as "."; a filled // buffer views the byte prefix. @test fn string_cases() void = { let buf = buffer { ... }; assert(!(string(&buf) != ".")); assert(!(push(&buf, "foo")! != "foo")); assert(!(string(&buf) != "foo")); }; // ---- dirname() / basename() ------------------------------------------- // ref/hare/path/posix.ha:51-71. Hare seeds each row through _local/local() // for cross-platform SEP; on ww (Linux, SEP='/') that is identity, so the // raw str literals stand in directly (local/_local defer to c3). POSIX // dirname/basename do NOT normalize the input (posix.ha:14-15,35), so no // seeding is needed. Parallel `[N]str` rows (cgen chained-field gap, as // above). Rows whose expected is a borrowed VIEW (not a "."/"/" literal) // compare a computed str to a literal — cstage-correct; wwstage mis- // compares via #146 (str== over a frombytes result), and this @test is // cstage-gated, so those rows are #146-deferred on wwstage only. @test fn dirname_basename_cases() void = { let inputs: [10]str; let wantdir: [10]str; let wantbase: [10]str; inputs[0]="usr"; wantdir[0]="."; wantbase[0]="usr"; inputs[1]="usr/"; wantdir[1]="."; wantbase[1]="usr"; inputs[2]=""; wantdir[2]="."; wantbase[2]="."; inputs[3]="/"; wantdir[3]="/"; wantbase[3]="/"; inputs[4]="//"; wantdir[4]="/"; wantbase[4]="/"; // impl-defined inputs[5]="///"; wantdir[5]="/"; wantbase[5]="/"; inputs[6]="/usr/"; wantdir[6]="/"; wantbase[6]="usr"; inputs[7]="/usr/lib"; wantdir[7]="/usr"; wantbase[7]="lib"; inputs[8]="//usr//lib//"; wantdir[8]="//usr"; wantbase[8]="lib"; inputs[9]="/home//dwc//test"; wantdir[9]="/home//dwc"; wantbase[9]="test"; let i: i32 = 0; for (i < 10) { assert(!(dirname(inputs[i]) != wantdir[i])); assert(!(basename(inputs[i]) != wantbase[i])); i += 1; }; }; // ---- local() ---------------------------------------------------------- // ref/hare/path/buffer.ha:55-77. Rewrites '/'→SEP into a static buffer; // on Linux (SEP=='/') that is identity. The returned view borrows the // module-private localbuf, so compare each row before the next call. @test fn local_cases() void = { let ins: [4]str; let want: [4]str; ins[0]="a/b"; want[0]="a/b"; ins[1]="/"; want[1]="/"; ins[2]="foo"; want[2]="foo"; ins[3]=""; want[3]=""; let i: i32 = 0; for (i < 4) { assert(!(local(ins[i])! != want[i])); i += 1; }; }; // ---- parent() --------------------------------------------------------- // ref/hare/path/stack.ha:172-180. Parent dir; root → root; empty → "..". // Each row reseeds a reset buffer (parent goes through appendnorm). push's // normalized echo is dropped so the empty-buffer row ("" pushes to ".", // not "") can share the table. @test fn parent_cases() void = { let buf = buffer { ... }; let ins: [5]str; let want: [5]str; ins[0]="/usr/lib"; want[0]="/usr"; ins[1]="foo"; want[1]="."; ins[2]="/"; want[2]="/"; ins[3]="a/b/c"; want[3]="a/b"; ins[4]=""; want[4]=".."; // empty buffer: appendnorm(dotdot) → ".." let i: i32 = 0; for (i < 5) { buf.end = 0; push(&buf, ins[i])!; assert(!(parent(&buf)! != want[i])); i += 1; }; }; // ---- split() / pop() / peek() ----------------------------------------- // ref/hare/path/stack.ha:147-170 (@test pop). split is the helper, covered // through pop/peek. Sequential off ONE buffer, reset buf.end=0 per case. // The peek-then-string-unchanged assert is load-bearing: it pins that the // #48 respelling didn't smuggle in pop's buf.end mutation. @test fn popsplit_cases() void = { let buf = buffer { ... }; // empty assert(!(!(pop(&buf) is void))); assert(!(string(&buf) != ".")); // root dir buf.end = 0; assert(!(push(&buf, "/")! != "/")); assert(!(!(pop(&buf) is void))); assert(!(string(&buf) != "/")); // relative file — peek is NON-mutating buf.end = 0; assert(!(push(&buf, "foo")! != "foo")); assert(!(peek(&buf) as str != "foo")); assert(!(string(&buf) != "foo")); assert(!(pop(&buf) as str != "foo")); assert(!(string(&buf) != ".")); // absolute file buf.end = 0; assert(!(push(&buf, "/foo")! != "/foo")); assert(!(peek(&buf) as str != "foo")); assert(!(pop(&buf) as str != "foo")); assert(!(string(&buf) != "/")); };