Unblocked by the package-mode ruling: same-package test files now
compose everywhere (coordinator dir-mode owns test-library and the
libbyteid dir entry owns byte identity), so the per-file standalone
constraint is gone and Go's stdlib layout applies directly. The
15-banner strings_test.ww monolith dissolves into per-ref-file
family files — dup, concat, trim, sub, utf8, iter, tokenize
(tokenize+splitn+cut, all of ref tokenize.ha), pad, replace — plus
helpers_test.ww holding exactly the one cross-family helper (streq),
Go's shared test-helper idiom. Pure moves; family-local helpers
(expect_str_token et al.) stay in their family file. Leading banners
that merely restated the filename dropped; 6 interior sub-family
boundaries remain. lib/ census 141 -> 132.
libbyteid gains the dx() dir-mode entry form decided in B2: compose
every *_test.ww under moddir exactly as pkgcombined does
(module-reset separators, byte-lex order) and byte-id the composed
root through both driver stages. The five per-file strings fx
entries retire into one dx("lib/strings") — every source line they
covered is inside the composed unit, and the completeness scan still
keys the module. NENTEXPECT 60 -> 56.
Recorded, not split: fmt_test's 13 banners are scenario groups over
the one fprint surface and ref/hare/fmt itself keeps a single
+test.ha — a print/wrappers split would fight the ref; memio stays
per the standing ruling (everything ww ports lives in ref
stream.ha).
108 lines
3.8 KiB
Plaintext
108 lines
3.8 KiB
Plaintext
// Vectors mirror ref/hare/strings/pad.ha where ww can express them.
|
||
// External strings_test package per the Go foo_test idiom (rule-5/9
|
||
// carve-out, task #16); the shared streq helper lives in
|
||
// helpers_test.ww — same-package test files compose in dir-mode
|
||
// (the package, not the file, is the unit of testing).
|
||
package strings_test;
|
||
|
||
import strings;
|
||
import os;
|
||
|
||
// ref/hare/strings/pad.ha:23 (@test fn lpad), :54 (@test fn rpad). Hare's
|
||
// row triple (s<maxlen, s==maxlen, s=="" empty) is mirrored; ww extras
|
||
// pin s>maxlen (early return path), multibyte s with byte-length
|
||
// counting, and multibyte pad rune (encoded width >1 byte). The
|
||
// multibyte-pad rows pin Hare's `[..maxlen]` slice contract — a
|
||
// multibyte trailing pad may be truncated mid-codepoint, exactly as
|
||
// Hare does.
|
||
|
||
@test fn lpad_cases() void = {
|
||
// Hare row 1: shorter s, ASCII pad.
|
||
let r1: str = strings.lpad("2", '0', 5);
|
||
assert(!(!streq(r1, "00002")));
|
||
assert(!(r1.len != 5));
|
||
os.free(r1.ptr: *void, r1.len: u64);
|
||
|
||
// Hare row 2: s.len == maxlen — early dup path.
|
||
let r2: str = strings.lpad("12345", '0', 5);
|
||
assert(!(!streq(r2, "12345")));
|
||
assert(!(r2.len != 5));
|
||
os.free(r2.ptr: *void, r2.len: u64);
|
||
|
||
// Hare row 3: empty s, full-width pad.
|
||
let r3: str = strings.lpad("", '0', 5);
|
||
assert(!(!streq(r3, "00000")));
|
||
assert(!(r3.len != 5));
|
||
os.free(r3.ptr: *void, r3.len: u64);
|
||
|
||
// ww row 1: s.len > maxlen — early dup path returns input copy.
|
||
let r4: str = strings.lpad("abcdef", '_', 3);
|
||
assert(!(!streq(r4, "abcdef")));
|
||
assert(!(r4.len != 6));
|
||
os.free(r4.ptr: *void, r4.len: u64);
|
||
|
||
// ww row 2: multibyte s — byte-length contract (Hare `len(s)`).
|
||
// "café" is 5 bytes; maxlen 7 → 2 pad bytes prepended.
|
||
let r5: str = strings.lpad("café", '_', 7);
|
||
assert(!(!streq(r5, "__café")));
|
||
assert(!(r5.len != 7));
|
||
os.free(r5.ptr: *void, r5.len: u64);
|
||
|
||
// ww row 3: multibyte pad rune. 'α' U+03B1 is 2 bytes (0xCE 0xB1).
|
||
// s="x" (1 byte), maxlen=5 → npads = 5 - 1 = 4, pad_bytes=2,
|
||
// total writes = 4*2 + 1 = 9 bytes; result `[..5]` = "αα" (4 bytes)
|
||
// + 0xCE (truncated leading byte of next α) — exactly Hare's slice.
|
||
let r6: str = strings.lpad("x", 0x03B1u32: rune, 5);
|
||
assert(!(r6.len != 5));
|
||
assert(!(r6.ptr[0] != 0xCEu8)); // α byte 0
|
||
assert(!(r6.ptr[1] != 0xB1u8)); // α byte 1
|
||
assert(!(r6.ptr[2] != 0xCEu8)); // α byte 0
|
||
assert(!(r6.ptr[3] != 0xB1u8)); // α byte 1
|
||
assert(!(r6.ptr[4] != 0xCEu8)); // truncated α byte 0
|
||
os.free(r6.ptr: *void, r6.len: u64);
|
||
};
|
||
|
||
@test fn rpad_cases() void = {
|
||
// Hare row 1: shorter s, ASCII pad.
|
||
let r1: str = strings.rpad("2", '0', 5);
|
||
assert(!(!streq(r1, "20000")));
|
||
assert(!(r1.len != 5));
|
||
os.free(r1.ptr: *void, r1.len: u64);
|
||
|
||
// Hare row 2: s.len == maxlen — early dup path.
|
||
let r2: str = strings.rpad("12345", '0', 5);
|
||
assert(!(!streq(r2, "12345")));
|
||
assert(!(r2.len != 5));
|
||
os.free(r2.ptr: *void, r2.len: u64);
|
||
|
||
// Hare row 3: empty s.
|
||
let r3: str = strings.rpad("", '0', 5);
|
||
assert(!(!streq(r3, "00000")));
|
||
assert(!(r3.len != 5));
|
||
os.free(r3.ptr: *void, r3.len: u64);
|
||
|
||
// ww row 1: s.len > maxlen — early dup path.
|
||
let r4: str = strings.rpad("abcdef", '_', 3);
|
||
assert(!(!streq(r4, "abcdef")));
|
||
assert(!(r4.len != 6));
|
||
os.free(r4.ptr: *void, r4.len: u64);
|
||
|
||
// ww row 2: multibyte s — byte-length contract.
|
||
let r5: str = strings.rpad("café", '_', 7);
|
||
assert(!(!streq(r5, "café__")));
|
||
assert(!(r5.len != 7));
|
||
os.free(r5.ptr: *void, r5.len: u64);
|
||
|
||
// ww row 3: multibyte pad rune (2-byte 'α'). s="x" (1 byte),
|
||
// maxlen=5 → "xαα" appended is 1 + 4 = 5 bytes (no truncation).
|
||
let r6: str = strings.rpad("x", 0x03B1u32: rune, 5);
|
||
assert(!(r6.len != 5));
|
||
assert(!(r6.ptr[0] != 'x'));
|
||
assert(!(r6.ptr[1] != 0xCEu8)); // α byte 0
|
||
assert(!(r6.ptr[2] != 0xB1u8)); // α byte 1
|
||
assert(!(r6.ptr[3] != 0xCEu8)); // α byte 0
|
||
assert(!(r6.ptr[4] != 0xB1u8)); // α byte 1
|
||
os.free(r6.ptr: *void, r6.len: u64);
|
||
};
|
||
|