MESH.md step 5, in the fabric under test; the products are still one node each until steps 8 and 9. manage.c: what Hera asks of whoever holds the fabric, eleven requests by KERNEL-WORD -- NODE-ME -BORN -WIRE -UNWIRE -SLEEP -WAKE -KILL -PARITY and CAPSULE-OPEN -CELL -LINE. Nucleus: PORT!, SEND-ON, AWAIT, (SEAL). capsules/v4/hera.4th: BIRTH and UNIT, the unit rule in FORTH and nowhere else. The dictionary hash moves to the engine (v4_image_dict_hash). test_host_unit.c, 34 checks, 64-bit: Hera is born empty, takes the nucleus through her port, FORTH-79, POST and her capsule; 10 UNIT; four nodes are born, each takes the nucleus and FORTH-79 through its port from Hera and passes POST 538/538; four parities, one dictionary hash; they talk, and a message between two corners not wired goes by Hera. All v4 tests at both widths and under ASan+UBSan. hosted-check on three ISAs. Bare metal: logs/20261006-143934 (amd64), -144204 (aarch64), -144601 (riscv64). Open, recorded in MESH.md: not run at 32 bits; a node that never answers leaves Hera waiting; the capsules are read from files in the test, not from the baked directory. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
387 lines
18 KiB
C
387 lines
18 KiB
C
/* test_host_unit.c -- five nodes come up from nothing.
|
|
* docs/v4.0.0/MESH.md section 9, step 5; acceptance 1 and 2.
|
|
*
|
|
* [11]---[12]
|
|
* | \ / | the four outer nodes are each on a
|
|
* console --1 [10] | port of Hera, 10, and joined in a
|
|
* kernel --0 | / \ | square; the corners across from each
|
|
* [13]---[14] other are not wired
|
|
*
|
|
* Hera is born empty and is given the nucleus through the port her requests
|
|
* go to, as the boot gives it (v4/system/boot.c). She is told who she is,
|
|
* given the words that ask for nodes (v4/include/v4/manage.h), FORTH-79,
|
|
* POST, and her own capsule, capsules/v4/hera.4th. Then she is told
|
|
* `10 UNIT`, and everything after that is hers: she asks for four nodes,
|
|
* wires them, sends each the nucleus cell by cell through the port that
|
|
* joins them and then FORTH-79 and POST a line at a time, and joins them.
|
|
*
|
|
* What stands in here for the host: the capsules are read from the files
|
|
* the build bakes in, and the nucleus is assembled as mkimage assembles it.
|
|
* Each node has a disk of its own, attached when it is born, as the boot's
|
|
* one node has; storage through the ports is step 6.
|
|
*/
|
|
#include "v4/capsule.h"
|
|
#include "v4/fabric.h"
|
|
#include "v4/image.h"
|
|
#include "v4/manage.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)
|
|
|
|
#define PLACES 10u
|
|
#define DISK_BLOCKS 64u
|
|
static v4_text tx;
|
|
static v4_node nucleus; /* the nucleus, assembled: what the capsule of F18 code is written from */
|
|
static v4_cell w_idle, w_fault;
|
|
static v4_fabric_node pool[PLACES];
|
|
static int pool_used[PLACES];
|
|
static unsigned char disks[PLACES][DISK_BLOCKS * V4_BLOCK_BYTES];
|
|
static v4_place places[PLACES];
|
|
static v4_fabric f;
|
|
static v4_image im; /* only what the dictionary hash needs of it */
|
|
|
|
/* ---- the capsules ---- */
|
|
static v4_cell f18[2u * V4_NODE_WORDS + 64u]; /* the nucleus as a capsule of F18 code */
|
|
static unsigned f18_count;
|
|
static void f18_put(void *self, v4_cell w) { (void)self; if (f18_count < sizeof f18 / sizeof f18[0]) f18[f18_count++] = w; }
|
|
|
|
typedef struct { char *text; long len, at; int in_block; unsigned cells_at; int is_f18; } reader;
|
|
|
|
static int read_file(reader *r, const char *name)
|
|
{
|
|
char path[600];
|
|
FILE *in;
|
|
snprintf(path, sizeof path, "%s/../../capsules/v4/%s", V4_CAPSULE_DIR, name);
|
|
in = fopen(path, "rb");
|
|
if (!in) return 0;
|
|
free(r->text);
|
|
fseek(in, 0, SEEK_END); r->len = ftell(in); fseek(in, 0, SEEK_SET);
|
|
r->text = (char *)malloc((size_t)r->len + 1);
|
|
if (!r->text || fread(r->text, 1, (size_t)r->len, in) != (size_t)r->len) { fclose(in); return 0; }
|
|
fclose(in);
|
|
r->at = 0; r->in_block = 0; r->is_f18 = 0;
|
|
return 1;
|
|
}
|
|
/* the next line of a block that has anything on it: its length, or -1 */
|
|
static int next_line(reader *r, char *text)
|
|
{
|
|
while (r->at < r->len) {
|
|
const char *p = r->text + r->at;
|
|
long n = 0;
|
|
while (r->at + n < r->len && p[n] != '\n') n++;
|
|
r->at += n + 1;
|
|
if (n >= 7 && memcmp(p, "Block ", 6) == 0 && p[6] >= '0' && p[6] <= '9') { r->in_block = 1; continue; }
|
|
if (n && p[n - 1] == '\r') n--;
|
|
if (!r->in_block || n == 0) continue;
|
|
if (n > V4_MANAGE_LINE) return -1;
|
|
memcpy(text, p, (size_t)n);
|
|
return (int)n;
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
/* ---- what Hera's requests are answered with ---- */
|
|
static reader hera_reads; /* the capsule Hera has open */
|
|
static unsigned born_count, parity_count;
|
|
static v4_cell parity_number[8];
|
|
static uint64_t parity_hash[8];
|
|
|
|
static int host_born(void *self)
|
|
{
|
|
unsigned k;
|
|
int place;
|
|
(void)self;
|
|
for (k = 0; k < PLACES && pool_used[k]; k++) { }
|
|
if (k == PLACES) return -1;
|
|
place = v4_fabric_add(&f, &pool[k], PORT);
|
|
if (place < 0) return -1;
|
|
pool_used[k] = 1;
|
|
born_count++;
|
|
/* what a node's hardware gives it; there is nothing in its memory */
|
|
v4_node_stack_regs_attach(&pool[k].n, DSTACK_REG, RSTACK_REG);
|
|
v4_node_error_attach(&pool[k].n, NODE_ERROR);
|
|
v4_node_fault_attach(&pool[k].n, w_fault);
|
|
memset(disks[k], 0, sizeof disks[k]);
|
|
v4_node_storage_attach(&pool[k].n, STORAGE_REG, disks[k], DISK_BLOCKS);
|
|
return place;
|
|
}
|
|
static void host_gone(void *self, unsigned place, v4_fabric_node *was)
|
|
{
|
|
(void)self; (void)place;
|
|
pool_used[was - pool] = 0;
|
|
}
|
|
static int host_open(void *self, const char *name, unsigned len)
|
|
{
|
|
(void)self;
|
|
if (len == 17 && memcmp(name, "v4:nucleus-64.f18", 17) == 0) { hera_reads.is_f18 = 1; hera_reads.cells_at = 0; return 1; }
|
|
if (len == 14 && memcmp(name, "v4:forth79.4th", 14) == 0) return read_file(&hera_reads, "forth79.4th");
|
|
if (len == 13 && memcmp(name, "v4:post79.4th", 13) == 0) return read_file(&hera_reads, "post79.4th");
|
|
return 0;
|
|
}
|
|
static int host_cell(void *self, v4_cell *w)
|
|
{
|
|
(void)self;
|
|
if (!hera_reads.is_f18 || hera_reads.cells_at >= f18_count) return 0;
|
|
*w = f18[hera_reads.cells_at++];
|
|
return 1;
|
|
}
|
|
static int host_line(void *self, char *text)
|
|
{
|
|
(void)self;
|
|
return hera_reads.is_f18 ? -1 : next_line(&hera_reads, text);
|
|
}
|
|
static void host_parity(void *self, unsigned place, v4_cell number, const v4_node *n)
|
|
{
|
|
(void)self; (void)place;
|
|
if (parity_count < 8) { parity_number[parity_count] = number; parity_hash[parity_count] = v4_image_dict_hash(n, &im); }
|
|
parity_count++;
|
|
}
|
|
static const v4_manage manage = { &f, host_born, host_gone, host_open, host_cell, host_line, host_parity, 0 };
|
|
|
|
/* ---- the kernel: a device on Hera's port 0 ---- */
|
|
#define FIRST_REQUEST 1 /* the number of NODE-ME on her */
|
|
static unsigned hera_place, given_cells, refused;
|
|
static int kernel_give(void *self, v4_cell *value) /* the nucleus, to a node that has nothing in it */
|
|
{
|
|
(void)self;
|
|
if (given_cells >= f18_count) return 0;
|
|
*value = f18[given_cells++];
|
|
return 1;
|
|
}
|
|
static int kernel_take(void *self, v4_cell value) /* a request */
|
|
{
|
|
(void)self;
|
|
if (value < FIRST_REQUEST || !v4_manage_serve(&manage, hera_place, (unsigned)(value - FIRST_REQUEST))) refused++;
|
|
return 1;
|
|
}
|
|
static const v4_device kernel = { kernel_take, kernel_give, 0 };
|
|
|
|
/* ---- the console: a device on Hera's port 1 ---- */
|
|
#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;
|
|
static int ended;
|
|
static v4_cell ended_from, ended_how;
|
|
static unsigned post_passed[32]; /* by node number: POST tallies with no failures seen from it */
|
|
static unsigned post_other; /* tallies with failures */
|
|
|
|
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), start = printed_len;
|
|
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);
|
|
if (strstr(printed + start, "PARITY:V4_POST")) {
|
|
if (strstr(printed + start, " fail=0\n") && printed_from >= 0 && printed_from < 32) post_passed[printed_from]++;
|
|
else post_other++;
|
|
}
|
|
} else if (v4_message_type(&coming) == V4_MSG_DONE) {
|
|
ended++;
|
|
ended_from = v4_message_from(&coming);
|
|
ended_how = coming.word[V4_MSG_HEADER];
|
|
}
|
|
coming.count = 0;
|
|
}
|
|
return 1;
|
|
}
|
|
static const v4_device console = { console_take, console_give, 0 };
|
|
|
|
/* Is there a node that will execute at the next step? A step in which a
|
|
* node only faulted counts nothing done (exec.h), and it goes on from its
|
|
* fault handler at the next. */
|
|
static int any_running(void)
|
|
{
|
|
unsigned k;
|
|
for (k = 0; k < PLACES; k++) {
|
|
const v4_fabric_node *x = v4_fabric_node_at(&f, k);
|
|
if (x && !places[k].asleep && !x->n.stopped && !x->n.asking && !(x->n.reading && !x->n.given)) return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/* Send text to a node and let everything that follows from it happen. */
|
|
static const char *tell(v4_cell node, const char *text)
|
|
{
|
|
unsigned long 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 < 4000000000ul && (v4_fabric_step(&f) != 0 || any_running())) steps++;
|
|
if (steps >= 4000000000ul) return "(still running)";
|
|
return printed;
|
|
}
|
|
static int told(v4_cell node, const char *text) { return tell(node, text)[0] == 0 && ended == 1 && ended_how == V4_TEXT_COMPLETED; }
|
|
|
|
/* Every line of a capsule of FORTH blocks, told to a node from the console:
|
|
* how many lines did not complete. */
|
|
static unsigned load(v4_cell node, const char *file)
|
|
{
|
|
static reader r;
|
|
char line[V4_MANAGE_LINE + 1];
|
|
unsigned bad = 0;
|
|
int n;
|
|
if (!read_file(&r, file)) return 1;
|
|
while ((n = next_line(&r, line)) >= 0) {
|
|
line[n] = 0;
|
|
(void)tell(node, line);
|
|
if (ended != 1 || ended_how != V4_TEXT_COMPLETED) { if (!bad) { const v4_node *q = &pool[0].n; printf(" %s: \"%s\" -> \"%s\" ended=%d how=%ld; stopped=%d asking=%d port=%u reading=%d rport=%u p=%ld fault=%u@%ld\n", file, line, printed, ended, (long)ended_how, q->stopped, q->asking, q->ask_port, q->reading, q->read_port, (long)q->p, q->fault_kind, (long)q->fault_addr); } bad++; }
|
|
}
|
|
return bad;
|
|
}
|
|
|
|
static v4_node *node_numbered(v4_cell number)
|
|
{
|
|
unsigned k;
|
|
for (k = 0; k < PLACES; k++) if (v4_fabric_node_at(&f, k) && v4_fabric_node_at(&f, k)->n.mem[ME] == number) return &v4_fabric_node_at(&f, k)->n;
|
|
return 0;
|
|
}
|
|
static v4_uheat_t clock_of(v4_cell number)
|
|
{
|
|
unsigned k;
|
|
for (k = 0; k < PLACES; k++) if (v4_fabric_node_at(&f, k) && v4_fabric_node_at(&f, k)->n.mem[ME] == number) return v4_fabric_node_at(&f, k)->es.anticlock;
|
|
return 0;
|
|
}
|
|
static int waiting(v4_cell number)
|
|
{
|
|
const v4_node *n = node_numbered(number);
|
|
return n && n->reading && !n->given && n->read_port == V4_PORT_ANY;
|
|
}
|
|
|
|
int main(void)
|
|
{
|
|
unsigned i;
|
|
int place;
|
|
|
|
printf("v4 unit tests: V4_CELL_BITS=%d, V4_NODE_WORDS=%u\n", V4_CELL_BITS, (unsigned)V4_NODE_WORDS);
|
|
if (V4_CELL_BITS != 64) {
|
|
/* The capsules the unit is built from are the 64-bit products': the
|
|
* expected results in post79.4th are v3's, on 64-bit cells, and the
|
|
* nucleus capsule is nucleus-64. POST has never been run on 32-bit
|
|
* cells (docs/v4.0.0/MESH.md, step 5). */
|
|
printf(" not run at this width: the capsules are the 64-bit products'\n");
|
|
return 0;
|
|
}
|
|
{
|
|
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);
|
|
(void)v4_capsule_write(&nucleus, w_idle, f18_put, 0);
|
|
CHECK(f18_count > 1000 && f18_count < sizeof f18 / sizeof f18[0], "the nucleus is a capsule of F18 code: %u cells", f18_count);
|
|
memset(&im, 0, sizeof im);
|
|
im.dp = DP; im.latest = LATEST;
|
|
|
|
/* ---- Hera is born empty, and takes in the nucleus ---- */
|
|
v4_fabric_init(&f, places, PLACES);
|
|
place = host_born(0);
|
|
CHECK(place == 0, "a node is born");
|
|
hera_place = (unsigned)place;
|
|
for (i = 0; i < 4096 && pool[0].n.mem[i] == 0; i++) { }
|
|
CHECK(i == 4096 && pool[0].n.p == PORT + (v4_cell)V4_PORT_ANY, "with nothing in it, listening at its ports");
|
|
CHECK(v4_fabric_wire_device(&f, hera_place, 0, &kernel) && v4_fabric_wire_device(&f, hera_place, 1, &console), "the kernel is on its port 0 and the console on its port 1");
|
|
going.count = 0; going_at = 0;
|
|
for (i = 0; i < 400000 && v4_fabric_step(&f) != 0; i++) { }
|
|
CHECK(given_cells == f18_count && pool[0].n.reading && pool[0].n.read_port == V4_PORT_ANY && !pool[0].n.stopped,
|
|
"it takes in the nucleus through its port and waits for a message: %u of %u cells", given_cells, f18_count);
|
|
|
|
CHECK(told(0, "10 (ME) ! 1 (CONSOLE) ! 1 1 ROUTE") && ended_from == 10, "it is told it is 10, and where the console is");
|
|
{
|
|
char text[600];
|
|
size_t at = 0;
|
|
for (i = 0; i < V4_MANAGE_WORDS; i++) at += (size_t)snprintf(text + at, sizeof text - at, "%u KERNEL-WORD %s ", FIRST_REQUEST + i, v4_manage_words[i]);
|
|
CHECK(told(10, text), "and given the words that ask for nodes and capsules: \"%s\"", printed);
|
|
}
|
|
CHECK(strcmp(tell(10, "NODE-ME ."), "0 ") == 0 && refused == 0, "it asks where it is, and is answered");
|
|
CHECK(load(10, "forth79.4th") == 0, "it takes in FORTH-79");
|
|
CHECK(load(10, "post79.4th") == 0 && post_passed[10] == 1 && post_other == 0, "and passes POST");
|
|
CHECK(load(10, "hera.4th") == 0, "and its own capsule, which makes it Hera");
|
|
CHECK(told(10, "(SEAL)"), "that is the system it comes back to");
|
|
|
|
/* ---- she births the unit ---- */
|
|
born_count = 0;
|
|
{
|
|
const char *r = tell(10, "10 UNIT");
|
|
CHECK(ended == 1 && ended_how == V4_TEXT_COMPLETED && ended_from == 10, "Hera births four nodes and joins them: \"%s\"", r);
|
|
}
|
|
CHECK(born_count == 4 && refused == 0, "four nodes were asked for: %u", born_count);
|
|
CHECK(post_passed[11] == 1 && post_passed[12] == 1 && post_passed[13] == 1 && post_passed[14] == 1 && post_other == 0,
|
|
"each took in the nucleus and FORTH-79 through its port from Hera, and passed POST: %u %u %u %u, %u failed",
|
|
post_passed[11], post_passed[12], post_passed[13], post_passed[14], post_other);
|
|
CHECK(parity_count == 4 && parity_number[0] == 11 && parity_number[1] == 12 && parity_number[2] == 13 && parity_number[3] == 14, "the parity of each was recorded: %u", parity_count);
|
|
CHECK(parity_hash[0] == parity_hash[1] && parity_hash[1] == parity_hash[2] && parity_hash[2] == parity_hash[3],
|
|
"and their dictionary hashes are the same: %016llx %016llx %016llx %016llx",
|
|
(unsigned long long)parity_hash[0], (unsigned long long)parity_hash[1], (unsigned long long)parity_hash[2], (unsigned long long)parity_hash[3]);
|
|
printf(" the four outer nodes: dict_hash=0x%016llx\n", (unsigned long long)parity_hash[0]);
|
|
CHECK(parity_hash[0] != v4_image_dict_hash(node_numbered(10), &im), "Hera's is not: she has her own capsule");
|
|
CHECK(waiting(10) && waiting(11) && waiting(12) && waiting(13) && waiting(14), "all five are waiting at their ports");
|
|
{
|
|
v4_uheat_t c[5];
|
|
for (i = 0; i < 5; i++) c[i] = clock_of(10 + (v4_cell)i);
|
|
for (i = 0; i < 200; i++) (void)v4_fabric_step(&f);
|
|
for (i = 0; i < 5 && clock_of(10 + (v4_cell)i) == c[i]; i++) { }
|
|
CHECK(i == 5, "and execute nothing while they wait");
|
|
}
|
|
|
|
/* ---- they talk ---- */
|
|
CHECK(strcmp(tell(10, "1 2 + ."), "3 ") == 0 && printed_from == 10, "a line typed at the console is done by Hera");
|
|
for (i = 11; i <= 14; i++) {
|
|
char text[32], want[16];
|
|
snprintf(text, sizeof text, "%u 100 * .", i);
|
|
snprintf(want, sizeof want, "%u ", i * 100);
|
|
CHECK(strcmp(tell((v4_cell)i, text), want) == 0 && printed_from == (v4_cell)i && ended_from == (v4_cell)i, "text for node %u is done there and what it prints comes back: \"%s\"", i, printed);
|
|
}
|
|
CHECK(strcmp(tell(13, "3 4 U* . ."), "0 12 ") == 0, "an outer node has FORTH-79: U* is a word of the capsule");
|
|
CHECK(strcmp(tell(13, ": SQ DUP * ; 7 SQ ."), "49 ") == 0 && strstr(tell(12, "7 SQ ."), "UNKNOWN WORD") != NULL, "each has its own dictionary");
|
|
CHECK(strstr(tell(13, "COLD"), "Cold Start") != NULL && strcmp(tell(13, "3 4 U* . ."), "0 12 ") == 0 && strstr(tell(13, "7 SQ ."), "UNKNOWN WORD") != NULL,
|
|
"COLD on it comes back to the nucleus and FORTH-79");
|
|
{
|
|
v4_uheat_t hera = clock_of(10);
|
|
CHECK(strcmp(tell(11, ": T S\" 65 EMIT\" 12 SEND ; T"), "A") == 0 && printed_from == 12, "an outer node sends text to the one beside it: \"%s\"", printed);
|
|
hera = clock_of(10);
|
|
CHECK(strcmp(tell(11, ": X S\" 66 EMIT\" 14 SEND ; X"), "B") == 0 && printed_from == 14, "and to the one across from it, which it is not wired to: \"%s\"", printed);
|
|
CHECK(clock_of(10) > hera, "that went by a node in between");
|
|
}
|
|
CHECK(strcmp(tell(10, "(LOST) @ ."), "0 ") == 0 && strcmp(tell(11, "(LOST) @ ."), "0 ") == 0 && strcmp(tell(14, "(LOST) @ ."), "0 ") == 0, "nothing was let go");
|
|
|
|
printf(" %d checks, %d failures\n", checks, failures);
|
|
return failures != 0;
|
|
}
|