~james/remy

4
b58768f9b4b75a882c2394ff6c2135d32062939d
main
remy/tests/cli.py -rw-r--r--6.0 KiB ViewLogBlameView rawPermalink b58768f9b4 feat(branches): compile conditionals to validated forward jumps 13 days ago
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
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
        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()