test: migrate 989_strglobeq to @test value pin, retire C twin (fold-3)
#154 cstage-only str== global-header miscompile (str== fast-path read the global str header off BP+0 instead of name(SB); byte-id 990-997 blind to a runtime-value miscompile). Lifted the run fixture into test/lang/strglobeq_test.ww (primitive @test value pin; byteid-eligible — post-fix cs==ww). byteid floor 55->56.
This commit is contained in:
7
Makefile
7
Makefile
@@ -511,7 +511,6 @@ TESTS = $(BIN)/test_smoke $(BIN)/test_lex $(BIN)/test_parse $(BIN)/test_check \
|
||||
$(BIN)/test_hex_run $(BIN)/test_utf8_run $(BIN)/test_bytes_run \
|
||||
$(BIN)/test_path_run \
|
||||
$(BIN)/test_letshadow_run \
|
||||
$(BIN)/test_strglobeq_run \
|
||||
$(BIN)/test_test_filter \
|
||||
$(BIN)/test_strconv_int_run \
|
||||
$(BIN)/test_stof_run $(BIN)/test_ftos_run \
|
||||
@@ -2732,10 +2731,6 @@ $(BIN)/test_letshadow_run: test/wcc/989_letshadow_run.c $(BIN)/ww $(BIN)/w6c \
|
||||
$(BIN)/w6a $(BIN)/w6l $(LIB)/libwwrt.a | $(BIN)
|
||||
$(CC) $(CFLAGS) -o $@ $<
|
||||
|
||||
$(BIN)/test_strglobeq_run: test/wcc/989_strglobeq_run.c $(BIN)/ww $(BIN)/w6c \
|
||||
$(BIN)/w6a $(BIN)/w6l $(LIB)/libwwrt.a | $(BIN)
|
||||
$(CC) $(CFLAGS) -o $@ $<
|
||||
|
||||
$(BIN)/test_ascii_run: test/wcc/904_ascii_run.c $(BIN)/ww $(BIN)/w6c \
|
||||
$(BIN)/w6a $(BIN)/w6l $(LIB)/libwwrt.a | $(BIN)
|
||||
$(CC) $(CFLAGS) -o $@ $<
|
||||
@@ -3274,7 +3269,7 @@ test-lang: all
|
||||
LANGBYTEID_DIR = $(OUT)/langbyteid
|
||||
LANGBYTEID_FILES = $(filter-out %_runonly_test.ww,$(wildcard test/lang/*_test.ww))
|
||||
LANGBYTEID_VERB = test -c
|
||||
LANGBYTEID_EXPECTED_MIN = 55
|
||||
LANGBYTEID_EXPECTED_MIN = 56
|
||||
$(if $(LANGBYTEID_FILES),,$(error test-lang-byteid: empty corpus))
|
||||
test-lang-byteid: all
|
||||
@set -e; \
|
||||
|
||||
73
test/lang/strglobeq_test.ww
Normal file
73
test/lang/strglobeq_test.ww
Normal file
@@ -0,0 +1,73 @@
|
||||
// strglobeq_test — #154 regression pin, migrated from test/wcc/989_strglobeq_run.c.
|
||||
//
|
||||
// `str == str` / `str != str` delegate to rt_streq, and cgen's str-compare
|
||||
// arm had an N_IDENT fast-path that ALWAYS read the header off BP+localfind(name).
|
||||
// For a module-GLOBAL str ident localfind→0, so it loaded saved-BP/retaddr
|
||||
// garbage instead of name(SB) — a silent CSTAGE miscompile (the header lives
|
||||
// at name(SB), not the frame). Fix mirrors #148's slice global branch: LEAQ
|
||||
// name(SB), load ptr/len off it. Now cs==ww, so this file is byteid-eligible.
|
||||
//
|
||||
// The bug is per-SHAPE in cgen, so each comparison shape is a separate fn the
|
||||
// @tests drive over three inputs: "/" (eqsep), "foo" (neither), "/usr"
|
||||
// (eqlong). Shapes cover const-global AND let-global operands, ident on RHS
|
||||
// AND on LHS, == AND !=, and a length>1 global ("/usr") so the LEN word — not
|
||||
// just the ptr — is read off name(SB). PRIMITIVE-only (bool) asserts — no
|
||||
// fmt/strconv; a wrong base/len yields a wrong boolean.
|
||||
|
||||
package strglobeq_test;
|
||||
|
||||
const csep: str = "/";
|
||||
let lsep: str = "/";
|
||||
const longsep: str = "/usr";
|
||||
|
||||
// rhs-ident global (p == g): const, let, len>1, and !=.
|
||||
fn eqr_const(p: str) bool = { return p == csep; };
|
||||
fn eqr_let(p: str) bool = { return p == lsep; };
|
||||
fn eqr_long(p: str) bool = { return p == longsep; };
|
||||
fn ner_const(p: str) bool = { return p != csep; };
|
||||
|
||||
// lhs-ident global (g == p): const and len>1.
|
||||
fn eql_const(p: str) bool = { return csep == p; };
|
||||
fn eql_long(p: str) bool = { return longsep == p; };
|
||||
|
||||
// Shape 1 — rhs const-global ==.
|
||||
@test fn rhs_const_eq() void = {
|
||||
assert(eqr_const("/") == true);
|
||||
assert(eqr_const("foo") == false);
|
||||
assert(eqr_const("/usr") == false);
|
||||
};
|
||||
|
||||
// Shape 2 — rhs let-global ==.
|
||||
@test fn rhs_let_eq() void = {
|
||||
assert(eqr_let("/") == true);
|
||||
assert(eqr_let("foo") == false);
|
||||
assert(eqr_let("/usr") == false);
|
||||
};
|
||||
|
||||
// Shape 3 — lhs const-global ==.
|
||||
@test fn lhs_const_eq() void = {
|
||||
assert(eql_const("/") == true);
|
||||
assert(eql_const("foo") == false);
|
||||
assert(eql_const("/usr") == false);
|
||||
};
|
||||
|
||||
// Shape 4 — rhs const-global != (negation arm).
|
||||
@test fn rhs_const_ne() void = {
|
||||
assert(ner_const("/") == false);
|
||||
assert(ner_const("foo") == true);
|
||||
assert(ner_const("/usr") == true);
|
||||
};
|
||||
|
||||
// Shape 5 — rhs len>1 const-global == (LEN word read off name(SB)).
|
||||
@test fn rhs_long_eq() void = {
|
||||
assert(eqr_long("/") == false);
|
||||
assert(eqr_long("foo") == false);
|
||||
assert(eqr_long("/usr") == true);
|
||||
};
|
||||
|
||||
// Shape 6 — lhs len>1 const-global == (LEN word, lhs sub-site).
|
||||
@test fn lhs_long_eq() void = {
|
||||
assert(eql_long("/") == false);
|
||||
assert(eql_long("foo") == false);
|
||||
assert(eql_long("/usr") == true);
|
||||
};
|
||||
@@ -1,67 +0,0 @@
|
||||
// strglobeq — #154 regression pin. `str == str` / `str != str` delegate to
|
||||
// rt_streq, and cgen's str-compare arm had an N_IDENT fast-path that ALWAYS
|
||||
// read the header off BP+localfind(name). For a module-GLOBAL str ident
|
||||
// localfind→0, so it loaded saved-BP/retaddr garbage instead of name(SB) —
|
||||
// a silent cstage miscompile (the header lives at name(SB), not the frame).
|
||||
// Fix mirrors #148's slice global branch: LEAQ name(SB), load ptr/len off it.
|
||||
//
|
||||
// Table-driven: each row is {input, expected}; the loop feeds every input
|
||||
// through the five compile-time-distinct comparison shapes (the bug is per
|
||||
// SHAPE in cgen, so the shapes are separate fns the rows drive). Shapes cover
|
||||
// const-global AND let-global operands, ident on RHS AND on LHS, == AND !=,
|
||||
// and a length>1 global ("/usr") so the LEN word — not just the ptr — is read
|
||||
// off name(SB) on both sub-sites. signalled = row*10+shape pinpoints failures.
|
||||
//
|
||||
// Run with `out/bin/ww run test/wcc/989_strglobeq.ww`; exit 0 = all pass.
|
||||
|
||||
package main;
|
||||
|
||||
import os;
|
||||
|
||||
const csep: str = "/";
|
||||
let lsep: str = "/";
|
||||
const longsep: str = "/usr";
|
||||
|
||||
// rhs-ident global (p == g): const, let, len>1, and !=.
|
||||
fn eqr_const(p: str) bool = { return p == csep; };
|
||||
fn eqr_let(p: str) bool = { return p == lsep; };
|
||||
fn eqr_long(p: str) bool = { return p == longsep; };
|
||||
fn ner_const(p: str) bool = { return p != csep; };
|
||||
|
||||
// lhs-ident global (g == p): const and len>1.
|
||||
fn eql_const(p: str) bool = { return csep == p; };
|
||||
fn eql_long(p: str) bool = { return longsep == p; };
|
||||
|
||||
type row = struct {
|
||||
in: str,
|
||||
eqsep: bool, // in == "/"
|
||||
eqlong: bool, // in == "/usr"
|
||||
};
|
||||
|
||||
let signalled: i32 = 0;
|
||||
fn fail() void = { os.exit(signalled + 10); };
|
||||
|
||||
export fn main() i32 = {
|
||||
let rows: [_]row = [
|
||||
row { in = "/", eqsep = true, eqlong = false },
|
||||
row { in = "foo", eqsep = false, eqlong = false },
|
||||
row { in = "/usr", eqsep = false, eqlong = true },
|
||||
];
|
||||
// len() stamps i32 on cstage (#26); the index must match for the bound.
|
||||
for (let i: i32 = 0; i < len(rows); i += 1) {
|
||||
let r = rows[i];
|
||||
signalled = i * 10 + 1;
|
||||
if (eqr_const(r.in) != r.eqsep) { fail(); };
|
||||
signalled = i * 10 + 2;
|
||||
if (eqr_let(r.in) != r.eqsep) { fail(); };
|
||||
signalled = i * 10 + 3;
|
||||
if (eql_const(r.in) != r.eqsep) { fail(); };
|
||||
signalled = i * 10 + 4;
|
||||
if (ner_const(r.in) != !r.eqsep) { fail(); };
|
||||
signalled = i * 10 + 5;
|
||||
if (eqr_long(r.in) != r.eqlong) { fail(); };
|
||||
signalled = i * 10 + 6;
|
||||
if (eql_long(r.in) != r.eqlong) { fail(); };
|
||||
};
|
||||
return 0;
|
||||
};
|
||||
@@ -1,51 +0,0 @@
|
||||
/*
|
||||
* 989_strglobeq_run — #154 regression pin. Compile + run the strglobeq
|
||||
* fixture under the C-side `ww run` driver (cstage w6c) and assert exit 0.
|
||||
*
|
||||
* The bug (str== fast-path read the global str header off BP+0 instead of
|
||||
* name(SB)) is a CSTAGE silent miscompile; byte-id 990-997 can't see a
|
||||
* runtime-value miscompile, so only this value check catches it. Same
|
||||
* thin-wrapper shape as 989_letshadow_run.
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/wait.h>
|
||||
|
||||
static int
|
||||
runwait(const char *cmd)
|
||||
{
|
||||
int rc = system(cmd);
|
||||
if (rc == -1) return -1;
|
||||
if (WIFEXITED(rc)) return WEXITSTATUS(rc);
|
||||
return 1;
|
||||
}
|
||||
|
||||
int
|
||||
main(void)
|
||||
{
|
||||
const char *bin = getenv("BIN");
|
||||
if (!bin) bin = "out/bin";
|
||||
char absbin[1024];
|
||||
if (bin[0] != '/') {
|
||||
char cwd[1024];
|
||||
if (getcwd(cwd, sizeof cwd) == NULL) return 1;
|
||||
snprintf(absbin, sizeof absbin, "%s/%s", cwd, bin);
|
||||
bin = absbin;
|
||||
}
|
||||
char cwd[1024];
|
||||
if (getcwd(cwd, sizeof cwd) == NULL) return 1;
|
||||
|
||||
const char *src = "test/wcc/989_strglobeq.ww";
|
||||
char path[1024], cmd[2048];
|
||||
snprintf(path, sizeof path, "%s/%s", cwd, src);
|
||||
snprintf(cmd, sizeof cmd, "%s/ww run %s", bin, path);
|
||||
int rc = runwait(cmd);
|
||||
if (rc != 0) {
|
||||
fprintf(stderr, "strglobeq_run FAIL: %s exited %d "
|
||||
"(row %d miscompiled — #154)\n", src, rc, rc - 10);
|
||||
return 1;
|
||||
}
|
||||
printf("strglobeq_run: %s ok\n", src);
|
||||
return 0;
|
||||
}
|
||||
Reference in New Issue
Block a user