driver: ignore package documentation sources
This commit is contained in:
7
Makefile
7
Makefile
@@ -65,7 +65,12 @@ WWTEST_SRC = cmd/wwtest/wwtest.ww \
|
||||
lib/strconv/decimal.ww lib/strconv/ftos.ww lib/strconv/ftos_data.ww \
|
||||
lib/strconv/stof.ww lib/strconv/stof_data.ww lib/strconv/strconv.ww \
|
||||
lib/strings/strings.ww lib/temp/temp.ww lib/time/time.ww \
|
||||
lib/types/types.ww
|
||||
lib/types/types.ww \
|
||||
lib/ww/syntax/ast.ww lib/ww/syntax/decl.ww \
|
||||
lib/ww/syntax/expr.ww lib/ww/syntax/lex.ww \
|
||||
lib/ww/syntax/parse.ww lib/ww/syntax/stmt.ww \
|
||||
lib/ww/syntax/sym.ww lib/ww/syntax/tok.ww \
|
||||
lib/ww/syntax/typ.ww
|
||||
|
||||
WWFIXTURE_BIN = $(BIN)/wwfixture
|
||||
WWFIXTURE_SRC = cmd/wwfixture/wwfixture.ww \
|
||||
|
||||
690
cmd/ww/main.c
690
cmd/ww/main.c
@@ -42,9 +42,15 @@ static const char *usage =
|
||||
|
||||
static char *self_dir;
|
||||
static const char *self_path;
|
||||
static struct {
|
||||
char *path;
|
||||
char *buf;
|
||||
u64 len;
|
||||
} source_documentation_preload;
|
||||
static char *sep_sprintf(const char *, ...);
|
||||
static int sep_reserve(void **, int *, int, size_t);
|
||||
static int sep_fail_size(void);
|
||||
static void source_operand_no_sources(const char *);
|
||||
|
||||
static const char *
|
||||
envpath(const char *name)
|
||||
@@ -536,11 +542,8 @@ static int sep_slurp(const char*, char**, u64*);
|
||||
* rather than a textual attribute scan, whether a production source contains
|
||||
* @test; otherwise valid whitespace/comments could silently drop a test. */
|
||||
static int
|
||||
source_has_test_decl(const char *path)
|
||||
source_has_test_decl(const char *path, char *buf, u64 len)
|
||||
{
|
||||
char *buf;
|
||||
u64 len;
|
||||
if (sep_slurp(path, &buf, &len) < 0) return -1;
|
||||
/* Keep directory-loader package-clause diagnostics stable. The full
|
||||
* parser reports its language-level "missing package clause" first;
|
||||
* the imports-only pass owns the package-loader wording and also avoids
|
||||
@@ -553,14 +556,12 @@ source_has_test_decl(const char *path)
|
||||
Node *imports = parseimports(&ip);
|
||||
if (il.errs || ip.errs) {
|
||||
freearena(ia);
|
||||
free(buf);
|
||||
return -1;
|
||||
}
|
||||
if (imports->module == NULL) {
|
||||
Pos pp = { path, 1, 1 };
|
||||
errorf(pp, "invalid or missing package clause");
|
||||
freearena(ia);
|
||||
free(buf);
|
||||
return -1;
|
||||
}
|
||||
freearena(ia);
|
||||
@@ -572,7 +573,6 @@ source_has_test_decl(const char *path)
|
||||
Node *file = parsefile(&p);
|
||||
if (l.errs || p.errs) {
|
||||
freearena(a);
|
||||
free(buf);
|
||||
return -1;
|
||||
}
|
||||
int found = 0;
|
||||
@@ -584,7 +584,6 @@ source_has_test_decl(const char *path)
|
||||
break;
|
||||
}
|
||||
freearena(a);
|
||||
free(buf);
|
||||
return found;
|
||||
}
|
||||
|
||||
@@ -655,14 +654,8 @@ sep_reserve(void **buf, int *cap, int need, size_t elemsz)
|
||||
* The coordinator chooses variants, but the command owns which real source
|
||||
* paths enter a package compilation. */
|
||||
static int
|
||||
source_package_name(const char *path, char **out)
|
||||
source_package_name(const char *path, char *buf, u64 len, char **out)
|
||||
{
|
||||
char *buf;
|
||||
u64 len;
|
||||
if (sep_slurp(path, &buf, &len) < 0) {
|
||||
fprintf(stderr, "ww: cannot read %s\n", path);
|
||||
return -1;
|
||||
}
|
||||
Arena *a = newarena();
|
||||
Lex l;
|
||||
Parser p;
|
||||
@@ -671,25 +664,21 @@ source_package_name(const char *path, char **out)
|
||||
Node *imports = parseimports(&p);
|
||||
if (l.errs || p.errs) {
|
||||
freearena(a);
|
||||
free(buf);
|
||||
return -1;
|
||||
}
|
||||
if (imports->module == NULL) {
|
||||
Pos pp = { path, 1, 1 };
|
||||
errorf(pp, "invalid or missing package clause");
|
||||
freearena(a);
|
||||
free(buf);
|
||||
return -1;
|
||||
}
|
||||
*out = strdup(imports->module);
|
||||
if (*out == NULL) {
|
||||
sep_fail_nomem();
|
||||
freearena(a);
|
||||
free(buf);
|
||||
return -1;
|
||||
}
|
||||
freearena(a);
|
||||
free(buf);
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -798,6 +787,205 @@ source_build_header(const char *path)
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Go 1.26.5 reserves the exact package name "documentation" for files that
|
||||
* are ignored by the package loader. Avoid parsing ordinary sources on this
|
||||
* early selection pass: only a quiet, exact package-clause candidate enters
|
||||
* the narrow package/import-header parser. */
|
||||
static int
|
||||
source_documentation_skip_trivia(const char *src, u64 len, u64 *at)
|
||||
{
|
||||
u64 i = *at;
|
||||
for (;;) {
|
||||
while (i < len && (src[i] == ' ' || src[i] == '\t'
|
||||
|| src[i] == '\r' || src[i] == '\n'))
|
||||
i++;
|
||||
if (i + 1 < len && src[i] == '/' && src[i + 1] == '/') {
|
||||
i += 2;
|
||||
while (i < len && src[i] != '\n') i++;
|
||||
continue;
|
||||
}
|
||||
if (i + 1 < len && src[i] == '/' && src[i + 1] == '*') {
|
||||
i += 2;
|
||||
while (i + 1 < len
|
||||
&& !(src[i] == '*' && src[i + 1] == '/'))
|
||||
i++;
|
||||
if (i + 1 >= len) return 0;
|
||||
i += 2;
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
}
|
||||
*at = i;
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int
|
||||
source_documentation_ident(unsigned char c)
|
||||
{
|
||||
return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')
|
||||
|| (c >= '0' && c <= '9') || c == '_';
|
||||
}
|
||||
|
||||
static int
|
||||
source_documentation_word(const char *src, u64 len, u64 *at,
|
||||
const char *word)
|
||||
{
|
||||
u64 n = (u64)strlen(word), i = *at;
|
||||
if (i > len || len - i < n || memcmp(src + i, word, (size_t)n) != 0)
|
||||
return 0;
|
||||
if (len - i > n
|
||||
&& source_documentation_ident((unsigned char)src[i + n]))
|
||||
return 0;
|
||||
*at = i + n;
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int
|
||||
source_documentation_candidate(const char *src, u64 len)
|
||||
{
|
||||
u64 at = 0;
|
||||
if (len >= 3 && (unsigned char)src[0] == 0xef
|
||||
&& (unsigned char)src[1] == 0xbb
|
||||
&& (unsigned char)src[2] == 0xbf)
|
||||
at = 3;
|
||||
if (!source_documentation_skip_trivia(src, len, &at)
|
||||
|| !source_documentation_word(src, len, &at, "package")
|
||||
|| !source_documentation_skip_trivia(src, len, &at)
|
||||
|| !source_documentation_word(src, len, &at, "documentation")
|
||||
|| !source_documentation_skip_trivia(src, len, &at))
|
||||
return 0;
|
||||
return at < len && src[at] == ';';
|
||||
}
|
||||
|
||||
/* -1: diagnosed header error; 0: ordinary source; 1: documentation source.
|
||||
* The caller owns BUF. The raw-'i' arm mirrors readGoInfo's attempt to read
|
||||
* another import before it knows whether the complete keyword follows. Exact
|
||||
* import tokens have already been consumed (or diagnosed) by the parser. */
|
||||
static int
|
||||
source_documentation_buffer(const char *path, char *buf, u64 len)
|
||||
{
|
||||
if (!source_documentation_candidate(buf, len)) return 0;
|
||||
Arena *a = newarena();
|
||||
Lex l;
|
||||
Parser p;
|
||||
lexinit(&l, a, path, buf, len);
|
||||
parserinit(&p, a, &l);
|
||||
Node *header = parsepackageheader(&p);
|
||||
if (l.errs || p.errs) {
|
||||
freearena(a);
|
||||
return -1;
|
||||
}
|
||||
if (header->module == NULL) {
|
||||
Pos pp = { path, 1, 1 };
|
||||
errorf(pp, "invalid or missing package clause");
|
||||
freearena(a);
|
||||
return -1;
|
||||
}
|
||||
if (l.pos < len && buf[l.pos] == 'i') {
|
||||
Pos ip = { path, l.line, l.col };
|
||||
errorf(ip, "expected top-level decl");
|
||||
freearena(a);
|
||||
return -1;
|
||||
}
|
||||
int documentation = strcmp(header->module, "documentation") == 0;
|
||||
freearena(a);
|
||||
return documentation;
|
||||
}
|
||||
|
||||
static void
|
||||
source_documentation_preload_clear(void)
|
||||
{
|
||||
free(source_documentation_preload.path);
|
||||
free(source_documentation_preload.buf);
|
||||
memset(&source_documentation_preload, 0,
|
||||
sizeof source_documentation_preload);
|
||||
}
|
||||
|
||||
/* A regular named root is read exactly once. An ordinary result carries the
|
||||
* same bytes into sep_scan_file; a documentation result is rejected before a
|
||||
* run/test scratch directory can be acquired. Nonregular sources never enter
|
||||
* this preflight and retain their established stream/error route. */
|
||||
static int
|
||||
source_preload_documentation(const char *path)
|
||||
{
|
||||
source_documentation_preload_clear();
|
||||
char *buf;
|
||||
u64 len;
|
||||
if (sep_slurp(path, &buf, &len) < 0) {
|
||||
fprintf(stderr, "ww: cannot read %s\n", path);
|
||||
return -1;
|
||||
}
|
||||
int documentation = source_documentation_buffer(path, buf, len);
|
||||
if (documentation != 0) {
|
||||
free(buf);
|
||||
return documentation;
|
||||
}
|
||||
char *copy = strdup(path);
|
||||
if (copy == NULL) {
|
||||
sep_fail_nomem();
|
||||
free(buf);
|
||||
return -1;
|
||||
}
|
||||
source_documentation_preload.path = copy;
|
||||
source_documentation_preload.buf = buf;
|
||||
source_documentation_preload.len = len;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
source_regular_file(const char *path)
|
||||
{
|
||||
struct stat st;
|
||||
return stat(path, &st) == 0 && S_ISREG(st.st_mode);
|
||||
}
|
||||
|
||||
struct sepsource {
|
||||
char *path;
|
||||
char *buf;
|
||||
u64 len;
|
||||
};
|
||||
|
||||
struct sepdirobs_source {
|
||||
char *name; /* one eligible directory member name */
|
||||
char *path; /* first request spelling, for diagnostics */
|
||||
char *buf; /* exact regular-file bytes, once observed */
|
||||
u64 len;
|
||||
char *package; /* test source's once-parsed package name */
|
||||
int is_test;
|
||||
int observed; /* stat/read/classification has run */
|
||||
int result; /* -2 diagnosed, 0 ignored, 1 ordinary */
|
||||
};
|
||||
|
||||
struct sepdirobs {
|
||||
char *canon; /* canonical directory cache key */
|
||||
char *entry; /* first request spelling */
|
||||
struct sepdirobs_source *source;
|
||||
int nsource;
|
||||
int sourcecap;
|
||||
int enumerated;
|
||||
int enum_result; /* -1 unreadable directory, 0 success */
|
||||
};
|
||||
|
||||
static int
|
||||
source_snapshot_add(struct sepsource **list, int *n, int *cap,
|
||||
const char *path, const char *buf, u64 len)
|
||||
{
|
||||
if (*n == INT_MAX) return sep_fail_size();
|
||||
if (sep_reserve((void **)list, cap, *n + 1, sizeof **list) < 0)
|
||||
return -1;
|
||||
char *pathcopy = strdup(path);
|
||||
char *bufcopy = malloc((size_t)len + 1);
|
||||
if (pathcopy == NULL || bufcopy == NULL) {
|
||||
free(pathcopy);
|
||||
free(bufcopy);
|
||||
return sep_fail_nomem();
|
||||
}
|
||||
memcpy(bufcopy, buf, (size_t)len + 1);
|
||||
(*list)[*n] = (struct sepsource){ pathcopy, bufcopy, len };
|
||||
(*n)++;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
source_list_add(char ***list, int *n, int *cap, const char *path)
|
||||
{
|
||||
@@ -823,15 +1011,14 @@ source_list_free(char **list, int n)
|
||||
* byte-sorted so compiler test discovery is deterministic without generated
|
||||
* package amalgamation. */
|
||||
static int
|
||||
enumerate_dir_ww(const char *dirpath, int variant, const char *test_package,
|
||||
char ***out_files)
|
||||
sep_dir_membership(struct sepdirobs *obs)
|
||||
{
|
||||
DIR *d = opendir(dirpath);
|
||||
if (d == NULL) { *out_files = NULL; return -1; }
|
||||
if (obs->enumerated) return obs->enum_result;
|
||||
obs->enumerated = 1;
|
||||
DIR *d = opendir(obs->entry);
|
||||
if (d == NULL) return obs->enum_result = -1;
|
||||
char **names = NULL;
|
||||
int nnames = 0, capnames = 0;
|
||||
char **prod = NULL, **tests = NULL;
|
||||
int nprod = 0, capprod = 0, ntests = 0, captests = 0;
|
||||
struct dirent *ent = NULL;
|
||||
for (;;) {
|
||||
errno = 0;
|
||||
@@ -842,147 +1029,104 @@ enumerate_dir_ww(const char *dirpath, int variant, const char *test_package,
|
||||
if (nl <= 3) continue;
|
||||
if (nm[0] == '.' || nm[0] == '_') continue;
|
||||
if (strcmp(nm + nl - 3, ".ww") != 0) continue;
|
||||
if (!sep_source_matches_target(nm)) continue;
|
||||
if (source_list_add(&names, &nnames, &capnames, nm) < 0) {
|
||||
source_list_free(names, nnames);
|
||||
closedir(d);
|
||||
*out_files = NULL;
|
||||
return -2;
|
||||
return obs->enum_result = -2;
|
||||
}
|
||||
}
|
||||
int direrr = errno;
|
||||
closedir(d);
|
||||
if (direrr != 0) {
|
||||
source_list_free(names, nnames);
|
||||
*out_files = NULL;
|
||||
return -1;
|
||||
return obs->enum_result = -1;
|
||||
}
|
||||
if (nnames > 1) qsort(names, (size_t)nnames, sizeof *names, strs_cmp);
|
||||
for (int ni = 0; ni < nnames; ni++) {
|
||||
const char *nm = names[ni];
|
||||
size_t nl = strlen(nm);
|
||||
if (!sep_source_matches_target(nm)) continue;
|
||||
int is_test = nl >= 8 && strcmp(nm + nl - 8, "_test.ww") == 0;
|
||||
if (variant == SEP_VARIANT_PRODUCTION && is_test) continue;
|
||||
if (variant == SEP_VARIANT_EXTERNAL && !is_test) continue;
|
||||
char path[PATH_MAX];
|
||||
int pn = snprintf(path, sizeof path, "%s/%s", dirpath, nm);
|
||||
if (pn < 0 || (size_t)pn >= sizeof path) {
|
||||
fprintf(stderr, "ww: package source path is too long\n");
|
||||
if (obs->nsource == INT_MAX
|
||||
|| sep_reserve((void **)&obs->source, &obs->sourcecap,
|
||||
obs->nsource + 1, sizeof *obs->source) < 0) {
|
||||
source_list_free(names, nnames);
|
||||
source_list_free(prod, nprod);
|
||||
source_list_free(tests, ntests);
|
||||
*out_files = NULL;
|
||||
return -2;
|
||||
return obs->enum_result = -2;
|
||||
}
|
||||
struct stat st;
|
||||
if (lstat(path, &st) != 0) {
|
||||
fprintf(stderr,
|
||||
"ww: %s: package source is not a regular file\n", path);
|
||||
struct sepdirobs_source *s = &obs->source[obs->nsource];
|
||||
memset(s, 0, sizeof *s);
|
||||
obs->nsource++;
|
||||
s->name = strdup(nm);
|
||||
if (s->name == NULL) {
|
||||
sep_fail_nomem();
|
||||
source_list_free(names, nnames);
|
||||
source_list_free(prod, nprod);
|
||||
source_list_free(tests, ntests);
|
||||
*out_files = NULL;
|
||||
return -2;
|
||||
}
|
||||
if (S_ISLNK(st.st_mode)) {
|
||||
if (stat(path, &st) != 0) {
|
||||
fprintf(stderr,
|
||||
"ww: %s: package source is not a regular file\n",
|
||||
path);
|
||||
source_list_free(names, nnames);
|
||||
source_list_free(prod, nprod);
|
||||
source_list_free(tests, ntests);
|
||||
*out_files = NULL;
|
||||
return -2;
|
||||
}
|
||||
/* go/build ignores a source-shaped symlink to a directory. */
|
||||
if (S_ISDIR(st.st_mode)) continue;
|
||||
}
|
||||
if (!S_ISREG(st.st_mode)) {
|
||||
fprintf(stderr,
|
||||
"ww: %s: package source is not a regular file\n", path);
|
||||
source_list_free(names, nnames);
|
||||
source_list_free(prod, nprod);
|
||||
source_list_free(tests, ntests);
|
||||
*out_files = NULL;
|
||||
return -2;
|
||||
}
|
||||
int has_test = !is_test ? source_has_test_decl(path) : 0;
|
||||
if (has_test < 0) {
|
||||
source_list_free(names, nnames);
|
||||
source_list_free(prod, nprod);
|
||||
source_list_free(tests, ntests);
|
||||
*out_files = NULL;
|
||||
return -2;
|
||||
}
|
||||
if (has_test > 0) {
|
||||
fprintf(stderr,
|
||||
"ww: %s: @test declaration outside *_test.ww\n",
|
||||
path);
|
||||
source_list_free(names, nnames);
|
||||
source_list_free(prod, nprod);
|
||||
source_list_free(tests, ntests);
|
||||
*out_files = NULL;
|
||||
return -2;
|
||||
}
|
||||
if (is_test) {
|
||||
char *package = NULL;
|
||||
if (source_package_name(path, &package) < 0) {
|
||||
source_list_free(names, nnames);
|
||||
source_list_free(prod, nprod);
|
||||
source_list_free(tests, ntests);
|
||||
*out_files = NULL;
|
||||
return -2;
|
||||
}
|
||||
if (test_package == NULL || strcmp(package, test_package) != 0) {
|
||||
free(package);
|
||||
continue;
|
||||
}
|
||||
free(package);
|
||||
}
|
||||
char ***list = is_test ? &tests : ∏
|
||||
int *n = is_test ? &ntests : &nprod;
|
||||
int *cap = is_test ? &captests : &capprod;
|
||||
if (source_list_add(list, n, cap, path) < 0) {
|
||||
source_list_free(names, nnames);
|
||||
source_list_free(prod, nprod);
|
||||
source_list_free(tests, ntests);
|
||||
*out_files = NULL;
|
||||
return -2;
|
||||
return obs->enum_result = -2;
|
||||
}
|
||||
s->is_test = nl >= 8 && strcmp(nm + nl - 8, "_test.ww") == 0;
|
||||
}
|
||||
source_list_free(names, nnames);
|
||||
if (nprod > 1) qsort(prod, (size_t)nprod, sizeof *prod, strs_cmp);
|
||||
if (ntests > 1) qsort(tests, (size_t)ntests, sizeof *tests, strs_cmp);
|
||||
if (nprod > INT_MAX - ntests) {
|
||||
sep_fail_size();
|
||||
source_list_free(prod, nprod);
|
||||
source_list_free(tests, ntests);
|
||||
*out_files = NULL;
|
||||
return -2;
|
||||
return obs->enum_result = 0;
|
||||
}
|
||||
|
||||
/* Observe a member lazily, after variant eligibility. The cached result is
|
||||
* authoritative for the rest of this request, including documentation and
|
||||
* diagnosed failures, so later variants cannot restat or reread the path. */
|
||||
static int
|
||||
sep_dir_observe_source(struct sepdirobs *obs, struct sepdirobs_source *s)
|
||||
{
|
||||
if (s->observed) return s->result;
|
||||
s->observed = 1;
|
||||
char path[PATH_MAX];
|
||||
int pn = snprintf(path, sizeof path, "%s/%s", obs->entry, s->name);
|
||||
if (pn < 0 || (size_t)pn >= sizeof path) {
|
||||
fprintf(stderr, "ww: package source path is too long\n");
|
||||
return s->result = -2;
|
||||
}
|
||||
int total = nprod + ntests;
|
||||
if ((size_t)total > (size_t)-1 / sizeof *prod) {
|
||||
sep_fail_size();
|
||||
source_list_free(prod, nprod);
|
||||
source_list_free(tests, ntests);
|
||||
*out_files = NULL;
|
||||
return -2;
|
||||
}
|
||||
char **all = total ? malloc((size_t)total * sizeof *all) : NULL;
|
||||
if (total && all == NULL) {
|
||||
s->path = strdup(path);
|
||||
if (s->path == NULL) {
|
||||
sep_fail_nomem();
|
||||
source_list_free(prod, nprod);
|
||||
source_list_free(tests, ntests);
|
||||
*out_files = NULL;
|
||||
return -2;
|
||||
return s->result = -2;
|
||||
}
|
||||
for (int i = 0; i < nprod; i++) all[i] = prod[i];
|
||||
for (int i = 0; i < ntests; i++) all[nprod + i] = tests[i];
|
||||
free(prod);
|
||||
free(tests);
|
||||
*out_files = all;
|
||||
return total;
|
||||
struct stat st;
|
||||
if (lstat(s->path, &st) != 0) {
|
||||
fprintf(stderr,
|
||||
"ww: %s: package source is not a regular file\n", s->path);
|
||||
return s->result = -2;
|
||||
}
|
||||
if (S_ISLNK(st.st_mode)) {
|
||||
if (stat(s->path, &st) != 0) {
|
||||
fprintf(stderr,
|
||||
"ww: %s: package source is not a regular file\n", s->path);
|
||||
return s->result = -2;
|
||||
}
|
||||
/* go/build ignores a source-shaped symlink to a directory. */
|
||||
if (S_ISDIR(st.st_mode)) return s->result = 0;
|
||||
}
|
||||
if (!S_ISREG(st.st_mode)) {
|
||||
fprintf(stderr,
|
||||
"ww: %s: package source is not a regular file\n", s->path);
|
||||
return s->result = -2;
|
||||
}
|
||||
if (sep_slurp(s->path, &s->buf, &s->len) < 0) {
|
||||
fprintf(stderr, "ww: cannot read %s\n", s->path);
|
||||
return s->result = -2;
|
||||
}
|
||||
int documentation = source_documentation_buffer(s->path, s->buf, s->len);
|
||||
if (documentation < 0) return s->result = -2;
|
||||
if (documentation > 0) return s->result = 0;
|
||||
if (s->is_test) {
|
||||
if (source_package_name(s->path, s->buf, s->len,
|
||||
&s->package) < 0)
|
||||
return s->result = -2;
|
||||
} else {
|
||||
int has_test = source_has_test_decl(s->path, s->buf, s->len);
|
||||
if (has_test < 0) return s->result = -2;
|
||||
if (has_test > 0) {
|
||||
fprintf(stderr,
|
||||
"ww: %s: @test declaration outside *_test.ww\n", s->path);
|
||||
return s->result = -2;
|
||||
}
|
||||
}
|
||||
return s->result = 1;
|
||||
}
|
||||
|
||||
/* ww build — separate-compilation driver (task #46/c3).
|
||||
@@ -1433,8 +1577,10 @@ struct seppkg {
|
||||
char *name; /* validated declared name; directory packages only */
|
||||
char *test_package; /* selected test package; root variants only */
|
||||
char *for_test; /* directory product owning a recompiled action */
|
||||
char **sources; /* owned, byte-sorted selected paths; dirs only */
|
||||
struct sepsource *sources; /* owned byte-sorted path+byte snapshots; dirs */
|
||||
int nsources;
|
||||
char *entry_buf; /* request snapshot for a regular one-file root */
|
||||
u64 entry_len;
|
||||
int is_dir;
|
||||
int variant; /* SEP_VARIANT_*; dependencies are production */
|
||||
int role; /* normal, reserved test support, or generated main */
|
||||
@@ -1473,6 +1619,9 @@ struct sepgraph {
|
||||
struct seppkg *pkg;
|
||||
int n;
|
||||
int pkgcap;
|
||||
struct sepdirobs *dirobs; /* request-owned canonical observations */
|
||||
int ndirobs;
|
||||
int dirobscap;
|
||||
struct sepcontext *context;
|
||||
int ncontext;
|
||||
int contextcap;
|
||||
@@ -1521,6 +1670,78 @@ sep_reserve_packages(struct sepgraph *g, int need)
|
||||
sizeof *g->pkg);
|
||||
}
|
||||
|
||||
static struct sepdirobs *
|
||||
sep_graph_dir_observation(struct sepgraph *g, const struct seppkg *p)
|
||||
{
|
||||
for (int i = 0; i < g->ndirobs; i++)
|
||||
if (strcmp(g->dirobs[i].canon, p->canon) == 0)
|
||||
return &g->dirobs[i];
|
||||
if (g->ndirobs == INT_MAX
|
||||
|| sep_reserve((void **)&g->dirobs, &g->dirobscap,
|
||||
g->ndirobs + 1, sizeof *g->dirobs) < 0)
|
||||
return NULL;
|
||||
struct sepdirobs *obs = &g->dirobs[g->ndirobs];
|
||||
memset(obs, 0, sizeof *obs);
|
||||
obs->canon = strdup(p->canon);
|
||||
obs->entry = strdup(p->entry);
|
||||
if (obs->canon == NULL || obs->entry == NULL) {
|
||||
sep_fail_nomem();
|
||||
free(obs->canon);
|
||||
free(obs->entry);
|
||||
memset(obs, 0, sizeof *obs);
|
||||
return NULL;
|
||||
}
|
||||
g->ndirobs++;
|
||||
return obs;
|
||||
}
|
||||
|
||||
/* Select one action's ordered source set from request-owned observations.
|
||||
* Production members precede same-package tests. Crucially, variant filters
|
||||
* run before sep_dir_observe_source, so a production-only request merely sees
|
||||
* test names in readdir data and never stats or opens their paths. */
|
||||
static int
|
||||
sep_select_dir_sources(struct sepgraph *g, struct seppkg *p)
|
||||
{
|
||||
struct sepdirobs *obs = sep_graph_dir_observation(g, p);
|
||||
if (obs == NULL) return -2;
|
||||
int er = sep_dir_membership(obs);
|
||||
if (er != 0) return er;
|
||||
/* Diagnose and cache observations in the directory's one global byte
|
||||
* order. Source assembly below retains the language's production-before-
|
||||
* test order without changing filesystem diagnostic precedence. */
|
||||
for (int i = 0; i < obs->nsource; i++) {
|
||||
struct sepdirobs_source *s = &obs->source[i];
|
||||
if (p->variant == SEP_VARIANT_PRODUCTION && s->is_test)
|
||||
continue;
|
||||
if (p->variant == SEP_VARIANT_EXTERNAL && !s->is_test)
|
||||
continue;
|
||||
if (sep_dir_observe_source(obs, s) < 0) return -2;
|
||||
}
|
||||
int cap = 0;
|
||||
for (int partition = 0; partition < 2; partition++) {
|
||||
for (int i = 0; i < obs->nsource; i++) {
|
||||
struct sepdirobs_source *s = &obs->source[i];
|
||||
if ((partition == 0 && s->is_test)
|
||||
|| (partition == 1 && !s->is_test))
|
||||
continue;
|
||||
if (p->variant == SEP_VARIANT_PRODUCTION && s->is_test)
|
||||
continue;
|
||||
if (p->variant == SEP_VARIANT_EXTERNAL && !s->is_test)
|
||||
continue;
|
||||
int observed = s->result;
|
||||
if (observed == 0) continue;
|
||||
if (s->is_test
|
||||
&& (p->test_package == NULL
|
||||
|| strcmp(s->package, p->test_package) != 0))
|
||||
continue;
|
||||
if (source_snapshot_add(&p->sources, &p->nsources, &cap,
|
||||
s->path, s->buf, s->len) < 0)
|
||||
return -2;
|
||||
}
|
||||
}
|
||||
return p->nsources;
|
||||
}
|
||||
|
||||
static int
|
||||
sep_reserve_folds(struct sepfoldset *set, int need)
|
||||
{
|
||||
@@ -2628,8 +2849,12 @@ sep_find_or_add_role(struct sepgraph *g, const char *path, const char *entry,
|
||||
static void
|
||||
sep_pkg_free_fields(struct seppkg *p)
|
||||
{
|
||||
for (int j = 0; j < p->nsources; j++) free(p->sources[j]);
|
||||
for (int j = 0; j < p->nsources; j++) {
|
||||
free(p->sources[j].path);
|
||||
free(p->sources[j].buf);
|
||||
}
|
||||
free(p->sources);
|
||||
free(p->entry_buf);
|
||||
for (int j = 0; j < p->bindings.n; j++) {
|
||||
free(p->bindings.v[j].name);
|
||||
free(p->bindings.v[j].source);
|
||||
@@ -2651,8 +2876,8 @@ sep_pkg_free_fields(struct seppkg *p)
|
||||
memset(p, 0, sizeof *p);
|
||||
}
|
||||
|
||||
/* Release the one package-owned directory-membership list. Every graph exit
|
||||
* funnels through this function; regular-file nodes own no source list. */
|
||||
/* Release variant-owned byte copies and request-owned canonical-directory
|
||||
* memberships/observations. Every graph exit funnels through this function. */
|
||||
static void
|
||||
sep_graph_free(struct sepgraph *g)
|
||||
{
|
||||
@@ -2674,8 +2899,21 @@ sep_graph_free(struct sepgraph *g)
|
||||
free(g->context[i].route);
|
||||
free(g->context[i].source_root);
|
||||
}
|
||||
for (int i = 0; i < g->ndirobs; i++) {
|
||||
struct sepdirobs *obs = &g->dirobs[i];
|
||||
for (int j = 0; j < obs->nsource; j++) {
|
||||
free(obs->source[j].name);
|
||||
free(obs->source[j].path);
|
||||
free(obs->source[j].buf);
|
||||
free(obs->source[j].package);
|
||||
}
|
||||
free(obs->source);
|
||||
free(obs->canon);
|
||||
free(obs->entry);
|
||||
}
|
||||
free(g->package_folds.v);
|
||||
free(g->file_folds.v);
|
||||
free(g->dirobs);
|
||||
free(g->context);
|
||||
free(g->pkg);
|
||||
free(g);
|
||||
@@ -3507,16 +3745,65 @@ static int
|
||||
sep_scan_file(struct sepgraph *g, int pi, const char *file,
|
||||
int context, struct ImportSet *filevisit,
|
||||
struct sepbindset *bindings, struct sepchildren *children,
|
||||
int owned_source)
|
||||
int owned_source, const char *selected_buf, u64 selected_len)
|
||||
{
|
||||
if (import_seen(filevisit, file)) return 0;
|
||||
if (import_add(filevisit, file) < 0) return -1;
|
||||
char *buf;
|
||||
u64 len;
|
||||
if (sep_slurp(file, &buf, &len) < 0) {
|
||||
int documentation_prechecked = 0;
|
||||
if (selected_buf != NULL) {
|
||||
buf = malloc((size_t)selected_len + 1);
|
||||
if (buf == NULL) return sep_fail_nomem();
|
||||
memcpy(buf, selected_buf, (size_t)selected_len + 1);
|
||||
len = selected_len;
|
||||
documentation_prechecked = 1;
|
||||
} else if (!owned_source && g->pkg[pi].entry_buf != NULL
|
||||
&& strcmp(g->pkg[pi].entry, file) == 0) {
|
||||
buf = malloc((size_t)g->pkg[pi].entry_len + 1);
|
||||
if (buf == NULL) return sep_fail_nomem();
|
||||
memcpy(buf, g->pkg[pi].entry_buf,
|
||||
(size_t)g->pkg[pi].entry_len + 1);
|
||||
len = g->pkg[pi].entry_len;
|
||||
documentation_prechecked = 1;
|
||||
} else if (!owned_source && source_documentation_preload.path != NULL
|
||||
&& strcmp(source_documentation_preload.path, file) == 0) {
|
||||
buf = source_documentation_preload.buf;
|
||||
len = source_documentation_preload.len;
|
||||
free(source_documentation_preload.path);
|
||||
source_documentation_preload.path = NULL;
|
||||
source_documentation_preload.buf = NULL;
|
||||
source_documentation_preload.len = 0;
|
||||
documentation_prechecked = 1;
|
||||
} else if (sep_slurp(file, &buf, &len) < 0) {
|
||||
fprintf(stderr, "ww: cannot read %s\n", file);
|
||||
return -1;
|
||||
}
|
||||
if (!owned_source && g->pkg[pi].entry_buf == NULL
|
||||
&& strcmp(g->pkg[pi].entry, file) == 0
|
||||
&& (documentation_prechecked || source_regular_file(file))) {
|
||||
g->pkg[pi].entry_buf = malloc((size_t)len + 1);
|
||||
if (g->pkg[pi].entry_buf == NULL) {
|
||||
sep_fail_nomem();
|
||||
free(buf);
|
||||
return -1;
|
||||
}
|
||||
memcpy(g->pkg[pi].entry_buf, buf, (size_t)len + 1);
|
||||
g->pkg[pi].entry_len = len;
|
||||
}
|
||||
if (!owned_source && !documentation_prechecked
|
||||
&& source_regular_file(file)) {
|
||||
int documentation = source_documentation_buffer(file, buf, len);
|
||||
if (documentation < 0) {
|
||||
free(buf);
|
||||
return -1;
|
||||
}
|
||||
if (documentation > 0) {
|
||||
source_operand_no_sources(file);
|
||||
free(buf);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
Arena *a = newarena();
|
||||
Lex l;
|
||||
Parser p;
|
||||
@@ -4052,10 +4339,21 @@ sep_clone_for_test(struct sepgraph *g, int original, const char *owner,
|
||||
}
|
||||
int sourcecap = 0;
|
||||
for (int i = 0; i < src->nsources; i++) {
|
||||
if (source_list_add(&p->sources, &p->nsources, &sourcecap,
|
||||
src->sources[i]) < 0)
|
||||
if (source_snapshot_add(&p->sources, &p->nsources, &sourcecap,
|
||||
src->sources[i].path, src->sources[i].buf,
|
||||
src->sources[i].len) < 0)
|
||||
goto fail;
|
||||
}
|
||||
if (src->entry_buf != NULL) {
|
||||
p->entry_buf = malloc((size_t)src->entry_len + 1);
|
||||
if (p->entry_buf == NULL) {
|
||||
sep_fail_nomem();
|
||||
goto fail;
|
||||
}
|
||||
memcpy(p->entry_buf, src->entry_buf,
|
||||
(size_t)src->entry_len + 1);
|
||||
p->entry_len = src->entry_len;
|
||||
}
|
||||
for (int i = 0; i < src->bindings.n; i++) {
|
||||
struct sepbind *b = &src->bindings.v[i];
|
||||
Pos pos = { b->source, b->line, b->col };
|
||||
@@ -4161,17 +4459,14 @@ sep_prepare_pkg_context(struct sepgraph *g, int pi, int context,
|
||||
if (!g->pkg[pi].loaded) {
|
||||
g->pkg[pi].loaded = 1;
|
||||
if (g->pkg[pi].is_dir) {
|
||||
const char *test_package = g->pkg[pi].test_package;
|
||||
g->pkg[pi].nsources = enumerate_dir_ww(g->pkg[pi].entry,
|
||||
g->pkg[pi].variant, test_package,
|
||||
&g->pkg[pi].sources);
|
||||
if (g->pkg[pi].nsources == -2) {
|
||||
rc = -1; /* diagnosed in enumerate_dir_ww */
|
||||
} else if (g->pkg[pi].nsources < 0) {
|
||||
int selected = sep_select_dir_sources(g, &g->pkg[pi]);
|
||||
if (selected == -2) {
|
||||
rc = -1; /* diagnosed during source observation */
|
||||
} else if (selected < 0) {
|
||||
fprintf(stderr, "ww: cannot read directory %s\n",
|
||||
g->pkg[pi].entry);
|
||||
rc = -1;
|
||||
} else if (g->pkg[pi].nsources == 0) {
|
||||
} else if (selected == 0) {
|
||||
fprintf(stderr,
|
||||
"ww: %s: directory contains no WW package sources\n",
|
||||
g->pkg[pi].entry);
|
||||
@@ -4179,16 +4474,18 @@ sep_prepare_pkg_context(struct sepgraph *g, int pi, int context,
|
||||
}
|
||||
for (int i = 0; i < g->pkg[pi].nsources && rc == 0; i++)
|
||||
rc = sep_register_file_fold(g, g->pkg[pi].canon,
|
||||
g->pkg[pi].sources[i]);
|
||||
g->pkg[pi].sources[i].path);
|
||||
}
|
||||
}
|
||||
if (g->pkg[pi].is_dir) {
|
||||
for (int i = 0; i < g->pkg[pi].nsources && rc == 0; i++)
|
||||
rc = sep_scan_file(g, pi, g->pkg[pi].sources[i],
|
||||
context, &fv, &bindings, children, 1);
|
||||
rc = sep_scan_file(g, pi, g->pkg[pi].sources[i].path,
|
||||
context, &fv, &bindings, children, 1,
|
||||
g->pkg[pi].sources[i].buf,
|
||||
g->pkg[pi].sources[i].len);
|
||||
} else if (rc == 0) {
|
||||
rc = sep_scan_file(g, pi, g->pkg[pi].entry, context,
|
||||
&fv, &bindings, children, 0);
|
||||
&fv, &bindings, children, 0, NULL, 0);
|
||||
}
|
||||
sep_import_set_free(&fv);
|
||||
if (bindings.n > 1)
|
||||
@@ -4827,13 +5124,18 @@ sep_validate_module_closure(struct sepgraph *g, const int *order, int n,
|
||||
* //ww:module-reset primary boundary. No source or export outside pi's
|
||||
* sorted owned-source set may enter this unit. */
|
||||
static int
|
||||
sep_emit_body(FILE *out, const char *path, const char *modpath)
|
||||
sep_emit_body(FILE *out, const char *path, const char *modpath,
|
||||
const char *selected_buf, u64 selected_len)
|
||||
{
|
||||
char *buf;
|
||||
u64 len;
|
||||
if (sep_slurp(path, &buf, &len) < 0) {
|
||||
fprintf(stderr, "ww: cannot read %s\n", path);
|
||||
return -1;
|
||||
char *owned = NULL;
|
||||
const char *buf = selected_buf;
|
||||
u64 len = selected_len;
|
||||
if (buf == NULL) {
|
||||
if (sep_slurp(path, &owned, &len) < 0) {
|
||||
fprintf(stderr, "ww: cannot read %s\n", path);
|
||||
return -1;
|
||||
}
|
||||
buf = owned;
|
||||
}
|
||||
/* #57: tag the primary body by its full dotted import path so the
|
||||
* definer mangles == the importer reference; a root build (path "")
|
||||
@@ -4859,7 +5161,7 @@ sep_emit_body(FILE *out, const char *path, const char *modpath)
|
||||
!= (size_t)(len - off)
|
||||
|| fputc('\n', out) == EOF)
|
||||
bad = 1;
|
||||
free(buf);
|
||||
free(owned);
|
||||
if (bad) {
|
||||
fprintf(stderr, "ww: cannot write package unit\n");
|
||||
return -1;
|
||||
@@ -4931,10 +5233,12 @@ sep_compose_unit(struct sepgraph *g, int pi, const char *scratch,
|
||||
bodyrc = -1;
|
||||
} else if (g->pkg[pi].is_dir) {
|
||||
for (int i = 0; i < g->pkg[pi].nsources && bodyrc == 0; i++)
|
||||
bodyrc = sep_emit_body(u, g->pkg[pi].sources[i],
|
||||
g->pkg[pi].path);
|
||||
bodyrc = sep_emit_body(u, g->pkg[pi].sources[i].path,
|
||||
g->pkg[pi].path, g->pkg[pi].sources[i].buf,
|
||||
g->pkg[pi].sources[i].len);
|
||||
} else {
|
||||
bodyrc = sep_emit_body(u, g->pkg[pi].entry, g->pkg[pi].path);
|
||||
bodyrc = sep_emit_body(u, g->pkg[pi].entry, g->pkg[pi].path,
|
||||
g->pkg[pi].entry_buf, g->pkg[pi].entry_len);
|
||||
}
|
||||
const char *own_suffix;
|
||||
size_t own_parents;
|
||||
@@ -7169,6 +7473,7 @@ build_one_sep(const char *src, int entry_is_dir, const char *root_identity,
|
||||
}
|
||||
}
|
||||
}
|
||||
source_documentation_preload_clear();
|
||||
return r;
|
||||
}
|
||||
|
||||
@@ -7619,6 +7924,18 @@ do_build(int argc, char **argv)
|
||||
free(incs);
|
||||
return 1;
|
||||
}
|
||||
if (!is_dir && source_regular_file(resolved)) {
|
||||
int documentation = source_preload_documentation(resolved);
|
||||
if (documentation < 0) {
|
||||
free(incs);
|
||||
return 1;
|
||||
}
|
||||
if (documentation > 0) {
|
||||
source_operand_no_sources(resolved);
|
||||
free(incs);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
char out[PATH_MAX];
|
||||
const char *objstem = NULL;
|
||||
int discard_output = strcmp(outflag, "/dev/null") == 0;
|
||||
@@ -7736,6 +8053,18 @@ do_run(int argc, char **argv)
|
||||
free(incs);
|
||||
return 1;
|
||||
}
|
||||
if (!is_dir && source_regular_file(resolved)) {
|
||||
int documentation = source_preload_documentation(resolved);
|
||||
if (documentation < 0) {
|
||||
free(incs);
|
||||
return 1;
|
||||
}
|
||||
if (documentation > 0) {
|
||||
source_operand_no_sources(resolved);
|
||||
free(incs);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
char tmpdir[PATH_MAX], tmp[PATH_MAX];
|
||||
snprintf(tmpdir, sizeof tmpdir, "/tmp/ww_run_%d", getpid());
|
||||
if (mkdir(tmpdir, 0700) != 0) {
|
||||
@@ -7797,6 +8126,17 @@ do_run(int argc, char **argv)
|
||||
return rc;
|
||||
}
|
||||
|
||||
static int
|
||||
source_test_preflight(const char *path, int compileonly, int emit_asm)
|
||||
{
|
||||
if (!source_regular_file(path)) return 0;
|
||||
int documentation = source_preload_documentation(path);
|
||||
if (documentation > 0) source_operand_no_sources(path);
|
||||
if (documentation == 0) return 0;
|
||||
if (!compileonly && !emit_asm) fputs("FAIL\n", stdout);
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int
|
||||
do_test(int argc, char **argv)
|
||||
{
|
||||
@@ -8293,6 +8633,8 @@ do_test(int argc, char **argv)
|
||||
"ww test: package-test variant needs one directory\n");
|
||||
return 2;
|
||||
}
|
||||
if (source_test_preflight(resolved, compileonly, emit_asm))
|
||||
return 1;
|
||||
char tmpdir[PATH_MAX] = {0}, tmp[PATH_MAX];
|
||||
const char *outp;
|
||||
int retain_output = outstem[0] && !discard_output;
|
||||
@@ -8375,6 +8717,8 @@ do_test(int argc, char **argv)
|
||||
"ww test: package options need a directory\n");
|
||||
return 2;
|
||||
}
|
||||
if (source_test_preflight(target, compileonly, emit_asm))
|
||||
return 1;
|
||||
char tmpdir[PATH_MAX] = {0}, tmp[PATH_MAX];
|
||||
const char *outp;
|
||||
int retain_output = outstem[0] && !discard_output;
|
||||
|
||||
@@ -10646,8 +10646,9 @@ suffix.
|
||||
- **behavior derived from the pinned implementation** — the rule does not
|
||||
complete the remaining suffix-first run front, multiple named sources,
|
||||
finite `.ww` FIFO capture, shared test-process state/failure topology,
|
||||
Go-compatible `-run` regular expressions, or `package documentation`
|
||||
suppression. Existing `.ww` directory slices, hidden-source exclusion,
|
||||
or Go-compatible `-run` regular expressions. Exact `package documentation`
|
||||
suppression was still open when section 11.54 closed and is completed in
|
||||
section 11.55. Existing `.ww` directory slices, hidden-source exclusion,
|
||||
named `_test.ww` build omission, recursive/multiple-root coordination,
|
||||
package syntax, and import syntax remain intact.
|
||||
|
||||
@@ -10655,6 +10656,202 @@ Build workdir format remains `18`, test workdir format remains `19`, and
|
||||
semantic storage format remains `3`. No schema, action descriptor, cache/result
|
||||
record, manifest, transaction protocol, or lock changes.
|
||||
|
||||
### 11.55 Implemented exact `package documentation` source suppression
|
||||
|
||||
A selected source whose successfully parsed package name is exactly
|
||||
`documentation` is a documentation source, not a semantic package source. It
|
||||
is removed by loading before source-family folding, import-edge construction,
|
||||
or any build, run, or test action. `documentation_test`, `documentationx`, and
|
||||
every other package name remain ordinary.
|
||||
|
||||
#### Pinned authority, header boundary, and applicability
|
||||
|
||||
- **behavior directly implemented or asserted by pinned Go** — the sole
|
||||
authority is official Go 1.26.5 commit
|
||||
`c19862e5f8415b4f24b189d065ed739517c548ba`. Its `go/build` loader records a
|
||||
package/import-header error before excluding an exact parsed package name
|
||||
`documentation`, and does so before `_test.go` or package-family
|
||||
classification. Named Go files pass through the same rule. Public Go command
|
||||
help also reserves the name and says such files are ignored.
|
||||
- **behavior directly implemented or asserted by pinned Go** — the pinned
|
||||
loader reads the package clause and contiguous import section, not an
|
||||
arbitrary body. A successful ordinary-body stop removes its one-byte
|
||||
lookahead. It first probes a raw following `i` as a possible `import`,
|
||||
however, so an `i` that does not form that exact keyword is a malformed
|
||||
attempted import rather than a successful body boundary. Exact `import`
|
||||
followed by malformed import syntax retains the ordinary import-parser
|
||||
diagnostic.
|
||||
- **behavior directly implemented or asserted by pinned Go** — official
|
||||
`src/go/build/read_test.go` tests the ordinary-body stop and malformed-import
|
||||
recovery, but no test or testdata in the pinned tree directly names
|
||||
`package documentation`. `cmd/go/testdata/script/mod_doc.txt` concerns module
|
||||
documentation and is not evidence for this rule. This absence is explicit;
|
||||
host-Go observations do not fill it.
|
||||
- **directly measured WW behavior** — before suppression, both driver stages
|
||||
incorrectly treated documentation files as ordinary sources: doc-only roots
|
||||
produced semantic artifacts, mixed roots conflicted, imported providers
|
||||
created edges and actions, and test routes created test products. Valid
|
||||
malformed bodies also exposed stage-specific recovery diagnostics, while
|
||||
malformed package and contiguous-import headers already failed before an
|
||||
action.
|
||||
- **behavior derived from the pinned implementation** — the quiet candidate
|
||||
recognizer admits one leading BOM, leading whitespace, comments, internal
|
||||
line directives, and trivia around `package`, exact identifier
|
||||
`documentation`, and `;`. It emits no diagnostic and sends only a plausible
|
||||
exact candidate through the existing package/import-header parser, so
|
||||
ordinary sources acquire no new early diagnostics. Malformed candidate
|
||||
headers retain that parser's positioned errors.
|
||||
- **behavior derived from the pinned implementation** — after a valid exact
|
||||
header, a non-`i` first raw byte at the body boundary ends loading and every
|
||||
later byte is ignored. If that first raw byte is `i` but does not form the
|
||||
exact `import` token, both stage drivers and the shared coordinator emit
|
||||
exactly `<FILE>:<LINE>:<COL>: error: expected top-level decl\n`, owned by
|
||||
that byte. A malformed exact import retains its existing diagnostic. A
|
||||
malformed package clause, malformed contiguous import, reached header NUL,
|
||||
or unterminated header-trivia comment also remains an error. No no-source
|
||||
diagnostic follows any such header error.
|
||||
|
||||
#### Selection, package identity, and imports
|
||||
|
||||
- **behavior derived from the pinned implementation** — existing CLI and
|
||||
operand-shape errors, logical resolution and file-kind checks, and suffix,
|
||||
hidden-prefix, target, and test-role eligibility retain precedence. The rule
|
||||
then applies to selected directory members, literal and logical named roots,
|
||||
raw tests, direct and recursive coordinator discovery, and every dotted
|
||||
dependency resolving to a directory. Source imports remain directory-only;
|
||||
a logical one-file provider is only a CLI-root compatibility route.
|
||||
- **behavior derived from the pinned implementation** — named-file
|
||||
documentation preflight is entered only after symlink-following `stat`
|
||||
classifies the selected source as regular. A symlink to a regular file is
|
||||
therefore included. The preflight owns one exact read buffer: a suppressed
|
||||
documentation source is discarded from that buffer, while an ordinary source
|
||||
carries the same bytes into graph loading instead of being reopened. FIFO and
|
||||
other nonregular named-source routes retain their existing handling and are
|
||||
neither classified nor otherwise changed by this preflight.
|
||||
- **behavior derived from the pinned implementation** — coordinator directory
|
||||
discovery collects metadata only. It canonicalizes and deduplicates selected
|
||||
paths before reading source, classifies each unique source exactly once, and
|
||||
passes the retained ordinary-source buffer to its existing source validation.
|
||||
Direct-root errors and recursive pattern/group accounting are computed per
|
||||
request from those classification results; a directory member is not
|
||||
reclassified for each spelling or pattern that found it. The delegated stage
|
||||
driver owns a separate request-graph observation: it enumerates each reached
|
||||
canonical directory once, lazily opens only role-eligible files, and reuses
|
||||
each observed path, classification, package-name/`@test` attestation, and byte
|
||||
snapshot across production, same-package-test, external-test, and copied test
|
||||
actions. A production-only request therefore still leaves `*_test.ww`
|
||||
unopened. The coordinator and delegated driver are distinct existing process
|
||||
boundaries; this slice does not add a cross-process atomic snapshot for a
|
||||
source concurrently rewritten between those observations.
|
||||
- **behavior derived from the pinned implementation** — the three test-source
|
||||
routes remain distinct. A visible literal named `_test.ww` build first
|
||||
validates its package/import header and then omits it by the already completed
|
||||
test-only rule; documentation classification, including the raw-`i` check,
|
||||
does not run. Directory production keeps eligible `*_test.ww` entries
|
||||
unopened. Raw `ww test` and selected directory test variants do run the
|
||||
documentation classifier. A logical request whose resolved file merely has
|
||||
an `_test.ww` physical basename is not the literal named-build special case.
|
||||
- **behavior derived from the pinned implementation** — an omitted source
|
||||
contributes no package member, declared-family or test-family candidate,
|
||||
import occurrence, binding or edge, qualifier, initializer, declaration,
|
||||
symbol, graph action, unit, `.wwi`, or persistence identity. Its physical
|
||||
pathname, parent, and symlink information remain loader observations only.
|
||||
A logical one-file CLI source receives `__root` identity only when retained
|
||||
as ordinary. A doc-only dotted directory provider has no package sources and
|
||||
cannot satisfy an import; a same-named `.ww` file remains an import decoy
|
||||
under the pre-existing directory-only import rule.
|
||||
- **behavior derived from the pinned implementation** — a mixed directory
|
||||
retains exactly the canonical local or dotted identity and source graph of
|
||||
the same tree with the documentation file absent. Imports appearing in the
|
||||
valid contiguous documentation header are checked only to establish the
|
||||
header; they never become dependency edges. Imports, declarations,
|
||||
initializers, tests, aborts, nonzero mains, missing dependencies, and syntax
|
||||
after a successful non-`i` body boundary are unobserved.
|
||||
|
||||
#### Build, run, and test empty selections
|
||||
|
||||
- **behavior derived from the pinned implementation** — a direct
|
||||
non-coordinator doc-only `ww build DIR` exits 1 with empty stdout and exact
|
||||
stderr `ww: DIR: directory contains no WW package sources\n`. A selected
|
||||
named or logical non-test source uses its physical containing directory in
|
||||
the same diagnostic, and `-S`, output, and `/dev/null` modes do not displace
|
||||
it. A direct root already routed through the coordinator, including an
|
||||
output-directory request, instead uses exact stderr
|
||||
`wwtest package: DIR: directory contains no WW package sources\n` with the
|
||||
same status and stdout.
|
||||
- **behavior derived from the pinned implementation** — the literal visible
|
||||
named `_test.ww` build retains section 11.51's outcome even when its valid
|
||||
declared name is `documentation`: no effective `-o` (including an
|
||||
assembly-only request), or exact `/dev/null`, succeeds silently; a
|
||||
non-directory output exits 1 with
|
||||
`ww: no packages to build\n`; an output-directory request exits 1 with
|
||||
`ww: no main packages to build\n`. Header errors still precede those
|
||||
outcomes.
|
||||
- **behavior derived from the pinned implementation** — `ww run` on a selected
|
||||
doc-only physical or logical directory exits 1 with empty stdout and
|
||||
`ww: DIR: directory contains no WW package sources\n`. The existing private
|
||||
`/tmp/ww_run_<pid>` is acquired before directory enumeration and then
|
||||
removed. A named or logical one-file run uses its physical parent's
|
||||
no-source diagnostic before acquiring run scratch. The separately open
|
||||
suffix-first run behavior may select a visible `_test.ww`; when it does, this
|
||||
rule suppresses that source. A `.ww`-spelled directory keeps the earlier
|
||||
stat-first run rejection.
|
||||
- **behavior derived from the pinned implementation** — direct doc-only
|
||||
`ww test DIR` exits 1 with stdout `FAIL\n` and the coordinator no-source
|
||||
stderr; `ww test -c DIR` exits 1 with empty stdout and the same stderr.
|
||||
Directory `test -S` without the required `-o` retains status 2 and exact
|
||||
`ww test: -S needs -o\n`; after a valid `-o`, the directory retains status 2
|
||||
and exact `ww test: -S needs a single test file\n`. Both checks precede
|
||||
source classification. Named or logical raw `ww test FILE` exits 1 with
|
||||
`FAIL\n` and the physical parent's driver no-source stderr; raw `-c` and
|
||||
`-S` exit 1 with empty stdout and the same stderr. No requested output or
|
||||
work state changes.
|
||||
- **behavior derived from the pinned implementation** — a recursive build
|
||||
pattern retaining only doc-only directories exits 0 with empty stdout and
|
||||
one `ww: warning: "PATTERN" matched no packages\n`; recursive test exits 1
|
||||
with empty stdout, that warning, then
|
||||
`ww test: no packages to test\n`. A pattern also containing ordinary
|
||||
directories builds or tests only those groups without warning. Multiple
|
||||
patterns warn once for each no-match pattern. Any directly named doc-only
|
||||
sibling root fails the complete request during discovery before any ordinary
|
||||
group starts.
|
||||
- **behavior derived from the pinned implementation** — the positioned raw-`i`
|
||||
header error has empty stdout for build, run, `test -c`, and raw `test -S`.
|
||||
An explicit running raw or directory/recursive test adds exactly `FAIL\n`.
|
||||
Literal named `_test.ww` build and directory `test -S` retain their earlier
|
||||
precedence and never reach this classifier.
|
||||
|
||||
#### Actions, lifecycle, parity, formats, and scope
|
||||
|
||||
- **behavior derived from the pinned implementation** — a doc-only source
|
||||
causes no compiler, assembler, archiver, linker, generated test support/main,
|
||||
test child, initializer, or program runtime. It creates no unit, `.wwi`,
|
||||
assembly, object, archive, executable, output directory, sidecar, workdir
|
||||
stamp, transaction, capture, or retained result. Mixed outputs and comparable
|
||||
semantic artifacts are byte-identical to the source-absent control.
|
||||
- **behavior derived from the pinned implementation** — a documentation source
|
||||
has no publication destination or persistent key. Adding, removing, or
|
||||
changing only an ignored documentation body does not invalidate, refresh,
|
||||
replace, or become a reuse input for committed ordinary actions. Doc-only
|
||||
cold failure publishes nothing; a warm no-source or header failure preserves
|
||||
prior public and semantic bytes. Ordinary sibling publication, producer
|
||||
failure, transaction rollback, and invalidation remain unchanged.
|
||||
- **behavior derived from the pinned implementation** — classification state
|
||||
is ephemeral and request-owned: the named-root preload is process-local and
|
||||
cleared at the build boundary, while directory observations live only in the
|
||||
request graph. Neither is persisted or shared across requests, and no lock,
|
||||
schema, cache, or cross-request identity is added. Normal and controlled
|
||||
failure use the existing reader and request-private cleanup. The verified
|
||||
direct-SIGTERM fixed-`.new` poisoning gap remains open and is neither reached
|
||||
nor repaired by this slice.
|
||||
- **behavior derived from the pinned implementation** — Cstage and WWstage
|
||||
must agree on selected status, stdout, stderr, source membership, graphs,
|
||||
actions, semantic artifacts, and lifecycle. The producer-provenance
|
||||
`.wwtool.ww` remains intentionally stage-specific. Build workdir format
|
||||
remains `18`, test workdir format remains `19`, and semantic storage format
|
||||
remains `3`; no action descriptor, cache/result record, transaction marker,
|
||||
manifest, database, or lock is added.
|
||||
|
||||
## 12. Candidate architectures and hard-gate decision
|
||||
|
||||
Five candidates were developed as coherent systems, not as feature bins.
|
||||
|
||||
101
docs/spec.md
101
docs/spec.md
@@ -319,6 +319,87 @@ ImportPath = ident { "." ident } .
|
||||
delegated graph-import scan or tools. An ordinary build compares production
|
||||
names only; one test product compares its production, same-package test, and
|
||||
external-test selections without merging their units.
|
||||
- After existing filename, target, kind, and test-role eligibility, a selected
|
||||
source whose package/import header successfully declares the exact name
|
||||
`documentation` is omitted before semantic source membership and import
|
||||
scanning. The loader accepts the ordinary leading BOM, whitespace, comments,
|
||||
internal line directives, and package-clause trivia when recognizing this
|
||||
exact candidate. `documentation_test`, `documentationx`, and all other names
|
||||
remain ordinary sources.
|
||||
|
||||
Candidate recognition is silent. A malformed package clause, malformed
|
||||
contiguous import, reached header NUL, or unterminated header-trivia comment
|
||||
retains its existing positioned load error before omission. After a valid
|
||||
exact package/import prefix, a non-`i` first raw byte establishes the body
|
||||
boundary and the remaining bytes are not parsed. A first raw `i` that does
|
||||
not form the exact `import` token is instead diagnosed at that byte as
|
||||
`expected top-level decl`; exact `import` followed by malformed import syntax
|
||||
retains the ordinary import-parser diagnostic. Thus syntax, types, late
|
||||
imports, imports missing only in the body, declarations, initializers,
|
||||
tests, and runtime behavior after a successful non-`i` boundary are not
|
||||
observed.
|
||||
|
||||
An omitted documentation source creates no package member or family,
|
||||
source-file import occurrence, binding or graph edge, qualifier,
|
||||
initializer, declaration, symbol, `.wwi`, action, publication destination,
|
||||
or persistence identity. A mixed package is semantically identical to the
|
||||
same source set without that file. A doc-only dotted directory provider has
|
||||
no package sources and cannot satisfy an import. A same-named `.ww` file is
|
||||
still not an import provider under the directory-only import rule; when such
|
||||
a file is selected as a logical CLI root, it receives the established
|
||||
`__root` identity only if retained as ordinary. The rule applies equally to
|
||||
selected literal and logical roots, directory members, raw tests, recursive
|
||||
discovery, and dotted directory providers.
|
||||
|
||||
Named-file documentation preflight applies only after symlink-following
|
||||
`stat` classifies the literal or resolved source as regular, including a
|
||||
symlink to a regular file. It retains one exact read buffer: documentation
|
||||
bytes are suppressed from that buffer, while ordinary bytes from the same
|
||||
preflight are reused by graph loading rather than reopened. FIFO and other
|
||||
nonregular named-source routes keep their prior behavior and are not touched
|
||||
by this preflight.
|
||||
|
||||
Coordinator directory discovery records metadata only. Selected paths are
|
||||
canonicalized and deduplicated before source reads; each unique source is
|
||||
then classified exactly once. For an ordinary result, the same buffered bytes
|
||||
feed existing coordinator source validation. Direct-root failure and
|
||||
recursive pattern/group accounting are performed per request after
|
||||
classification, without rereading or reclassifying a source reached by more
|
||||
than one spelling or pattern. Independently, one delegated driver request
|
||||
enumerates each canonical directory once and lazily snapshots each
|
||||
role-eligible regular source once; that observation supplies classification,
|
||||
package/test attestation, import scanning, copied test actions, and unit
|
||||
emission across every variant in the request. A production-only request does
|
||||
not open excluded `*_test.ww` entries. Coordinator and delegated-driver
|
||||
observations remain separate process boundaries; concurrent rewriting between
|
||||
them gains no new atomic-snapshot guarantee.
|
||||
|
||||
Test-role precedence remains route-specific. A visible literal named
|
||||
`_test.ww` passed to `ww build` validates its package/import header and then
|
||||
follows the test-only empty-selection rule below without documentation
|
||||
classification. A production directory build leaves `*_test.ww` unopened.
|
||||
Raw `ww test` and selected directory test variants do classify documentation
|
||||
sources. A logical request whose resolved provider merely has an `_test.ww`
|
||||
physical basename is not the literal named-build special case. Directory
|
||||
`ww test -S` without its required `-o` rejects as
|
||||
`ww test: -S needs -o`; with a valid `-o`, a directory rejects as
|
||||
`ww test: -S needs a single test file`. Both checks precede classification.
|
||||
|
||||
A doc-only direct build or run root takes its route's existing
|
||||
`directory contains no WW package sources` result; recursive doc-only
|
||||
directories do not become packages, while mixed roots are exactly the
|
||||
source-absent control. No compiler, assembler, archiver, linker, generated
|
||||
test support/main, test child, initializer, or program runtime is attributable
|
||||
to the omitted source, and it creates no unit, interface, assembly, object,
|
||||
archive, executable, output, transaction, capture, or retained result.
|
||||
Changing only an ignored documentation body cannot invalidate or replace a
|
||||
committed semantic generation. Cold failure publishes nothing; warm
|
||||
no-source or header failure preserves prior public and semantic bytes.
|
||||
Classification state is ephemeral: a process-local named-root preload is
|
||||
cleared at the build boundary, and directory observations die with the
|
||||
request graph. No state is shared or persisted across requests, and no lock,
|
||||
schema, cache, or serialized identity is added. Build workdir format remains
|
||||
18, test workdir format remains 19, and semantic storage format remains 3.
|
||||
- A single existing raw `.ww` operand is also subject to the unconditional
|
||||
leading-name rule: if its final requested basename begins `.` or `_`, it is
|
||||
ignored before the source is opened. Named raw sources otherwise retain their
|
||||
@@ -983,6 +1064,26 @@ support, generated main, link, binary, captured runtime result, or process. The
|
||||
coordinator reports that successful validation exactly as
|
||||
`? <package> [no test files]\n`.
|
||||
|
||||
An exact `package documentation` source that reaches a test selection is
|
||||
suppressed by the package rule in §4 before production, same-package, or
|
||||
external-test family construction. It contributes no `@test`, dependency,
|
||||
initializer, support action, generated-main input, binary, process, captured
|
||||
result, or retained output. In a mixed directory the test product is exactly
|
||||
the product of the ordinary sources alone; a doc-only directory is not the
|
||||
successful production-with-no-tests case. A direct running directory request
|
||||
instead fails with `FAIL\n` and the coordinator's
|
||||
`directory contains no WW package sources` diagnostic, while `-c` omits
|
||||
`FAIL`. A selected named or logical raw running request likewise emits `FAIL`
|
||||
and its physical parent's driver no-source diagnostic; raw `-c` and `-S` omit
|
||||
the marker. Directory `-S` without the required `-o` retains
|
||||
`ww test: -S needs -o`; after a valid `-o`, it retains
|
||||
`ww test: -S needs a single test file`. Both precede source classification.
|
||||
Recursive doc-only matches are omitted as packages: running test reports the
|
||||
per-pattern no-match warning followed by `ww test: no packages to test`, while
|
||||
mixed recursive selections run only ordinary groups. A documentation-header
|
||||
error retains precedence over every no-source result; an explicit running test
|
||||
adds `FAIL`, but compile-only and assembly-only routes do not.
|
||||
|
||||
Every test-bearing directory product links one request-private runnable.
|
||||
The test output-option name is exactly `o`, with the accepted forms `-o VALUE`,
|
||||
`--o VALUE`, `-o=VALUE`, and `--o=VALUE`. Equals forms split only at their
|
||||
|
||||
@@ -265,6 +265,80 @@ directory/recursive selection, package/import identities, graph/action
|
||||
identities, persistence formats (build 18, test 19, semantic 3), and test
|
||||
process topology are explicit non-effects.
|
||||
|
||||
Exact `package documentation` is a separate source-suppression rule after the
|
||||
completed filename, target, file-kind, and test-role gates. Official Go 1.26.5
|
||||
`go/build` directly implements the exclusion after package/import-header
|
||||
loading and before `_test.go` or package-family classification. Its
|
||||
`read_test.go` directly tests the ordinary-body stop and malformed-import
|
||||
recovery, but the pinned official test/testdata tree has no test that directly
|
||||
names `package documentation`; `mod_doc.txt` is unrelated. That test absence is
|
||||
recorded rather than replaced with host-Go behavior.
|
||||
|
||||
The focused package observer is therefore WW-native dual-stage proof. It
|
||||
covers one leading BOM, whitespace/comments/internal line directives and
|
||||
package-clause trivia; exact-name controls; malformed package and contiguous
|
||||
import headers; the raw body-boundary `i` that does not form exact `import` and
|
||||
must diagnose `expected top-level decl`; and ignored non-`i` bodies containing
|
||||
late imports, missing imports, declarations, initializers, `@test`, aborts, or
|
||||
nonzero mains. It also covers literal and logical single files, direct and
|
||||
recursive directories, the unchanged one-file import-decoy boundary, dotted
|
||||
directory providers, mixed and doc-only source sets, and exact Cstage/WWstage
|
||||
status, stream, source-set, diagnostic, and semantic-artifact parity.
|
||||
|
||||
Named-file documentation preflight is restricted to sources whose
|
||||
symlink-following `stat` result is regular, including symlinks to regular
|
||||
files. It retains one exact read buffer: documentation sources are suppressed
|
||||
from it, while ordinary sources carry those same bytes into graph loading.
|
||||
FIFO and other nonregular named-source routes are untouched. Coordinator
|
||||
directory discovery is metadata-only; after canonicalization and deduplication,
|
||||
each unique selected source is classified exactly once. Buffered ordinary
|
||||
bytes are then reused for coordinator source validation, and per-request
|
||||
direct/recursive error, match, and group accounting follows classification.
|
||||
The delegated driver separately owns a request-graph directory observation:
|
||||
one canonical membership list and one lazy regular-source snapshot feed every
|
||||
reached production/test variant, including package-name/`@test` checks, import
|
||||
scanning, copied test actions, and unit emission. Production-only selection
|
||||
still does not open excluded `*_test.ww` files. The coordinator and delegated
|
||||
driver retain their separate process observations; the slice does not promise
|
||||
an atomic source snapshot across a concurrent rewrite between them.
|
||||
|
||||
The test route matrix remains intentional. A visible literal named
|
||||
`_test.ww` build validates only its package/import header and then follows the
|
||||
existing test-only empty-selection rule; it does not run documentation
|
||||
classification or the synthetic raw-`i` check. Directory production leaves
|
||||
`*_test.ww` unopened. Raw `ww test` and selected directory test variants do
|
||||
classify exact documentation sources, while a logical request whose resolved
|
||||
provider merely has an `_test.ww` physical basename is not the literal named
|
||||
build special case. Directory `test -S` without its required `-o` first rejects
|
||||
with `ww test: -S needs -o`; after a valid `-o`, a directory rejects with
|
||||
`ww test: -S needs a single test file`. Both branches precede documentation
|
||||
classification.
|
||||
|
||||
A doc-only direct running directory test exits 1 with exact `FAIL\n` stdout and
|
||||
the coordinator's no-source stderr; `-c` has the same error and empty stdout.
|
||||
A doc-only named or logical raw running test likewise emits `FAIL\n` plus the
|
||||
driver's physical-parent no-source diagnostic; raw `-c` and `-S` have empty
|
||||
stdout. A documentation-header error precedes no-source, with `FAIL` added only
|
||||
by an explicit running raw or directory/recursive test. Recursive doc-only
|
||||
patterns become no matches: running test emits the per-pattern warning then
|
||||
`ww test: no packages to test`, while mixed patterns retain and run only
|
||||
ordinary groups without warning. A directly named doc-only sibling root fails
|
||||
discovery for the complete request before any group starts.
|
||||
|
||||
Suppression happens before package folders, internal/external families,
|
||||
products, graph edges, support, generated main, or execution. The documentation
|
||||
source creates no compiler, assembler, archiver, linker, test child, capture,
|
||||
result, retained binary, public output, work-state artifact, transaction, or
|
||||
persistence key. Mixed products and semantic artifacts equal the source-absent
|
||||
control byte for byte; changing only an ignored documentation body cannot
|
||||
invalidate or replace a committed generation. Cold failure publishes nothing,
|
||||
warm header/no-source failure preserves prior public and semantic bytes, and
|
||||
normal request-private cleanup and concurrency isolation remain unchanged.
|
||||
The known external-driver fixed-`.new` interruption poisoning remains open.
|
||||
Build workdir format remains 18, test workdir format remains 19, and semantic
|
||||
storage format remains 3; no test-result cache, schema, action descriptor,
|
||||
transaction marker, or lock is introduced.
|
||||
|
||||
An existing local directory whose requested build basename ends `.ww`
|
||||
(including a visible `_test.ww` symlink to a directory) remains a directory
|
||||
package, not a raw named test source. WWstage `ww build` now uses the same
|
||||
|
||||
@@ -6,6 +6,7 @@ import strconv;
|
||||
import strings;
|
||||
import temp;
|
||||
import time;
|
||||
import ww.syntax;
|
||||
|
||||
type pkgsource = struct {
|
||||
path: str,
|
||||
@@ -165,6 +166,20 @@ type pkgdiscover = struct {
|
||||
fatal: bool,
|
||||
};
|
||||
|
||||
type pkgrequestselection = struct {
|
||||
requested: str,
|
||||
discoverroot: str,
|
||||
start: i32,
|
||||
end: i32,
|
||||
recurse: bool,
|
||||
haderrors: bool,
|
||||
};
|
||||
|
||||
fn pkgallocrequests(cap: i32) ([]pkgrequestselection | nomem) = {
|
||||
let value: []pkgrequestselection = alloc([], cap: u64)?;
|
||||
return value;
|
||||
};
|
||||
|
||||
fn pkggrowcap(current: i32, need: i32) i32 = {
|
||||
if (need < 0) { return -1; };
|
||||
if (need <= current) { return current; };
|
||||
@@ -765,6 +780,60 @@ fn pkgskipspace(src: str, start: i32) i32 = {
|
||||
return i;
|
||||
};
|
||||
|
||||
fn pkgdocumentationword(src: str, at: *i32, word: str) bool = {
|
||||
let i: i32 = *at;
|
||||
if (i < 0 || i > src.len || src.len - i < word.len) { return false; };
|
||||
let j: i32 = 0;
|
||||
for (j < word.len) {
|
||||
if (src[i + j] != word[j]) { return false; };
|
||||
j += 1;
|
||||
};
|
||||
if (src.len - i > word.len && pkgident(src[i + word.len])) {
|
||||
return false;
|
||||
};
|
||||
*at = i + word.len;
|
||||
return true;
|
||||
};
|
||||
|
||||
// This is deliberately a quiet exact-name recognizer. Only a plausible
|
||||
// reserved package clause enters the narrow syntax header parser, so ordinary
|
||||
// sources retain their existing full-body diagnostic timing.
|
||||
fn pkgdocumentationcandidate(src: str) bool = {
|
||||
let at: i32 = 0;
|
||||
if (src.len >= 3 && src[0] == 0xefu8 && src[1] == 0xbbu8
|
||||
&& src[2] == 0xbfu8) { at = 3; };
|
||||
at = pkgskipspace(src, at);
|
||||
if (!pkgdocumentationword(src, &at, "package")) { return false; };
|
||||
at = pkgskipspace(src, at);
|
||||
if (!pkgdocumentationword(src, &at, "documentation")) { return false; };
|
||||
at = pkgskipspace(src, at);
|
||||
return at < src.len && src[at] == ';';
|
||||
};
|
||||
|
||||
// -1 is a diagnosed package/import-header error, 0 an ordinary source, and
|
||||
// 1 an exact documentation source. A remaining raw 'i' reproduces pinned
|
||||
// readGoInfo's failed-import-keyword branch; an exact import token was already
|
||||
// consumed or diagnosed by parsepackageheader.
|
||||
fn pkgdocumentation(path: str, src: str) i32 = {
|
||||
if (!pkgdocumentationcandidate(src)) { return 0; };
|
||||
let l: syntax.lex;
|
||||
syntax.lexinit(&l, path, src.ptr, src.len: u64);
|
||||
let ps: syntax.parser;
|
||||
syntax.parserinit(&ps, &l);
|
||||
let header: *syntax.node = syntax.parsepackageheader(&ps);
|
||||
if (l.errs > 0 || ps.errs > 0) { return -1; };
|
||||
if (header.nmod.len == 0) {
|
||||
pkgfailsource(path, 1, 1, "invalid or missing package clause");
|
||||
return -1;
|
||||
};
|
||||
if (l.lpos < src.len: u64 && src[l.lpos] == 'i') {
|
||||
pkgfailsource(path, l.line, l.col, "expected top-level decl");
|
||||
return -1;
|
||||
};
|
||||
if (strings.compare(header.nmod, "documentation") == 0) { return 1; };
|
||||
return 0;
|
||||
};
|
||||
|
||||
fn pkgclause(src: str, out: *str) bool = {
|
||||
// Match the compiler readers: one UTF-8 BOM is invisible only at the
|
||||
// first raw source position. The full stage driver owns later-BOM errors.
|
||||
@@ -1412,6 +1481,19 @@ fn pkgdedup(ss: []str) []str = {
|
||||
return ss;
|
||||
};
|
||||
|
||||
fn pkgindexselected(ss: []str, value: str) i32 = {
|
||||
let i: i32 = 0;
|
||||
for (i < ss.len) {
|
||||
if (strings.compare(ss[i], value) == 0) { return i; };
|
||||
i += 1;
|
||||
};
|
||||
return -1;
|
||||
};
|
||||
|
||||
fn pkgcontainsselected(ss: []str, value: str) bool = {
|
||||
return pkgindexselected(ss, value) >= 0;
|
||||
};
|
||||
|
||||
fn pkgparsedec(s: str, max: i64) i64 = {
|
||||
if (s.len == 0) { return -1i64; };
|
||||
let i: i32 = 0;
|
||||
@@ -2182,6 +2264,16 @@ export fn packagecommand(args: []str) int = {
|
||||
directroots.paths = emptydirect;
|
||||
directroots.errors = 0;
|
||||
directroots.fatal = false;
|
||||
let requestallocation: ([]pkgrequestselection | nomem) =
|
||||
pkgallocrequests(roots.len);
|
||||
let requests: []pkgrequestselection;
|
||||
match (requestallocation) {
|
||||
case let value: []pkgrequestselection => requests = value;
|
||||
case nomem => {
|
||||
pkgputln(os.STDERR_FILENO, "wwtest package: out of memory");
|
||||
return 1;
|
||||
};
|
||||
};
|
||||
let anyrecurse: bool = false;
|
||||
i = 0;
|
||||
for (i < roots.len) {
|
||||
@@ -2231,14 +2323,32 @@ export fn packagecommand(args: []str) int = {
|
||||
};
|
||||
pkgfreematcher(&matcher);
|
||||
};
|
||||
if (recurse && ds.paths.len == before && ds.errors == errorsbefore) {
|
||||
pkgput(os.STDERR_FILENO, "ww: warning: ");
|
||||
pkgputquoted(os.STDERR_FILENO, requested);
|
||||
pkgputln(os.STDERR_FILENO, " matched no packages");
|
||||
} else if (!recurse && ds.paths.len == before
|
||||
&& ds.errors == errorsbefore) {
|
||||
pkgfailpath(discoverroot, "directory contains no WW package sources");
|
||||
ds.errors += 1;
|
||||
let request: pkgrequestselection;
|
||||
request.requested = requested;
|
||||
request.discoverroot = discoverroot;
|
||||
request.start = before;
|
||||
request.end = ds.paths.len;
|
||||
request.recurse = recurse;
|
||||
request.haderrors = ds.errors != errorsbefore;
|
||||
append(requests, request);
|
||||
i += 1;
|
||||
};
|
||||
// Preserve established empty-discovery diagnostics before any new source
|
||||
// read. Requests with raw candidates are accounted again only after exact
|
||||
// documentation sources have been removed from the canonical unique set.
|
||||
i = 0;
|
||||
for (i < requests.len) {
|
||||
if (requests[i].start == requests[i].end
|
||||
&& !requests[i].haderrors) {
|
||||
if (requests[i].recurse) {
|
||||
pkgput(os.STDERR_FILENO, "ww: warning: ");
|
||||
pkgputquoted(os.STDERR_FILENO, requests[i].requested);
|
||||
pkgputln(os.STDERR_FILENO, " matched no packages");
|
||||
} else {
|
||||
pkgfailpath(requests[i].discoverroot,
|
||||
"directory contains no WW package sources");
|
||||
ds.errors += 1;
|
||||
};
|
||||
};
|
||||
i += 1;
|
||||
};
|
||||
@@ -2264,8 +2374,112 @@ export fn packagecommand(args: []str) int = {
|
||||
ds.paths[i] = canonicalsource;
|
||||
i += 1;
|
||||
};
|
||||
pkgsort(ds.paths);
|
||||
ds.paths = pkgdedup(ds.paths);
|
||||
// Keep the canonical occurrence array in discovery order for per-request
|
||||
// membership. Classification reads each canonical path exactly once after
|
||||
// the established directory/source ordering and deduplication, so overlap
|
||||
// cannot duplicate a header diagnostic, pre-existing diagnostic precedence
|
||||
// is retained, and ordinary bytes can feed source validation unchanged.
|
||||
let occurrencepaths: []str = ds.paths;
|
||||
let selectedallocation: ([]str | nomem) = pkgallocstrs(ds.paths.len);
|
||||
let selected: []str;
|
||||
match (selectedallocation) {
|
||||
case let value: []str => selected = value;
|
||||
case nomem => {
|
||||
pkgputln(os.STDERR_FILENO, "wwtest package: out of memory");
|
||||
return 1;
|
||||
};
|
||||
};
|
||||
i = 0;
|
||||
for (i < ds.paths.len) { append(selected, ds.paths[i]); i += 1; };
|
||||
pkgsort(selected);
|
||||
selected = pkgdedup(selected);
|
||||
let bodyallocation: ([]str | nomem) = pkgallocstrs(selected.len);
|
||||
let bodies: []str;
|
||||
match (bodyallocation) {
|
||||
case let value: []str => bodies = value;
|
||||
case nomem => {
|
||||
pkgputln(os.STDERR_FILENO, "wwtest package: out of memory");
|
||||
return 1;
|
||||
};
|
||||
};
|
||||
let readallocation: ([]i32 | nomem) = pkgalloci32(selected.len);
|
||||
let readstates: []i32;
|
||||
match (readallocation) {
|
||||
case let value: []i32 => readstates = value;
|
||||
case nomem => {
|
||||
pkgputln(os.STDERR_FILENO, "wwtest package: out of memory");
|
||||
return 1;
|
||||
};
|
||||
};
|
||||
let retained: i32 = 0;
|
||||
i = 0;
|
||||
for (i < selected.len) {
|
||||
let path: str = selected[i];
|
||||
let body: str = "";
|
||||
let readstate: i32 = 0;
|
||||
let documentation: i32 = 0;
|
||||
if (buildonly && strings.hassuffix(pkgbase(path), "_test.ww")) {
|
||||
readstate = 2;
|
||||
} else if (pkgread(path, &body)) {
|
||||
readstate = 1;
|
||||
documentation = pkgdocumentation(path, body);
|
||||
if (documentation < 0) {
|
||||
return pkgteststatusfail(explicitstatus, compileonly);
|
||||
};
|
||||
};
|
||||
if (documentation == 0) {
|
||||
selected[retained] = path;
|
||||
append(bodies, body);
|
||||
append(readstates, readstate);
|
||||
retained += 1;
|
||||
};
|
||||
i += 1;
|
||||
};
|
||||
selected.len = retained;
|
||||
// A raw nonempty request can become empty only through this suppression.
|
||||
// Each recursive spelling owns one warning; a direct empty root is a
|
||||
// request error that prevents every retained sibling from launching.
|
||||
i = 0;
|
||||
for (i < requests.len) {
|
||||
if (requests[i].start != requests[i].end
|
||||
&& !requests[i].haderrors) {
|
||||
let found: bool = false;
|
||||
let oi: i32 = requests[i].start;
|
||||
for (oi < requests[i].end && !found) {
|
||||
found = pkgcontainsselected(selected, occurrencepaths[oi]);
|
||||
oi += 1;
|
||||
};
|
||||
if (!found) {
|
||||
if (requests[i].recurse) {
|
||||
pkgput(os.STDERR_FILENO, "ww: warning: ");
|
||||
pkgputquoted(os.STDERR_FILENO,
|
||||
requests[i].requested);
|
||||
pkgputln(os.STDERR_FILENO, " matched no packages");
|
||||
} else {
|
||||
pkgfailpath(requests[i].discoverroot,
|
||||
"directory contains no WW package sources");
|
||||
ds.errors += 1;
|
||||
};
|
||||
};
|
||||
};
|
||||
i += 1;
|
||||
};
|
||||
if (ds.errors != 0) {
|
||||
return pkgteststatusfail(explicitstatus, compileonly);
|
||||
};
|
||||
let sortedallocation: ([]str | nomem) = pkgallocstrs(selected.len);
|
||||
let sortedpaths: []str;
|
||||
match (sortedallocation) {
|
||||
case let value: []str => sortedpaths = value;
|
||||
case nomem => {
|
||||
pkgputln(os.STDERR_FILENO, "wwtest package: out of memory");
|
||||
return 1;
|
||||
};
|
||||
};
|
||||
i = 0;
|
||||
for (i < selected.len) { append(sortedpaths, selected[i]); i += 1; };
|
||||
pkgsort(sortedpaths);
|
||||
ds.paths = sortedpaths;
|
||||
if (ds.paths.len == 0) {
|
||||
if (buildonly) {
|
||||
if (explicitout && !buildnull && pkgoutputdir(outname)) {
|
||||
@@ -2304,9 +2518,15 @@ export fn packagecommand(args: []str) int = {
|
||||
i += 1;
|
||||
continue;
|
||||
};
|
||||
let body: str;
|
||||
let selectedindex: i32 = pkgindexselected(selected, ds.paths[i]);
|
||||
if (selectedindex < 0) {
|
||||
pkgputln(os.STDERR_FILENO,
|
||||
"wwtest package: package graph is inconsistent");
|
||||
return 1;
|
||||
};
|
||||
let body: str = bodies[selectedindex];
|
||||
let pn: str;
|
||||
if (!pkgread(ds.paths[i], &body)) {
|
||||
if (readstates[selectedindex] != 1) {
|
||||
pkgfailpath(ds.paths[i], "invalid or missing package clause");
|
||||
return pkgteststatusfail(explicitstatus, compileonly);
|
||||
};
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -14278,6 +14278,550 @@ fn runtimepath(relative: str) str = {
|
||||
return strings.concat(repo(), "/test/package/runtime/", relative);
|
||||
};
|
||||
|
||||
// `documentation` is a source-loader reservation, not a package identity.
|
||||
// Keep this matrix here rather than splitting it between command tests: both
|
||||
// drivers and the shared package coordinator must make the same decision
|
||||
// before a body, import edge, action, or public artifact exists.
|
||||
fn documentationrecord(out: *commandout, code: i32, records: *[]str) void = {
|
||||
expectexit(out, code);
|
||||
append(*records, strings.dup(out.stdout));
|
||||
append(*records, strings.dup(out.stderr));
|
||||
};
|
||||
|
||||
fn documentationrecordssame(a: []str, b: []str) void = {
|
||||
assert(a.len == b.len);
|
||||
let i: i32 = 0;
|
||||
for (i < a.len) { assert(same(a[i], b[i])); i += 1; };
|
||||
};
|
||||
|
||||
fn documentationclean(root: str) void = {
|
||||
assert(!directoryhasnew(root)
|
||||
&& !directoryhasfragment(root, ".wwtxn.")
|
||||
&& !directoryhasfragment(root, ".install")
|
||||
&& !directoryhasfragment(root, ".sepwork")
|
||||
&& !wrongsuffixpathfragment(root, ".new"));
|
||||
};
|
||||
|
||||
@test fn package_documentation_is_omitted_before_body_or_actions() void = {
|
||||
let root: str = fresh();
|
||||
let docs: str = strings.concat(root, "/docs");
|
||||
let controls: str = strings.concat(root, "/controls");
|
||||
let bomdir: str = strings.concat(root, "/bom");
|
||||
let trivia: str = strings.concat(root, "/trivia");
|
||||
let rawidir: str = strings.concat(root, "/raw-i");
|
||||
let rawitree: str = strings.concat(root, "/raw-i-tree");
|
||||
let literal: str = strings.concat(root, "/literal");
|
||||
let include: str = strings.concat(root, "/include");
|
||||
let logicaldir: str = strings.concat(include, "/logic");
|
||||
let logicaltestdir: str = strings.concat(include, "/logicaltest");
|
||||
let tree: str = strings.concat(root, "/tree");
|
||||
let doconlytree: str = strings.concat(root, "/doconlytree");
|
||||
let doconlydocs: str = strings.concat(doconlytree, "/docs");
|
||||
let treedocs: str = strings.concat(tree, "/docs");
|
||||
let treemain: str = strings.concat(tree, "/main");
|
||||
let mixed: str = strings.concat(root, "/mixed");
|
||||
let control: str = strings.concat(root, "/control");
|
||||
let warm: str = strings.concat(root, "/warm");
|
||||
let exact: str = strings.concat(root, "/exact");
|
||||
let importer: str = strings.concat(root, "/importer");
|
||||
let importerone: str = strings.concat(root, "/importer-one");
|
||||
let docdep: str = strings.concat(include, "/docdep");
|
||||
mkdirall(docs); mkdirall(controls); mkdirall(bomdir); mkdirall(trivia);
|
||||
mkdirall(rawidir); mkdirall(rawitree); mkdirall(literal); mkdirall(logicaldir);
|
||||
mkdirall(logicaltestdir); mkdirall(treedocs); mkdirall(treemain);
|
||||
mkdirall(doconlydocs);
|
||||
mkdirall(mixed); mkdirall(control); mkdirall(warm); mkdirall(exact); mkdirall(importer); mkdirall(importerone);
|
||||
mkdirall(docdep);
|
||||
let doc: str = strings.concat(docs, "/doc.ww");
|
||||
let doclink: str = strings.concat(root, "/doclink.ww");
|
||||
let logical: str = strings.concat(logicaldir, "/doc.ww");
|
||||
let rawi: str = strings.concat(rawidir, "/rawi.ww");
|
||||
let rawitreefile: str = strings.concat(rawitree, "/rawi.ww");
|
||||
let badheader: str = strings.concat(controls, "/badheader.ww");
|
||||
let badpackage: str = strings.concat(controls, "/badpackage.ww");
|
||||
let bom: str = strings.concat(bomdir, "/bom.ww");
|
||||
let triviafile: str = strings.concat(trivia, "/trivia.ww");
|
||||
let literaltest: str = strings.concat(literal, "/literal_test.ww");
|
||||
let logicaltest: str = strings.concat(logicaltestdir, "/provider_test.ww");
|
||||
let mixdoc: str = strings.concat(mixed, "/doc.ww");
|
||||
let warmfile: str = strings.concat(warm, "/main.ww");
|
||||
let maintext: str = "package main;\nfn main() i32 = { return 31; };\n";
|
||||
let doctext: str = strings.concat("// leading trivia\npackage documentation;\n",
|
||||
"this is deliberately malformed ordinary body;\n",
|
||||
"import absent.after.boundary;\nfn main() i32 = { return 99; };\n");
|
||||
writefile(doc, doctext); writefile(logical, doctext);
|
||||
assert(os.symlink(doc, doclink) == 0);
|
||||
writefile(rawi, "package documentation;\ni this_is_not_import;\n");
|
||||
writefile(badheader, "package documentation;\nimport ;\n");
|
||||
writefile(badpackage, "package ;\n");
|
||||
putbomfile(bom, "// bom and comment\npackage documentation;\n",
|
||||
"this malformed body is past the header;\n", false);
|
||||
writefile(triviafile, strings.concat("//ww:module ignored.header\n",
|
||||
"package /* comment between clause and name */ documentation;\n",
|
||||
"this malformed body is past the header;\n"));
|
||||
writefile(rawitreefile,
|
||||
"package documentation;\ni this_is_not_import;\n");
|
||||
writefile(literaltest, "package documentation;\n");
|
||||
writefile(logicaltest, doctext);
|
||||
writefile(mixdoc, doctext); writefile(strings.concat(mixed, "/main.ww"), maintext);
|
||||
writefile(strings.concat(control, "/main.ww"), maintext);
|
||||
writefile(warmfile, maintext);
|
||||
writefile(strings.concat(treedocs, "/doc.ww"), doctext);
|
||||
writefile(strings.concat(doconlydocs, "/doc.ww"), doctext);
|
||||
writefile(strings.concat(treemain, "/main.ww"), maintext);
|
||||
writefile(strings.concat(treemain, "/main_test.ww"),
|
||||
"package main;\n@test fn retained() void = { assert(true); };\n");
|
||||
writefile(strings.concat(docdep, "/doc.ww"), doctext);
|
||||
writefile(strings.concat(importer, "/user.ww"), strings.concat(
|
||||
"package user;\nimport docdep;\n",
|
||||
"export fn value() i32 = { return docdep.value(); };\n"));
|
||||
writefile(strings.concat(include, "/docone.ww"), doctext);
|
||||
writefile(strings.concat(importerone, "/user.ww"), strings.concat(
|
||||
"package userone;\nimport docone;\n",
|
||||
"export fn value() i32 = { return docone.value(); };\n"));
|
||||
writefile(strings.concat(exact, "/x.ww"),
|
||||
"package documentationx;\nimport absent.exact;\n");
|
||||
writefile(strings.concat(exact, "/t.ww"),
|
||||
"package documentation_test;\nimport absent.exact;\n");
|
||||
|
||||
let stages: []str = ["ww", "ww_ww"];
|
||||
let tags: []str = ["c", "ww"];
|
||||
let compilers: []str = ["w6c", "w6c_ww"];
|
||||
let assemblers: []str = ["w6a", "w6a_ww"];
|
||||
let linkers: []str = ["w6l", "w6l_ww"];
|
||||
let baseenv: []str = os.getenvs();
|
||||
let records: []str = alloc([], 96u64)!;
|
||||
let cross: []str = alloc([], 96u64)!;
|
||||
let mixedcross: str = "";
|
||||
let mixedsemanticcross: str = "";
|
||||
let warmcross: str = "";
|
||||
let out: commandout;
|
||||
let si: i32 = 0;
|
||||
for (si < stages.len) {
|
||||
records.len = 0;
|
||||
let work: str = strings.concat(root, "/docs-work-", tags[si]);
|
||||
let rawwork: str = strings.concat(root, "/raw-work-", tags[si]);
|
||||
let mixwork: str = strings.concat(root, "/mixed-work-", tags[si]);
|
||||
let ctlwork: str = strings.concat(root, "/control-work-", tags[si]);
|
||||
let outputdir: str = strings.concat(root, "/outputs-", tags[si]);
|
||||
let mixedout: str = strings.concat(root, "/mixed-", tags[si]);
|
||||
let controlout: str = strings.concat(root, "/control-", tags[si]);
|
||||
let trace: str = strings.concat(root, "/tools-", tags[si], ".trace");
|
||||
let cwrap: str = strings.concat(root, "/w6c-", tags[si], ".sh");
|
||||
let awrap: str = strings.concat(root, "/w6a-", tags[si], ".sh");
|
||||
let lwrap: str = strings.concat(root, "/w6l-", tags[si], ".sh");
|
||||
mkdirall(work); mkdirall(rawwork); mkdirall(mixwork); mkdirall(ctlwork);
|
||||
mkdirall(outputdir); writefile(trace, "");
|
||||
writeexecutable(cwrap, "#!/bin/sh\nprintf C >> \"$WW_DOC_TRACE\"\nexec \"$WW_DOC_REAL_C\" \"$@\"\n");
|
||||
writeexecutable(awrap, "#!/bin/sh\nprintf A >> \"$WW_DOC_TRACE\"\nexec \"$WW_DOC_REAL_A\" \"$@\"\n");
|
||||
writeexecutable(lwrap, "#!/bin/sh\nprintf L >> \"$WW_DOC_TRACE\"\nexec \"$WW_DOC_REAL_L\" \"$@\"\n");
|
||||
let env: []str = alloc([], (baseenv.len + 6): u64)!;
|
||||
let ei: i32 = 0;
|
||||
for (ei < baseenv.len) {
|
||||
if (!strings.hasprefix(baseenv[ei], "WW_W6C=")
|
||||
&& !strings.hasprefix(baseenv[ei], "WW_W6A=")
|
||||
&& !strings.hasprefix(baseenv[ei], "WW_W6L=")
|
||||
&& !strings.hasprefix(baseenv[ei], "WW_DOC_")) { append(env, baseenv[ei]); };
|
||||
ei += 1;
|
||||
};
|
||||
append(env, strings.concat("WW_W6C=", cwrap));
|
||||
append(env, strings.concat("WW_W6A=", awrap));
|
||||
append(env, strings.concat("WW_W6L=", lwrap));
|
||||
append(env, strings.concat("WW_DOC_REAL_C=", driver(compilers[si])));
|
||||
append(env, strings.concat("WW_DOC_REAL_A=", driver(assemblers[si])));
|
||||
append(env, strings.concat("WW_DOC_REAL_L=", driver(linkers[si])));
|
||||
append(env, strings.concat("WW_DOC_TRACE=", trace));
|
||||
let directerr: str = strings.concat("ww: ", docs,
|
||||
": directory contains no WW package sources\n");
|
||||
let namederr: str = strings.concat("ww: ", docs,
|
||||
": directory contains no WW package sources\n");
|
||||
let coordinatorerr: str = strings.concat("wwtest package: ", docs,
|
||||
": directory contains no WW package sources\n");
|
||||
let before: str = treesnapshot(work);
|
||||
let av: []str = [driver(stages[si]), "build", "-w", work, docs];
|
||||
runcommandenv(root, strings.concat("documentation-build-", tags[si]), av,
|
||||
env, (30i64 * (time.second: i64)): time.duration, &out);
|
||||
documentationrecord(&out, 1, &records);
|
||||
assert(out.stdout.len == 0 && same(out.stderr, directerr)
|
||||
&& same(before, treesnapshot(work)) && readfile(trace).len == 0);
|
||||
let namedav: []str = [driver(stages[si]), "build", "-w", work, doc];
|
||||
runcommandenv(root, strings.concat("documentation-named-", tags[si]), namedav,
|
||||
env, (30i64 * (time.second: i64)): time.duration, &out);
|
||||
documentationrecord(&out, 1, &records);
|
||||
assert(out.stdout.len == 0 && same(out.stderr, namederr)
|
||||
&& same(before, treesnapshot(work)) && readfile(trace).len == 0);
|
||||
let linkav: []str = [driver(stages[si]), "build", "-w", work, doclink];
|
||||
runcommandenv(root, strings.concat("documentation-named-link-", tags[si]),
|
||||
linkav, env, (30i64 * (time.second: i64)): time.duration, &out);
|
||||
documentationrecord(&out, 1, &records);
|
||||
assert(out.stdout.len == 0 && same(out.stderr, strings.concat("ww: ", root,
|
||||
": directory contains no WW package sources\n"))
|
||||
&& same(before, treesnapshot(work)) && readfile(trace).len == 0);
|
||||
let logicalav: []str = [driver(stages[si]), "build", "-w", work,
|
||||
"-I", include, "logic.doc"];
|
||||
runcommandenv(root, strings.concat("documentation-logical-", tags[si]), logicalav,
|
||||
env, (30i64 * (time.second: i64)): time.duration, &out);
|
||||
documentationrecord(&out, 1, &records);
|
||||
assert(out.stdout.len == 0 && same(out.stderr, strings.concat("ww: ",
|
||||
logicaldir, ": directory contains no WW package sources\n")));
|
||||
let logicaltestav: []str = [driver(stages[si]), "build", "-w", work,
|
||||
"-I", include, "logicaltest.provider_test"];
|
||||
runcommandenv(root, strings.concat("documentation-logical-test-", tags[si]),
|
||||
logicaltestav, env, (30i64 * (time.second: i64)): time.duration, &out);
|
||||
documentationrecord(&out, 1, &records);
|
||||
assert(out.stdout.len == 0 && same(out.stderr, strings.concat("ww: ",
|
||||
logicaltestdir, ": directory contains no WW package sources\n")));
|
||||
let coordav: []str = [driver(stages[si]), "build", "-o", outputdir, docs];
|
||||
runcommandenv(root, strings.concat("documentation-coordinator-", tags[si]), coordav,
|
||||
env, (30i64 * (time.second: i64)): time.duration, &out);
|
||||
documentationrecord(&out, 1, &records);
|
||||
assert(out.stdout.len == 0 && same(out.stderr, coordinatorerr)
|
||||
&& !directoryhasnew(outputdir) && readfile(trace).len == 0);
|
||||
|
||||
let runav: []str = [driver(stages[si]), "run", docs];
|
||||
runcommandenv(root, strings.concat("documentation-run-dir-", tags[si]), runav,
|
||||
env, (30i64 * (time.second: i64)): time.duration, &out);
|
||||
documentationrecord(&out, 1, &records);
|
||||
assert(out.stdout.len == 0 && same(out.stderr, directerr) && readfile(trace).len == 0);
|
||||
let runnamed: []str = [driver(stages[si]), "run", doc];
|
||||
runcommandenv(root, strings.concat("documentation-run-file-", tags[si]), runnamed,
|
||||
env, (30i64 * (time.second: i64)): time.duration, &out);
|
||||
documentationrecord(&out, 1, &records);
|
||||
assert(out.stdout.len == 0 && same(out.stderr, namederr) && readfile(trace).len == 0);
|
||||
let runlogical: []str = [driver(stages[si]), "run", "-I", include, "logic.doc"];
|
||||
runcommandenv(root, strings.concat("documentation-run-logical-", tags[si]),
|
||||
runlogical, env, (30i64 * (time.second: i64)): time.duration, &out);
|
||||
documentationrecord(&out, 1, &records);
|
||||
assert(out.stdout.len == 0 && same(out.stderr, strings.concat("ww: ",
|
||||
logicaldir, ": directory contains no WW package sources\n")));
|
||||
let literalplain: []str = [driver(stages[si]), "build", literaltest];
|
||||
runcommandenv(root, strings.concat("documentation-literal-plain-", tags[si]),
|
||||
literalplain, env, (30i64 * (time.second: i64)): time.duration, &out);
|
||||
documentationrecord(&out, 0, &records);
|
||||
assert(out.stdout.len == 0 && out.stderr.len == 0);
|
||||
let literalnull: []str = [driver(stages[si]), "build", "-o", "/dev/null", literaltest];
|
||||
runcommandenv(root, strings.concat("documentation-literal-null-", tags[si]),
|
||||
literalnull, env, (30i64 * (time.second: i64)): time.duration, &out);
|
||||
documentationrecord(&out, 0, &records);
|
||||
assert(out.stdout.len == 0 && out.stderr.len == 0);
|
||||
let literalfile: str = strings.concat(root, "/literal-file-", tags[si]);
|
||||
let literalfileav: []str = [driver(stages[si]), "build", "-o", literalfile, literaltest];
|
||||
runcommandenv(root, strings.concat("documentation-literal-file-", tags[si]),
|
||||
literalfileav, env, (30i64 * (time.second: i64)): time.duration, &out);
|
||||
documentationrecord(&out, 1, &records);
|
||||
assert(out.stdout.len == 0 && same(out.stderr, "ww: no packages to build\n")
|
||||
&& !os.exists(literalfile));
|
||||
let literaloutdir: str = strings.concat(root, "/literal-output-", tags[si]);
|
||||
mkdirall(literaloutdir);
|
||||
let literaldirav: []str = [driver(stages[si]), "build", "-o", literaloutdir, literaltest];
|
||||
runcommandenv(root, strings.concat("documentation-literal-dir-", tags[si]),
|
||||
literaldirav, env, (30i64 * (time.second: i64)): time.duration, &out);
|
||||
documentationrecord(&out, 1, &records);
|
||||
assert(out.stdout.len == 0 && same(out.stderr, "ww: no main packages to build\n")
|
||||
&& !directoryhasnew(literaloutdir));
|
||||
|
||||
let rawbefore: str = treesnapshot(rawwork);
|
||||
let rawtest: []str = [driver(stages[si]), "test", "-w", rawwork, doc];
|
||||
runcommandenv(root, strings.concat("documentation-test-raw-", tags[si]), rawtest,
|
||||
env, (30i64 * (time.second: i64)): time.duration, &out);
|
||||
documentationrecord(&out, 1, &records);
|
||||
assert(same(out.stdout, "FAIL\n") && same(out.stderr, namederr)
|
||||
&& same(rawbefore, treesnapshot(rawwork)) && readfile(trace).len == 0);
|
||||
let logicalraw: []str = [driver(stages[si]), "test", "-I", include, "logic.doc"];
|
||||
runcommandenv(root, strings.concat("documentation-test-logical-", tags[si]),
|
||||
logicalraw, env, (30i64 * (time.second: i64)): time.duration, &out);
|
||||
documentationrecord(&out, 1, &records);
|
||||
assert(same(out.stdout, "FAIL\n") && same(out.stderr, strings.concat("ww: ",
|
||||
logicaldir, ": directory contains no WW package sources\n")));
|
||||
let rawcompile: []str = [driver(stages[si]), "test", "-c", "-w", rawwork, doc];
|
||||
runcommandenv(root, strings.concat("documentation-test-c-", tags[si]), rawcompile,
|
||||
env, (30i64 * (time.second: i64)): time.duration, &out);
|
||||
documentationrecord(&out, 1, &records);
|
||||
assert(out.stdout.len == 0 && same(out.stderr, namederr));
|
||||
let rawasmout: str = strings.concat(root, "/raw-asm-", tags[si]);
|
||||
let rawasm: []str = [driver(stages[si]), "test", "-S", "-w", rawwork,
|
||||
"-o", rawasmout, doc];
|
||||
runcommandenv(root, strings.concat("documentation-test-s-", tags[si]), rawasm,
|
||||
env, (30i64 * (time.second: i64)): time.duration, &out);
|
||||
documentationrecord(&out, 1, &records);
|
||||
assert(out.stdout.len == 0 && same(out.stderr, namederr) && !os.exists(rawasmout));
|
||||
let dirtest: []str = [driver(stages[si]), "test", docs];
|
||||
runcommandenv(root, strings.concat("documentation-test-dir-", tags[si]), dirtest,
|
||||
env, (30i64 * (time.second: i64)): time.duration, &out);
|
||||
documentationrecord(&out, 1, &records);
|
||||
assert(same(out.stdout, "FAIL\n") && same(out.stderr, coordinatorerr));
|
||||
let dircompile: []str = [driver(stages[si]), "test", "-c", docs];
|
||||
runcommandenv(root, strings.concat("documentation-test-dir-c-", tags[si]), dircompile,
|
||||
env, (30i64 * (time.second: i64)): time.duration, &out);
|
||||
documentationrecord(&out, 1, &records);
|
||||
assert(out.stdout.len == 0 && same(out.stderr, coordinatorerr));
|
||||
let dirasm: []str = [driver(stages[si]), "test", "-S", docs];
|
||||
runcommandenv(root, strings.concat("documentation-test-dir-s-", tags[si]), dirasm,
|
||||
env, (30i64 * (time.second: i64)): time.duration, &out);
|
||||
documentationrecord(&out, 2, &records);
|
||||
assert(out.stdout.len == 0 && same(out.stderr,
|
||||
"ww test: -S needs -o\n"));
|
||||
let dirasmout: str = strings.concat(root, "/dir-asm-", tags[si]);
|
||||
let dirasmwithoutput: []str = [driver(stages[si]), "test", "-S",
|
||||
"-o", dirasmout, docs];
|
||||
runcommandenv(root, strings.concat("documentation-test-dir-s-output-", tags[si]),
|
||||
dirasmwithoutput, env, (30i64 * (time.second: i64)): time.duration, &out);
|
||||
documentationrecord(&out, 2, &records);
|
||||
assert(out.stdout.len == 0 && same(out.stderr,
|
||||
"ww test: -S needs a single test file\n"));
|
||||
|
||||
let spec: str = strings.concat(doconlytree, "/...");
|
||||
let recbuild: []str = [driver(stages[si]), "build", spec];
|
||||
runcommandenv(root, strings.concat("documentation-rec-build-", tags[si]), recbuild,
|
||||
env, (30i64 * (time.second: i64)): time.duration, &out);
|
||||
documentationrecord(&out, 0, &records);
|
||||
assert(out.stdout.len == 0 && same(out.stderr, strings.concat("ww: warning: \"",
|
||||
spec, "\" matched no packages\n")));
|
||||
let recbuildtwice: []str = [driver(stages[si]), "build", spec, spec];
|
||||
runcommandenv(root, strings.concat("documentation-rec-build-twice-", tags[si]),
|
||||
recbuildtwice, env, (30i64 * (time.second: i64)): time.duration, &out);
|
||||
documentationrecord(&out, 0, &records);
|
||||
assert(out.stdout.len == 0 && same(out.stderr, strings.concat("ww: warning: \"",
|
||||
spec, "\" matched no packages\nww: warning: \"", spec,
|
||||
"\" matched no packages\n")));
|
||||
let rectest: []str = [driver(stages[si]), "test", spec];
|
||||
runcommandenv(root, strings.concat("documentation-rec-test-", tags[si]), rectest,
|
||||
env, (30i64 * (time.second: i64)): time.duration, &out);
|
||||
documentationrecord(&out, 1, &records);
|
||||
assert(out.stdout.len == 0 && same(out.stderr, strings.concat("ww: warning: \"",
|
||||
spec, "\" matched no packages\nww test: no packages to test\n")));
|
||||
let rectesttwice: []str = [driver(stages[si]), "test", spec, spec];
|
||||
runcommandenv(root, strings.concat("documentation-rec-test-twice-", tags[si]),
|
||||
rectesttwice, env, (30i64 * (time.second: i64)): time.duration, &out);
|
||||
documentationrecord(&out, 1, &records);
|
||||
assert(out.stdout.len == 0 && same(out.stderr, strings.concat("ww: warning: \"",
|
||||
spec, "\" matched no packages\nww: warning: \"", spec,
|
||||
"\" matched no packages\nww test: no packages to test\n")));
|
||||
let mixedspec: str = strings.concat(tree, "/...");
|
||||
let mixedrecbuild: []str = [driver(stages[si]), "build", "-o", "/dev/null",
|
||||
mixedspec];
|
||||
runcommandenv(root, strings.concat("documentation-rec-mixed-build-", tags[si]),
|
||||
mixedrecbuild, env, (60i64 * (time.second: i64)): time.duration, &out);
|
||||
documentationrecord(&out, 0, &records);
|
||||
assert(out.stdout.len == 0 && out.stderr.len == 0);
|
||||
let mixedrectest: []str = [driver(stages[si]), "test", mixedspec];
|
||||
runcommandenv(root, strings.concat("documentation-rec-mixed-test-", tags[si]),
|
||||
mixedrectest, env, (60i64 * (time.second: i64)): time.duration, &out);
|
||||
documentationrecord(&out, 0, &records);
|
||||
assert(out.stderr.len == 0 && has(out.stdout, "retained ... ok\n")
|
||||
&& !has(out.stdout, "documentation"));
|
||||
rewritefile(trace, "");
|
||||
|
||||
let rawiav: []str = [driver(stages[si]), "build", rawi];
|
||||
runcommandenv(root, strings.concat("documentation-raw-i-", tags[si]), rawiav,
|
||||
env, (30i64 * (time.second: i64)): time.duration, &out);
|
||||
documentationrecord(&out, 1, &records);
|
||||
assert(out.stdout.len == 0 && same(out.stderr, strings.concat(rawi,
|
||||
":2:1: error: expected top-level decl\n")) && readfile(trace).len == 0);
|
||||
let rawirun: []str = [driver(stages[si]), "run", rawi];
|
||||
runcommandenv(root, strings.concat("documentation-raw-i-run-", tags[si]), rawirun,
|
||||
env, (30i64 * (time.second: i64)): time.duration, &out);
|
||||
documentationrecord(&out, 1, &records);
|
||||
assert(out.stdout.len == 0 && same(out.stderr, strings.concat(rawi,
|
||||
":2:1: error: expected top-level decl\n")));
|
||||
let rawitest: []str = [driver(stages[si]), "test", rawi];
|
||||
runcommandenv(root, strings.concat("documentation-raw-i-test-", tags[si]), rawitest,
|
||||
env, (30i64 * (time.second: i64)): time.duration, &out);
|
||||
documentationrecord(&out, 1, &records);
|
||||
assert(same(out.stdout, "FAIL\n") && same(out.stderr, strings.concat(rawi,
|
||||
":2:1: error: expected top-level decl\n")));
|
||||
let rawicompile: []str = [driver(stages[si]), "test", "-c", rawi];
|
||||
runcommandenv(root, strings.concat("documentation-raw-i-c-", tags[si]), rawicompile,
|
||||
env, (30i64 * (time.second: i64)): time.duration, &out);
|
||||
documentationrecord(&out, 1, &records);
|
||||
assert(out.stdout.len == 0 && same(out.stderr, strings.concat(rawi,
|
||||
":2:1: error: expected top-level decl\n")));
|
||||
let rawiasmout: str = strings.concat(root, "/raw-i-asm-", tags[si]);
|
||||
let rawiasm: []str = [driver(stages[si]), "test", "-S", "-o", rawiasmout, rawi];
|
||||
runcommandenv(root, strings.concat("documentation-raw-i-s-", tags[si]), rawiasm,
|
||||
env, (30i64 * (time.second: i64)): time.duration, &out);
|
||||
documentationrecord(&out, 1, &records);
|
||||
assert(out.stdout.len == 0 && same(out.stderr, strings.concat(rawi,
|
||||
":2:1: error: expected top-level decl\n")) && !os.exists(rawiasmout));
|
||||
let rawidirectorytest: []str = [driver(stages[si]), "test", rawidir];
|
||||
runcommandenv(root, strings.concat("documentation-raw-i-dir-test-", tags[si]),
|
||||
rawidirectorytest, env, (30i64 * (time.second: i64)): time.duration, &out);
|
||||
documentationrecord(&out, 1, &records);
|
||||
assert(same(out.stdout, "FAIL\n") && same(out.stderr, strings.concat(rawi,
|
||||
":2:1: error: expected top-level decl\n")));
|
||||
let rawispec: str = strings.concat(rawitree, "/...");
|
||||
let rawirectest: []str = [driver(stages[si]), "test", rawispec];
|
||||
runcommandenv(root, strings.concat("documentation-raw-i-rec-test-", tags[si]),
|
||||
rawirectest, env, (30i64 * (time.second: i64)): time.duration, &out);
|
||||
documentationrecord(&out, 1, &records);
|
||||
assert(same(out.stdout, "FAIL\n") && same(out.stderr, strings.concat(rawitreefile,
|
||||
":2:1: error: expected top-level decl\n")));
|
||||
let headerav: []str = [driver(stages[si]), "build", badheader];
|
||||
runcommandenv(root, strings.concat("documentation-header-", tags[si]), headerav,
|
||||
env, (30i64 * (time.second: i64)): time.duration, &out);
|
||||
documentationrecord(&out, 1, &records);
|
||||
assert(out.stdout.len == 0 && same(out.stderr, strings.concat(badheader,
|
||||
":2:8: error: expected identifier, got ;\n")));
|
||||
let packageav: []str = [driver(stages[si]), "build", badpackage];
|
||||
runcommandenv(root, strings.concat("documentation-package-", tags[si]), packageav,
|
||||
env, (30i64 * (time.second: i64)): time.duration, &out);
|
||||
documentationrecord(&out, 1, &records);
|
||||
assert(out.stdout.len == 0 && same(out.stderr, strings.concat(badpackage,
|
||||
":1:9: error: invalid or missing package clause\n")));
|
||||
let bomav: []str = [driver(stages[si]), "build", bom];
|
||||
runcommandenv(root, strings.concat("documentation-bom-", tags[si]), bomav,
|
||||
env, (30i64 * (time.second: i64)): time.duration, &out);
|
||||
documentationrecord(&out, 1, &records);
|
||||
assert(out.stdout.len == 0 && same(out.stderr, strings.concat("ww: ",
|
||||
bomdir, ": directory contains no WW package sources\n")));
|
||||
let triviaav: []str = [driver(stages[si]), "build", triviafile];
|
||||
runcommandenv(root, strings.concat("documentation-trivia-", tags[si]), triviaav,
|
||||
env, (30i64 * (time.second: i64)): time.duration, &out);
|
||||
documentationrecord(&out, 1, &records);
|
||||
assert(out.stdout.len == 0 && same(out.stderr, strings.concat("ww: ",
|
||||
trivia, ": directory contains no WW package sources\n")));
|
||||
let exactav: []str = [driver(stages[si]), "build", strings.concat(exact, "/x.ww")];
|
||||
runcommandenv(root, strings.concat("documentation-exact-x-", tags[si]), exactav,
|
||||
env, (30i64 * (time.second: i64)): time.duration, &out);
|
||||
expectexit(&out, 1); assert(!has(out.stderr, "directory contains no WW package sources"));
|
||||
let exacttav: []str = [driver(stages[si]), "build", strings.concat(exact, "/t.ww")];
|
||||
runcommandenv(root, strings.concat("documentation-exact-t-", tags[si]), exacttav,
|
||||
env, (30i64 * (time.second: i64)): time.duration, &out);
|
||||
expectexit(&out, 1); assert(!has(out.stderr, "directory contains no WW package sources"));
|
||||
let importav: []str = [driver(stages[si]), "build", "-I", include, importer];
|
||||
runcommandenv(root, strings.concat("documentation-imported-", tags[si]), importav,
|
||||
env, (30i64 * (time.second: i64)): time.duration, &out);
|
||||
expectexit(&out, 1); assert(!os.exists(strings.concat(root, "/imported-", tags[si]))
|
||||
&& readfile(trace).len == 0);
|
||||
let importoneav: []str = [driver(stages[si]), "build", "-I", include, importerone];
|
||||
runcommandenv(root, strings.concat("documentation-imported-one-", tags[si]),
|
||||
importoneav, env, (30i64 * (time.second: i64)): time.duration, &out);
|
||||
expectexit(&out, 1); assert(has(out.stderr,
|
||||
":2:1: error: cannot find package docone\n")
|
||||
&& !has(out.stderr, "directory contains no WW package sources")
|
||||
&& readfile(trace).len == 0);
|
||||
|
||||
let mixav: []str = [driver(stages[si]), "build", "-w", mixwork,
|
||||
"-o", mixedout, mixed];
|
||||
let ctlav: []str = [driver(stages[si]), "build", "-w", ctlwork,
|
||||
"-o", controlout, control];
|
||||
runcommandenv(root, strings.concat("documentation-mixed-cold-", tags[si]), mixav,
|
||||
env, (60i64 * (time.second: i64)): time.duration, &out);
|
||||
expectexit(&out, 0); assert(out.stdout.len == 0 && out.stderr.len == 0 && os.exists(mixedout));
|
||||
runcommandenv(root, strings.concat("documentation-control-cold-", tags[si]), ctlav,
|
||||
env, (60i64 * (time.second: i64)): time.duration, &out);
|
||||
expectexit(&out, 0); assert(out.stdout.len == 0 && out.stderr.len == 0
|
||||
&& same(readfile(mixedout), readfile(controlout)));
|
||||
let mixedbytes: str = strings.dup(readfile(mixedout));
|
||||
let mixedtree: str = strings.dup(artifacttreesnapshot(mixwork));
|
||||
let mixedsemantic: str = strings.dup(wrongsuffixsemantictreesnapshot(mixwork));
|
||||
if (si == 0) {
|
||||
mixedcross = strings.dup(mixedbytes);
|
||||
mixedsemanticcross = strings.dup(mixedsemantic);
|
||||
} else {
|
||||
assert(same(mixedcross, mixedbytes)
|
||||
&& same(mixedsemanticcross, mixedsemantic));
|
||||
};
|
||||
let mixabsent: str = strings.concat(mixdoc, ".absent");
|
||||
assert(os.rename(mixdoc, mixabsent) == 0);
|
||||
rewritefile(trace, "");
|
||||
runcommandenv(root, strings.concat("documentation-mixed-absent-", tags[si]), mixav,
|
||||
env, (60i64 * (time.second: i64)): time.duration, &out);
|
||||
documentationrecord(&out, 0, &records);
|
||||
assert(out.stdout.len == 0 && out.stderr.len == 0
|
||||
&& same(mixedbytes, readfile(mixedout))
|
||||
&& same(mixedtree, artifacttreesnapshot(mixwork))
|
||||
&& same(mixedsemantic, wrongsuffixsemantictreesnapshot(mixwork))
|
||||
&& same(readfile(trace), "L"));
|
||||
assert(os.rename(mixabsent, mixdoc) == 0);
|
||||
rewritefile(trace, ""); rewritefile(mixdoc, strings.concat(doctext, "still ignored\n"));
|
||||
runcommandenv(root, strings.concat("documentation-mixed-warm-", tags[si]), mixav,
|
||||
env, (60i64 * (time.second: i64)): time.duration, &out);
|
||||
expectexit(&out, 0); assert(out.stdout.len == 0 && out.stderr.len == 0
|
||||
&& same(mixedbytes, readfile(mixedout)) && same(mixedtree, artifacttreesnapshot(mixwork))
|
||||
&& same(readfile(trace), "L"));
|
||||
let warmwork: str = strings.concat(root, "/warm-work-", tags[si]);
|
||||
let warmout: str = strings.concat(root, "/warm-output-", tags[si]);
|
||||
mkdirall(warmwork);
|
||||
let warmav: []str = [driver(stages[si]), "build", "-w", warmwork,
|
||||
"-o", warmout, warm];
|
||||
runcommandenv(root, strings.concat("documentation-warm-cold-", tags[si]), warmav,
|
||||
env, (60i64 * (time.second: i64)): time.duration, &out);
|
||||
expectexit(&out, 0); assert(out.stdout.len == 0 && out.stderr.len == 0);
|
||||
let warmbytes: str = strings.dup(readfile(warmout));
|
||||
let warmtree: str = strings.dup(artifacttreesnapshot(warmwork));
|
||||
if (si == 0) { warmcross = strings.dup(warmbytes); }
|
||||
else { assert(same(warmcross, warmbytes)); };
|
||||
rewritefile(trace, ""); rewritefile(warmfile, doctext);
|
||||
runcommandenv(root, strings.concat("documentation-warm-docs-", tags[si]), warmav,
|
||||
env, (30i64 * (time.second: i64)): time.duration, &out);
|
||||
expectexit(&out, 1);
|
||||
assert(out.stdout.len == 0 && same(out.stderr, strings.concat("ww: ", warm,
|
||||
": directory contains no WW package sources\n")) && same(warmbytes, readfile(warmout))
|
||||
&& same(warmtree, artifacttreesnapshot(warmwork)) && readfile(trace).len == 0);
|
||||
rewritefile(warmfile, "package documentation;\nimport ;\n");
|
||||
runcommandenv(root, strings.concat("documentation-warm-header-", tags[si]), warmav,
|
||||
env, (30i64 * (time.second: i64)): time.duration, &out);
|
||||
expectexit(&out, 1);
|
||||
assert(out.stdout.len == 0 && same(out.stderr, strings.concat(warmfile,
|
||||
":2:8: error: expected identifier, got ;\n")) && same(warmbytes, readfile(warmout))
|
||||
&& same(warmtree, artifacttreesnapshot(warmwork)) && readfile(trace).len == 0);
|
||||
rewritefile(warmfile, maintext);
|
||||
documentationclean(root);
|
||||
if (si == 0) {
|
||||
let ri: i32 = 0;
|
||||
for (ri < records.len) { append(cross, strings.dup(records[ri])); ri += 1; };
|
||||
} else { documentationrecordssame(cross, records); };
|
||||
si += 1;
|
||||
};
|
||||
// Header classification has no shared action identity or lock: simultaneous
|
||||
// Cstage/WWstage requests may read the same source but must leave separate
|
||||
// caller-owned work roots untouched.
|
||||
let cwork: str = strings.concat(root, "/parallel-c-work");
|
||||
let wwork: str = strings.concat(root, "/parallel-ww-work");
|
||||
mkdirall(cwork); mkdirall(wwork);
|
||||
let cbefore: str = treesnapshot(cwork);
|
||||
let wbefore: str = treesnapshot(wwork);
|
||||
let cav: []str = [driver("ww"), "build", "-w", cwork, docs];
|
||||
let wav: []str = [driver("ww_ww"), "build", "-w", wwork, docs];
|
||||
let cc: exec.command;
|
||||
cc.path = cav[0]; cc.argv = cav; cc.env = os.getenvs(); cc.dir = repo();
|
||||
cc.stdoutpath = strings.concat(root, "/documentation-parallel-c.stdout");
|
||||
cc.stderrpath = strings.concat(root, "/documentation-parallel-c.stderr");
|
||||
cc.deadline = time.add(time.now(time.clock.monotonic),
|
||||
(30i64 * (time.second: i64)): time.duration);
|
||||
cc.grace = (100i64 * (time.millisecond: i64)): time.duration;
|
||||
let wc: exec.command;
|
||||
wc.path = wav[0]; wc.argv = wav; wc.env = os.getenvs(); wc.dir = repo();
|
||||
wc.stdoutpath = strings.concat(root, "/documentation-parallel-ww.stdout");
|
||||
wc.stderrpath = strings.concat(root, "/documentation-parallel-ww.stderr");
|
||||
wc.deadline = time.add(time.now(time.clock.monotonic),
|
||||
(30i64 * (time.second: i64)): time.duration);
|
||||
wc.grace = (100i64 * (time.millisecond: i64)): time.duration;
|
||||
let cp: exec.process;
|
||||
let wp: exec.process;
|
||||
exec.start(&cp, &cc); exec.start(&wp, &wc);
|
||||
let cdone: bool = false;
|
||||
let wdone: bool = false;
|
||||
for (!cdone || !wdone) {
|
||||
if (!cdone) { cdone = exec.poll(&cp); };
|
||||
if (!wdone) { wdone = exec.poll(&wp); };
|
||||
if (!cdone || !wdone) { time.sleep(time.millisecond, time.clock.monotonic); };
|
||||
};
|
||||
let parallelerr: str = strings.concat("ww: ", docs,
|
||||
": directory contains no WW package sources\n");
|
||||
assert(cp.result.errno == 0 && cp.result.cleanuperrno == 0
|
||||
&& cp.result.termination == exec.termination.EXIT && cp.result.code == 1
|
||||
&& wp.result.errno == 0 && wp.result.cleanuperrno == 0
|
||||
&& wp.result.termination == exec.termination.EXIT && wp.result.code == 1
|
||||
&& readfile(cc.stdoutpath).len == 0 && readfile(wc.stdoutpath).len == 0
|
||||
&& same(readfile(cc.stderrpath), parallelerr)
|
||||
&& same(readfile(wc.stderrpath), parallelerr)
|
||||
&& same(cbefore, treesnapshot(cwork)) && same(wbefore, treesnapshot(wwork)));
|
||||
documentationclean(root);
|
||||
clean(root);
|
||||
};
|
||||
|
||||
// The single-FILE `ww test <file>` route wraps the same in-binary test
|
||||
// runtime as the directory-package route (cases/*), but through the
|
||||
// driver's single-file leg. One row per outcome class keeps that leg's
|
||||
|
||||
Reference in New Issue
Block a user