Files
ww/lib/path/pathtest.ww
Hojun-Cho 3c7f1aa027 lib/path: #138 c3-posix dirname/basename — faithful posix.ha port, table-driven @test (cstage)
dirname/basename ported verbatim from ref/hare/path/posix.ha:17,37,
re-routing Hare's byte scanning to bytes.rtrim/rindex. Additive to the
c2-stack buffer core; no harness/Makefile/module-const changes.

Work-var renamed path->p to avoid Hare's param-shadowing
`let path = toutf8(path)`: ww's cgen N_LET prepends the new local into the
name-keyed localfind chain BEFORE the init is emitted, so the shadow-init
reads the fresh uninit slot — a silent miscompile, both-wrong-identical,
filed #152 and fixed independently next. drew ruled path->p a permanent
acceptable spelling (internal work-var, not API surface; avoids a
param-shadow footgun; not a rule-7 workaround). WHY-comment + #152 pointer
at both sites.

Tests: 10-row dirname/basename table (posix.ha:51-71 verbatim) — root,
no-sep, empty, trailing-sep, multi-sep all covered, expecteds traced.

C-first: cstage @test green; the str-view-vs-literal rows are #146-deferred
(wwstage byte-id, rides #125). Gate: all 324 passed, byte-id 990-997 green,
w6c/w6c_ww unchanged (lib-only additive).
2026-06-08 11:47:43 +09:00

158 lines
5.9 KiB
Plaintext

// pathtest — exercises lib/path (c2-stack subset). Run with
// `out/bin/ww run lib/path/pathtest.ww`. Same signalled-then-fail()
// -with-+10 pattern as bytes / getopt tests: a non-zero exit code
// pinpoints the failing scenario.
//
// 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 (getopttest.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;
import path;
import os;
let signalled: i32 = 0;
fn fail() void = { os.exit(signalled + 10); };
// ---- 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 { ... };
if (string(&buf) != ".") { fail(); };
// 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) {
if (push(&buf, segs[i])! != wants[i]) { fail(); };
i += 1;
};
// set(&buf) resets to "." (stack.ha:91). set forwards an EMPTY
// variadic into push (KEN-oracle flag 1: zero-arg gather+forward).
if (set(&buf)! != ".") { fail(); };
// 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) {
if (push(&buf, segs2[k])! != wants2[k]) { fail(); };
k += 1;
};
// multi-segment (stack.ha:107-111). Variadic arity varies (1 or 2)
// → inline.
if (push(&buf, "a", "b")! != "a/b") { fail(); };
if (push(&buf, "..", "c")! != "a/c") { fail(); };
if (push(&buf, "..")! != "a") { fail(); };
// stack.ha:110; local()→literal, SEP='/' on Linux, proper local() rides c3
if (push(&buf, "/d")! != "a/d") { fail(); };
if (push(&buf, "..", "..")! != ".") { fail(); }; // 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).
if (set(&buf)! != ".") { fail(); };
if (push(&buf, "/foo")! != "/foo") { fail(); };
};
// ---- isroot() ---------------------------------------------------------
// ref/hare/path/buffer.ha:44-51 (*buffer arm). Heterogeneous seeds
// (manual root seed vs push) → inline rather than a uniform table.
@test fn isroot_cases() void = {
let buf = buffer { ... };
// empty buffer (end 0)
if (isroot(&buf)) { fail(); };
// "/" — root. local() is c3, so seed the SEP byte directly.
buf.buf[0] = SEP; buf.end = 1;
if (!isroot(&buf)) { fail(); };
if (string(&buf) != "/") { fail(); };
// "foo" — relative, not root.
buf.end = 0;
if (push(&buf, "foo")! != "foo") { fail(); };
if (isroot(&buf)) { fail(); };
// "/foo" — absolute but not root.
buf.buf[0] = SEP; buf.end = 1;
if (push(&buf, "foo")! != "/foo") { fail(); };
if (isroot(&buf)) { fail(); };
};
// ---- 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 { ... };
if (string(&buf) != ".") { fail(); };
if (push(&buf, "foo")! != "foo") { fail(); };
if (string(&buf) != "foo") { fail(); };
};
// ---- 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) {
if (dirname(inputs[i]) != wantdir[i]) { fail(); };
if (basename(inputs[i]) != wantbase[i]) { fail(); };
i += 1;
};
};
export fn main() i32 = {
signalled = 1; push_cases();
signalled = 2; isroot_cases();
signalled = 3; string_cases();
signalled = 4; dirname_basename_cases();
return 0;
};