Files
LithosAnanake/v4/tests/test_host_mesh.c
T
rajamesandClaude Opus 5.5 1c720ab2c7 fix(v4.0.0): what a node passes on waits with it, offered -- a relay is not held by a stuck node
Found after the last commit: a message being passed on toward a node that
was stuck held the node passing it on in the wait, taking in what came
but serving none of it; text typed for a stuck node left Hera deaf.  And
a node that had done text for a sender that then stuck waited on it to
give its answer.

A node with nothing to do now deals first with what is for itself; then
offers at once the first message for each port and each refusal owed,
and sleeps in the wait.  What is taken is written and done with; the
rest stays with the messages waiting, in order.  How text ended is put
with the messages waiting, not written from (FINISH).  The stack is kept
as shallow between lines as it was.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 23:51:30 -04:00

493 lines
34 KiB
C

/* test_host_mesh.c -- StarForth nodes that talk: a console wired to one
* node, and two more in a row behind it. docs/v4.0.0/MESH.md sections 6
* and 7, steps 3 and 4.
*
* console --1 [10] 2--2 [11] 3--2 [12]
*
* Each node is the nucleus (v4/capsule/ *.v4) and nothing else. They start
* alike, numbered 0 and knowing no way anywhere; everything they come to
* know they are told by text sent from the console, as whoever wires a node
* tells it. The console is a device on a port and speaks messages
* (v4/include/v4/message.h).
*/
#include "v4/image.h"
#include "v4/fabric.h"
#include "v4/message.h"
#include "v4/text.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "host_map.h"
static int failures = 0, checks = 0;
#define CHECK(c,...) do{checks++; if(!(c)){failures++; printf("FAIL %s:%d: ",__FILE__,__LINE__); printf(__VA_ARGS__); printf("\n");}}while(0)
static v4_text tx;
static v4_node nucleus; /* one node with the nucleus in it: what every node here starts as */
static v4_cell w_idle, w_fault;
static v4_fabric_node pool[3];
static v4_place places[3];
static v4_fabric f;
/* ---- the console: a device on a port ---- */
#define CONSOLE_ID 1
static v4_message going, coming;
static unsigned going_at;
static char printed[8192]; /* what has come back since the console last sent */
static unsigned printed_len;
static v4_cell printed_from; /* which node the last of it came from */
static int ended; /* how many messages of how text ended have come, */
static v4_cell ended_from, ended_how; /* and the last of them */
static int nacks; /* how many NACKs have come to the console, */
static v4_cell nack_from, nack_about; /* and the last of them: who refused, and the node the refused message was for */
static int console_give(void *self, v4_cell *value)
{
(void)self;
if (going_at >= going.count) return 0;
*value = going.word[going_at++];
return 1;
}
static int console_take(void *self, v4_cell value)
{
(void)self;
if (v4_message_word(&coming, value)) {
if (v4_message_type(&coming) == V4_MSG_OUTPUT) {
unsigned i, chars = v4_message_length(&coming);
for (i = 0; i < chars && printed_len + 1 < sizeof printed; i++) printed[printed_len++] = v4_message_char(&coming, i);
printed[printed_len] = 0;
printed_from = v4_message_from(&coming);
} else if (v4_message_type(&coming) == V4_MSG_DONE) {
ended++;
ended_from = v4_message_from(&coming);
ended_how = coming.word[V4_MSG_HEADER];
} else if (v4_message_type(&coming) == V4_MSG_NACK) {
nacks++;
nack_from = v4_message_from(&coming);
nack_about = coming.word[V4_MSG_HEADER];
}
coming.count = 0;
}
return 1;
}
static int console_pending(void *self) { (void)self; return going_at < going.count; }
static const v4_device console = { console_take, console_give, 0, console_pending };
static int is_empty(const char *s) { return s[0] == 0; }
static unsigned step_cap = 20000000; /* how long a line may take before it is given up on */
/* Send text to a node and let everything that follows from it happen. */
static const char *tell(v4_cell node, const char *text)
{
unsigned steps = 0;
printed_len = 0; printed[0] = 0; printed_from = -1;
ended = 0; ended_from = -1; ended_how = -1;
if (!v4_message_text(&going, node, CONSOLE_ID, V4_MSG_TEXT, text, (unsigned)strlen(text))) return "(too long)";
going_at = 0;
while (steps < step_cap && v4_fabric_step(&f) != 0) steps++;
if (steps >= step_cap) {
if (getenv("V4_MESH_STUCK")) {
unsigned k;
for (k = 0; k < 3; k++) if (v4_fabric_node_at(&f, k)) { const v4_node *n = &pool[k].n;
printf(" stuck after \"%s\": node %ld p=%ld asking=%d port=%u reading=%d rport=%u mq=%ld owed=%ld lost=%ld fault=%u@%ld\n", text, (long)n->mem[ME], (long)n->p, n->asking, n->ask_port, n->reading, n->read_port,
(long)n->mem[MQ_COUNT], (long)n->mem[OWED_COUNT], (long)n->mem[LOST], n->fault_kind, (long)n->fault_addr); }
}
return "(still running)";
}
return printed;
}
/* A StarForth node in the fabric: born, then given the nucleus and the
* registers the nucleus expects, waiting for a message. */
static unsigned starforth_node(unsigned k)
{
int id = v4_fabric_add(&f, &pool[k], PORT);
v4_node *n = &pool[k].n;
memcpy(n->mem, nucleus.mem, sizeof n->mem);
v4_node_stack_regs_attach(n, DSTACK_REG, RSTACK_REG);
v4_node_error_attach(n, NODE_ERROR);
v4_node_fault_attach(n, w_fault);
n->p = w_idle;
return (unsigned)id;
}
/* The same, answering with what has been printed: for when a node is
* spinning and the fabric never comes to rest. */
static const char *said(v4_cell node, const char *text) { (void)tell(node, text); return printed; }
static int waiting(unsigned id)
{
const v4_node *n = &v4_fabric_node_at(&f, id)->n;
return n->reading && !n->given && n->read_port == V4_PORT_ANY;
}
int main(void)
{
unsigned hera, mid, far, i;
printf("v4 mesh tests: V4_CELL_BITS=%d, V4_NODE_WORDS=%u\n", V4_CELL_BITS, (unsigned)V4_NODE_WORDS);
{
static const char *const files[] = { "core.v4", "input.v4", "dict.v4", "codegen.v4", "compile.v4", "quit.v4", "forth.v4",
"numout.v4", "words.v4", "system.v4", "qmath.v4", "blocks.v4", "log.v4", "acl.v4" };
CHECK(host_load(&tx, &nucleus, files, 14), "the nucleus assembles");
}
CHECK(v4_text_finish(&tx), "everything is defined: %s", v4_text_error(&tx));
if (failures) { printf(" %d checks, %d failures\n", checks, failures); return 1; }
w_idle = v4_text_word(&tx, "(IDLE)");
w_fault = v4_text_word(&tx, "(FAULTS)");
nucleus.mem[DP] = DICT_W * 4; /* the variables, as at switch-on (tools/mkimage.c) */
nucleus.mem[LATEST] = v4_text_latest(&tx);
nucleus.mem[CFP] = CFS_W;
nucleus.mem[BASE] = 10;
nucleus.mem[FENCE] = DICT_W;
nucleus.mem[BOOT_CELLS] = nucleus.mem[DP];
nucleus.mem[BOOT_CELLS + 1] = nucleus.mem[LATEST];
nucleus.mem[LOG_LEVEL] = 2;
nucleus.mem[CONTEXT] = LATEST;
nucleus.mem[CURRENT] = LATEST;
nucleus.mem[SRC] = TIB;
nucleus.mem[LINE_STATUS] = 1;
HOST_MESSAGES_EMPTY(&nucleus);
v4_fabric_init(&f, places, 3);
hera = starforth_node(0);
mid = starforth_node(1);
far = starforth_node(2);
CHECK(v4_fabric_wire_device(&f, hera, 1, &console), "the console is wired to one node's port 1");
CHECK(v4_fabric_wire(&f, hera, 2, mid, 2) && v4_fabric_wire(&f, mid, 3, far, 2), "and the three nodes in a row");
going.count = 0; going_at = 0;
for (i = 0; i < 20; i++) (void)v4_fabric_step(&f);
CHECK(waiting(hera) && waiting(mid) && waiting(far), "all three wait at their ports");
/* ---- they are told who they are and the way to each other, by text ---- */
CHECK(is_empty(tell(0, "10 (ME) ! 1 (CONSOLE) ! 1 1 ROUTE 0 2 ROUTE")) && ended == 1 && ended_how == V4_TEXT_COMPLETED && ended_from == 10,
"the node at the console takes text sent to 0, and is 10 from then on: %ld", (long)ended_from);
CHECK(is_empty(tell(0, "11 (ME) ! 1 (CONSOLE) ! 2 DEFAULT-ROUTE 0 3 ROUTE")) && ended == 1 && ended_from == 11 && ended_how == V4_TEXT_COMPLETED,
"text sent to 0 now passes through 10 to the next, which is 11 from then on: %ld", (long)ended_from);
CHECK(is_empty(tell(0, "12 (ME) ! 1 (CONSOLE) ! 2 DEFAULT-ROUTE")) && ended == 1 && ended_from == 12 && ended_how == V4_TEXT_COMPLETED,
"and then through both to the last, 12: %ld", (long)ended_from);
CHECK(is_empty(tell(10, "11 2 ROUTE 12 2 ROUTE")) && ended_from == 10 && is_empty(tell(11, "12 3 ROUTE")) && ended_from == 11,
"each is told the way to the others");
/* ---- they talk ---- */
CHECK(strcmp(tell(10, "1 2 + ."), "3 ") == 0 && printed_from == 10 && ended_how == V4_TEXT_COMPLETED, "text for the node at the console is done there");
CHECK(strcmp(tell(11, "20 22 + ."), "42 ") == 0 && printed_from == 11 && ended_from == 11, "text for the next node is done there, and what it prints comes back");
CHECK(strcmp(tell(12, "7 8 * ."), "56 ") == 0 && printed_from == 12 && ended_from == 12 && ended_how == V4_TEXT_COMPLETED,
"text for the last passes through two nodes, and what it prints comes back through two: \"%s\" from %ld", printed, (long)printed_from);
CHECK(strcmp(tell(12, "NOSUCH"), "UNKNOWN WORD: 'NOSUCH'\n") == 0 && ended_from == 12 && ended_how == V4_TEXT_ERROR, "an error there is reported from there");
CHECK(strcmp(tell(12, ": SQ DUP * ;"), "") == 0 && strcmp(tell(12, "9 SQ ."), "81 ") == 0 && strstr(tell(11, "9 SQ ."), "UNKNOWN WORD") != NULL,
"each node has its own dictionary: a word defined on one is not on another");
{
const char *all = tell(12, "WORDS");
CHECK(strlen(all) > 1000 && printed_from == 12 && ended == 1, "more than a message holds comes back in several: %u characters", (unsigned)strlen(all));
}
{
static char line[1100];
memset(line, ' ', 1024);
memcpy(line, "66 EMIT", 7);
memcpy(line + 1017, "67 EMIT", 7);
line[1024] = 0;
CHECK(strcmp(tell(12, line), "BC") == 0, "a message as long as a message may be is passed on whole: 1024 characters");
}
/* ---- one node sends to another ---- */
CHECK(strcmp(tell(10, ": TELL S\" 65 EMIT 12 . \" 12 SEND ; TELL"), "A12 ") == 0 && printed_from == 12,
"text one node SENDs another is done there, and what that prints goes to its console: \"%s\" from %ld", printed, (long)printed_from);
CHECK(strstr(tell(10, ": NOWAY S\" 1\" 99 SEND ; NOWAY"), "Argument out of range") != NULL && ended_how == V4_TEXT_ERROR,
"SEND to a node there is no way to is an error");
/* ---- no way ---- */
nacks = 0;
CHECK(is_empty(tell(99, "1 2 + .")) && ended == 0, "text for a node there is no way to is not done by anyone");
CHECK(nacks == 1 && nack_from == 10 && nack_about == 99, "the console, which sent it, is told it was refused, by the node that could not pass it on: %d from %ld about %ld",
nacks, (long)nack_from, (long)nack_about);
CHECK(strcmp(tell(10, "(LOST) @ ."), "1 ") == 0, "and that node has counted it");
CHECK(strcmp(tell(11, "(LOST) @ ."), "0 ") == 0 && strcmp(tell(12, "(LOST) @ ."), "0 ") == 0, "the others have lost nothing");
/* ---- waiting ---- */
{
v4_uheat_t c0 = pool[0].es.anticlock, c1 = pool[1].es.anticlock, c2 = pool[2].es.anticlock;
for (i = 0; i < 100; i++) (void)v4_fabric_step(&f);
CHECK(waiting(hera) && waiting(mid) && waiting(far), "with nothing to do, all three wait at their ports");
CHECK(pool[0].es.anticlock == c0 && pool[1].es.anticlock == c1 && pool[2].es.anticlock == c2, "and execute nothing");
}
/* ---- the ways are forgotten and told again: the wiring has changed ---- */
CHECK(v4_fabric_wire(&f, hera, 3, far, 3), "the far node is wired straight to the one at the console as well");
CHECK(is_empty(tell(10, "NO-ROUTES 1 1 ROUTE 11 2 ROUTE 12 3 ROUTE")) && is_empty(tell(12, "NO-ROUTES 3 DEFAULT-ROUTE")), "and both are told");
{
v4_uheat_t before = pool[1].es.anticlock;
CHECK(strcmp(tell(12, "5 5 + ."), "10 ") == 0 && pool[1].es.anticlock == before, "text for it no longer passes through the middle node, which executes nothing");
}
/* ---- two neighbours with something for each other at the same moment ----
* A write blocks until the neighbour reads, and a node that is writing
* is not reading: so a node looks before it begins a message, and of
* two neighbours only the one with the lower number may wait to write
* to the other (v4/capsule/core.v4, (GATE); docs/v4.0.0/MESH.md 7a).
* Each node is told who is on each of its ports. */
CHECK(is_empty(tell(10, "NO-ROUTES 1 1 ROUTE 11 2 ROUTE 12 2 ROUTE")) && is_empty(tell(12, "NO-ROUTES 2 DEFAULT-ROUTE")), "the row of three again");
CHECK(is_empty(tell(10, "11 2 NEIGHBOUR 0 3 NEIGHBOUR")) && is_empty(tell(11, "10 2 NEIGHBOUR 12 3 NEIGHBOUR")) && is_empty(tell(12, "11 2 NEIGHBOUR 0 3 NEIGHBOUR")),
"each is told who is on the other end of its ports");
CHECK(strstr(tell(10, "11 8 NEIGHBOUR"), "Argument out of range") != NULL && strstr(tell(10, "11 -1 NEIGHBOUR"), "Argument out of range") != NULL,
"a port a node has not got is an error");
/* the far node sends the middle one text, and then word of how its own
* text ended, which goes by the middle one; the middle one does the
* text and answers the far one: the two messages meet on one wire */
CHECK(strcmp(tell(12, ": HI S\" 72 EMIT\" 11 SEND ; HI"), "H") == 0 && printed_from == 11 && ended == 1 && ended_from == 12,
"from the far end to the middle, with the answers crossing: \"%s\" from %ld", printed, (long)printed_from);
CHECK(waiting(hera) && waiting(mid) && waiting(far), "and all three are waiting again");
/* every node sends every other a number of messages, all at once: each
* counts what it is sent */
CHECK(is_empty(tell(10, "VARIABLE GOT : HIT 1 GOT +! ; VARIABLE WHO VARIABLE SENT : BURST WHO ! 0 DO S\" HIT\" WHO @ SEND 1 SENT +! LOOP ;")) &&
is_empty(tell(11, "VARIABLE GOT : HIT 1 GOT +! ; VARIABLE WHO VARIABLE SENT : BURST WHO ! 0 DO S\" HIT\" WHO @ SEND 1 SENT +! LOOP ;")) &&
is_empty(tell(12, "VARIABLE GOT : HIT 1 GOT +! ; VARIABLE WHO VARIABLE SENT : BURST WHO ! 0 DO S\" HIT\" WHO @ SEND 1 SENT +! LOOP ;")), "each node is given a word to count with and one to send with");
{
const char *r = tell(10, ": GO S\" 6 10 BURST 6 12 BURST\" 11 SEND S\" 6 11 BURST 6 10 BURST\" 12 SEND 6 11 BURST 6 12 BURST ; GO");
CHECK(is_empty(r), "six messages from each node to each other, all at once: \"%s\"", r);
CHECK(waiting(hera) && waiting(mid) && waiting(far), "all three come to rest");
CHECK(strcmp(tell(10, "GOT @ . (LOST) @ ."), "12 1 ") == 0 && strcmp(tell(11, "GOT @ . (LOST) @ ."), "12 0 ") == 0 && strcmp(tell(12, "GOT @ . (LOST) @ ."), "12 0 ") == 0,
"every one of them arrived, and none was let go: \"%s\"", printed);
}
/* ---- more than a node has room to keep ----
* A node that takes messages in while it waits to write keeps them, and
* when it has no room for one more it lets that one go and counts it.
* Far more are sent here than the nodes can keep. */
{
long got = 0, lost = 0, sent = 0, v;
unsigned k;
static const v4_cell who[3] = { 10, 11, 12 };
const char *r;
CHECK(is_empty(tell(10, "0 GOT ! 0 SENT !")) && is_empty(tell(11, "0 GOT ! 0 SENT !")) && is_empty(tell(12, "0 GOT ! 0 SENT !")), "the counts begin again");
r = tell(10, ": GO2 S\" 200 10 BURST 200 12 BURST\" 11 SEND S\" 200 11 BURST 200 10 BURST\" 12 SEND 200 11 BURST 200 12 BURST ; GO2");
CHECK(strcmp(r, "(still running)") != 0, "two hundred from each to each: it ends");
CHECK(waiting(hera) && waiting(mid) && waiting(far), "and all three come to rest, none waiting to write");
for (k = 0; k < 3; k++) {
r = tell(who[k], "GOT @ .");
v = strtol(r, NULL, 10); got += v;
CHECK(v > 0 && v <= 400, "node %ld took %ld of the 400 for it", (long)who[k], v);
r = tell(who[k], "(LOST) @ .");
lost += strtol(r, NULL, 10);
r = tell(who[k], "SENT @ .");
sent += strtol(r, NULL, 10);
CHECK(pool[k].n.mem[MQ_COUNT] == 0, "node %ld has none left waiting", (long)who[k]);
}
lost -= 1; /* the one for node 99, earlier */
/* what is let go is of every kind: the text that would have set a
* node sending, the answers to text, as well as what was sent */
printf(" of %ld messages the nodes sent each other at once %ld arrived; %ld messages of all kinds were let go for want of room\n", sent, got, lost);
CHECK(lost > 0, "there was not room for all of them");
CHECK(got <= sent && got + lost >= sent, "every message sent either arrived or was counted: %ld sent, %ld arrived, %ld let go", sent, got, lost);
CHECK(strcmp(tell(12, "7 8 * ."), "56 ") == 0 && printed_from == 12, "and the nodes go on as before");
CHECK(pool[0].n.mem[OWED_COUNT] == 0 && pool[1].n.mem[OWED_COUNT] == 0 && pool[2].n.mem[OWED_COUNT] == 0 && waiting(hera) && waiting(mid) && waiting(far),
"every refusal a node owed has been sent, and all three are at rest");
CHECK(pool[0].n.mem[REFUSED] + pool[1].n.mem[REFUSED] + pool[2].n.mem[REFUSED] > 0, "senders were told of what was refused: %ld %ld %ld",
(long)pool[0].n.mem[REFUSED], (long)pool[1].n.mem[REFUSED], (long)pool[2].n.mem[REFUSED]);
}
/* ---- refusals and waits (docs/v4.0.0/MESH.md 7b) ---------------------------
* No message is lost without its sender being told, and no node waits
* for ever. */
/* no way: the refusal travels back to the node that sent the message */
{
v4_cell refused = pool[2].n.mem[REFUSED], lost = pool[0].n.mem[LOST];
CHECK(is_empty(tell(12, ": N77 S\" 1 DROP\" 77 SEND ; N77")) && ended_how == V4_TEXT_COMPLETED, "node 12 sends text to node 77: it has a way for everything, toward 11");
CHECK(pool[0].n.mem[LOST] == lost + 1, "node 10, two away, has no way to 77 and counts the message");
CHECK(pool[2].n.mem[REFUSED] == refused + 1, "and node 12 is told: its count of refusals is one more");
}
/* a sender that is waiting for the answer stops waiting */
CHECK(strstr(tell(12, ": W77 S\" 1 DROP\" 77 SEND 77 AWAIT . ; W77"), "Message refused") != NULL && ended == 1 && ended_how == V4_TEXT_ERROR && ended_from == 12,
"a node that sends to 77 and waits for its answer: the wait ends, with an error that says the message was refused: \"%s\"", printed);
CHECK(strcmp(tell(12, "7 8 * ."), "56 ") == 0 && pool[2].n.mem[AWAIT_FROM] == 0, "and it goes on to its next line, waiting for no one");
/* no room: a node that is waiting keeps what it is sent, and has room for only so much */
{
v4_cell lost = pool[1].n.mem[LOST], refused = pool[0].n.mem[REFUSED], got, not_taken;
CHECK(is_empty(tell(11, "0 GOT !")) && is_empty(tell(11, "12 AWAIT .")) && ended == 0 && pool[1].n.mem[AWAIT_FROM] == 12, "node 11 waits for an answer from 12, which is not going to send one");
CHECK(is_empty(tell(10, ": F 49 0 DO S\" HIT\" 11 SEND LOOP ; F")) && ended == 1 && ended_from == 10, "node 10 sends it 49 messages meanwhile");
CHECK(pool[1].n.mem[MQ_COUNT] > 300 && pool[1].n.mem[MQ_COUNT] <= MQ_CELLS - 36, "node 11 has kept what it has room for, and left the room that is kept for refusals: %ld cells", (long)pool[1].n.mem[MQ_COUNT]);
CHECK(pool[1].n.mem[OWED_COUNT] == 8, "it owes a refusal for each of the rest, as many as it can owe: %ld", (long)pool[1].n.mem[OWED_COUNT]);
CHECK(pool[0].n.mem[REFUSED] == refused, "and has sent none yet: it is still waiting");
/* one from its centre ends the wait */
CHECK(is_empty(tell(10, "12 NO-ROUTE")), "node 10 forgets the way to 12");
CHECK(strstr(tell(10, "12 11 GONE"), "Node gone") != NULL && printed_from == 11 && ended_from != -1, "node 10, its centre, tells it 12 is gone: its wait ends with an error that says so: \"%s\"", printed);
CHECK(pool[1].n.mem[AWAIT_FROM] == 0 && waiting(mid), "it is waiting for no one, and is back at its ports");
got = strtol(tell(11, "GOT @ ."), NULL, 10);
not_taken = 49 - got;
CHECK(got >= 30 && not_taken > 8, "it then did every message it had kept: %ld of the 49", (long)got);
CHECK(pool[0].n.mem[REFUSED] == refused + 8, "and sent the eight refusals it owed: node 10 was told of %ld", (long)(pool[0].n.mem[REFUSED] - refused));
CHECK(pool[1].n.mem[LOST] == lost + not_taken + (not_taken - 8), "it counted each message it did not take, and each refusal it could not owe: %ld for %ld not taken",
(long)(pool[1].n.mem[LOST] - lost), (long)not_taken);
/* the way to 12 is forgotten: what is sent there now is refused */
refused = pool[1].n.mem[REFUSED];
CHECK(is_empty(tell(11, ": P12 S\" 1 DROP\" 12 SEND ; P12")) && pool[1].n.mem[REFUSED] == refused + 1, "having forgotten the way to 12, what node 11 sends there goes toward its centre, which has no way either, and is refused");
CHECK(is_empty(tell(10, "12 2 ROUTE")) && is_empty(tell(11, "12 3 ROUTE")) && strcmp(tell(12, "7 8 * ."), "56 ") == 0, "told the way again, they reach 12 as before");
}
/* a line from the console breaks a wait (MESH.md 7b.6) */
CHECK(is_empty(tell(10, "12 AWAIT .")) && ended == 0 && pool[0].n.mem[AWAIT_FROM] == 12 && waiting(hera), "node 10 waits for an answer from 12, which is not going to send one");
CHECK(is_empty(tell(11, "20 22 + .")) && ended == 0 && pool[0].n.mem[AWAIT_FROM] == 12, "text from the console for node 11 comes to node 10 on its way: node 10 keeps it, and goes on waiting");
{
const char *r = tell(10, "65 EMIT");
CHECK(strstr(r, "Interrupted\n") != NULL && pool[0].n.mem[AWAIT_FROM] == 0, "text from the console for node 10 itself ends its wait, with an error that says so: \"%s\"", r);
CHECK(strstr(r, "A") != NULL && strstr(r, "42 ") != NULL && ended == 3, "the line is then done as usual, and so is what was kept for node 11: \"%s\", %d ended", r, ended);
CHECK(strstr(r, "Interrupted\n") < strstr(r, "A"), "the wait ends before the line that ended it is done");
}
CHECK(strcmp(tell(10, "1 2 + ."), "3 ") == 0 && waiting(hera) && waiting(mid) && waiting(far), "and all three go on as before");
CHECK(strstr(tell(10, "5 NO-ROUTE"), "ERROR") == NULL && strcmp(tell(11, "20 22 + ."), "42 ") == 0, "forgetting the way to a node there was no way to changes nothing");
/* ---- what the review of step 6c found (MESH.md step 6c) ------------------- */
step_cap = 3000000;
v4_cell ways11 = 0;
/* a GONE is believed only from the node's centre: not by another port, and not from another node by the centre's port */
CHECK(is_empty(tell(10, "12 NO-ROUTE 12 3 ROUTE 12 3 NEIGHBOUR")) && is_empty(tell(12, "NO-ROUTES 3 DEFAULT-ROUTE 11 2 ROUTE 10 3 NEIGHBOUR")),
"node 12 is reached straight from node 10 now, and has its own wire to 11");
CHECK(is_empty(tell(11, "12 AWAIT .")) && ended == 0 && pool[1].n.mem[AWAIT_FROM] == 12, "node 11 waits for node 12's answer");
ways11 = pool[1].n.mem[ROUTE_COUNT];
CHECK(is_empty(tell(12, "12 11 GONE")) && ended == 1 && ended_from == 12 && pool[1].n.mem[AWAIT_FROM] == 12 && pool[1].n.mem[ROUTE_COUNT] == ways11,
"node 12 tells it, by the wire between them, that 12 is gone: that is not by its centre's port, and it goes on waiting");
CHECK(is_empty(tell(12, "11 NO-ROUTE 12 11 GONE")) && ended == 1 && ended_from == 12 && pool[1].n.mem[AWAIT_FROM] == 12 && pool[1].n.mem[ROUTE_COUNT] == ways11,
"node 12 says it again by way of node 10: it comes by the centre's port but is not from the centre, and node 11 goes on waiting, its ways as they were");
CHECK(strstr(tell(10, "12 11 GONE"), "Node gone") != NULL && pool[1].n.mem[AWAIT_FROM] == 0, "node 10, its centre, says it: the wait ends: \"%s\"", printed);
CHECK(is_empty(tell(11, "12 3 ROUTE")) && is_empty(tell(12, "11 2 ROUTE")), "the ways are told again");
/* an answer that came before the wait began is in the messages waiting, and is found there */
CHECK(strstr(tell(12, ": QF S\" 1 DROP\" 77 SEND 3000 0 DO LOOP S\" 1 DROP\" 10 SEND 77 AWAIT . ; QF"), "Message refused") != NULL && ended_how == V4_TEXT_ERROR
&& pool[2].n.mem[AWAIT_FROM] == 0 && waiting(far),
"node 12 sends to 77, is busy a while, sends another message -- taking in the refusal as it does -- and then waits for 77's answer: the wait ends at once: \"%s\"", printed);
CHECK(strcmp(tell(12, "7 8 * ."), "56 ") == 0, "and it goes on");
/* a waiting sender whose message found no room */
{
CHECK(is_empty(tell(11, "0 GOT !")) && is_empty(tell(11, "12 AWAIT .")) && pool[1].n.mem[AWAIT_FROM] == 12, "node 11 waits on 12 again");
CHECK(is_empty(tell(10, ": F40 40 0 DO S\" HIT\" 11 SEND LOOP ; F40")) && pool[1].n.mem[OWED_COUNT] == 0, "node 10 sends it as many messages as it has room for");
/* node 11 does what it has kept before it pays what it owes (7b.3): forty answers come first */
(void)tell(10, ": ONE S\" HIT\" 11 SEND 12 NO-ROUTE 12 11 GONE 41 0 DO 11 AWAIT DROP LOOP ; ONE");
CHECK(strstr(printed, "Message refused") != NULL && pool[0].n.mem[AWAIT_FROM] == 0,
"it sends one more, for which there is no room, ends node 11's wait, and waits for node 11's answers: after the forty, its wait ends, the message refused: \"%s\"", printed);
CHECK(strtol(tell(11, "GOT @ ."), NULL, 10) == 40 && is_empty(tell(10, "12 3 ROUTE")) && is_empty(tell(11, "12 3 ROUTE")), "node 11 did the forty it had kept; the ways are told again");
}
/* an answer that is refused on its way back is told to the node that waits for it, not to the node that answered */
{
v4_cell r12 = pool[2].n.mem[REFUSED];
CHECK(is_empty(tell(12, "10 2 ROUTE")), "node 12 is told the way to node 10 is by node 11");
CHECK(is_empty(tell(11, "0 GOT !")) && is_empty(tell(11, "55 AWAIT .")) && pool[1].n.mem[AWAIT_FROM] == 55, "node 11 waits on a node there is not");
CHECK(is_empty(tell(10, "F40")) && pool[1].n.mem[OWED_COUNT] == 0, "node 10 sends it as many messages as it has room for");
(void)tell(10, ": RA S\" 1 DROP\" 12 SEND 3000 0 DO LOOP 55 11 GONE 12 AWAIT . ; RA");
CHECK(strstr(printed, "Message refused") != NULL && pool[0].n.mem[AWAIT_FROM] == 0,
"node 10 sends node 12 text; its answer comes back by node 11, which has no room for it; node 10 then ends node 11's wait and waits for the answer: it is told the message was refused: \"%s\"", printed);
CHECK(pool[2].n.mem[REFUSED] == r12, "node 12, which answered, is told nothing");
CHECK(strtol(tell(11, "GOT @ ."), NULL, 10) == 40 && is_empty(tell(12, "10 3 ROUTE")) && strcmp(tell(12, "7 8 * ."), "56 ") == 0, "node 11 did the forty it had kept; the way is told again");
}
/* a message that would go round for ever is refused after 16 nodes have passed it on */
{
v4_cell refused = pool[0].n.mem[REFUSED], l10 = pool[0].n.mem[LOST], l11 = pool[1].n.mem[LOST];
CHECK(is_empty(tell(10, "66 2 ROUTE")), "node 10 is told the way to 66 is by node 11, whose way for everything it has not been told of is back by node 10");
CHECK(is_empty(tell(10, ": H66 S\" 1 DROP\" 66 SEND ; H66")) && ended == 1 && ended_how == V4_TEXT_COMPLETED, "node 10 sends 66 a message: it comes to rest");
CHECK(pool[0].n.mem[LOST] + pool[1].n.mem[LOST] == l10 + l11 + 1 && pool[0].n.mem[REFUSED] == refused + 1,
"one of the two let it go when it had been passed on 16 times, and node 10 was told it was refused");
CHECK(is_empty(tell(10, "66 NO-ROUTE")) && waiting(hera) && waiting(mid) && waiting(far), "the way is forgotten, and all three are at rest");
}
/* ---- what a node passes on keeps its order, however long it must be kept (MESH.md 7c) ---- */
CHECK(is_empty(tell(11, "12 2 ROUTE")), "node 11 is told the way to node 12 is by node 10");
CHECK(is_empty(tell(12, ": SLOW 30000 0 DO LOOP ;")), "node 12 is given a word that takes a while");
CHECK(strcmp(tell(11, ": TRIO S\" SLOW 68 EMIT\" 12 SEND S\" 69 EMIT\" 12 SEND S\" 70 EMIT\" 12 SEND ; TRIO"), "DEF") == 0,
"it sends node 12 three texts, the first slow to do: node 10 keeps the other two until node 12 reads again, and they are done in order: \"%s\"", printed);
CHECK(is_empty(tell(11, "12 3 ROUTE")) && waiting(hera) && waiting(mid) && waiting(far) && pool[0].n.mem[MQ_COUNT] == 0, "the way is told again; all three are at rest, keeping nothing");
/* ---- a node that is stuck holds up no one (MESH.md 7c) ------------------- */
v4_fabric_gone_error(&f, V4_ERROR_NO_ONE);
/* From here a node is spinning, so the fabric never comes to rest: said()
* is tell(), answering with what was printed by the time it gave up. */
/* a refusal owed to a stuck node whose number is the lower */
CHECK(is_empty(said(12, "NO-ROUTES 1 3 ROUTE 10 3 ROUTE 11 2 ROUTE")) && is_empty(said(11, "55 3 ROUTE")), "node 12 has no way for 55; node 11 is told the way to 55 is by node 12");
CHECK(strcmp(tell(11, ": C1 S\" 1 DROP\" 55 SEND S\" 1 DROP\" 12 SEND BEGIN 0 UNTIL ; C1"), "(still running)") == 0, "node 11 sends 55 a message, and node 12 text, and is then stuck in a loop");
CHECK(pool[2].n.mem[OWED_COUNT] == 1, "node 12 owes node 11 a refusal, and cannot give it: node 11 is not reading");
CHECK(strcmp(said(12, "7 8 * ."), "56 ") == 0 && printed_from == 12, "node 12 goes on doing what it is sent: \"%s\"", printed);
{
v4_uheat_t clock = pool[2].es.anticlock;
unsigned k;
for (k = 0; k < 1000; k++) (void)v4_fabric_step(&f);
CHECK(v4_node_in_wait(&pool[2].n) && pool[2].es.anticlock == clock && pool[2].n.mem[OWED_COUNT] == 1, "between times it sleeps in the wait, still owing, and executes nothing");
}
CHECK(strcmp(said(10, ": X12 S\" 66 EMIT\" 12 SEND ; X12"), "B") == 0 && printed_from == 12, "another node writes to it and is answered: \"%s\"", printed);
/* two refusals owed: one to the stuck node, one to a node that waits for it */
CHECK(is_empty(said(10, "77 3 ROUTE")), "node 10 is told the way to 77 is by node 12, which has none");
CHECK(strstr(said(10, ": W77B S\" 1 DROP\" 77 SEND 77 AWAIT . ; W77B"), "Message refused") != NULL && pool[0].n.mem[AWAIT_FROM] == 0 && pool[2].n.mem[OWED_COUNT] == 1,
"node 10 sends 77 a message and waits: node 12, which still owes the stuck node, tells node 10 it was refused: \"%s\"", printed);
CHECK(is_empty(said(10, "77 NO-ROUTE")), "the way is forgotten");
/* a message passed on toward the stuck node waits with the node passing it on, which goes on serving */
{
v4_cell kept = pool[0].n.mem[MQ_COUNT];
CHECK(is_empty(said(11, "1 DROP")) && ended == 0 && pool[0].n.mem[MQ_COUNT] > kept && v4_node_in_wait(&pool[0].n),
"text from the console for the stuck node comes to node 10, which cannot hand it on: it keeps it, offered, and sleeps");
CHECK(strcmp(said(10, "1 2 + ."), "3 ") == 0 && strcmp(said(12, "7 8 * ."), "56 ") == 0 && printed_from == 12,
"node 10 goes on doing what it is sent, and passing on what is for others: \"%s\"", printed);
CHECK(pool[0].n.mem[MQ_COUNT] > kept && v4_node_in_wait(&pool[0].n), "still keeping what is for the stuck node");
}
/* the stuck node's own answer: node 12 did text node 11 sent before it stuck, and cannot give it the answer */
CHECK(pool[2].n.mem[MQ_COUNT] > 0 && v4_node_in_wait(&pool[2].n), "node 12 keeps the answer it has for node 11, offered, and sleeps");
/* a line that must begin a message to the stuck node waits, and the node takes in what comes */
CHECK(is_empty(said(10, ": TO11 S\" 1 DROP\" 11 SEND 65 EMIT ; TO11")) && ended == 0 && v4_node_in_wait(&pool[0].n) && !pool[0].n.asking,
"node 10 sends the stuck node a message: its line waits in the wait, not blocked writing");
{
v4_cell kept = pool[0].n.mem[MQ_COUNT];
CHECK(is_empty(said(12, "7 8 * .")) && ended == 0 && pool[0].n.mem[MQ_COUNT] > kept && v4_node_in_wait(&pool[0].n),
"text from the console for node 12 comes to node 10: it keeps it, and its line goes on waiting");
}
{
const char *r = said(10, "66 EMIT");
CHECK(strstr(r, "Interrupted\n") != NULL && strstr(r, "A") == NULL && strstr(r, "B") != NULL && strstr(r, "Interrupted\n") < strstr(r, "B"),
"text from the console for node 10 itself ends the line that waited, with an error that says so, and is then done: \"%s\"", r);
CHECK(strstr(r, "56 ") != NULL && ended == 3, "and what it kept for node 12 is passed on and done: \"%s\", %d ended", r, ended);
}
/* the stuck node is removed while a line waits to begin a message to it */
CHECK(is_empty(said(10, "TO11")) && ended == 0 && v4_node_in_wait(&pool[0].n), "node 10's line waits on the stuck node again");
CHECK(v4_fabric_remove(&f, mid) == &pool[1], "node 11 is removed");
{
unsigned k;
for (k = 0; k < 200000 && ended == 0; k++) (void)v4_fabric_step(&f);
CHECK(strstr(printed, "No one on that port") != NULL && strstr(printed, "A") == NULL && ended == 1, "the line ends in an error that says no one is there: \"%s\"", printed);
}
step_cap = 20000000;
CHECK(strcmp(said(12, "7 8 * ."), "56 ") == 0 && pool[2].n.mem[OWED_COUNT] == 0 && pool[2].n.mem[MQ_COUNT] == 0 && pool[0].n.mem[MQ_COUNT] == 0 && waiting(far) && !v4_node_in_wait(&pool[2].n) && waiting(hera),
"node 12 goes on; what it owed node 11 and kept for it, and what node 10 kept for it, are let go: they owe and keep nothing, and are at rest");
/* a refusal owed to a stuck node whose number is the higher */
step_cap = 3000000;
CHECK(is_empty(said(12, "55 3 ROUTE")), "node 12 is told the way to 55 is by node 10, which has none");
CHECK(strcmp(tell(12, ": C2 S\" 1 DROP\" 55 SEND BEGIN 0 UNTIL ; C2"), "(still running)") == 0 && pool[0].n.mem[OWED_COUNT] == 1 && !pool[0].n.asking,
"node 12 sends 55 a message and is then stuck in a loop: node 10 owes it a refusal, and is not blocked writing it");
step_cap = 20000000;
CHECK(strcmp(said(10, "1 2 + ."), "3 ") == 0 && pool[0].n.mem[OWED_COUNT] == 1, "node 10 goes on doing what it is sent, still owing: \"%s\"", printed);
{
v4_uheat_t clock;
unsigned k;
for (k = 0; k < 1000; k++) (void)v4_fabric_step(&f);
clock = pool[0].es.anticlock;
for (k = 0; k < 1000; k++) (void)v4_fabric_step(&f);
CHECK(v4_node_in_wait(&pool[0].n) && pool[0].es.anticlock == clock, "and sleeps in the wait between times");
}
CHECK(v4_fabric_remove(&f, far) == &pool[2], "node 12 is removed");
CHECK(strcmp(said(10, "1 2 + ."), "3 ") == 0 && pool[0].n.mem[OWED_COUNT] == 0 && waiting(hera) && !v4_node_in_wait(&pool[0].n), "node 10 lets the refusal go and is at rest: \"%s\"", printed);
printf(" %d checks, %d failures\n", checks, failures);
return failures != 0;
}