diff --git a/Makefile b/Makefile index f2d2eadc..8f1f6399 100644 --- a/Makefile +++ b/Makefile @@ -497,7 +497,8 @@ LIBRARY_TESTS = lib/errors/errno_test.ww lib/ascii/ascii_test.ww \ lib/encoding/base64/base64_test.ww \ lib/hash/adler32/adler32_test.ww lib/hash/crc16/crc16_test.ww \ lib/hash/crc32/crc32_test.ww lib/hash/crc64/crc64_test.ww \ - lib/path/path_test.ww lib/crypto/sha256/sha256_test.ww \ + lib/path/buffer_test.ww lib/path/posix_test.ww \ + lib/path/stack_test.ww lib/crypto/sha256/sha256_test.ww \ lib/hash/siphash/siphash_test.ww \ lib/os/os_test.ww lib/os/stat_test.ww lib/dirs/dirs_test.ww diff --git a/lib/path/buffer_test.ww b/lib/path/buffer_test.ww new file mode 100644 index 00000000..ad4e2e70 --- /dev/null +++ b/lib/path/buffer_test.ww @@ -0,0 +1,101 @@ +package path_test; + +import path; + + +// 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 +}; + +// 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; + }; +}; + +// 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")); +}; + +// 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; + }; +}; diff --git a/lib/path/path_test.ww b/lib/path/path_test.ww deleted file mode 100644 index cb001890..00000000 --- a/lib/path/path_test.ww +++ /dev/null @@ -1,269 +0,0 @@ -// 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) != "/")); -}; diff --git a/lib/path/posix_test.ww b/lib/path/posix_test.ww new file mode 100644 index 00000000..e0b13b9a --- /dev/null +++ b/lib/path/posix_test.ww @@ -0,0 +1,36 @@ +package path_test; + +import path; + + +// 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; + }; +}; diff --git a/lib/path/stack_test.ww b/lib/path/stack_test.ww new file mode 100644 index 00000000..01305e90 --- /dev/null +++ b/lib/path/stack_test.ww @@ -0,0 +1,128 @@ +// 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; + + +// 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")); +}; + +// 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; + }; +}; + +// 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) != "/")); +};