Step 6e, tasks 2 and 3 (docs/v4.0.0/MESH.md 7d). The nucleus puts, looks at, takes, moves and drops whole messages through the six addresses after its ports; its own ring of messages waiting, the refusals owed, (GATE) and the looking words' use are gone. SEND, an answer, a NACK and a GONE go or are refused at once; what a text prints waits for room; a text is not begun until the wire its answer goes by has room for it, and that room is kept. The lone node's host (v4/system/boot.c) keeps its two wires and does its operations; the test hosts' consoles put and take whole messages through the fabric. New: tests/test_host_depth.c, every message path of a mesh node at every depth of both stacks. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
635 lines
44 KiB
C
635 lines
44 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 whole messages
|
|
* (v4/include/v4/message.h): it puts them on the wire to its node and
|
|
* takes them off the wire from it, through the fabric (MESH.md 7d.2).
|
|
*
|
|
* Each scene after the first is played on a row made afresh, row(), so
|
|
* that none depends on what an earlier one left.
|
|
*/
|
|
#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;
|
|
#define QUEUES 12u /* two for each wire there may be: the console's, 10-11, 11-12, 10-12, and two wires to spare */
|
|
static v4_wire_queue queues[QUEUES];
|
|
|
|
/* ---- the console: a device on a port ---- */
|
|
#define CONSOLE_ID 1
|
|
static const v4_device console = { 0, 0, 0, 0 }; /* it takes and gives no words: only whole messages */
|
|
static unsigned hera, mid, far; /* where the three nodes are: 10, 11, 12 */
|
|
static char printed[32768]; /* 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 unsigned ended_at; /* and how much had been printed when it came */
|
|
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 */
|
|
|
|
/* Everything the node at the console has put on the console's wire is taken, in order. How many messages. */
|
|
static unsigned console_takes(void)
|
|
{
|
|
static v4_cell hd[V4_WIRE_HEADER], text[V4_MSG_MAX_CHARS / 4u];
|
|
unsigned took = 0, i;
|
|
while (v4_fabric_device_take(&f, hera, 1, hd, text, V4_MSG_MAX_CHARS / 4u) == V4_WIRE_DONE) {
|
|
took++;
|
|
if (hd[2] == V4_MSG_OUTPUT) {
|
|
for (i = 0; i < (unsigned)hd[6] && printed_len + 1 < sizeof printed; i++) printed[printed_len++] = (char)(((v4_ucell)text[i / 4u] >> (8u * (i % 4u))) & 0xFFu);
|
|
printed[printed_len] = 0;
|
|
printed_from = hd[1];
|
|
} else if (hd[2] == V4_MSG_DONE) {
|
|
ended++; ended_from = hd[1]; ended_how = text[0]; ended_at = printed_len;
|
|
} else if (hd[2] == V4_MSG_NACK) {
|
|
nacks++; nack_from = hd[1]; nack_about = text[0];
|
|
}
|
|
}
|
|
return took;
|
|
}
|
|
|
|
static int is_empty(const char *s) { return s[0] == 0; }
|
|
|
|
/* 1 if the node is asleep (operation 8): it executes nothing until a message comes. */
|
|
static int asleep(unsigned id)
|
|
{
|
|
const v4_fabric_node *x = v4_fabric_node_at(&f, id);
|
|
return x && x->n.doing && x->n.do_op == 8;
|
|
}
|
|
/* 1 if it is asleep waiting for room on a wire (operation 9). */
|
|
static int waits_for_room(unsigned id)
|
|
{
|
|
const v4_fabric_node *x = v4_fabric_node_at(&f, id);
|
|
return x && x->n.doing && x->n.do_op == 9;
|
|
}
|
|
/* the queue of messages going from a node's port, and coming to it */
|
|
static const v4_wire_queue *going_from(unsigned id, unsigned port) { return places[id].wire[port].tx; }
|
|
static const v4_wire_queue *coming_to(unsigned id, unsigned port) { return places[id].wire[port].rx; }
|
|
|
|
static unsigned step_cap = 20000000; /* how long a line may take before it is given up on */
|
|
static unsigned stuck; /* bit k: the node at place k is known to be going round for ever, and is not waited for */
|
|
static void (*watch)(void); /* called after every step, if set */
|
|
static unsigned long steps_run;
|
|
static unsigned between_data, between_ret; /* the most of each stack any node has used while doing nothing that is a text's (QUIET), */
|
|
static int was_quiet[3], clean[3]; /* after a text that left nothing on the data stack */
|
|
|
|
static void show_stuck(const char *text)
|
|
{
|
|
unsigned k, q;
|
|
if (!getenv("V4_MESH_STUCK")) return;
|
|
for (k = 0; k < 3; k++) if (v4_fabric_node_at(&f, k)) { const v4_node *n = &pool[k].n;
|
|
printf(" stuck after \"%.40s\": node %ld p=%ld doing=%d op=%ld how=%ld wire=%u asking=%d d=%u r=%u await=%ld keep=%ld lost=%ld refused=%ld fault=%u@%ld err=%ld\n", text, (long)n->mem[ME], (long)n->p, n->doing, (long)n->do_op, (long)n->how, n->wire,
|
|
n->asking, (unsigned)n->ds.depth, (unsigned)n->rs.depth, (long)n->mem[AWAIT_FROM], (long)n->mem[KEEP], (long)n->mem[LOST], (long)n->mem[REFUSED], n->fault_kind, (long)n->fault_addr, (long)n->mem[NODE_ERROR]);
|
|
for (q = 0; q < V4_PORTS; q++) if (places[k].wire[q].rx) printf(" port %u: %u cells coming, %u going\n", q, places[k].wire[q].rx->used, places[k].wire[q].tx->used); }
|
|
}
|
|
/* Let everything that follows from what has been put happen: until nothing can, or -- with a node known to be
|
|
* stuck -- until every other node has been asleep, and the console has been sent nothing, for a while. */
|
|
static int run(void)
|
|
{
|
|
unsigned steps = 0, quiet = 0, k;
|
|
while (steps < step_cap) {
|
|
unsigned did = v4_fabric_step(&f), took = console_takes(), awake = 0, running = 0;
|
|
steps++; steps_run++;
|
|
if (watch) watch();
|
|
for (k = 0; k < 3; k++) if (v4_fabric_node_at(&f, k)) {
|
|
int quiet_now = pool[k].n.mem[QUIET] == 1;
|
|
if (quiet_now && !was_quiet[k]) clean[k] = pool[k].n.ds.depth <= 1; /* its text has ended: did it leave anything? (how it ended may still be there) */
|
|
was_quiet[k] = quiet_now;
|
|
if (!quiet_now || !clean[k]) continue;
|
|
if (pool[k].n.ds.depth > between_data) between_data = (unsigned)pool[k].n.ds.depth;
|
|
if (pool[k].n.rs.depth > between_ret) between_ret = (unsigned)pool[k].n.rs.depth;
|
|
}
|
|
/* a step in which a node only faulted counts nothing done (exec.h): it goes on from its handler at the next */
|
|
for (k = 0; k < 3; k++) if (v4_fabric_node_at(&f, k) && !pool[k].n.doing && !pool[k].n.asking && !pool[k].n.stopped && !(pool[k].n.reading && !pool[k].n.given)) running = 1;
|
|
if (did == 0 && took == 0 && !running) return 1;
|
|
for (k = 0; k < 3; k++) if (v4_fabric_node_at(&f, k) && !(stuck & 1u << k) && !(pool[k].n.doing && (pool[k].n.do_op == 8 || pool[k].n.do_op == 9))) awake = 1;
|
|
quiet = (stuck && !awake && took == 0) ? quiet + 1 : 0;
|
|
if (quiet >= 400000) return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
/* The console sends text to a node: the message is put on the wire to the node the console is on. */
|
|
static int put_text(v4_cell node, const char *text)
|
|
{
|
|
static v4_message m;
|
|
if (!v4_message_text(&m, node, CONSOLE_ID, V4_MSG_TEXT, text, (unsigned)strlen(text))) return 0;
|
|
return v4_fabric_device_put(&f, hera, 1, m.word, m.word + V4_MSG_HEADER) == V4_WIRE_DONE;
|
|
}
|
|
static void forget(void)
|
|
{
|
|
printed_len = 0; printed[0] = 0; printed_from = -1;
|
|
ended = 0; ended_from = -1; ended_how = -1; ended_at = 0;
|
|
}
|
|
/* Send text to a node and let everything that follows from it happen. */
|
|
static const char *tell(v4_cell node, const char *text)
|
|
{
|
|
forget();
|
|
if (!put_text(node, text)) return "(not put)";
|
|
if (!run()) { show_stuck(text); 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;
|
|
}
|
|
|
|
/* Three nodes that know nothing, in a row, the console on the first. */
|
|
static int fresh(void)
|
|
{
|
|
unsigned i;
|
|
int ok;
|
|
v4_fabric_init(&f, places, 3);
|
|
v4_fabric_queues(&f, queues, QUEUES);
|
|
v4_fabric_gone_error(&f, V4_ERROR_NO_ONE);
|
|
hera = starforth_node(0);
|
|
mid = starforth_node(1);
|
|
far = starforth_node(2);
|
|
ok = v4_fabric_wire_device(&f, hera, 1, &console) && v4_fabric_wire(&f, hera, 2, mid, 2) && v4_fabric_wire(&f, mid, 3, far, 2);
|
|
stuck = 0; watch = 0; nacks = 0; step_cap = 20000000;
|
|
was_quiet[0] = was_quiet[1] = was_quiet[2] = 0; clean[0] = clean[1] = clean[2] = 0;
|
|
forget();
|
|
for (i = 0; i < 200; i++) (void)v4_fabric_step(&f);
|
|
return ok && asleep(hera) && asleep(mid) && asleep(far);
|
|
}
|
|
static int told(v4_cell node, const char *text) { return is_empty(tell(node, text)) && ended == 1 && ended_how == V4_TEXT_COMPLETED; }
|
|
/* The same, told who they are, the way to each other, and who is on each port:
|
|
* console --1 [10] 2--2 [11] 3--2 [12] */
|
|
static int row(void)
|
|
{
|
|
return fresh()
|
|
&& told(0, "10 (ME) ! 1 (CONSOLE) ! 1 1 ROUTE 0 2 ROUTE")
|
|
&& told(0, "11 (ME) ! 1 (CONSOLE) ! 2 DEFAULT-ROUTE 0 3 ROUTE")
|
|
&& told(0, "12 (ME) ! 1 (CONSOLE) ! 2 DEFAULT-ROUTE")
|
|
&& told(10, "0 NO-ROUTE 11 2 ROUTE 12 2 ROUTE 11 2 NEIGHBOUR") && told(11, "0 NO-ROUTE 12 3 ROUTE 10 2 NEIGHBOUR 12 3 NEIGHBOUR") && told(12, "11 2 NEIGHBOUR");
|
|
}
|
|
/* And with the far node wired straight to the one at the console as well, port 3 to port 3; node 10 reaches
|
|
* 12 by that wire, and 12 reaches everything by it but 11. */
|
|
static int triangle(void)
|
|
{
|
|
return row() && v4_fabric_wire(&f, hera, 3, far, 3)
|
|
&& told(10, "12 NO-ROUTE 12 3 ROUTE 12 3 NEIGHBOUR") && told(12, "NO-ROUTES 3 DEFAULT-ROUTE 11 2 ROUTE 10 3 NEIGHBOUR");
|
|
}
|
|
static v4_uheat_t clock_of(unsigned id) { return pool[id].es.anticlock; }
|
|
|
|
/* what a scene watches for while it runs */
|
|
static unsigned saw_room_wait, saw_full;
|
|
static void watch_far_waits(void)
|
|
{
|
|
if (waits_for_room(far)) { saw_room_wait++; if (going_from(far, 3) && going_from(far, 3)->used == V4_WIRE_CELLS - 8u) saw_full++; }
|
|
}
|
|
|
|
int main(void)
|
|
{
|
|
unsigned 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);
|
|
|
|
CHECK(fresh(), "the console is wired to one node's port 1, the three nodes are in a row, and all three sleep");
|
|
|
|
/* ---- 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 && ended_at == printed_len, "more than a message holds comes back in several, and then how it ended: %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");
|
|
CHECK(strstr(tell(10, ": LONG PAD 1025 12 SEND ; LONG"), "Argument out of range") != NULL && strstr(tell(10, ": SHORT PAD -1 12 SEND ; SHORT"), "Argument out of range") != NULL
|
|
&& strcmp(tell(10, "DEPTH ."), "0 ") == 0, "and so is more text than a message carries, or less than none; nothing is left on the stack");
|
|
CHECK(strstr(tell(10, ": P5 S\" 1\" 0 5 SEND-ON ; P5"), "No one on that port") != NULL && ended_how == V4_TEXT_ERROR, "SEND-ON by a port with no one on it is an error that says so");
|
|
|
|
/* ---- 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 = clock_of(0), c1 = clock_of(1), c2 = clock_of(2);
|
|
for (i = 0; i < 100; i++) (void)v4_fabric_step(&f);
|
|
CHECK(asleep(hera) && asleep(mid) && asleep(far), "with nothing to do, all three sleep");
|
|
CHECK(clock_of(0) == c0 && clock_of(1) == c1 && clock_of(2) == 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 = clock_of(1);
|
|
CHECK(strcmp(tell(12, "5 5 + ."), "10 ") == 0 && clock_of(1) == 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 ----
|
|
* Each puts its message on the wire to the other, whole, and neither waits for the other (MESH.md 7d.2).
|
|
* Each node is told who is on each of its ports: it is how it knows its centre. */
|
|
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(asleep(hera) && asleep(mid) && asleep(far), "and all three are asleep again");
|
|
|
|
/* every node sends every other a number of messages, all at once: each
|
|
* counts what it is sent */
|
|
#define COUNTING "VARIABLE GOT : HIT 1 GOT +! ; VARIABLE WHO VARIABLE SENT VARIABLE TRIED : BURST WHO ! 0 DO 1 TRIED +! S\" HIT\" WHO @ SEND 1 SENT +! LOOP ;"
|
|
CHECK(is_empty(tell(10, COUNTING)) && is_empty(tell(11, COUNTING)) && is_empty(tell(12, COUNTING)), "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(asleep(hera) && asleep(mid) && asleep(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);
|
|
}
|
|
|
|
/* ---- THE FLOOD: more than the wires hold (MESH.md 7d.6, acceptance 2) ----
|
|
* 800 messages among three nodes, all at once. A SEND the wire has no room for is refused at once: the
|
|
* word that sends ends there, error 19, and the refusal is counted. A message a middle node has no room
|
|
* to pass on is let go there, counted, and its sender told. Nothing waits, and it comes to rest. */
|
|
{
|
|
long got = 0, lost = 0, refused = 0, sent = 0, tried = 0, v;
|
|
unsigned k, q, left = 0;
|
|
static const v4_cell who[3] = { 10, 11, 12 };
|
|
const char *r;
|
|
CHECK(is_empty(tell(10, "0 GOT ! 0 SENT ! 0 TRIED !")) && is_empty(tell(11, "0 GOT ! 0 SENT ! 0 TRIED !")) && is_empty(tell(12, "0 GOT ! 0 SENT ! 0 TRIED !")), "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(asleep(hera) && asleep(mid) && asleep(far), "and all three come to rest, asleep");
|
|
for (k = 0; k < 3; k++) for (q = 0; q < V4_PORTS; q++) if (coming_to(k, q)) left += coming_to(k, q)->used;
|
|
CHECK(left == 0, "with nothing left on any wire: %u cells", left);
|
|
for (k = 0; k < 3; k++) {
|
|
v = strtol(tell(who[k], "GOT @ ."), NULL, 10); got += v;
|
|
CHECK(v >= 0 && v <= 400, "node %ld did %ld of the messages for it", (long)who[k], v);
|
|
sent += strtol(tell(who[k], "SENT @ ."), NULL, 10);
|
|
tried += strtol(tell(who[k], "TRIED @ ."), NULL, 10);
|
|
lost += pool[k].n.mem[LOST];
|
|
refused += pool[k].n.mem[REFUSED];
|
|
}
|
|
lost -= 1; /* the one for node 99, earlier */
|
|
printf(" the flood: %ld sends tried, %ld on a wire, %ld refused at once; %ld done; (LOST) %ld, (REFUSED) %ld in all\n", tried, sent, tried - sent, got, lost, refused);
|
|
CHECK(tried > sent && sent >= got && got > 0, "there was not room for all of them: some were refused at once");
|
|
/* Every message that got on a wire and was let go on its way is counted exactly twice: once in (LOST)
|
|
* where it was let go, and once where its NACK ended -- in (REFUSED) at the node it was for, or in
|
|
* (LOST) if it could not go either. Every SEND refused at once is counted once, in (REFUSED). So
|
|
* (LOST) + (REFUSED) - the refused at once is twice the messages let go; and those are the messages
|
|
* the nodes sent each other that were not done, and answers to them. */
|
|
CHECK((lost + refused - (tried - sent)) % 2 == 0 && lost + refused - (tried - sent) >= 2 * (sent - got),
|
|
"every message sent was done or was counted: %ld on a wire, %ld done; (LOST) + (REFUSED) %ld, of which %ld were refused at once", sent, got, lost + refused, tried - sent);
|
|
CHECK(strcmp(tell(12, "7 8 * ."), "56 ") == 0 && printed_from == 12, "and the nodes go on as before");
|
|
}
|
|
|
|
/* ---- acceptance 2 and 3: REFUSED AT ONCE, and a stuck node holds up no one ---- */
|
|
CHECK(row(), "a row made afresh");
|
|
{
|
|
v4_cell r0;
|
|
char text[64];
|
|
CHECK(told(10, ": F 0 DO S\" HIT\" 11 SEND LOOP ;"), "node 10 is given a word that sends node 11 short texts");
|
|
stuck = 1u << mid;
|
|
(void)tell(11, ": C BEGIN 0 UNTIL ; C");
|
|
CHECK(ended == 0 && !asleep(mid) && coming_to(mid, 2)->used == 0, "node 11 is stuck in a loop that never ends; it took the text that stuck it");
|
|
r0 = pool[hera].n.mem[REFUSED];
|
|
snprintf(text, sizeof text, "%u F", (unsigned)(V4_WIRE_CELLS / 8u));
|
|
CHECK(is_empty(tell(10, text)) && ended == 1 && ended_how == V4_TEXT_COMPLETED && pool[hera].n.mem[REFUSED] == r0 && going_from(hera, 2)->used == V4_WIRE_CELLS,
|
|
"as many as the wire holds are taken, and none is refused: the wire is full, %u cells", going_from(hera, 2)->used);
|
|
for (i = 1; i <= 5; i++) {
|
|
const char *r = tell(10, "1 F");
|
|
CHECK(strstr(r, "Message refused") != NULL && ended == 1 && ended_how == V4_TEXT_ERROR && pool[hera].n.mem[REFUSED] == r0 + (v4_cell)i,
|
|
"one more is refused at once, and counted: the line ends, having waited for nothing: \"%s\", %ld", r, (long)(pool[hera].n.mem[REFUSED] - r0));
|
|
CHECK(strcmp(tell(10, "1 2 + ."), "3 ") == 0, "node 10 answers");
|
|
}
|
|
CHECK(asleep(hera) && asleep(far) && pool[hera].n.mem[LOST] == 0, "nothing waits: nodes 10 and 12 are asleep, and nothing was let go");
|
|
|
|
/* text for node 12, beyond the stuck one */
|
|
{
|
|
static char line[1100];
|
|
v4_cell lost = pool[hera].n.mem[LOST];
|
|
memset(line, ' ', 1024); memcpy(line, "1 DROP", 6); line[1024] = 0;
|
|
nacks = 0;
|
|
(void)tell(12, line);
|
|
CHECK(nacks == 1 && nack_from == 10 && nack_about == 12 && pool[hera].n.mem[LOST] == lost + 1 && ended == 0,
|
|
"text for node 12, beyond the stuck node, is refused at node 10, whose wire to 11 is full, and the console is sent the NACK: %d from %ld about %ld", nacks, (long)nack_from, (long)nack_about);
|
|
CHECK(strcmp(tell(10, "1 2 + ."), "3 ") == 0 && asleep(hera), "node 10 answers, and sleeps");
|
|
}
|
|
}
|
|
|
|
/* ---- a refusal owed to a stuck node ---- */
|
|
CHECK(triangle(), "a row made afresh, with nodes 10 and 12 wired straight to each other");
|
|
{
|
|
v4_cell lost = pool[far].n.mem[LOST];
|
|
v4_uheat_t c;
|
|
CHECK(told(12, "NO-ROUTES 1 3 ROUTE 10 3 ROUTE 11 2 ROUTE") && told(11, "55 3 ROUTE"), "node 12 has no way for 55; node 11 is told the way to 55 is by node 12");
|
|
stuck = 1u << mid;
|
|
(void)tell(11, ": C1 S\" 1 DROP\" 55 SEND BEGIN 0 UNTIL ; C1");
|
|
CHECK(ended == 0 && pool[far].n.mem[LOST] == lost + 1, "node 11 sends 55 a message by node 12 and is then stuck in a loop: node 12 has let the message go");
|
|
CHECK(going_from(far, 2)->used == 8 && coming_to(far, 2)->used == 0, "its NACK is on the wire to node 11, which does not take it: %u cells", going_from(far, 2)->used);
|
|
c = clock_of(far);
|
|
for (i = 0; i < 500; i++) (void)v4_fabric_step(&f);
|
|
CHECK(asleep(far) && clock_of(far) == c, "node 12 is asleep, not going round: it owes nothing");
|
|
CHECK(strcmp(tell(12, "7 8 * ."), "56 ") == 0 && printed_from == 12 && strcmp(tell(10, "1 2 + ."), "3 ") == 0, "and it and node 10 answer: \"%s\"", printed);
|
|
}
|
|
|
|
/* ---- acceptance 5: PRINTING IS NOT LOST ---- */
|
|
CHECK(triangle(), "the same made afresh");
|
|
{
|
|
static char want[32768];
|
|
size_t at = 0;
|
|
for (i = 0; i < 3000; i++) at += (size_t)snprintf(want + at, sizeof want - at, "%u ", i);
|
|
CHECK(told(12, ": P 3000 0 DO I . LOOP ;") && told(10, ": SLOW 600000 0 DO LOOP ;"), "node 12 has a word that prints 3000 numbers, and node 10 one that takes a while");
|
|
forget();
|
|
saw_room_wait = 0; watch = watch_far_waits;
|
|
CHECK(put_text(12, "P") && put_text(10, "SLOW") && put_text(10, "SLOW") && put_text(10, "SLOW") && put_text(10, "SLOW") && run(),
|
|
"node 12 prints them while node 10, by which they go to the console, is busy with one line after another");
|
|
watch = 0;
|
|
CHECK(saw_room_wait > 0, "the wire to node 10 filled, and node 12 slept for room on it");
|
|
CHECK(strcmp(printed, want) == 0, "every number arrived, in order: %u characters of %u", printed_len, (unsigned)strlen(want));
|
|
CHECK(ended == 5 && pool[far].n.mem[LOST] == 0 && pool[hera].n.mem[LOST] == 0 && pool[far].n.mem[LINE_STATUS] == 1, "and then how the text ended; nothing was let go");
|
|
CHECK(strcmp(tell(12, "7 8 * ."), "56 ") == 0 && asleep(hera) && asleep(mid) && asleep(far), "and all three go on");
|
|
}
|
|
|
|
/* ---- printing held by a stuck node (MESH.md 7d.6) ---- */
|
|
CHECK(triangle(), "the same made afresh");
|
|
{
|
|
v4_cell lost, refused;
|
|
v4_uheat_t c;
|
|
CHECK(told(12, ": P 3000 0 DO I . LOOP ;") && told(12, "NO-ROUTES 2 DEFAULT-ROUTE 10 3 ROUTE") && told(10, "77 3 ROUTE : X S\" 1 DROP\" 77 SEND ;"),
|
|
"node 12's way to the console is by node 11; node 10 is told the way to 77 is by node 12");
|
|
stuck = 1u << mid;
|
|
(void)tell(11, ": C BEGIN 0 UNTIL ; C");
|
|
(void)tell(12, "P");
|
|
CHECK(ended == 0 && waits_for_room(far) && going_from(far, 2)->used > V4_WIRE_CELLS - 8u - 71u && going_from(far, 2)->used <= V4_WIRE_CELLS - 8u,
|
|
"node 12 prints until the wire to the stuck node is full but for the room kept for its answer, and sleeps for room: %u cells", going_from(far, 2)->used);
|
|
c = clock_of(far);
|
|
for (i = 0; i < 500; i++) (void)v4_fabric_step(&f);
|
|
CHECK(waits_for_room(far) && clock_of(far) == c, "executing nothing");
|
|
lost = pool[far].n.mem[LOST]; refused = pool[hera].n.mem[REFUSED];
|
|
/* its way for 77 is the full wire: while one more fits beside the room kept it is passed on, and
|
|
* after that it is refused and node 10 told. It is never kept. */
|
|
for (i = 0; i < 12 && pool[far].n.mem[LOST] == lost; i++) {
|
|
unsigned before = going_from(far, 2)->used;
|
|
CHECK(is_empty(tell(10, "X")) && ended == 1 && ended_from == 10, "node 10 sends a message for node 77 by node 12");
|
|
CHECK(coming_to(far, 3)->used == 0 && waits_for_room(far), "node 12, woken, does not keep it, and sleeps for room again");
|
|
CHECK((going_from(far, 2)->used == before + 9u && pool[far].n.mem[LOST] == lost) || (going_from(far, 2)->used == before && pool[far].n.mem[LOST] == lost + 1),
|
|
"it is passed on, or it is refused: %u cells then, %u now", before, going_from(far, 2)->used);
|
|
}
|
|
CHECK(pool[far].n.mem[LOST] == lost + 1 && pool[hera].n.mem[REFUSED] == refused + 1 && going_from(far, 2)->used <= V4_WIRE_CELLS - 8u,
|
|
"when one more would take the room kept for the answer it is refused, and node 10 told: the 8 cells are still there (%u used)", going_from(far, 2)->used);
|
|
CHECK(strcmp(tell(10, "1 2 + ."), "3 ") == 0, "node 10 answers meanwhile");
|
|
lost = pool[far].n.mem[LOST];
|
|
CHECK(v4_fabric_remove(&f, mid) == &pool[1], "node 11 is removed");
|
|
stuck = 0;
|
|
CHECK(run() && asleep(far) && pool[far].n.mem[NODE_ERROR] == 18 && pool[far].n.mem[LINE_STATUS] == 2 && pool[far].n.mem[OUT_PTR] == OUT_W,
|
|
"node 12's text ends in error 18, No one on that port, and it sleeps: error %ld, ended %ld", (long)pool[far].n.mem[NODE_ERROR], (long)pool[far].n.mem[LINE_STATUS]);
|
|
CHECK(pool[far].n.mem[LOST] == lost + 2, "the error's message and the word of how the text ended had no one to go to, and are counted: %ld", (long)(pool[far].n.mem[LOST] - lost));
|
|
(void)tell(12, "NO-ROUTES 3 DEFAULT-ROUTE");
|
|
CHECK(strcmp(tell(12, "7 8 * ."), "56 ") == 0 && printed_from == 12 && asleep(far) && asleep(hera), "told another way to the console, it goes on: \"%s\"", printed);
|
|
}
|
|
|
|
/* ---- a line from the console breaks a wait for room (MESH.md 7d.4) ---- */
|
|
CHECK(triangle(), "the same made afresh");
|
|
{
|
|
CHECK(told(12, ": P 3000 0 DO I . LOOP ;") && told(12, "NO-ROUTES 2 DEFAULT-ROUTE 10 3 ROUTE"), "node 12's way to the console is by node 11 again");
|
|
stuck = 1u << mid;
|
|
(void)tell(11, ": C BEGIN 0 UNTIL ; C");
|
|
(void)tell(12, "P");
|
|
CHECK(ended == 0 && waits_for_room(far) && pool[far].n.mem[NODE_ERROR] == 0, "node 11 is stuck, and node 12 sleeps for room to print");
|
|
(void)tell(11, "1 DROP");
|
|
(void)tell(10, ": T12 S\" 1 DROP\" 12 SEND ; T12");
|
|
CHECK(waits_for_room(far) && pool[far].n.mem[NODE_ERROR] == 0 && coming_to(far, 3)->used == 9,
|
|
"text for node 12 from another node does not break the wait: it is left on the wire, and node 12 sleeps on: %u cells", coming_to(far, 3)->used);
|
|
(void)tell(12, "65 EMIT");
|
|
CHECK(pool[far].n.mem[NODE_ERROR] == 21 && waits_for_room(far) && coming_to(far, 3)->used == 18,
|
|
"text for it from its console does: the text it was doing ends in error 21, Interrupted; the line that broke it stays on the wire: error %ld, %u cells",
|
|
(long)pool[far].n.mem[NODE_ERROR], coming_to(far, 3)->used);
|
|
(void)tell(12, "66 EMIT");
|
|
CHECK(pool[far].n.mem[NODE_ERROR] == 21 && waits_for_room(far) && coming_to(far, 3)->used == 27 && pool[far].n.mem[OUT_PTR] <= OUT_W + OUT_CELLS,
|
|
"its message has still to wait for the room, and is not broken a second time: MESH.md 7d.6");
|
|
CHECK(strcmp(tell(10, "1 2 + ."), "3 ") == 0, "node 10 answers meanwhile");
|
|
}
|
|
|
|
/* ---- Review Focus 4: THE ANSWER HAS ROOM KEPT ----
|
|
* Node 11 sends the console 127 short messages by way of node 12, which passes them on to the wire to
|
|
* node 10 while node 10 is busy: that wire is then full but for 8 cells. Node 10 has sent node 12 text
|
|
* that prints. Node 12 begins it -- there is room for its answer -- and its printing waits for room, since
|
|
* it may not take the 8 cells kept; when node 10 takes what is on the wire, the printing goes and then
|
|
* the answer, which node 10 is waiting for. */
|
|
CHECK(triangle(), "the same made afresh");
|
|
{
|
|
CHECK(told(11, "1 3 ROUTE : F1 0 DO S\" HIT\" 1 SEND LOOP ;") && told(10, ": WAIT 60000 0 DO LOOP ;")
|
|
&& told(10, ": G S\" 127 F1\" 11 SEND WAIT S\" 65 EMIT\" 12 SEND WAIT WAIT 12 AWAIT . ;"), "nodes 11 and 10 are given their words");
|
|
saw_room_wait = 0; saw_full = 0; watch = watch_far_waits;
|
|
(void)tell(10, "G");
|
|
watch = 0;
|
|
CHECK(saw_full > 0, "node 12 slept for room to print with the wire to node 10 holding exactly the 8 cells kept for its answer (%u, %u)", saw_room_wait, saw_full);
|
|
CHECK(strcmp(printed, "A1 ") == 0 && pool[far].n.mem[LOST] == 0 && pool[hera].n.mem[LOST] == 0,
|
|
"what it printed arrived, and then how the text ended, which node 10 waited for: the answer was not refused: \"%s\"", printed);
|
|
CHECK(asleep(hera) && asleep(mid) && asleep(far) && pool[far].n.mem[KEEP] == -1, "all three sleep, and node 12 keeps no room");
|
|
}
|
|
|
|
/* ---- Review Focus 2 and 3: LEFT FOR ITSELF ----
|
|
* Node 10 waits for node 12's answer. On the one wire, from node 11, there then come: text for node 10
|
|
* itself; a message for the console; and node 12's answer. */
|
|
CHECK(row(), "a row made afresh");
|
|
{
|
|
v4_uheat_t c;
|
|
CHECK(told(12, ": LL 400000 0 DO LOOP ;") && told(11, ": S1 S\" 66 EMIT\" 10 SEND ;"), "node 12 has a word that takes a long while, node 11 one that sends node 10 text");
|
|
forget();
|
|
stuck = 1u << far; /* it is busy, not stuck: it is not waited for */
|
|
CHECK(put_text(10, ": Q S\" LL\" 12 SEND 12 AWAIT . ; Q") && run() && pool[hera].n.mem[AWAIT_FROM] == 12 && asleep(hera) && ended == 0,
|
|
"node 10 sends node 12 text that takes a long while, and waits for its answer, asleep");
|
|
CHECK(is_empty(tell(11, "S1")) && ended == 1 && ended_from == 11, "node 11 sends node 10 text, and then word of how its own text ended, for the console: that is passed on by node 10");
|
|
CHECK(coming_to(hera, 2)->used == 9 && pool[hera].n.mem[AWAIT_FROM] == 12 && asleep(hera), "the text for node 10 itself is left on the wire, in front, and node 10 waits on, asleep: %u cells", coming_to(hera, 2)->used);
|
|
c = clock_of(hera);
|
|
for (i = 0; i < 2000; i++) (void)v4_fabric_step(&f);
|
|
CHECK(clock_of(hera) == c && asleep(hera), "with a message for itself on a wire it executes nothing until another comes: it does not go round");
|
|
stuck = 0;
|
|
forget();
|
|
CHECK(run() && strcmp(printed, "1 B") == 0 && ended == 1 && ended_at == 2 && pool[hera].n.mem[AWAIT_FROM] == 0,
|
|
"node 12's answer comes behind it and ends the wait; and then the text that was left is done: \"%s\"", printed);
|
|
CHECK(asleep(hera) && asleep(mid) && asleep(far) && coming_to(hera, 2)->used == 0, "all three sleep, with nothing left on the wire");
|
|
}
|
|
|
|
/* ---- acceptance 8: ORDER ---- */
|
|
CHECK(triangle(), "a row made afresh, with nodes 10 and 12 wired straight to each other");
|
|
CHECK(told(11, "12 2 ROUTE") && told(12, ": SLOW 30000 0 DO LOOP ;"), "node 11 is told the way to node 12 is by node 10; 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: the other two wait on the wire, and they are done in the order they were sent: \"%s\"", printed);
|
|
CHECK(strcmp(tell(12, ": TWO 65 EMIT 66 EMIT NOSUCHWORD ; 67 EMIT"), "UNKNOWN WORD: 'NOSUCHWORD'\n") == 0 && ended == 1 && ended_at == printed_len,
|
|
"what a text prints, and then how it ended, reach the console in that order");
|
|
|
|
/* ---- AWAIT's endings (MESH.md 7b) ---- */
|
|
CHECK(row(), "a row made afresh");
|
|
/* refused: a NACK about the node waited for */
|
|
{
|
|
v4_cell refused = pool[far].n.mem[REFUSED], lost = pool[hera].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[hera].n.mem[LOST] == lost + 1 && pool[far].n.mem[REFUSED] == refused + 1, "node 10, two away, has no way to 77 and counts the message; node 12 is told, and counts the refusal");
|
|
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[far].n.mem[AWAIT_FROM] == 0 && pool[far].n.mem[REFUSED] == refused + 1, "and it goes on to its next line, waiting for no one");
|
|
/* the refusal came before the wait began: it is on the wire, and is found there */
|
|
CHECK(strstr(tell(12, ": QF S\" 1 DROP\" 77 SEND 3000 0 DO LOOP 77 AWAIT . ; QF"), "Message refused") != NULL && ended_how == V4_TEXT_ERROR && pool[far].n.mem[AWAIT_FROM] == 0 && asleep(far),
|
|
"node 12 sends to 77, is busy a while, and then waits: the refusal is on its wire already, and the wait ends at once: \"%s\"", printed);
|
|
/* a NACK about another node does not end it, and is counted */
|
|
refused = pool[far].n.mem[REFUSED];
|
|
(void)tell(12, ": QO S\" 1 DROP\" 77 SEND 11 AWAIT . ; QO");
|
|
CHECK(ended == 0 && pool[far].n.mem[AWAIT_FROM] == 11 && pool[far].n.mem[REFUSED] == refused + 1 && asleep(far), "waiting on node 11, it is told its message for 77 was refused: that is counted, and it waits on");
|
|
}
|
|
/* gone: a GONE from its centre, and not from another */
|
|
{
|
|
v4_cell ways = pool[far].n.mem[ROUTE_COUNT];
|
|
CHECK(told(11, "44 3 ROUTE"), "node 11 is given a way");
|
|
ways = pool[mid].n.mem[ROUTE_COUNT];
|
|
CHECK(is_empty(tell(11, "44 AWAIT .")) && ended == 0 && pool[mid].n.mem[AWAIT_FROM] == 44, "node 11 waits for an answer from a node 44");
|
|
CHECK(is_empty(tell(10, ": G12 44 11 GONE ;")) && told(10, "12 2 ROUTE"), "(node 10 is given a word)");
|
|
/* node 12 is still waiting on 11: a line from the console ends that */
|
|
CHECK(strstr(tell(12, "1 DROP"), "Interrupted") != NULL, "(node 12's wait is ended from the console)");
|
|
CHECK(told(12, "44 11 GONE") && pool[mid].n.mem[AWAIT_FROM] == 44 && pool[mid].n.mem[ROUTE_COUNT] == ways,
|
|
"node 12 tells it that 44 is gone: node 12 is not its centre, and it goes on waiting, its ways as they were");
|
|
CHECK(strstr(tell(10, "G12"), "Node gone") != NULL && printed_from == 11 && pool[mid].n.mem[AWAIT_FROM] == 0 && pool[mid].n.mem[ROUTE_COUNT] == ways - 1,
|
|
"node 10, its centre, tells it: the wait ends with an error that says so, and the way is forgotten: \"%s\"", printed);
|
|
CHECK(asleep(mid) && coming_to(mid, 3)->used == 0 && coming_to(mid, 2)->used == 0, "it sleeps; the GONE it did not believe was not kept");
|
|
(void)tell(11, "33 AWAIT .");
|
|
CHECK(ended == 0 && told(10, "44 11 GONE") && pool[mid].n.mem[AWAIT_FROM] == 33 && asleep(mid), "a GONE from its centre about another node does not end a wait");
|
|
}
|
|
/* interrupted: text from the console */
|
|
CHECK(row(), "a row made afresh");
|
|
{
|
|
const char *r;
|
|
CHECK(is_empty(tell(10, "12 AWAIT .")) && ended == 0 && pool[hera].n.mem[AWAIT_FROM] == 12 && asleep(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[hera].n.mem[AWAIT_FROM] == 12 && asleep(hera),
|
|
"text from the console for node 11 comes to node 10 on its way: node 10 passes it on, and what comes back, and goes on waiting");
|
|
r = tell(10, "65 EMIT");
|
|
CHECK(strcmp(r, "Interrupted\nA") == 0 && ended == 2 && pool[hera].n.mem[AWAIT_FROM] == 0, "text from the console for node 10 itself ends its wait, with an error that says so, and is then done: \"%s\"", r);
|
|
CHECK(strcmp(tell(10, "1 2 + ."), "3 ") == 0 && asleep(hera) && asleep(mid) && asleep(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");
|
|
}
|
|
|
|
/* ---- an answer refused on its way back; a message that would go round for ever ---- */
|
|
CHECK(triangle(), "a row made afresh, with nodes 10 and 12 wired straight to each other");
|
|
{
|
|
v4_cell refused, l10, l11, r12;
|
|
/* a message that would go round for ever is refused after 16 nodes have passed it on */
|
|
refused = pool[hera].n.mem[REFUSED]; l10 = pool[hera].n.mem[LOST]; l11 = pool[mid].n.mem[LOST];
|
|
CHECK(told(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[hera].n.mem[LOST] + pool[mid].n.mem[LOST] == l10 + l11 + 1 && pool[hera].n.mem[REFUSED] == refused + 1,
|
|
"one of the two let it go when it had been passed on as often as a message may be, and node 10 was told it was refused");
|
|
nacks = 0;
|
|
CHECK(is_empty(tell(66, "1 DROP")) && nacks == 1 && nack_about == 66, "the same from the console, which sends it with no count: it too is let go, and the console told");
|
|
CHECK(strstr(tell(10, ": W66 S\" 1 DROP\" 66 SEND 66 AWAIT . ; W66"), "Message refused") != NULL && ended_how == V4_TEXT_ERROR && pool[hera].n.mem[AWAIT_FROM] == 0,
|
|
"and waiting for 66's answer, node 10 is the one that lets its own message go when it comes back the last time: its wait ends, the message refused: \"%s\"", printed);
|
|
CHECK(told(10, "66 NO-ROUTE") && asleep(hera) && asleep(mid) && asleep(far), "the way is forgotten, and all three are at rest");
|
|
|
|
/* An answer that cannot be passed on: the NACK is for the node the answer was for, not the node that
|
|
* answered. Here it cannot go either -- it has the answer's own way to go -- and whoever waits is
|
|
* not told: MESH.md 7d.6. That wait is ended from the console. */
|
|
r12 = pool[far].n.mem[REFUSED]; l11 = pool[mid].n.mem[LOST];
|
|
CHECK(told(12, "10 2 ROUTE") && told(11, "10 4 ROUTE"), "node 12 is told the way to node 10 is by node 11, which is told it is by a port with no one on it");
|
|
(void)tell(10, ": RA S\" 1 DROP\" 12 SEND 12 AWAIT . ; RA");
|
|
CHECK(ended == 0 && pool[hera].n.mem[AWAIT_FROM] == 12 && pool[mid].n.mem[LOST] == l11 + 2,
|
|
"node 10 sends node 12 text and waits; the answer comes back by node 11, which cannot pass it on, nor the NACK for it: both are counted: %ld", (long)(pool[mid].n.mem[LOST] - l11));
|
|
CHECK(pool[far].n.mem[REFUSED] == r12 && asleep(far), "node 12, which answered, is told nothing: the NACK was not for it");
|
|
CHECK(strcmp(tell(10, "1 2 + ."), "Interrupted\n3 ") == 0 && asleep(hera), "node 10's wait is ended from the console: \"%s\"", printed);
|
|
}
|
|
|
|
/* ---- the stacks, between texts (MESH.md 7c.6, 7d.4) ----
|
|
* After a text that left nothing on the data stack, what a node's stacks hold while it does nothing that
|
|
* is a text's is what the nucleus uses: passing on, refusing, taking in, and sending what a text printed and
|
|
* how it ended. It is looked at once an instruction word, so it is the least that was used; that 28
|
|
* values may wait is measured in test_host_quit.c and, for passing on and refusing, in test_host_depth.c.
|
|
* The return stack is empty when a text ends; (FINISH)'s wait for room goes seven deep on it. */
|
|
printf(" between texts the nucleus used at most %u cells of the data stack and %u entries of the return stack\n", between_data, between_ret);
|
|
CHECK(between_data <= 4 && between_ret <= 7, "between texts: four cells of the data stack, and seven return entries");
|
|
|
|
printf(" %d checks, %d failures\n", checks, failures);
|
|
return failures != 0;
|
|
}
|