test: port 737_direnum to ww; retire the C carrier
This commit is contained in:
3
Makefile
3
Makefile
@@ -382,7 +382,8 @@ LIBENV_WW_TARGETS = $(LIBENV_WW_TESTS:%=wwtest/%)
|
||||
# they run under test-compiler.
|
||||
XMOD_WW_TESTS = test/xmod/collide_test.ww test/xmod/m1_test.ww \
|
||||
test/xmod/label_test.ww test/xmod/typecheck_test.ww \
|
||||
test/xmod/enumcap_test.ww test/xmod/modreset_test.ww
|
||||
test/xmod/enumcap_test.ww test/xmod/modreset_test.ww \
|
||||
test/xmod/direnum_test.ww
|
||||
XMOD_WW_TARGETS = $(XMOD_WW_TESTS:%=wwtest/%)
|
||||
|
||||
# Ww-native asm-window observers: single-file ww tests under test/asm/
|
||||
|
||||
@@ -1,510 +0,0 @@
|
||||
/*
|
||||
* 737_direnum — driver-level sentinel for directory-package loading.
|
||||
* Existing imported-directory fixtures plus a private dynamic table pin:
|
||||
*
|
||||
* ok — multi-file dir is concatenated by both stages; the entry
|
||||
* reads cross-pkg bare-leaf fns from sibling files. Build
|
||||
* must succeed for both C-built `ww` and ww-built `ww_ww`.
|
||||
* bad — multi-file dirs with conflicting `package <name>;` clauses;
|
||||
* root — direct multi-file directory builds, deterministic source order,
|
||||
* missing/invalid clauses, and root conflicts;
|
||||
* leaf — an imported directory with one consistent but wrong package name.
|
||||
*
|
||||
* Asm-presence isn't checked separately — 968_utf8_run, 966_strings_
|
||||
* run, 995_self_rebuild already exercise dir-enum end-to-end at
|
||||
* binary level. This file pins the cstage/wwstage symmetric error
|
||||
* path so a regression on either driver fails loud.
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <errno.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/stat.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;
|
||||
}
|
||||
|
||||
static int
|
||||
stderr_contains(const char *path, const char *needle)
|
||||
{
|
||||
FILE *f = fopen(path, "rb");
|
||||
if (!f) return 0;
|
||||
char buf[4096];
|
||||
size_t n = fread(buf, 1, sizeof buf - 1, f);
|
||||
fclose(f);
|
||||
buf[n] = '\0';
|
||||
return strstr(buf, needle) != NULL;
|
||||
}
|
||||
|
||||
static int
|
||||
write_file(const char *path, const char *body)
|
||||
{
|
||||
FILE *f = fopen(path, "wb");
|
||||
if (!f) return -1;
|
||||
int failed = fputs(body, f) == EOF;
|
||||
if (fclose(f) != 0) failed = 1;
|
||||
return failed ? -1 : 0;
|
||||
}
|
||||
|
||||
static int
|
||||
files_equal(const char *a, const char *b)
|
||||
{
|
||||
FILE *fa = fopen(a, "rb"), *fb = fopen(b, "rb");
|
||||
if (!fa || !fb) {
|
||||
if (fa) fclose(fa);
|
||||
if (fb) fclose(fb);
|
||||
return 0;
|
||||
}
|
||||
int ca, cb;
|
||||
do {
|
||||
ca = fgetc(fa);
|
||||
cb = fgetc(fb);
|
||||
} while (ca == cb && ca != EOF);
|
||||
fclose(fa);
|
||||
fclose(fb);
|
||||
return ca == cb;
|
||||
}
|
||||
|
||||
static int
|
||||
file_orders(const char *path, const char *first, const char *second)
|
||||
{
|
||||
FILE *f = fopen(path, "rb");
|
||||
if (!f) return 0;
|
||||
char buf[16384];
|
||||
size_t n = fread(buf, 1, sizeof buf - 1, f);
|
||||
fclose(f);
|
||||
buf[n] = '\0';
|
||||
char *a = strstr(buf, first);
|
||||
char *b = strstr(buf, second);
|
||||
return a != NULL && b != NULL && a < b;
|
||||
}
|
||||
|
||||
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 ww[1280], ww_ww[1280];
|
||||
snprintf(ww, sizeof ww, "%s/ww", bin);
|
||||
snprintf(ww_ww, sizeof ww_ww, "%s/ww_ww", bin);
|
||||
|
||||
int total = 0, fail = 0;
|
||||
int have_owned = 0, have_rootok = 0, have_rootbad = 0;
|
||||
int have_importbad = 0, have_wanted = 0;
|
||||
int have_missing = 0, have_invalid = 0;
|
||||
char owner[128];
|
||||
snprintf(owner, sizeof owner, "/tmp/direnum_owner_%d", getpid());
|
||||
if (mkdir(owner, 0755) != 0) {
|
||||
fprintf(stderr, "737[setup]: cannot acquire temp directory: %s\n",
|
||||
strerror(errno));
|
||||
return 1;
|
||||
}
|
||||
char errp[512];
|
||||
|
||||
/* ok: cross-pkg bare-leaf via dir-enum, both stages. `ww run` owns
|
||||
* /tmp/ww_run_<pid>/{main,main.sepwork} and removes both itself, so no
|
||||
* per-invocation build directory is needed here. */
|
||||
{
|
||||
const char *src = "test/wcc/data/direnum/entry.ww";
|
||||
char cmd[2048];
|
||||
|
||||
snprintf(cmd, sizeof cmd, "%s run %s >/dev/null 2>&1", ww, src);
|
||||
total++;
|
||||
if (runwait(cmd) != 0) {
|
||||
fprintf(stderr, "737[ok-cstage]: ww run %s failed\n", src);
|
||||
fail++;
|
||||
}
|
||||
|
||||
if (access(ww_ww, X_OK) == 0) {
|
||||
snprintf(cmd, sizeof cmd, "%s run %s >/dev/null 2>&1",
|
||||
ww_ww, src);
|
||||
total++;
|
||||
if (runwait(cmd) != 0) {
|
||||
fprintf(stderr, "737[ok-wwstage]: ww_ww run %s failed\n", src);
|
||||
fail++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* bad: the package loader validates the complete stored source list
|
||||
* conflict-first, before checking the import-path leaf. Both stages must
|
||||
* surface the same directory-level conflict. */
|
||||
{
|
||||
const char *src = "test/wcc/data/direnum/bad_entry.ww";
|
||||
|
||||
/* Build INPUT stays in-repo. Output, diagnostic, and the retained
|
||||
* exact <stem>.sepwork tree stay beneath this invocation's root. */
|
||||
char out[512];
|
||||
char cmd[2048];
|
||||
|
||||
snprintf(out, sizeof out, "%s/bad-c", owner);
|
||||
snprintf(errp, sizeof errp, "%s/bad-c.err", owner);
|
||||
snprintf(cmd, sizeof cmd, "%s build -o '%s' %s 2>'%s' >/dev/null",
|
||||
ww, out, src, errp);
|
||||
int rc = runwait(cmd);
|
||||
total++;
|
||||
if (rc == 0) {
|
||||
fprintf(stderr, "737[bad-cstage]: expected build failure, succeeded\n");
|
||||
fail++;
|
||||
} else if (!stderr_contains(errp, "conflicting package names")) {
|
||||
fprintf(stderr, "737[bad-cstage]: stderr missing 'conflicting package names'\n");
|
||||
fail++;
|
||||
}
|
||||
|
||||
if (access(ww_ww, X_OK) == 0) {
|
||||
snprintf(out, sizeof out, "%s/bad-w", owner);
|
||||
snprintf(errp, sizeof errp, "%s/bad-w.err", owner);
|
||||
snprintf(cmd, sizeof cmd,
|
||||
"%s build -o '%s' %s 2>'%s' >/dev/null",
|
||||
ww_ww, out, src, errp);
|
||||
rc = runwait(cmd);
|
||||
total++;
|
||||
if (rc == 0) {
|
||||
fprintf(stderr, "737[bad-wwstage]: expected build failure, succeeded\n");
|
||||
fail++;
|
||||
} else if (!stderr_contains(errp, "conflicting package names")) {
|
||||
fprintf(stderr, "737[bad-wwstage]: stderr missing 'conflicting package names'\n");
|
||||
fail++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* bad-deep: same conflict, but one clause sits behind a >2048-byte
|
||||
* comment header. The loader's one whole-source header/import read must
|
||||
* still validate it in both stages. */
|
||||
{
|
||||
const char *src = "test/wcc/data/direnum/bad_deep_entry.ww";
|
||||
|
||||
/* Same owned-root layout as the shallow conflict above. */
|
||||
char out[512];
|
||||
char cmd[2048];
|
||||
|
||||
snprintf(out, sizeof out, "%s/baddeep-c", owner);
|
||||
snprintf(errp, sizeof errp, "%s/baddeep-c.err", owner);
|
||||
snprintf(cmd, sizeof cmd, "%s build -o '%s' %s 2>'%s' >/dev/null",
|
||||
ww, out, src, errp);
|
||||
int rc = runwait(cmd);
|
||||
total++;
|
||||
if (rc == 0) {
|
||||
fprintf(stderr, "737[bad-deep-cstage]: expected build failure, succeeded\n");
|
||||
fail++;
|
||||
} else if (!stderr_contains(errp, "conflicting package names")) {
|
||||
fprintf(stderr, "737[bad-deep-cstage]: stderr missing 'conflicting package names'\n");
|
||||
fail++;
|
||||
}
|
||||
|
||||
if (access(ww_ww, X_OK) == 0) {
|
||||
snprintf(out, sizeof out, "%s/baddeep-w", owner);
|
||||
snprintf(errp, sizeof errp, "%s/baddeep-w.err", owner);
|
||||
snprintf(cmd, sizeof cmd,
|
||||
"%s build -o '%s' %s 2>'%s' >/dev/null",
|
||||
ww_ww, out, src, errp);
|
||||
rc = runwait(cmd);
|
||||
total++;
|
||||
if (rc == 0) {
|
||||
fprintf(stderr, "737[bad-deep-wwstage]: expected build failure, succeeded\n");
|
||||
fail++;
|
||||
} else if (!stderr_contains(errp, "conflicting package names")) {
|
||||
fprintf(stderr, "737[bad-deep-wwstage]: stderr missing 'conflicting package names'\n");
|
||||
fail++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Authoritative Package rows. All inputs, outputs, and retained build
|
||||
* scratch live beneath the invocation-owned root and are removed below. */
|
||||
{
|
||||
char td[512], rootok[512], rootbad[512], importbad[512];
|
||||
char wanted[512], missing[512], invalid[512];
|
||||
char path[1024], cmd[4096];
|
||||
snprintf(td, sizeof td, "%s/owned", owner);
|
||||
snprintf(rootok, sizeof rootok, "%s/rootok", td);
|
||||
snprintf(rootbad, sizeof rootbad, "%s/rootbad", td);
|
||||
snprintf(importbad, sizeof importbad, "%s/importbad", td);
|
||||
snprintf(wanted, sizeof wanted, "%s/wanted", importbad);
|
||||
snprintf(missing, sizeof missing, "%s/missing", td);
|
||||
snprintf(invalid, sizeof invalid, "%s/invalid", td);
|
||||
|
||||
int setup = 0;
|
||||
if (mkdir(td, 0755) != 0) setup = 1;
|
||||
else have_owned = 1;
|
||||
if (!setup && mkdir(rootok, 0755) != 0) setup = 1;
|
||||
else if (!setup) have_rootok = 1;
|
||||
if (!setup && mkdir(rootbad, 0755) != 0) setup = 1;
|
||||
else if (!setup) have_rootbad = 1;
|
||||
if (!setup && mkdir(importbad, 0755) != 0) setup = 1;
|
||||
else if (!setup) have_importbad = 1;
|
||||
if (!setup && mkdir(wanted, 0755) != 0) setup = 1;
|
||||
else if (!setup) have_wanted = 1;
|
||||
if (!setup && mkdir(missing, 0755) != 0) setup = 1;
|
||||
else if (!setup) have_missing = 1;
|
||||
if (!setup && mkdir(invalid, 0755) != 0) setup = 1;
|
||||
else if (!setup) have_invalid = 1;
|
||||
if (!setup) snprintf(path, sizeof path, "%s/z.ww", rootok);
|
||||
if (!setup) setup = write_file(path,
|
||||
"package rootok;\n// ORDER-Z\n"
|
||||
"fn value() i32 = { return 17; };\n");
|
||||
if (!setup) snprintf(path, sizeof path, "%s/a.ww", rootok);
|
||||
if (!setup) setup = write_file(path,
|
||||
"// leading comment is part of the loader grammar\n"
|
||||
"package rootok;\n// ORDER-A\n"
|
||||
"fn main() i32 = { return value(); };\n");
|
||||
if (!setup) snprintf(path, sizeof path, "%s/a.ww", rootbad);
|
||||
if (!setup) setup = write_file(path,
|
||||
"package rootbad;\nfn main() i32 = { return 0; };\n");
|
||||
if (!setup) snprintf(path, sizeof path, "%s/b.ww", rootbad);
|
||||
if (!setup) setup = write_file(path,
|
||||
"package other;\nfn spare() i32 = { return 0; };\n");
|
||||
if (!setup) snprintf(path, sizeof path, "%s/entry.ww", importbad);
|
||||
if (!setup) setup = write_file(path,
|
||||
"package main;\nimport wanted;\n"
|
||||
"fn main() i32 = { return wanted.value(); };\n");
|
||||
if (!setup) snprintf(path, sizeof path, "%s/a.ww", wanted);
|
||||
if (!setup) setup = write_file(path,
|
||||
"package other;\nexport fn value() i32 = { return 0; };\n");
|
||||
if (!setup) snprintf(path, sizeof path, "%s/a.ww", missing);
|
||||
if (!setup) setup = write_file(path,
|
||||
"fn main() i32 = { return 0; };\n");
|
||||
if (!setup) snprintf(path, sizeof path, "%s/a.ww", invalid);
|
||||
if (!setup) setup = write_file(path,
|
||||
"package 7bad;\nfn main() i32 = { return 0; };\n");
|
||||
|
||||
if (setup) {
|
||||
fprintf(stderr, "737[owned-setup]: cannot create private fixtures\n");
|
||||
fail++;
|
||||
total++;
|
||||
} else {
|
||||
const char *drivers[] = { ww, ww_ww };
|
||||
const char *stages[] = { "cstage", "wwstage" };
|
||||
char units[2][512];
|
||||
int built[2] = {0, 0};
|
||||
for (int s = 0; s < 2; s++) {
|
||||
if (access(drivers[s], X_OK) != 0) continue;
|
||||
char out[512], err[512];
|
||||
snprintf(out, sizeof out, "%s/root-%d", td, s);
|
||||
snprintf(err, sizeof err, "%s/root-%d.err", td, s);
|
||||
snprintf(cmd, sizeof cmd,
|
||||
"%s build -o '%s' '%s' "
|
||||
">/dev/null 2>'%s'", drivers[s], out,
|
||||
rootok, err);
|
||||
int rc = runwait(cmd);
|
||||
total++;
|
||||
if (rc != 0 || runwait(out) != 17) {
|
||||
fprintf(stderr,
|
||||
"737[root-%s]: build/run failed (build=%d)\n",
|
||||
stages[s], rc);
|
||||
fail++;
|
||||
continue;
|
||||
}
|
||||
snprintf(units[s], sizeof units[s],
|
||||
"%s.sepwork/__root.unit.ww", out);
|
||||
if (!file_orders(units[s], "ORDER-A", "ORDER-Z")) {
|
||||
fprintf(stderr,
|
||||
"737[root-%s]: stored sources are not byte-sorted\n",
|
||||
stages[s]);
|
||||
fail++;
|
||||
} else {
|
||||
built[s] = 1;
|
||||
}
|
||||
}
|
||||
if (built[0] && built[1]) {
|
||||
total++;
|
||||
if (!files_equal(units[0], units[1])) {
|
||||
fprintf(stderr,
|
||||
"737[root-parity]: C/WW units differ\n");
|
||||
fail++;
|
||||
}
|
||||
}
|
||||
|
||||
struct rejectrow {
|
||||
const char *tag;
|
||||
const char *target;
|
||||
const char *needle;
|
||||
} rows[] = {
|
||||
{ "root-conflict", rootbad, "conflicting package names" },
|
||||
{ "import-leaf", NULL, "does not match import path" },
|
||||
{ "root-missing", missing, "invalid or missing package clause" },
|
||||
{ "root-invalid", invalid, "invalid or missing package clause" },
|
||||
};
|
||||
char entry[512];
|
||||
snprintf(entry, sizeof entry, "%s/entry.ww", importbad);
|
||||
rows[1].target = entry;
|
||||
for (int r = 0; r < 4; r++) {
|
||||
char errors[2][512];
|
||||
int ran[2] = {0, 0};
|
||||
for (int s = 0; s < 2; s++) {
|
||||
if (access(drivers[s], X_OK) != 0) continue;
|
||||
char out[512];
|
||||
snprintf(out, sizeof out, "%s/%s-%d", td,
|
||||
rows[r].tag, s);
|
||||
snprintf(errors[s], sizeof errors[s], "%s/%s-%d.err",
|
||||
td, rows[r].tag, s);
|
||||
snprintf(cmd, sizeof cmd,
|
||||
"%s build -o '%s' '%s' "
|
||||
">/dev/null 2>'%s'", drivers[s], out,
|
||||
rows[r].target, errors[s]);
|
||||
int rc = runwait(cmd);
|
||||
total++;
|
||||
ran[s] = 1;
|
||||
if (rc == 0
|
||||
|| !stderr_contains(errors[s], rows[r].needle)) {
|
||||
fprintf(stderr,
|
||||
"737[%s-%s]: reject/diagnostic mismatch\n",
|
||||
rows[r].tag, stages[s]);
|
||||
fail++;
|
||||
}
|
||||
}
|
||||
if (ran[0] && ran[1]) {
|
||||
total++;
|
||||
if (!files_equal(errors[0], errors[1])) {
|
||||
fprintf(stderr,
|
||||
"737[%s-parity]: C/WW diagnostics differ\n",
|
||||
rows[r].tag);
|
||||
fail++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int cleanup_fail = 0;
|
||||
char path[1024], cmd[2048];
|
||||
const char *top_stems[] = { "bad-c", "bad-w", "baddeep-c", "baddeep-w" };
|
||||
const char *suffixes[] = { "", ".err" };
|
||||
for (size_t i = 0; i < sizeof top_stems / sizeof top_stems[0]; i++) {
|
||||
snprintf(path, sizeof path, "%s/%s.sepwork", owner, top_stems[i]);
|
||||
snprintf(cmd, sizeof cmd, "rm -rf -- '%s'", path);
|
||||
if (runwait(cmd) != 0) {
|
||||
fprintf(stderr, "737[cleanup]: remove %s failed\n", path);
|
||||
cleanup_fail = 1;
|
||||
}
|
||||
}
|
||||
const char *reject_tags[] = {
|
||||
"root-conflict", "import-leaf", "root-missing", "root-invalid"
|
||||
};
|
||||
if (have_owned) {
|
||||
for (int s = 0; s < 2; s++) {
|
||||
snprintf(path, sizeof path, "%s/owned/root-%d.sepwork", owner, s);
|
||||
snprintf(cmd, sizeof cmd, "rm -rf -- '%s'", path);
|
||||
if (runwait(cmd) != 0) {
|
||||
fprintf(stderr, "737[cleanup]: remove %s failed\n", path);
|
||||
cleanup_fail = 1;
|
||||
}
|
||||
for (size_t r = 0; r < sizeof reject_tags / sizeof reject_tags[0]; r++) {
|
||||
snprintf(path, sizeof path, "%s/owned/%s-%d.sepwork",
|
||||
owner, reject_tags[r], s);
|
||||
snprintf(cmd, sizeof cmd, "rm -rf -- '%s'", path);
|
||||
if (runwait(cmd) != 0) {
|
||||
fprintf(stderr, "737[cleanup]: remove %s failed\n", path);
|
||||
cleanup_fail = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < sizeof top_stems / sizeof top_stems[0]; i++) {
|
||||
for (size_t k = 0; k < sizeof suffixes / sizeof suffixes[0]; k++) {
|
||||
snprintf(path, sizeof path, "%s/%s%s", owner,
|
||||
top_stems[i], suffixes[k]);
|
||||
if (unlink(path) != 0 && errno != ENOENT) {
|
||||
fprintf(stderr, "737[cleanup]: unlink %s: %s\n",
|
||||
path, strerror(errno));
|
||||
cleanup_fail = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (have_owned) {
|
||||
for (int s = 0; s < 2; s++) {
|
||||
for (size_t k = 0; k < sizeof suffixes / sizeof suffixes[0]; k++) {
|
||||
snprintf(path, sizeof path, "%s/owned/root-%d%s",
|
||||
owner, s, suffixes[k]);
|
||||
if (unlink(path) != 0 && errno != ENOENT) {
|
||||
fprintf(stderr, "737[cleanup]: unlink %s: %s\n",
|
||||
path, strerror(errno));
|
||||
cleanup_fail = 1;
|
||||
}
|
||||
}
|
||||
for (size_t r = 0; r < sizeof reject_tags / sizeof reject_tags[0]; r++) {
|
||||
for (size_t k = 0; k < sizeof suffixes / sizeof suffixes[0]; k++) {
|
||||
snprintf(path, sizeof path, "%s/owned/%s-%d%s",
|
||||
owner, reject_tags[r], s, suffixes[k]);
|
||||
if (unlink(path) != 0 && errno != ENOENT) {
|
||||
fprintf(stderr, "737[cleanup]: unlink %s: %s\n",
|
||||
path, strerror(errno));
|
||||
cleanup_fail = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct owned_path {
|
||||
const char *name;
|
||||
int owned;
|
||||
} fixture_files[] = {
|
||||
{ "owned/rootok/z.ww", have_rootok },
|
||||
{ "owned/rootok/a.ww", have_rootok },
|
||||
{ "owned/rootbad/a.ww", have_rootbad },
|
||||
{ "owned/rootbad/b.ww", have_rootbad },
|
||||
{ "owned/importbad/entry.ww", have_importbad },
|
||||
{ "owned/importbad/wanted/a.ww", have_wanted },
|
||||
{ "owned/missing/a.ww", have_missing },
|
||||
{ "owned/invalid/a.ww", have_invalid },
|
||||
};
|
||||
for (size_t i = 0; i < sizeof fixture_files / sizeof fixture_files[0]; i++) {
|
||||
if (!fixture_files[i].owned) continue;
|
||||
snprintf(path, sizeof path, "%s/%s", owner, fixture_files[i].name);
|
||||
if (unlink(path) != 0 && errno != ENOENT) {
|
||||
fprintf(stderr, "737[cleanup]: unlink %s: %s\n",
|
||||
path, strerror(errno));
|
||||
cleanup_fail = 1;
|
||||
}
|
||||
}
|
||||
struct owned_path fixture_dirs[] = {
|
||||
{ "owned/importbad/wanted", have_wanted },
|
||||
{ "owned/rootok", have_rootok },
|
||||
{ "owned/rootbad", have_rootbad },
|
||||
{ "owned/importbad", have_importbad },
|
||||
{ "owned/missing", have_missing },
|
||||
{ "owned/invalid", have_invalid },
|
||||
{ "owned", have_owned },
|
||||
};
|
||||
for (size_t i = 0; i < sizeof fixture_dirs / sizeof fixture_dirs[0]; i++) {
|
||||
if (!fixture_dirs[i].owned) continue;
|
||||
snprintf(path, sizeof path, "%s/%s", owner, fixture_dirs[i].name);
|
||||
if (rmdir(path) != 0 && errno != ENOENT) {
|
||||
fprintf(stderr, "737[cleanup]: rmdir %s: %s\n",
|
||||
path, strerror(errno));
|
||||
cleanup_fail = 1;
|
||||
}
|
||||
}
|
||||
if (rmdir(owner) != 0) {
|
||||
fprintf(stderr, "737[cleanup]: rmdir %s: %s\n",
|
||||
owner, strerror(errno));
|
||||
cleanup_fail = 1;
|
||||
}
|
||||
if (cleanup_fail) fail++;
|
||||
|
||||
if (fail) {
|
||||
fprintf(stderr, "737_direnum: %d/%d fixtures failed\n", fail, total);
|
||||
return 1;
|
||||
}
|
||||
printf("737_direnum: %d/%d ok\n", total, total);
|
||||
return 0;
|
||||
}
|
||||
200
test/xmod/direnum_test.ww
Normal file
200
test/xmod/direnum_test.ww
Normal file
@@ -0,0 +1,200 @@
|
||||
package direnum_test;
|
||||
|
||||
// Directory-package loader observers on both driver stages. Port of
|
||||
// the retired native carrier test/wcc/737_direnum.c; every assertion
|
||||
// preserved, the bad/bad-deep rows strengthened with the stderr
|
||||
// parity leg the C carrier reserved for the reject table.
|
||||
//
|
||||
// okrun — the in-repo tree test/wcc/data/direnum/ (entry imports
|
||||
// multi-file dir ok/ with a.ww+b.ww both package ok, cross-pkg
|
||||
// bare-leaf calls): `ww run` exits 0 on BOTH stages.
|
||||
//
|
||||
// conflict — bad_entry.ww imports a dir with conflicting `package`
|
||||
// clauses; bad_deep_entry.ww the same with one clause behind a
|
||||
// >2048-byte comment header (the loader's whole-source header read).
|
||||
// Both stages must FAIL with "conflicting package names", and the
|
||||
// two stages' diagnostics must be byte-identical.
|
||||
//
|
||||
// rootorder — a private rootok/ dir built DIRECTLY (z.ww ORDER-Z
|
||||
// value()=17 vs a.ww ORDER-A leading comment + main): build exit 0,
|
||||
// the binary exits 17, and the driver's <out>.sepwork/__root.unit.ww
|
||||
// stores ORDER-A strictly before ORDER-Z (byte-sorted deterministic
|
||||
// source order); the two stages' units are byte-identical.
|
||||
//
|
||||
// rejects — the authoritative loader-reject table over private trees:
|
||||
// root-conflict ("conflicting package names"), import-leaf ("does
|
||||
// not match import path"), root-missing and root-invalid ("invalid
|
||||
// or missing package clause"); per row both stages fail, carry the
|
||||
// needle, and their captured stderr is byte-identical.
|
||||
//
|
||||
// Dropped C machinery, not assertions: the ww_ww-absent skip gate
|
||||
// (the Make target declares both drivers) and the unlink/rmdir
|
||||
// accounting (testenv.clean asserts the removal).
|
||||
|
||||
import os;
|
||||
import os.exec;
|
||||
import strings;
|
||||
import testenv;
|
||||
import time;
|
||||
|
||||
fn fail(label: str, why: str) void = {
|
||||
let m: str = strings.concat("direnum FAIL: ", label, " -- ", why,
|
||||
"\n");
|
||||
os.write(2, m.ptr, m.len: u64);
|
||||
assert(false);
|
||||
};
|
||||
|
||||
fn tmo() time.duration = {
|
||||
return (240i64 * (time.second: i64)): time.duration;
|
||||
};
|
||||
|
||||
// -1 encodes an abnormal (non-EXIT) termination, never a valid code.
|
||||
fn runcode(dir: str, name: str, argv: []str) i32 = {
|
||||
let co: testenv.commandout;
|
||||
testenv.runcommand(dir, dir, name, argv, tmo(), &co);
|
||||
if (co.termination != exec.termination.EXIT) { return -1; };
|
||||
return co.code;
|
||||
};
|
||||
|
||||
fn datasrc(name: str) str = {
|
||||
return strings.concat(testenv.repo(), "/test/wcc/data/direnum/",
|
||||
name);
|
||||
};
|
||||
|
||||
@test fn okrun() void = {
|
||||
let td: str = testenv.fresh();
|
||||
let src: str = datasrc("entry.ww");
|
||||
let drvs: []str = ["ww", "ww_ww"];
|
||||
let s: i32 = 0;
|
||||
for (s < 2) {
|
||||
let av: []str = [testenv.driver(drvs[s]), "run", src];
|
||||
if (runcode(td, strings.concat("run_", drvs[s]), av) != 0) {
|
||||
fail("okrun", strings.concat(drvs[s], " run failed on the ",
|
||||
"multi-file imported dir (cross-pkg bare-leaf calls)"));
|
||||
};
|
||||
s += 1;
|
||||
};
|
||||
testenv.clean(td);
|
||||
};
|
||||
|
||||
// A build expected to FAIL with `needle` on both stages, diagnostics
|
||||
// byte-identical across stages.
|
||||
fn rejectpair(label: str, td: str, target: str, needle: str) void = {
|
||||
let drvs: []str = ["ww", "ww_ww"];
|
||||
let errs: []str = ["", ""];
|
||||
let s: i32 = 0;
|
||||
for (s < 2) {
|
||||
let out: str = strings.concat(td, "/", label, "-", drvs[s]);
|
||||
let co: testenv.commandout;
|
||||
let av: []str = [testenv.driver(drvs[s]), "build", "-o", out,
|
||||
target];
|
||||
testenv.runcommand(td, td,
|
||||
strings.concat(label, "_", drvs[s]), av, tmo(), &co);
|
||||
if (co.termination != exec.termination.EXIT || co.code == 0) {
|
||||
fail(label, strings.concat(drvs[s],
|
||||
" expected build failure, succeeded"));
|
||||
};
|
||||
if (!testenv.has(co.stderr, needle)) {
|
||||
fail(label, strings.concat(drvs[s], " stderr missing '",
|
||||
needle, "'"));
|
||||
};
|
||||
errs[s] = co.stderr;
|
||||
s += 1;
|
||||
};
|
||||
if (!testenv.same(errs[0], errs[1])) {
|
||||
fail(label, "C/WW diagnostics differ");
|
||||
};
|
||||
};
|
||||
|
||||
@test fn conflict() void = {
|
||||
let td: str = testenv.fresh();
|
||||
rejectpair("bad", td, datasrc("bad_entry.ww"),
|
||||
"conflicting package names");
|
||||
rejectpair("bad-deep", td, datasrc("bad_deep_entry.ww"),
|
||||
"conflicting package names");
|
||||
testenv.clean(td);
|
||||
};
|
||||
|
||||
@test fn rootorder() void = {
|
||||
let td: str = testenv.fresh();
|
||||
let rootok: str = strings.concat(td, "/rootok");
|
||||
assert(os.mkdir(rootok, 493) == 0);
|
||||
testenv.writefile(strings.concat(rootok, "/z.ww"), strings.concat(
|
||||
"package rootok;\n// ORDER-Z\n",
|
||||
"fn value() i32 = { return 17; };\n"));
|
||||
testenv.writefile(strings.concat(rootok, "/a.ww"), strings.concat(
|
||||
"// leading comment is part of the loader grammar\n",
|
||||
"package rootok;\n// ORDER-A\n",
|
||||
"fn main() i32 = { return value(); };\n"));
|
||||
let drvs: []str = ["ww", "ww_ww"];
|
||||
let units: []str = ["", ""];
|
||||
let s: i32 = 0;
|
||||
for (s < 2) {
|
||||
let out: str = strings.concat(td, "/root-", drvs[s]);
|
||||
let av: []str = [testenv.driver(drvs[s]), "build", "-o", out,
|
||||
rootok];
|
||||
if (runcode(td, strings.concat("build_", drvs[s]), av) != 0) {
|
||||
fail("rootorder", strings.concat(drvs[s],
|
||||
" direct dir build failed"));
|
||||
};
|
||||
let rav: []str = [out];
|
||||
if (runcode(td, strings.concat("run_", drvs[s]), rav) != 17) {
|
||||
fail("rootorder", strings.concat(drvs[s], " exit != 17"));
|
||||
};
|
||||
units[s] = testenv.readfile(strings.concat(out,
|
||||
".sepwork/__root.unit.ww"));
|
||||
let a: i32 = testenv.pos(units[s], "ORDER-A");
|
||||
let z: i32 = testenv.pos(units[s], "ORDER-Z");
|
||||
if (a < 0 || z < 0 || a >= z) {
|
||||
fail("rootorder", strings.concat(drvs[s],
|
||||
" stored sources are not byte-sorted"));
|
||||
};
|
||||
s += 1;
|
||||
};
|
||||
if (!testenv.same(units[0], units[1])) {
|
||||
fail("rootorder", "C/WW __root.unit.ww differ");
|
||||
};
|
||||
testenv.clean(td);
|
||||
};
|
||||
|
||||
@test fn rejects() void = {
|
||||
let td: str = testenv.fresh();
|
||||
let rootbad: str = strings.concat(td, "/rootbad");
|
||||
let importbad: str = strings.concat(td, "/importbad");
|
||||
let wanted: str = strings.concat(importbad, "/wanted");
|
||||
let missing: str = strings.concat(td, "/missing");
|
||||
let invalid: str = strings.concat(td, "/invalid");
|
||||
assert(os.mkdir(rootbad, 493) == 0);
|
||||
assert(os.mkdir(importbad, 493) == 0);
|
||||
assert(os.mkdir(wanted, 493) == 0);
|
||||
assert(os.mkdir(missing, 493) == 0);
|
||||
assert(os.mkdir(invalid, 493) == 0);
|
||||
testenv.writefile(strings.concat(rootbad, "/a.ww"),
|
||||
"package rootbad;\nfn main() i32 = { return 0; };\n");
|
||||
testenv.writefile(strings.concat(rootbad, "/b.ww"),
|
||||
"package other;\nfn spare() i32 = { return 0; };\n");
|
||||
testenv.writefile(strings.concat(importbad, "/entry.ww"),
|
||||
strings.concat("package main;\nimport wanted;\n",
|
||||
"fn main() i32 = { return wanted.value(); };\n"));
|
||||
testenv.writefile(strings.concat(wanted, "/a.ww"),
|
||||
"package other;\nexport fn value() i32 = { return 0; };\n");
|
||||
testenv.writefile(strings.concat(missing, "/a.ww"),
|
||||
"fn main() i32 = { return 0; };\n");
|
||||
testenv.writefile(strings.concat(invalid, "/a.ww"),
|
||||
"package 7bad;\nfn main() i32 = { return 0; };\n");
|
||||
|
||||
let tags: []str = ["root-conflict", "import-leaf", "root-missing",
|
||||
"root-invalid"];
|
||||
let targets: []str = [rootbad, strings.concat(importbad,
|
||||
"/entry.ww"), missing, invalid];
|
||||
let needles: []str = ["conflicting package names",
|
||||
"does not match import path",
|
||||
"invalid or missing package clause",
|
||||
"invalid or missing package clause"];
|
||||
let r: i32 = 0;
|
||||
for (r < tags.len) {
|
||||
rejectpair(tags[r], td, targets[r], needles[r]);
|
||||
r += 1;
|
||||
};
|
||||
testenv.clean(td);
|
||||
};
|
||||
Reference in New Issue
Block a user