132 lines
6.1 KiB
Python
132 lines
6.1 KiB
Python
from pathlib import Path
|
|
import os
|
|
import resource
|
|
import signal
|
|
import subprocess
|
|
import tempfile
|
|
|
|
|
|
ROOT = Path(__file__).resolve().parents[1]
|
|
|
|
|
|
def invoke(*args, ok=True, data=None, preexec_fn=None):
|
|
result = subprocess.run([str(ROOT / '.build/remy'), *map(str, args)], input=data,
|
|
capture_output=True, text=True, timeout=30, preexec_fn=preexec_fn)
|
|
assert (result.returncode == 0) == ok, (args, result.stdout, result.stderr)
|
|
return result.stdout if ok else result.stderr
|
|
|
|
|
|
def main():
|
|
with tempfile.TemporaryDirectory() as directory:
|
|
source, binary = Path(directory) / 'source', Path(directory) / 'binary'
|
|
for text, expected in [('0x0',0),('0xff',255),('0X7F',127),('0xffffffffffffffff',(1 << 64)-1),
|
|
('(+ 0xffffffffffffffff 1)',0),('(< 0x8000000000000000 0xffffffffffffffff)',1)]:
|
|
source.write_text(text)
|
|
assert int(invoke('eval',source,'0','0','0')) == expected, text
|
|
source.write_text('(+ x y)')
|
|
assert int(invoke('eval',source,'0xFF','0x1','0x0')) == 256
|
|
invoke(source,binary)
|
|
assert int(invoke('simulate',binary,'0xFF','0x1','0x0')) == 256
|
|
invoke('simulate',binary,'invalid','0','0',ok=False)
|
|
source.write_text('(let x1 7 (let x1 (+ x1 2) (* x1 x1)))')
|
|
assert int(invoke('eval',source,'0','0','0')) == 81
|
|
source.write_text('(let 1x 3 1x)')
|
|
invoke(source,binary,ok=False)
|
|
for text in ['(+ x; ignore ) tokens\ny) ; end', '(+ x; comment\r\ny)', '(+ x y);end']:
|
|
source.write_text(text)
|
|
assert int(invoke('eval',source,'3','4','0')) == 7, text
|
|
for text, position in [('(+ x)',3),('(div x y)',1),('(let a 3 b)',4),('(+ x y',4),('(+ x y) 1',5)]:
|
|
source.write_text(text)
|
|
assert f'token {position}\n' in invoke(source,binary,ok=False), text
|
|
source.write_text('(+ 1 ' * 129 + '0' + ')' * 129)
|
|
assert 'parse depth limit' in invoke(source,binary,ok=False)
|
|
large = '0'
|
|
for _ in range(12):
|
|
large = f'(+ {large} {large})'
|
|
source.write_text(large)
|
|
assert '4096 nodes' in invoke(source,binary,ok=False)
|
|
source.write_text('(+ x y)')
|
|
invoke('limits',4,3,2,source,binary)
|
|
for depth, nodes, stack, message in [(4,2,2,'2 nodes'),(4,3,1,'1 words'),
|
|
(0,3,2,'parse depth'),(129,3,2,'compiler bounds'),('bad',3,2,'invalid resource')]:
|
|
binary.write_bytes(b'preserve')
|
|
assert message in invoke('limits',depth,nodes,stack,source,binary,ok=False)
|
|
assert binary.read_bytes() == b'preserve'
|
|
source.write_text('(+ x y)')
|
|
invoke(source,binary)
|
|
listing = invoke('disassemble',binary)
|
|
assert 'mov rax r9' in listing and 'arithmetic add' in listing and 'ret' in listing
|
|
assert 'push' not in listing and 'pop' not in listing
|
|
source.write_text('(- x (- y (- t x)))')
|
|
invoke(source,binary)
|
|
listing = invoke('disassemble',binary)
|
|
assert 'push rax' in listing and 'pop rcx' in listing
|
|
source.write_text('(if x (+ y 7) (- t 9))')
|
|
invoke(source,binary)
|
|
listing = invoke('disassemble',binary)
|
|
assert 'jz ' in listing and 'jmp ' in listing and 'conditional ' not in listing
|
|
invoke('check',binary)
|
|
original = binary.read_bytes()
|
|
branch = original.index(bytes.fromhex('48 85 c0 0f 84'))
|
|
for offset in (0,4,0x7fffffff,0xffffffff):
|
|
damaged = bytearray(original)
|
|
damaged[branch+5:branch+9] = offset.to_bytes(4,'little')
|
|
binary.write_bytes(damaged)
|
|
invoke('check',binary,ok=False)
|
|
for cut in range(branch+1,branch+9):
|
|
binary.write_bytes(original[:cut]+bytes([195]))
|
|
invoke('check',binary,ok=False)
|
|
for malformed in [b'',bytes([195,0]),bytes([72,199,192,0]),bytes([80])]:
|
|
binary.write_bytes(malformed)
|
|
assert 'invalid native' in invoke('disassemble',binary,ok=False)
|
|
binary.write_bytes(bytes([72,184,42,0,0,0,0,0,0,0,195]))
|
|
assert '10 ret' in invoke('disassemble',binary)
|
|
source.write_text('(+ x y)')
|
|
invoke(source,binary)
|
|
assert 'verified' in invoke('check',binary)
|
|
for code in [[88,195],[80,195],[65,88,195],[73,137,192,195],[72,139,68,36,0,195]]:
|
|
binary.write_bytes(bytes(code))
|
|
invoke('check',binary,ok=False)
|
|
binary.write_bytes(bytes([72,199,192,42,0,0,0,195]))
|
|
invoke('check',binary)
|
|
source.write_text('(+ x y)')
|
|
binary.write_bytes(b'preserve')
|
|
binary.chmod(0o600)
|
|
invoke(source,binary,preexec_fn=lambda: os.umask(0))
|
|
assert binary.stat().st_mode & 0o777 == 0o600
|
|
def restricted():
|
|
signal.signal(signal.SIGXFSZ,signal.SIG_IGN)
|
|
resource.setrlimit(resource.RLIMIT_FSIZE,(1,1))
|
|
for args in [(source,binary),('vectors',source,binary)]:
|
|
binary.write_bytes(b'preserve')
|
|
invoke(*args,ok=False,preexec_fn=restricted)
|
|
assert binary.read_bytes() == b'preserve'
|
|
assert not list(binary.parent.glob('.binary.*'))
|
|
target = Path(directory) / 'folder'
|
|
target.mkdir()
|
|
invoke(source,target,ok=False)
|
|
assert not list(target.parent.glob('.folder.*'))
|
|
source.write_text('(+ x y)')
|
|
invoke(source,source)
|
|
assert source.read_bytes().endswith(bytes([195]))
|
|
invoke('-',binary,data='(+ x y)')
|
|
assert int(invoke('eval','-',3,4,0,data='(+ x y)')) == 7
|
|
invoke('vectors','-',binary,data='(+ x y)')
|
|
assert binary.stat().st_size == 16384
|
|
for data in ['(+ x)', ' ' * 65537]:
|
|
binary.write_bytes(b'preserve')
|
|
invoke('-',binary,data=data,ok=False)
|
|
assert binary.read_bytes() == b'preserve'
|
|
source.write_text('é')
|
|
assert 'ASCII' in invoke(source,binary,ok=False)
|
|
for text in ['0x','0x10000000000000000','0xfg','0xffextra','-0x1']:
|
|
source.write_text(text)
|
|
binary.write_bytes(b'preserve')
|
|
invoke(source,binary,ok=False)
|
|
assert binary.read_bytes() == b'preserve', text
|
|
print('CLI literal and rejection checks passed')
|
|
|
|
|
|
if __name__ == '__main__':
|
|
main()
|