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
91 lines
4.4 KiB
Python
Executable File
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)
|