test/804: reject-row diagnostic-text checks + delete-in-a-loop row (#35 review)

A BUILD_FAIL row now asserts the expected stderr substring on both
stages (the #35 cite on the range form included) — without it a row
passes vacuously on any unrelated build failure. The new
delete_in_loop row drains [1,2,3,4] to empty via repeated
delete(xs[0]) with base-4 positional accumulation, pinning the len
bookkeeping under iteration. 38 -> 41 fixtures.
This commit is contained in:
2026-06-04 06:50:16 +09:00
parent 37febab9d5
commit 912b9acef6

View File

@@ -40,12 +40,20 @@
* regex_shape | delete((*threads)[i]) behind *[]thread |
* | — the fold-2b delete_thread shape | 24
* | (deref-of-local base, struct element) |
* delete_in_loop | drain [1,2,3,4] to empty via repeated | 112
* | delete(xs[0]) — len bookkeeping under |
* | iteration, base-4 order accumulation |
* reject_array | delete(t[0]) on [3]i64 — "delete must | BUILD_FAIL
* | operate on a slice" |
* reject_nonindex | delete(xs) — operand must be xs[i] | BUILD_FAIL
* reject_range | delete(xs[0:2]) — range form deferred | BUILD_FAIL
* reject_range | delete(xs[0:2]) — range form deferred, | BUILD_FAIL
* | message must cite task #35 |
* reject_arity | delete(xs[0], xs[1]) | BUILD_FAIL
*
* BUILD_FAIL rows also assert the diagnostic TEXT (stderr substring,
* both stages) — a build that fails for any other reason (parse error,
* crash) is a vacuous reject and fails the row.
*
* Every non-BUILD_FAIL row also asserts cstage/wwstage asm byte-id,
* which subsumes the frame canary (TEXT main,$N) and the del_l/del_e
* label-counter symmetry.
@@ -66,12 +74,19 @@ runwait(const char *cmd)
return -1;
}
/* want == BUILD_FAIL: the row must FAIL to build on both stages (the
* checker reject set is part of the contract — rule 7, never a silent
* acceptance). */
/* want == BUILD_FAIL: the row must FAIL to build on both stages AND
* emit expect_err on stderr (the checker reject set — message text
* included, esp. the #35 cite on the deferred range form — is part of
* the contract: rule 7, never a silent acceptance; without the message
* check a row would pass vacuously on any unrelated build failure). */
#define BUILD_FAIL (-2147483647 - 1)
struct row { const char *label; const char *src; int want; };
struct row {
const char *label;
const char *src;
int want;
const char *expect_err; /* BUILD_FAIL rows: required stderr substring */
};
static const struct row rows[] = {
{ "i64_first",
@@ -84,7 +99,7 @@ static const struct row rows[] = {
"\tdelete(xs[0]);\n"
"\treturn (xs[0] + xs[1]): i32 + len(xs)*10;\n"
"};\n",
44 },
44, NULL },
{ "i64_middle",
"package main;\n"
@@ -96,7 +111,7 @@ static const struct row rows[] = {
"\tdelete(xs[1]);\n"
"\treturn (xs[0] + xs[1]): i32 + len(xs)*10;\n"
"};\n",
40 },
40, NULL },
{ "i64_last",
"package main;\n"
@@ -108,7 +123,7 @@ static const struct row rows[] = {
"\tdelete(xs[2]);\n"
"\treturn (xs[0] + xs[1]): i32 + len(xs)*10;\n"
"};\n",
38 },
38, NULL },
{ "i32_narrow",
"package main;\n"
@@ -120,7 +135,7 @@ static const struct row rows[] = {
"\tdelete(xs[0]);\n"
"\treturn xs[0] + xs[1]*10 + len(xs)*100;\n"
"};\n",
229 },
229, NULL },
{ "u8_narrow",
"package main;\n"
@@ -132,7 +147,7 @@ static const struct row rows[] = {
"\tdelete(xs[1]);\n"
"\treturn (xs[0] + xs[1]): i32 + len(xs)*10;\n"
"};\n",
28 },
28, NULL },
{ "cap_unchanged",
"package main;\n"
@@ -142,7 +157,7 @@ static const struct row rows[] = {
"\tdelete(xs[1]);\n"
"\treturn (xs.cap*10 + xs.len): i32 + xs[1]: i32;\n"
"};\n",
91 },
91, NULL },
{ "delete_to_empty",
"package main;\n"
@@ -152,7 +167,7 @@ static const struct row rows[] = {
"\tdelete(xs[0]);\n"
"\treturn len(xs) + 7;\n"
"};\n",
7 },
7, NULL },
{ "str_elem",
"package main;\n"
@@ -167,7 +182,7 @@ static const struct row rows[] = {
"\tdelete(xs[1]);\n"
"\treturn (xs[0].len*10 + xs[1].len): i32 + len(xs)*100;\n"
"};\n",
234 },
234, NULL },
/* 56B element: tag qword + 48B struct payload — seven whole-qword
* moves; tag AND payload must both survive the raw move (a tagged
@@ -200,7 +215,7 @@ static const struct row rows[] = {
"\tr += bp[104]: i32;\n"
"\treturn r + len(xs)*100;\n"
"};\n",
231 },
231, NULL },
/* The fold-2b consumer shape: regex.ha:547-551 delete_thread takes
* threads: *[]thread and deletes through the pointer. ww spells the
@@ -220,7 +235,29 @@ static const struct row rows[] = {
"\tlet r: i32 = (xs[0].pc + xs[1].pc): i32;\n"
"\treturn r + len(xs)*10;\n"
"};\n",
24 },
24, NULL },
/* Pins the len bookkeeping under iteration: each pass reads the
* new head then deletes it; base-4 positional accumulation makes
* any wrong order, double-shift, or stale len visible (a stuck
* len would never terminate; the row would time out as a wrong
* exit via the harness). 1,2,3,4 -> ((1*4+2)*4+3)*4+4 = 112. */
{ "delete_in_loop",
"package main;\n"
"export fn main() i32 = {\n"
"\tlet xs: []i64 = [];\n"
"\tappend(xs, 1);\n"
"\tappend(xs, 2);\n"
"\tappend(xs, 3);\n"
"\tappend(xs, 4);\n"
"\tlet acc: i64 = 0;\n"
"\tfor (len(xs) > 0) {\n"
"\t\tacc = acc*4 + xs[0];\n"
"\t\tdelete(xs[0]);\n"
"\t};\n"
"\treturn acc: i32 + len(xs)*1000;\n"
"};\n",
112, NULL },
{ "reject_array",
"package main;\n"
@@ -229,7 +266,7 @@ static const struct row rows[] = {
"\tdelete(t[0]);\n"
"\treturn 0;\n"
"};\n",
BUILD_FAIL },
BUILD_FAIL, "delete must operate on a slice" },
{ "reject_nonindex",
"package main;\n"
@@ -239,7 +276,7 @@ static const struct row rows[] = {
"\tdelete(xs);\n"
"\treturn 0;\n"
"};\n",
BUILD_FAIL },
BUILD_FAIL, "delete: operand must be an indexing expression" },
{ "reject_range",
"package main;\n"
@@ -250,7 +287,7 @@ static const struct row rows[] = {
"\tdelete(xs[0:2]);\n"
"\treturn 0;\n"
"};\n",
BUILD_FAIL },
BUILD_FAIL, "range form delete(xs[i..j]) unimplemented (task #35)" },
{ "reject_arity",
"package main;\n"
@@ -261,15 +298,31 @@ static const struct row rows[] = {
"\tdelete(xs[0], xs[1]);\n"
"\treturn 0;\n"
"};\n",
BUILD_FAIL },
BUILD_FAIL, "delete: takes exactly one argument" },
};
/* errlog_has — the build-failure stderr must carry the row's expected
* diagnostic; any other failure (parse error, crash) is a vacuous
* reject and must not pass. */
static int
errlog_has(const char *path, const char *needle)
{
FILE *f = fopen(path, "rb");
if (!f) return 0;
char buf[8192];
size_t got = fread(buf, 1, sizeof buf - 1, f);
fclose(f);
buf[got] = '\0';
return strstr(buf, needle) != NULL;
}
static int
run_driver(const char *driver, const struct row *r, int i)
{
char src[64], tmpdir[64], cmd[1024];
char src[64], tmpdir[64], errlog[80], cmd[1200];
snprintf(src, sizeof src, "/tmp/dele_%d_%d.ww", getpid(), i);
snprintf(tmpdir, sizeof tmpdir, "/tmp/dele_%d_d_%d", getpid(), i);
snprintf(errlog, sizeof errlog, "%s.err", src);
FILE *f = fopen(src, "wb");
if (!f) return -1;
@@ -277,14 +330,22 @@ run_driver(const char *driver, const struct row *r, int i)
fclose(f);
mkdir(tmpdir, 0755);
snprintf(cmd, sizeof cmd, "cd %s && %s build %s 2>/dev/null",
tmpdir, driver, src);
snprintf(cmd, sizeof cmd, "cd %s && %s build %s 2>%s",
tmpdir, driver, src, errlog);
if (runwait(cmd) != 0) {
if (r->want != BUILD_FAIL)
int rc = -1;
if (r->want != BUILD_FAIL) {
fprintf(stderr, "row[%s]: build via %s failed\n",
r->label, driver);
unlink(src); rmdir(tmpdir);
return -1;
} else if (r->expect_err &&
!errlog_has(errlog, r->expect_err)) {
fprintf(stderr, "row[%s]: %s build failed without "
"expected diagnostic \"%s\"\n",
r->label, driver, r->expect_err);
rc = -3; /* failed, but for the wrong reason */
}
unlink(src); unlink(errlog); rmdir(tmpdir);
return rc;
}
const char *base = strrchr(src, '/');
@@ -295,7 +356,7 @@ run_driver(const char *driver, const struct row *r, int i)
if (dot && strcmp(dot, ".ww") == 0) *dot = '\0';
int got = runwait(outbin);
unlink(src); unlink(outbin); rmdir(tmpdir);
unlink(src); unlink(errlog); unlink(outbin); rmdir(tmpdir);
return got;
}