diff --git a/v4/capsule/core.v4 b/v4/capsule/core.v4 index 80efae00..bbe82fb7 100644 --- a/v4/capsule/core.v4 +++ b/v4/capsule/core.v4 @@ -21,6 +21,8 @@ \ 13 Block out of range 14 No block is being loaded \ 15 Deferred word not set 16 Not a word \ 17 Storage refused 18 No one on that port +\ 19 Message refused 20 Node gone +\ 21 Interrupted \ -1 the word has printed its own message macro SWAP over push push drop pop pop endmacro @@ -182,6 +184,27 @@ header CMOVE SET: (MQ-HEAD) a! ! (MQ#) a! @ -1 + ! ; +\ REFUSALS (MESH.md section 7b). A message this node has no room for, or +\ no way to pass on, is let go and counted in (LOST); and its sender is +\ told, by a message of type 4, a NACK, whose one word is the node the +\ refused message was for. A node finds it has no room while it is taking +\ messages in so as to be free to write, where it cannot begin a message +\ of its own: so it notes what it owes, (OWE), in the eight pairs at +\ (OWED) -- whom it is owed to, and the node the message was for -- and +\ sends it when it next has nothing else to do, (PAY). With eight owed +\ already, one more is let go and counted. Nothing is owed for a NACK or +\ for a GONE, type 5: there is no NACK for a NACK. The last 36 cells of +\ the messages waiting are kept for those two types, so that ordinary +\ messages cannot take all the room. + +\ ( to about -- ) a refusal is owed to the node `to`, about the node `about` +: (OWE) + (OWED#) a! @ -8 + -if NOROOM + drop (OWED#) a! @ 2* (OWED) + a! + push !+ pop ! + (OWED#) a! @ 1 + ! ; + NOROOM: drop drop drop (LOST) a! @ 1 + ! ; + \ ( port to -- ) the seven words of a message being taken in, and the \ port, go into (MQ-HDR). B is at the port, by its address, and `to` is \ the message's first word, already read from it. @@ -199,15 +222,22 @@ header CMOVE SIZED: dup -1025 + -if LONG drop 3 + 2/ 2/ jump COUNTED LONG: drop 3 + 2/ 2/ -1 + FOR @b drop UNEXT -1 (MQ-HDR)+6 a! ! 0 COUNTED: \ ( n ) how many words of text follow - dup (MQ#) a! @ + -MQ-ROOM + -if FULL + dup (MQ#) a! @ + \ what it would come to + (MQ-HDR)+2 a! @ -4 + -if KEPT-FOR \ a NACK or a GONE may use the room kept for them + drop -MQ-ORDINARY + jump ROOM + KEPT-FOR: drop -MQ-ROOM + + ROOM: -if FULL drop push (MQ-HDR) a! @ (MQ!) (MQ-HDR)+1 a! @ (MQ!) (MQ-HDR)+2 a! @ (MQ!) (MQ-HDR)+3 a! @ (MQ!) (MQ-HDR)+4 a! @ (MQ!) (MQ-HDR)+5 a! @ (MQ!) (MQ-HDR)+6 a! @ (MQ!) (MQ-HDR)+7 a! @ (MQ!) pop if NOTEXT -1 + FOR @b (MQ!) NEXT ; NOTEXT: drop ; - FULL: drop if GONE -1 + FOR @b drop UNEXT jump LOSE - GONE: drop - LOSE: (LOST) a! @ 1 + ! ; + FULL: drop if READ -1 + FOR @b drop UNEXT jump LOSE + READ: drop + LOSE: (LOST) a! @ 1 + ! + (MQ-HDR)+2 a! @ -4 + -if NOT-OWED \ nothing is owed for a NACK or a GONE + drop (MQ-HDR)+1 a! @ (MQ-HDR) a! @ jump (OWE) \ to whoever sent it, about whom it was for + NOT-OWED: drop ; \ ( port to -- ) take in a message and keep it : (TAKE) (TAKE-HDR) jump (TAKE-KEEP) @@ -238,6 +268,18 @@ header CMOVE !b \ type 0 !b 0 !b 0 !b ; \ heat and TTL, ACL tag, sequence +\ ( -- ) send one refusal this node owes, if it owes any: the one noted +\ last. With no way to whom it is owed, it is let go and counted. +: (PAY) + (OWED#) a! @ if NONE + -1 + dup ! + 2* (OWED) + a! @+ @ \ to about + over (PORT-FOR) if NOWAY + (GATE) over !b \ to + 4 (HDR) 4 !b !b drop ; \ from, type 4; four characters; about + NOWAY: drop drop drop (LOST) a! @ 1 + ! ; + NONE: drop ; + \ ( -- ) send what has been printed, to where (PRINT-TO) says, by the port \ (PRINT-PORT): its length, then its characters, four to a word, the first \ lowest. quit.v4 sets those two when text for this node arrives. diff --git a/v4/capsule/quit.v4 b/v4/capsule/quit.v4 index b378d5e6..21ef5366 100644 --- a/v4/capsule/quit.v4 +++ b/v4/capsule/quit.v4 @@ -78,7 +78,8 @@ macro R-CLEAR RSTACK-DEPTH b! a !b endmacro \ line was, and (FINISH) reports how it ended. A message for another node \ is passed on, (PASS-ON). : (IDLE) - L: (MQ#) a! @ if WAIT drop jump HAVE + L: (PAY) \ a refusal owed is sent first (core.v4) + (MQ#) a! @ if WAIT drop jump HAVE WAIT: drop (PORT)+8 b! @b (PORT)+9 b! @b (PORT) + dup b! \ ( to port ) B: the port it came on SWAP (TAKE) jump L HAVE: @@ -96,7 +97,12 @@ macro R-CLEAR RSTACK-DEPTH b! a !b endmacro (MSG)+6 a! @ SPAN a! ! (MSG) a! @ (ME) a! @ xor if MINE drop jump (PASS-ON) MINE: drop - (MSG)+2 a! @ -1 + if TEXT drop jump (IDLE) \ for this node and not text: let go + (MSG)+2 a! @ -1 + if TEXT \ for this node, and + -3 + if NACK -1 + if GONE drop jump (IDLE) \ neither text nor these: let go + NACK: drop (REFUSED) a! @ 1 + ! jump (IDLE) \ a message of this node's was refused: counted + GONE: drop (REPLY) a! @ (ROUTE-DEFAULT) a! @ xor if CENTRE \ a node is gone, if its centre says so: + drop jump (IDLE) + CENTRE: drop TIB 2/ 2/ a! @ NO-ROUTE jump (IDLE) \ the way to it is forgotten TEXT: drop (MSG)+1 a! @ (PORT-FOR) if D0 jump D1 \ the way back to whoever sent it: D0: drop (REPLY) a! @ \ the port it came on, if no other is known @@ -119,7 +125,10 @@ macro R-CLEAR RSTACK-DEPTH b! a !b endmacro -1 + TIB 2/ 2/ a! FOR @+ !b UNEXT \ and its text jump (IDLE) SENT: drop jump (IDLE) - NOWAY: drop (LOST) a! @ 1 + ! jump (IDLE) + NOWAY: drop (LOST) a! @ 1 + ! + (MSG)+2 a! @ -4 + -if NOT-OWED \ its sender is told, unless it is a NACK or a GONE + drop (MSG)+1 a! @ (MSG) a! @ (OWE) jump (IDLE) + NOT-OWED: drop jump (IDLE) \ ( -- ) THE TEXT, INTERPRETED. It is in TIB with a zero after it and its \ length in SPAN, and the return stack is empty. It is interpreted with one @@ -160,6 +169,35 @@ header NEIGHBOUR header DEFAULT-ROUTE : DEFAULT-ROUTE (PORT) + (ROUTE-DEFAULT) a! ! ; +\ ( node -- ) forget the way to that node, if one is known: what is for it +\ then goes by the way for every node not told of, if there is one. The +\ last way in the table takes its place. +header NO-ROUTE +: NO-ROUTE + (ROUTE#) a! @ if NONE -1 + (ROUTES) a! + FOR dup @+ xor if HIT drop @+ drop NEXT + drop ; + NONE: drop drop ; + HIT: drop drop pop drop + a -1 + push \ where its entry is + (ROUTE#) a! @ -1 + dup ! \ one way fewer + 2* (ROUTES) + a! @+ @ \ the last entry: node port + pop a! SWAP !+ ! ; + +\ ( about node type -- ) send the node a message of one word, `about`: +\ a NACK is type 4, a GONE type 5 (MESH.md 7b). With no way to the node +\ it is error 12. +: (SEND1) + push dup (PORT-FOR) if NOWAY + (GATE) !b pop (HDR) 4 !b !b ; + NOWAY: drop drop drop pop drop NODE-ERROR b! 12 !b ; + +\ ( n node -- ) tell the node that node n is gone. It acts on that only +\ if this node is its centre: it forgets the way to n, and if it is waiting +\ for n's answer the wait ends. +header GONE +: GONE 5 jump (SEND1) + \ ( -- ) forget every way: for when the wiring has changed header NO-ROUTES : NO-ROUTES 0 (ROUTE#) a! ! 0 (ROUTE-DEFAULT) a! ! ; @@ -197,17 +235,34 @@ header SEND-ON \ ( node -- how ) WAIT FOR THE NODE'S WORD OF HOW TEXT ENDED: 0 QUIT, \ 1 completed, 2 an error. This node is blocked reading its ports until a \ message of type 3 comes for it from that node; every other message that -\ comes meanwhile is kept with the messages waiting. It waits for ever -\ for a node that never answers. +\ comes meanwhile is kept with the messages waiting. (AWAIT-FROM) holds the +\ node while it waits, and 0 otherwise. +\ THE WAIT ALSO ENDS, in an error, when the answer is not going to come +\ (MESH.md section 7b): a NACK about that node, error 19, Message refused; +\ or a GONE about it from this node's centre, error 20, Node gone. A NACK +\ about another node is counted, and a GONE about another has its way +\ forgotten, as they would be if this node were not waiting. header AWAIT : AWAIT (AWAIT-FROM) a! ! L: (PORT)+8 b! @b (PORT)+9 b! @b (PORT) + dup b! SWAP (TAKE-HDR) (MQ-HDR) a! @ (ME) a! @ xor if A1 drop jump KEEP - A1: drop (MQ-HDR)+2 a! @ -3 + if A2 drop jump KEEP - A2: drop (MQ-HDR)+1 a! @ (AWAIT-FROM) a! @ xor if A3 drop jump KEEP + A1: drop (MQ-HDR)+2 a! @ \ for this node: its type + -3 + if ANSWER -1 + if NACK -1 + if GONE drop jump KEEP + ANSWER: drop (MQ-HDR)+1 a! @ (AWAIT-FROM) a! @ xor if A3 drop jump KEEP A3: drop (MQ-HDR)+6 a! @ -4 + if A4 drop jump KEEP - A4: drop @b ; \ its one word + A4: drop 0 (AWAIT-FROM) a! ! @b ; \ its one word + NACK: drop (MQ-HDR)+6 a! @ -4 + if N4 drop jump KEEP + N4: drop @b (AWAIT-FROM) a! @ xor if REFUSED \ about the node waited for? + drop (REFUSED) a! @ 1 + ! jump L \ another: counted + REFUSED: drop 0 (AWAIT-FROM) a! ! NODE-ERROR b! 19 !b ; + GONE: drop (MQ-HDR)+6 a! @ -4 + if G4 drop jump KEEP + G4: drop @b \ the node that is gone + (MQ-HDR)+7 a! @ (ROUTE-DEFAULT) a! @ xor if CENTRE \ only its centre is believed + drop drop jump L + CENTRE: drop dup NO-ROUTE + (AWAIT-FROM) a! @ xor if LEFT drop jump L + LEFT: drop 0 (AWAIT-FROM) a! ! NODE-ERROR b! 20 !b ; KEEP: (TAKE-KEEP) jump L \ ( w port -- ) write a cell to the port, by its number. It waits until @@ -239,6 +294,7 @@ header NODE-ERROR inline : NODE-ERROR' NODE-ERROR ; header (ME) inline : (ME)' (ME) ; header (CONSOLE) inline : (CONSOLE)' (CONSOLE) ; header (LOST) inline : (LOST)' (LOST) ; +header (REFUSED) inline : (REFUSED)' (REFUSED) ; header (WORD-DEFINED) inline : (WORD-DEFINED)' (WORD-DEFINED) ; header (WORD-FORGOTTEN) inline : (WORD-FORGOTTEN)' (WORD-FORGOTTEN) ; @@ -285,6 +341,7 @@ header ABORT -1 + if M5 -1 + if M6 -1 + if M7 -1 + if M8 -1 + if M9 -1 + if M10 -1 + if M11 -1 + if M12 -1 + if M13 -1 + if M14 -1 + if M15 -1 + if M16 -1 + if M17 -1 + if M18 + -1 + if M19 -1 + if M20 -1 + if M21 drop 2 jump (DONE) M1: drop $6167654E (EMIT4) $65766974 (EMIT4) $756F6320 (EMIT4) $746E (EMIT4) CR 2 jump (DONE) M2: drop $20746F4E (EMIT4) $756E2061 (EMIT4) $7265626D (EMIT4) CR 2 jump (DONE) @@ -304,6 +361,9 @@ header ABORT M16: drop $20746F4E (EMIT4) $6F772061 (EMIT4) $6472 (EMIT4) CR 2 jump (DONE) M17: drop $726F7453 (EMIT4) $20656761 (EMIT4) $75666572 (EMIT4) $646573 (EMIT4) CR 2 jump (DONE) M18: drop $6F206F4E (EMIT4) $6F20656E (EMIT4) $6874206E (EMIT4) $70207461 (EMIT4) $74726F (EMIT4) CR 2 jump (DONE) + M19: drop $7373654D (EMIT4) $20656761 (EMIT4) $75666572 (EMIT4) $646573 (EMIT4) CR 2 jump (DONE) + M20: drop $65646F4E (EMIT4) $6E6F6720 (EMIT4) $65 (EMIT4) CR 2 jump (DONE) + M21: drop $65746E49 (EMIT4) $70757272 (EMIT4) $646574 (EMIT4) CR 2 jump (DONE) \ The table the loader gives the node: six words, one for each kind of \ fault in the node's order, each a jump. A jump fills its word, so they diff --git a/v4/include/v4/message.h b/v4/include/v4/message.h index 02b3dd18..14aa3525 100644 --- a/v4/include/v4/message.h +++ b/v4/include/v4/message.h @@ -33,6 +33,8 @@ #define V4_MSG_TEXT 1 /* text to be interpreted */ #define V4_MSG_OUTPUT 2 /* what a node printed */ #define V4_MSG_DONE 3 /* how the text ended: one word, V4_TEXT_* */ +#define V4_MSG_NACK 4 /* a message was refused: one word, the node it was for (MESH.md 7b) */ +#define V4_MSG_GONE 5 /* a node is gone: one word, its number */ /* how text ended */ #define V4_TEXT_QUIT 0 /* QUIT: nothing is to be said */ diff --git a/v4/tests/host_map.h b/v4/tests/host_map.h index 664a2fe6..ca5b3e84 100644 --- a/v4/tests/host_map.h +++ b/v4/tests/host_map.h @@ -64,6 +64,9 @@ #define BLK (BVARS + 7) #define SRC (BVARS + 8) #define SRC_HOOK (BVARS + 9) +#define REFUSED (BUF0_W - 7) /* how many of this node's messages it has been told were refused */ +#define OWED_COUNT (BUF0_W - 8) /* how many refusals this node owes and has not sent yet, at most 8 */ +#define OWED (BUF0_W - 112) /* those: for each, the node it is owed to and the node the refused message was for */ #define ACL_HOOK (BUF0_W - 6) /* the xt of the access control recheck word, or 0 */ #define LINE_STATUS (BUF0_W - 9) /* how the last line ended: 0 QUIT, 1 completed, 2 an error */ #define PORT (BUF0_W - 32) /* the node's ports (node.h): V4_PORTS of them, then "any port" and "which port". Port 0 is where its requests go. */ @@ -192,6 +195,10 @@ static int host_load(v4_text *tx, v4_node *n, const char *const *files, unsigned v4_text_constant(tx, "(AWAIT-FROM)", AWAIT_FROM); v4_text_constant(tx, "(MQ)", MQ_W); v4_text_constant(tx, "(MQ-END)", MQ_W + MQ_CELLS); + v4_text_constant(tx, "-MQ-ORDINARY", -(v4_cell)(MQ_CELLS - 7 - 36)); /* the same for a message that is not a refusal or a GONE: 36 cells are kept for those */ + v4_text_constant(tx, "(REFUSED)", REFUSED); + v4_text_constant(tx, "(OWED#)", OWED_COUNT); + v4_text_constant(tx, "(OWED)", OWED); v4_text_constant(tx, "-MQ-ROOM", -(v4_cell)(MQ_CELLS - 7)); /* cells taken + a text's words + this is below zero while the message fits */ v4_text_constant(tx, "(MQ-HEAD)", MQ_HEAD); v4_text_constant(tx, "(MQ-TAIL)", MQ_TAIL); diff --git a/v4/tests/test_host_mesh.c b/v4/tests/test_host_mesh.c index 5d608ef2..810d0aef 100644 --- a/v4/tests/test_host_mesh.c +++ b/v4/tests/test_host_mesh.c @@ -38,6 +38,8 @@ 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) { @@ -59,6 +61,10 @@ static int console_take(void *self, v4_cell value) 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; } @@ -177,8 +183,11 @@ int main(void) "SEND to a node there is no way to is an error"); /* ---- no way ---- */ - CHECK(is_empty(tell(99, "1 2 + .")) && ended == 0, "text for a node there is no way to: nothing comes back"); - CHECK(strcmp(tell(10, "(LOST) @ ."), "1 ") == 0, "and the node that could not pass it on has counted it"); + 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 ---- */ @@ -258,8 +267,58 @@ int main(void) 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"); + + /* a GONE that does not come from its centre is not acted on */ + CHECK(is_empty(tell(12, "12 11 GONE")) && pool[1].n.mem[AWAIT_FROM] == 12, "node 12 tells it that 12 is gone: that is not its centre speaking, and it goes on 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)); + /* two more than node 10's: node 12's word of how "12 11 GONE" ended came this way for the console while + * node 11 was full, and was not taken; nor could the refusal for it be owed */ + CHECK(pool[1].n.mem[LOST] == lost + (not_taken + 1) + (not_taken + 1 - 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 + 1)); + /* 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"); + } + 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"); + printf(" %d checks, %d failures\n", checks, failures); return failures != 0; }