lib: split strings tests per ref/hare/strings families + helpers file

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).
This commit is contained in:
2026-08-08 19:42:49 +09:00
parent 5a263cb115
commit 51cb8e7d64
12 changed files with 1483 additions and 1387 deletions

110
lib/strings/replace_test.ww Normal file
View File

@@ -0,0 +1,110 @@
// Vectors mirror ref/hare/strings/replace.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/replace.ha:46 (#4). Hare rows 1-5 (target found
// once / multiple times / shorter / longer / multibyte) plus ww rows
// (no-match returns fresh copy, empty-replacement, result-shrinks-to-
// empty).
@test fn replace_cases() void = {
// Hare row 1: target found once.
match (strings.replace("Hello world!", "world", "there")) {
case let s: str => {
assert(!(!streq(s, "Hello there!")));
os.free(s.ptr: *void, s.len: u64);
};
case nomem => { abort(); };
};
// Hare row 2: needle found four times (replacement same length).
match (strings.replace("I like dogs, dogs, birds, dogs", "dogs", "cats")) {
case let s: str => {
assert(!(!streq(s, "I like cats, cats, birds, cats")));
os.free(s.ptr: *void, s.len: u64);
};
case nomem => { abort(); };
};
// Hare row 3: replacement shorter than needle, non-overlapping
// matches (Hare advances by needle.len, not 1).
match (strings.replace("aaaaaa", "aa", "a")) {
case let s: str => {
assert(!(!streq(s, "aaa")));
os.free(s.ptr: *void, s.len: u64);
};
case nomem => { abort(); };
};
// Hare row 4: replacement longer than needle.
match (strings.replace("aaa", "a", "aa")) {
case let s: str => {
assert(!(!streq(s, "aaaaaa")));
os.free(s.ptr: *void, s.len: u64);
};
case nomem => { abort(); };
};
// Hare row 5: multibyte UTF-8 (3-byte runes).
match (strings.replace("こんにちは", "にち", "ばん")) {
case let s: str => {
assert(!(!streq(s, "こんばんは")));
os.free(s.ptr: *void, s.len: u64);
};
case nomem => { abort(); };
};
// ww row 1: needle not found — fresh copy (distinct from
// borrowed input ptr).
let src: str = "hello world";
match (strings.replace(src, "xyz", "abc")) {
case let s: str => {
assert(!(!streq(s, "hello world")));
assert(!(s.ptr == src.ptr));
os.free(s.ptr: *void, s.len: u64);
};
case nomem => { abort(); };
};
// ww row 2: empty replacement — needle is removed.
match (strings.replace("foo-bar-baz", "-", "")) {
case let s: str => {
assert(!(!streq(s, "foobarbaz")));
os.free(s.ptr: *void, s.len: u64);
};
case nomem => { abort(); };
};
// ww row 3: result shrinks to empty — total==0 nil-alloc path.
match (strings.replace("aaaa", "aa", "")) {
case let s: str => {
assert(!(!streq(s, "")));
assert(!(s.len != 0));
};
case nomem => { abort(); };
};
// ww row 4: multibyte single-rune replace — every "に" → "X".
match (strings.replace("こんにちは", "に", "X")) {
case let s: str => {
assert(!(!streq(s, "こんXちは")));
os.free(s.ptr: *void, s.len: u64);
};
case nomem => { abort(); };
};
// ww row 5: empty input — returns empty.
match (strings.replace("", "x", "y")) {
case let s: str => {
assert(!(s.len != 0));
};
case nomem => { abort(); };
};
};