feat(v4.0.0): the POST runner -- the kernel feeds a case and judges it from outside
v4/system/post.c: for each case it empties the node's data stack, sends DECIMAL FORTH DEFINITIONS, sends the case's lines keeping what the node prints, reads the stack, and judges: an error exactly if one is expected, and otherwise the stack and every character printed. A failing case is named with what it printed and left. Nothing of it is on the node. Its tests are in test_host_quit.c, on that test's node: 29 checks of cases that must pass and cases that must fail -- a wrong value, depth, order or output, an error wanted or unwanted, a case of two lines, depth-only cases, the starting state, more printing than is kept, and a line that never comes back. Both widths and the sanitizers. The boot still runs the capsule; that is the next commit. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
c2f4b9808a
commit
2bf958ce3a
@@ -53,6 +53,7 @@ static int failures = 0, checks = 0;
|
||||
#define CANARY ((v4_cell)0x0C0FFEE5)
|
||||
|
||||
#include "v4/blocks.h"
|
||||
#include "v4/post.h"
|
||||
#include "block_subsystem.h"
|
||||
#include "host_map.h"
|
||||
|
||||
@@ -358,6 +359,53 @@ static const char *say(const char *input)
|
||||
}
|
||||
return out;
|
||||
}
|
||||
/* POST's RUNNER (v4/system/post.c), with these tests as its host: a line is
|
||||
* sent to the node as the console sends one, and what the node prints is
|
||||
* handed back and not shown. `post_lost` is the text of a line that is
|
||||
* never to come back, and `post_state_fails` makes the line that sets the
|
||||
* state end in an error; `post_said` is what the runner printed. */
|
||||
static const char *post_lost;
|
||||
static int post_state_fails;
|
||||
static char post_said[4096];
|
||||
static unsigned post_said_len;
|
||||
static int post_line(void *self, const char *text, unsigned text_len, char *o, unsigned cap, unsigned *len)
|
||||
{
|
||||
int ended;
|
||||
(void)self;
|
||||
*len = 0;
|
||||
if (post_lost && strlen(post_lost) == text_len && memcmp(post_lost, text, text_len) == 0) return -1;
|
||||
if (post_state_fails && text_len == 25 && memcmp(text, "DECIMAL FORTH DEFINITIONS", 25) == 0) text = "NOSUCHWORD-FOR-STATE ";
|
||||
if (!v4_message_text(&to_node, 0, 1, V4_MSG_TEXT, text, text_len)) return -1;
|
||||
to_sent = 0; from_node.count = 0;
|
||||
shown_len = 0; shown_dropped = 0;
|
||||
ended = run_line(40000000);
|
||||
if (ended != 1) return -1;
|
||||
*len = shown_dropped ? (unsigned)sizeof shown + 1u : shown_len;
|
||||
memcpy(o, shown, shown_len < cap ? shown_len : cap);
|
||||
return text_ended;
|
||||
}
|
||||
static void post_say(void *self, const char *text, unsigned len)
|
||||
{
|
||||
(void)self;
|
||||
if (post_said_len + len < sizeof post_said) { memcpy(post_said + post_said_len, text, len); post_said_len += len; post_said[post_said_len] = 0; }
|
||||
}
|
||||
static const v4_post_host post_host = { 0, &n, post_line, post_say };
|
||||
/* Run one case; 1 if it passed. What the runner said is in post_said. */
|
||||
static int post_one(const char *name, const char *l0, const char *l1, int expect, const long long *stack, unsigned depth, const char *output)
|
||||
{
|
||||
static const char *lines[2];
|
||||
v4_post_case c;
|
||||
v4_post_tally t;
|
||||
int ok;
|
||||
lines[0] = l0; lines[1] = l1;
|
||||
c.name = name; c.lines = lines; c.line_count = l1 ? 2u : 1u; c.expect = expect;
|
||||
c.stack = stack; c.depth = depth; c.output = output; c.output_len = (unsigned)strlen(output);
|
||||
post_said_len = 0; post_said[0] = 0;
|
||||
ok = v4_post_run(&post_host, &c, 1, &t);
|
||||
if (t.tests != 1 || t.pass + t.fail != 1 || (int)t.pass != ok) return -1;
|
||||
return ok;
|
||||
}
|
||||
|
||||
/* Feed a file of FORTH source to the prompt, a line at a time, as if typed.
|
||||
* True if every line was accepted: the node said " ok" and nothing else. */
|
||||
static int load_source(const char *name)
|
||||
@@ -1172,6 +1220,63 @@ int main(void)
|
||||
CHECK(is(say("FORGET G3 G1 G2\n"), "AB ok\nok> ") && is(say("G3\n"), "UNKNOWN WORD: 'G3'\n ERROR\nok> "), "one above it can");
|
||||
}
|
||||
|
||||
/* ---- POST's runner: the kernel feeds a case and judges it from outside (NUCLEUS.md 6.3) ---- */
|
||||
{
|
||||
static const long long three[] = { 3 }, four[] = { 4 }, two_vals[] = { 3, 3 }, seven[] = { 7 }, ab[] = { 1, 2 };
|
||||
static char big[2100];
|
||||
boot();
|
||||
CHECK(post_one("add.right", "1 2 +", 0, V4_POST_STACK, three, 1, "") == 1 && strstr(post_said, "POST FAIL") == NULL
|
||||
&& strstr(post_said, "PARITY:V4_POST tests=1 pass=1 fail=0\n") != NULL, "a case that leaves what the table has passes, and the tally says so: \"%s\"", post_said);
|
||||
CHECK(post_one("add.wrong", "1 2 +", 0, V4_POST_STACK, four, 1, "") == 0 && strstr(post_said, "POST FAIL: add.wrong ") != NULL && strstr(post_said, "stack<3 >") != NULL
|
||||
&& strstr(post_said, "PARITY:V4_POST tests=1 pass=0 fail=1\n") != NULL, "one that leaves another value fails, by name, with the stack it left: \"%s\"", post_said);
|
||||
CHECK(post_one("add.depth", "1 2 +", 0, V4_POST_STACK, two_vals, 2, "") == 0, "one that leaves fewer values than the table has fails");
|
||||
CHECK(post_one("two.order", "1 2", 0, V4_POST_STACK, ab, 2, "") == 1 && post_one("two.swapped", "2 1", 0, V4_POST_STACK, ab, 2, "") == 0, "the stack is compared in order, the deepest first");
|
||||
CHECK(post_one("dot.right", "5 DUP . . CR", 0, V4_POST_STACK, 0, 0, "5 5 \n") == 1, "what a case prints is compared");
|
||||
CHECK(strstr(post_said, "5 5") == NULL, "and what a passing case prints does not reach the console: \"%s\"", post_said);
|
||||
CHECK(post_one("emit.wrong", "65 EMIT", 0, V4_POST_STACK, 0, 0, "B") == 0 && strstr(post_said, "POST FAIL: emit.wrong out<A>") != NULL, "one that prints another character fails, with what it printed: \"%s\"", post_said);
|
||||
CHECK(post_one("emit.short", "65 EMIT 66 EMIT", 0, V4_POST_STACK, 0, 0, "A") == 0 && post_one("emit.long", "65 EMIT", 0, V4_POST_STACK, 0, 0, "AB") == 0, "and one that prints more, or less");
|
||||
CHECK(post_one("err.right", "NOSUCHWORD", 0, V4_POST_ERROR, 0, 0, "") == 1, "a case that must end in an error and does passes");
|
||||
CHECK(strstr(post_said, "UNKNOWN") == NULL, "and the message of an expected error does not reach the console");
|
||||
CHECK(post_one("err.none", "1 2 +", 0, V4_POST_ERROR, 0, 0, "") == 0, "one that must end in an error and does not fails");
|
||||
CHECK(post_one("err.unwanted", "NOSUCHWORD", 0, V4_POST_STACK, 0, 0, "") == 0, "one that ends in an error and must not fails");
|
||||
CHECK(post_one("err.first", "NOSUCHWORD", "1 DROP", V4_POST_ERROR, 0, 0, "") == 1, "a case has ended in an error if any of its lines did");
|
||||
CHECK(post_one("two.lines", ": PQ 7 ;", "PQ", V4_POST_STACK, seven, 1, "") == 1, "a case of two lines is judged on what both leave");
|
||||
CHECK(is(say("PQ .\n"), "7 ok\nok> "), "and what a case defines stays on the node");
|
||||
CHECK(post_one("here.depth", "HERE", 0, V4_POST_DEPTH, 0, 1, "") == 1 && post_one("here.depth2", "HERE", 0, V4_POST_DEPTH, 0, 2, "") == 0, "a case may be checked for how many values it leaves and no more");
|
||||
CHECK(post_one("here.out", "HERE 65 EMIT", 0, V4_POST_DEPTH_OUTPUT, 0, 1, "A") == 1 && post_one("here.out2", "HERE 65 EMIT", 0, V4_POST_DEPTH_OUTPUT, 0, 1, "B") == 0, "or for that and what it prints");
|
||||
CHECK(post_one("depth.noout", "HERE 65 EMIT", 0, V4_POST_DEPTH, 0, 1, "") == 1, "a case checked for depth alone may print what it likes");
|
||||
|
||||
/* the state every case starts from */
|
||||
CHECK(is(say("1 2 3 HEX\n"), " ok\nok> "), "the node is left in HEX with three values on its stack");
|
||||
CHECK(post_one("state", "10 .", 0, V4_POST_STACK, 0, 0, "10 ") == 1, "a case still starts in decimal with nothing on the stack");
|
||||
post_state_fails = 1;
|
||||
CHECK(post_one("state.bad", "1 2 +", 0, V4_POST_STACK, three, 1, "") == 0 && strstr(post_said, "POST FAIL: state.bad ") != NULL, "if the state cannot be set the case fails, by name");
|
||||
post_state_fails = 0;
|
||||
|
||||
/* a case that prints more than is kept */
|
||||
CHECK(is(say(": MANY 2000 0 DO 65 EMIT LOOP ;\n"), " ok\nok> "), "a word that prints 2,000 characters");
|
||||
memset(big, 'A', 2000); big[2000] = 0;
|
||||
CHECK(post_one("many", "MANY", 0, V4_POST_STACK, 0, 0, big) == 0 && strstr(post_said, "POST FAIL: many ") != NULL, "a case that prints more than the runner keeps fails, by name");
|
||||
CHECK(post_one("after.many", "1 2 +", 0, V4_POST_STACK, three, 1, "") == 1, "and the next case is judged as usual");
|
||||
|
||||
/* a line that never comes back */
|
||||
post_lost = "LOST-LINE";
|
||||
{
|
||||
static const char *const la[] = { "1 2 +" }, *const lb[] = { "LOST-LINE" }, *const lc[] = { "65 EMIT" };
|
||||
static const v4_post_case three_cases[] = {
|
||||
{ "first", la, 1u, V4_POST_STACK, three, 1u, "", 0u },
|
||||
{ "lost", lb, 1u, V4_POST_STACK, 0, 0u, "", 0u },
|
||||
{ "last", lc, 1u, V4_POST_STACK, 0, 0u, "A", 1u },
|
||||
};
|
||||
v4_post_tally t;
|
||||
post_said_len = 0; post_said[0] = 0;
|
||||
CHECK(v4_post_run(&post_host, three_cases, 3, &t) == 0 && t.tests == 3 && t.pass == 2 && t.fail == 1
|
||||
&& strstr(post_said, "POST FAIL: lost ") != NULL && strstr(post_said, "PARITY:V4_POST tests=3 pass=2 fail=1\n") != NULL,
|
||||
"a line that does not come back fails its case; the others are still judged, and the run is not a pass: \"%s\"", post_said);
|
||||
}
|
||||
post_lost = 0;
|
||||
}
|
||||
|
||||
/* ---- storage that refuses a write (MESH.md 8.2) ---- */
|
||||
boot();
|
||||
CHECK(is(say(REFUSING_FIRST " BLOCK DROP UPDATE SAVE-BUFFERS 65 EMIT\n"), "Storage refused\n ERROR\nok> "), "a write storage will not take says so, and the prompt returns");
|
||||
|
||||
Reference in New Issue
Block a user