fix: harden Claro diagnostics and runtime guards
Preserve source locations, validate malformed JSON, and add opt-in resource limits. Include executable error-guide coverage, learner pilot materials, native Windows acceptance helpers, and updated CI/documentation.
This commit is contained in:
@@ -0,0 +1,48 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Verify real runtime diagnostic origins, including functions/imports/TRY."""
|
||||
from pathlib import Path
|
||||
import os
|
||||
import shlex
|
||||
import subprocess
|
||||
import tempfile
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
|
||||
|
||||
def main():
|
||||
with tempfile.TemporaryDirectory(prefix='claro-error-locations-', dir=os.environ.get('TMPDIR')) as temp:
|
||||
work = Path(temp)
|
||||
override = os.environ.get('CLARO_EXE')
|
||||
exe = Path(override).resolve() if override else work / 'claro'
|
||||
if not override:
|
||||
compiler = shlex.split(os.environ.get('CLARO_CC', 'gcc'))
|
||||
subprocess.run(compiler + ['-std=c89', '-pedantic-errors', str(ROOT / 'src/claro.c'), '-o', str(exe), '-lm'], check=True)
|
||||
cases = [
|
||||
('quote.claro', 'SAY "Start"\nSAY "unfinished\n', 2, 'Text is missing a closing quote.'),
|
||||
('operand.claro', 'SET score 1\nSAY score +\n', 2, 'needs a value after the operator'),
|
||||
('parenthesis.claro', 'SAY "Start"\nSAY (1 + 2\n', 2, 'needs a closing parenthesis'),
|
||||
('function.claro', 'TEACH broken\n SAY 1 +\nEND\nDO broken\n', 2, 'needs a value after the operator'),
|
||||
('repeat.claro', 'REPEAT\n SAY "Once"\nUNTIL (\n', 3, 'needs a value after the operator'),
|
||||
]
|
||||
for name, text, line, message in cases:
|
||||
(work / name).write_text(text)
|
||||
result = subprocess.run([str(exe), name], cwd=work, capture_output=True, text=True, timeout=10)
|
||||
output = result.stdout + result.stderr
|
||||
assert not any(t in output for t in ["AddressSanitizer", "LeakSanitizer", "runtime error:"]), output
|
||||
assert result.returncode == 1, (name, result.returncode, output)
|
||||
assert f'{name}:{line}:' in output and ':0:' not in output and message in output, (name, output)
|
||||
print('PASS origin:', name, line)
|
||||
(work / 'broken-module.claro').write_text('TEACH broken\n SAY 1 +\nEND\n')
|
||||
(work / 'caller.claro').write_text('IMPORT "broken-module.claro" AS problem\nDO problem.broken\n')
|
||||
result = subprocess.run([str(exe), 'caller.claro'], cwd=work, capture_output=True, text=True, timeout=10)
|
||||
assert result.returncode == 1 and 'broken-module.claro:2:' in result.stderr, result.stderr
|
||||
print('PASS origin: imported function')
|
||||
(work / 'caught.claro').write_text('TRY\n DO problem.broken\nCATCH\n SAY LASTERRORFILE\n SAY LASTERRORLINE\nENDTRY\n')
|
||||
(work / 'caller.claro').write_text('IMPORT "broken-module.claro" AS problem\nTRY\n DO problem.broken\nCATCH\n SAY LASTERRORFILE\n SAY LASTERRORLINE\nENDTRY\n')
|
||||
result = subprocess.run([str(exe), 'caller.claro'], cwd=work, capture_output=True, text=True, timeout=10)
|
||||
assert result.returncode == 0 and result.stdout.replace('\\', '/').endswith('broken-module.claro\n2\n'), (result.stdout, result.stderr)
|
||||
print('PASS origin: caught error metadata')
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
Reference in New Issue
Block a user