Files
LithosAnanake/v4/tools/depthsweep.py
T
rajamesandClaude Opus 5.5 b1d09af043 feat(v4.0.0): messages are whole and wait on the wire -- a node keeps none but the one it is doing
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>
2026-10-08 15:59:37 -04:00

89 lines
4.3 KiB
Python
Executable File

#!/usr/bin/env python3
"""depthsweep.py -- the hosted product at every depth of both stacks.
depthsweep.py <starforth4 binary> <data depth> <return depth>
Every path by which the node begins, writes or reads a message is run with
the data stack holding 0 .. depth+2 values, and from 1 .. depth+2 calls
deep. At each, every line typed must be answered -- " ok" or " ERROR" --
and the node must come back: it may refuse with an error, but it may not
hang, and it may not swallow a line. Where the stack is shallow the
answers themselves are checked.
Why: docs/v4.0.0/MESH.md 7c.6. A fault that came after a message's first
word had moved left it half done; and a fault inside a flush of the output
buffer let EMIT run past the buffer's end. Nothing ran these paths on a
deep stack.
"""
import subprocess, sys
from concurrent.futures import ThreadPoolExecutor
binary, ddepth, rdepth = sys.argv[1], int(sys.argv[2]), int(sys.argv[3])
cases = []
def run(lines):
text = '\n'.join(lines) + '\n'
try:
r = subprocess.run([binary], input=text.encode(), capture_output=True, timeout=30)
return r.stdout.decode('latin1'), False
except subprocess.TimeoutExpired as e:
return (e.stdout or b'').decode('latin1'), True
def answers(out):
# what follows POST: one answer to a line typed
body = out.split('POST: PASSED', 1)[-1]
return body.count(' ok\n') + body.count(' ERROR\n')
def check(name, k, lines, expect=None):
cases.append((name, k, lines, expect))
def judge(case):
name, k, lines, expect = case
out, hung = run(lines)
why = None
if 'POST: PASSED' not in out: why = 'POST did not pass'
elif hung: why = 'it hung'
elif answers(out) != len(lines): why = '%d lines typed, %d answered' % (len(lines), answers(out))
elif expect is not None and expect not in out: why = 'no "%s"' % expect.strip()
if why is None: return None
tail = out.split('POST: PASSED', 1)[-1][-160:].replace('\n', '|')
return 'depthsweep: FAIL %s at %d: %s: ...%s' % (name, k, why, tail)
def vals(k): return ' '.join(['1'] * k)
# The stack shallow, and then every depth from well below where anything
# changes to past its end. An error empties the stack, and a wait that a
# typed line breaks ends in an error: so the line that broke it is always
# done, whatever the depth.
shallow = ddepth - 12
for k in [0, 1, shallow] + list(range(ddepth - 10, ddepth + 3)):
v = [vals(k)] if k else []
sh = k <= shallow
check('a number printed', k, v + ['7 .', '77 .'], '77 ok' if sh else None)
check('WORDS', k, v + ['WORDS', '77 .'], 'DUP' if sh else None)
check('a line typed during AWAIT', k, [': AW 5 AWAIT ;'] + v + ['AW', '77 .', '88 .'], '77 ok')
check('SEND with no way', k, [': SD S" 1 DROP" 5 SEND ;'] + v + ['SD', '77 .'], 'Argument out of range' if sh else None)
check('a write to an empty port', k, v + ['5 7 PORT!', '77 .'], 'No one on that port' if sh else None)
check('an unknown word', k, v + ['NOSUCHWORD', '77 .'], 'UNKNOWN WORD' if sh else None)
check('a loop that prints 400 numbers', k, [': PR 400 0 DO I . LOOP ;'] + v + ['PR', '77 .'], ' 398 399 ok' if sh else None)
check('SEND by a way with no one on the port', k, ['5 7 ROUTE', ': SP S" 1 DROP" 5 SEND ;'] + v + ['SP', '77 .'], 'No one on that port' if sh else None)
def nest(n, inner):
return [': N1 %s ;' % inner] + [': N%d N%d ;' % (i, i - 1) for i in range(2, n + 1)] + ['N%d' % n]
rshallow = rdepth - 14
for n in [1, rshallow] + list(range(rdepth - 12, rdepth + 3)):
sh = n <= rshallow
check('a number printed, calls deep', n, nest(n, '7 .') + ['77 .'], '77 ok')
check('WORDS, calls deep', n, nest(n, 'WORDS') + ['77 .'], '77 ok')
check('a line typed during AWAIT, calls deep', n, nest(n, '5 AWAIT') + ['77 .', '88 .'], '77 ok')
check('SEND with no way, calls deep', n, nest(n, 'S" 1 DROP" 5 SEND') + ['77 .'], '77 ok')
check('a loop that prints 400 numbers, calls deep', n, nest(n, '400 0 DO I . LOOP') + ['77 .'], '77 ok')
check('SEND by a way with no one on the port, calls deep', n, ['5 7 ROUTE'] + nest(n, 'S" 1 DROP" 5 SEND') + ['77 .'], '77 ok')
with ThreadPoolExecutor(max_workers=8) as pool:
found = [f for f in pool.map(judge, cases) if f]
for f in found: print(f)
print('depthsweep: %d runs, %d failures' % (len(cases), len(found)))
sys.exit(1 if found else 0)