Files
LithosAnanke/v4/tools/depthsweep.py
T
Claude 3bd81097e1 chore(license): an SPDX header on every source file that had none (LicenseRef-Starship-1.0) [skip ci]
The header scan (Captain Bob, 2026-11-10): 788 source files -- 351 with the
SL-1.0 header, 12 with the old name (docs/working/experiments, kept by
ruling), 31 under someone else's licence (tools/kconfig, Linux's, GPL-2.0:
kept), and 394 with none. Ruled: the SPDX form; capsules (.4th, .f18) and
proofs (.thy) left for a ruling of their own (mkcapsule's 64x16 block format,
content-addressed capsules).

253 files now begin
    SPDX-License-Identifier: LicenseRef-Starship-1.0
    Copyright (c) 2023-2026 Robert A. James
as a /* */ block (C, headers, assembly, linker scripts) or # lines (scripts,
Makefiles, Kconfig; below a shebang or a coding line); tools/mkcapsule.c,
which had the copyright line, gets the SPDX line only.

Not touched: docs/working/ (archive and old campaign material, as the
ruling kept the experiments); v4/system/post_cases.c (generated by
v4/tools/mkpost.py, "do not edit" -- the generator has the header).

Comments only: the kernel builds on amd64, aarch64, riscv64; make -C v4
test-32: 39 programs, 0 failures; every changed script parses.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GE3SDvQn5EoxdGufk84zz2
2026-11-10 04:12:49 +00:00

91 lines
4.4 KiB
Python
Executable File

#!/usr/bin/env python3
# SPDX-License-Identifier: LicenseRef-Starship-1.0
# Copyright (c) 2023-2026 Robert A. James
"""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)