wcc: reject exported/bodyless/duplicate @test shapes
Both frontends, byte-stable fragments; -T-mode checks, so the rows live in test/package (the fixture compile cell cannot reach -T).
This commit is contained in:
@@ -3230,14 +3230,31 @@ check_file(Checker *c, Node *file)
|
|||||||
for (Node *d = file->list; d; d = d->next) {
|
for (Node *d = file->list; d; d = d->next) {
|
||||||
if (d->kind != N_FNDECL)
|
if (d->kind != N_FNDECL)
|
||||||
continue;
|
continue;
|
||||||
int istest = 0;
|
int ntestattr = 0;
|
||||||
for (Node *at = d->attr; at; at = at->next)
|
for (Node *at = d->attr; at; at = at->next)
|
||||||
if (at->str && strcmp(at->str, "test") == 0) {
|
if (at->str && strcmp(at->str, "test") == 0)
|
||||||
istest = 1;
|
ntestattr++;
|
||||||
break;
|
if (ntestattr == 0)
|
||||||
}
|
|
||||||
if (!istest)
|
|
||||||
continue;
|
continue;
|
||||||
|
/* Attribute-shape rejects, fixed order, first failure
|
||||||
|
* wins per fn; wording is byte-stable with the wwstage
|
||||||
|
* twin (check.ww). A silently-dropped shape here ships
|
||||||
|
* a test that never runs. */
|
||||||
|
if (ntestattr > 1) {
|
||||||
|
err(c, d->pos, "duplicate @test on fn '%s'",
|
||||||
|
d->str);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (d->export) {
|
||||||
|
err(c, d->pos, "@test fn '%s' cannot be exported",
|
||||||
|
d->str);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (d->body == NULL) {
|
||||||
|
err(c, d->pos, "@test fn '%s' needs a body",
|
||||||
|
d->str);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
/* `fn f() void` parses the explicit `void` into d->lhs
|
/* `fn f() void` parses the explicit `void` into d->lhs
|
||||||
* (parse.c:1329), so void-returning is lhs==NULL OR an
|
* (parse.c:1329), so void-returning is lhs==NULL OR an
|
||||||
* N_TNAME "void" — not lhs==NULL alone. */
|
* N_TNAME "void" — not lhs==NULL alone. */
|
||||||
|
|||||||
@@ -7134,16 +7134,38 @@ export fn checkfile(c: *checker, file: *syntax.node) void = {
|
|||||||
let t: *syntax.node = file.list;
|
let t: *syntax.node = file.list;
|
||||||
for (t != nil) {
|
for (t != nil) {
|
||||||
if (t.kind == syntax.nkind.N_FNDECL) {
|
if (t.kind == syntax.nkind.N_FNDECL) {
|
||||||
let istest: bool = false;
|
let ntestattr: i32 = 0;
|
||||||
let at: *syntax.node = t.attr;
|
let at: *syntax.node = t.attr;
|
||||||
for (at != nil) {
|
for (at != nil) {
|
||||||
if (at.kind == syntax.nkind.N_ATTR
|
if (at.kind == syntax.nkind.N_ATTR
|
||||||
&& syntax.streq(at.str, "test")) {
|
&& syntax.streq(at.str, "test")) {
|
||||||
istest = true;
|
ntestattr += 1;
|
||||||
};
|
};
|
||||||
at = at.next;
|
at = at.next;
|
||||||
};
|
};
|
||||||
if (istest) {
|
// Attribute-shape rejects, fixed order, first
|
||||||
|
// failure wins per fn; wording byte-stable with
|
||||||
|
// the cstage twin (check.c). A silently-dropped
|
||||||
|
// shape here ships a test that never runs.
|
||||||
|
if (ntestattr > 1) {
|
||||||
|
cerr(t.file);
|
||||||
|
cerr(": error: duplicate @test on fn '");
|
||||||
|
cerr(t.str);
|
||||||
|
cerr("'\n");
|
||||||
|
c.errs += 1;
|
||||||
|
} else { if (ntestattr == 1 && t.exported != 0) {
|
||||||
|
cerr(t.file);
|
||||||
|
cerr(": error: @test fn '");
|
||||||
|
cerr(t.str);
|
||||||
|
cerr("' cannot be exported\n");
|
||||||
|
c.errs += 1;
|
||||||
|
} else { if (ntestattr == 1 && t.body == nil) {
|
||||||
|
cerr(t.file);
|
||||||
|
cerr(": error: @test fn '");
|
||||||
|
cerr(t.str);
|
||||||
|
cerr("' needs a body\n");
|
||||||
|
c.errs += 1;
|
||||||
|
} else { if (ntestattr == 1) {
|
||||||
// `fn f() void` parses the explicit void
|
// `fn f() void` parses the explicit void
|
||||||
// into t.lhs, so void-returning is lhs==nil
|
// into t.lhs, so void-returning is lhs==nil
|
||||||
// OR an N_TNAME "void".
|
// OR an N_TNAME "void".
|
||||||
@@ -7172,7 +7194,7 @@ export fn checkfile(c: *checker, file: *syntax.node) void = {
|
|||||||
rtail = row;
|
rtail = row;
|
||||||
ntest += 1i32;
|
ntest += 1i32;
|
||||||
};
|
};
|
||||||
};
|
}; }; }; };
|
||||||
};
|
};
|
||||||
t = t.next;
|
t = t.next;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -443,6 +443,52 @@ fn packagepath(relative: str) str = {
|
|||||||
clean(root);
|
clean(root);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Invalid @test attribute shapes reject at build with stable text on
|
||||||
|
// BOTH frontends (the -T synth checker owns them; the fixture corpus
|
||||||
|
// cannot reach -T, so these rows live here). Fragments only — the
|
||||||
|
// stages' error-line prefixes legitimately differ (file:line:col vs
|
||||||
|
// file:).
|
||||||
|
@test fn invalid_test_shapes_reject() void = {
|
||||||
|
let root: str = fresh();
|
||||||
|
let shapes: []str = [
|
||||||
|
"@test export fn t() void = { };",
|
||||||
|
"@test fn t() void;",
|
||||||
|
"@test fn t(x: i32) void = { };",
|
||||||
|
"@test fn t() i32 = { return 0; };",
|
||||||
|
"@test @test fn t() void = { };",
|
||||||
|
];
|
||||||
|
let frags: []str = [
|
||||||
|
"@test fn 't' cannot be exported",
|
||||||
|
"@test fn 't' needs a body",
|
||||||
|
"@test fn 't' must be fn() void",
|
||||||
|
"@test fn 't' must be fn() void",
|
||||||
|
"duplicate @test on fn 't'",
|
||||||
|
];
|
||||||
|
let digits: []str = ["0", "1", "2", "3", "4"];
|
||||||
|
let outc: commandout;
|
||||||
|
let outw: commandout;
|
||||||
|
let i: i32 = 0;
|
||||||
|
for (i < shapes.len) {
|
||||||
|
let dir: str = strings.concat(root, "/shape", digits[i]);
|
||||||
|
assert(os.mkdir(dir, 448i32) == 0);
|
||||||
|
let src: str = strings.concat(dir, "/bad_test.ww");
|
||||||
|
writefile(src, strings.concat("package main;\n",
|
||||||
|
shapes[i], "\n"));
|
||||||
|
let avc: []str = [driver("ww"), "test", src];
|
||||||
|
let avw: []str = [driver("ww_ww"), "test", src];
|
||||||
|
runcommand(root, strings.concat("shape-c", digits[i]), avc,
|
||||||
|
(30i64 * (time.second: i64)): time.duration, &outc);
|
||||||
|
runcommand(root, strings.concat("shape-ww", digits[i]), avw,
|
||||||
|
(30i64 * (time.second: i64)): time.duration, &outw);
|
||||||
|
expectexit(&outc, 1);
|
||||||
|
expectexit(&outw, 1);
|
||||||
|
assert(has(outc.stderr, frags[i]));
|
||||||
|
assert(has(outw.stderr, frags[i]));
|
||||||
|
i += 1;
|
||||||
|
};
|
||||||
|
clean(root);
|
||||||
|
};
|
||||||
|
|
||||||
// Go's ./... parity: a trailing "..." element walks the tree, one package
|
// Go's ./... parity: a trailing "..." element walks the tree, one package
|
||||||
// run per test-bearing directory, dot- and underscore-prefixed directory
|
// run per test-bearing directory, dot- and underscore-prefixed directory
|
||||||
// names excluded. The excluded sentinels hold failing tests so a wrong
|
// names excluded. The excluded sentinels hold failing tests so a wrong
|
||||||
|
|||||||
Reference in New Issue
Block a user