Found in the five-node test, each with a check that failed first: - A node with answers it could not give to two stuck senders began no text at all. It now holds back only text from a sender whose own answer is still with it. - KILL waited to tell a stuck node, and a typed line that broke it left the nodes after it untold. GONE is put with the messages waiting, as how text ended is, and goes when it can. - So that 'tell, then wait' still works, AWAIT does what (IDLE) does but for beginning text: it looks through the messages waiting, offers what is to be passed on and what is owed, and sleeps in the wait. A waiting node no longer keeps back what passes through it. - A node in the wait with an offer on a port that turns out to have nothing on it is woken and told so (which port: 2 * V4_PORTS + k); no error is raised on it. (GATE) makes it error 18; (IDLE) and AWAIT let the message go. Before, removing a node raised 'No one on that port' on the unrelated text of a neighbour that had something queued for it. make -C v4 test and sanitize at both widths, hosted-check. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
494 lines
34 KiB
C
494 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, told_of;
|
|
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] == 0 && pool[0].n.mem[REFUSED] >= refused + 8, "it owed a refusal for each of the rest, as many as it could owe at once, and paid them while it waited: node 10 was told of %ld",
|
|
(long)(pool[0].n.mem[REFUSED] - refused));
|
|
|
|
/* 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);
|
|
told_of = pool[0].n.mem[REFUSED] - refused;
|
|
CHECK(told_of >= 8 && told_of <= not_taken, "node 10 was told of %ld of them", (long)told_of);
|
|
CHECK(pool[1].n.mem[LOST] == lost + not_taken + (not_taken - told_of), "it counted each message it did not take, and each refusal it could not owe: %ld for %ld not taken, %ld told",
|
|
(long)(pool[1].n.mem[LOST] - lost), (long)not_taken, (long)told_of);
|
|
/* 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(strcmp(tell(11, "20 22 + ."), "42 ") == 0 && ended == 1 && pool[0].n.mem[AWAIT_FROM] == 12, "text from the console for node 11 comes to node 10 on its way: node 10 passes it on, and goes on waiting: \"%s\"", printed);
|
|
{
|
|
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 && ended == 2, "the line is then done as usual: \"%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;
|
|
}
|