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>
89 lines
4.3 KiB
Python
Executable File
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)
|