diff options
Diffstat (limited to 'test/fs.py')
| -rw-r--r-- | test/fs.py | 99 |
1 files changed, 89 insertions, 10 deletions
@@ -121,6 +121,10 @@ def look(client, path, source=None): client.write(f'/pane/{serial}/look', f'{path}\n'.encode()) results = [int(row) for row in client.read('/look').split()] assert len(results) == 1 and results[0] != serial, (path, results) + # The scratch it looked from goes: a column holds only so many panes, + # each its tag and two rows. + if source is None: + client.remove(f'/pane/{serial}') return results[0] @@ -129,6 +133,16 @@ def execute(client, serial, command): client.write(f'/pane/{serial}/exec', f'{command}\n'.encode()) +def execute_refused(client, serial, command, words): + """A middle click on a builtin that fails: the write fails, saying why.""" + try: + execute(client, serial, command) + except OSError as why: + assert words in str(why), (command, why) + return + raise AssertionError(f'{command!r} was taken') + + def walk_tree(client, path='/', skip=('/log', '/os', '/screen')): """Stat every entry below `path`, as `find` does, without opening anything.""" seen = [] @@ -148,7 +162,8 @@ def discovery(binary, embedded=False): top = client.list('/') # A set: which files the root holds, not the order a listing gives. assert set(top) - {'src', 'shaders'} == {'README', 'index', 'status', 'focus', 'ctl', 'commands', - 'look', 'exec', 'log', 'screen', 'listeners', 'pane', 'os'}, top + 'look', 'exec', 'log', 'screen', 'listeners', 'layout', 'tag', 'tagexec', + 'pane', 'col', 'os'}, top assert 'self' not in top and 'cons' not in top and 'new' not in top, top assert ('src' in top) == embedded, (top, embedded) before = client.read('/index') @@ -180,7 +195,7 @@ def discovery(binary, embedded=False): assert client.read('/pane/1/dirty') == b'0\n' assert client.read('/pane/1/scroll') == b'1\n' fixture = int(before.split()[0]) - assert before.split()[1:3] == [b'text', b'0'] and before.endswith(b'/discovery.txt\n'), before + assert before.split()[1:3] == [b'text', b'0'] and before.rsplit(b' ', 1)[0].endswith(b'/discovery.txt'), before # /status reports the editor; /exec has nothing to report yet. status = dict(line.split(maxsplit=1) for line in client.read('/status').decode().splitlines()) assert int(status['pid']) > 0 and status['version'] and int(status['panes']) >= 1, status @@ -265,9 +280,13 @@ def discovery(binary, embedded=False): except OSError as refused: assert why in str(refused), refused commands = client.read('/commands').decode().splitlines() - assert 'Newcol root' in commands and 'Save arg pane' in commands, commands - assert all(row.split()[-1] in ('root', 'pane') for row in commands), commands - assert client.stat('/commands')['length'] == len('\n'.join(commands)) + 1 + def row(start): + return any(r == start or r.startswith(start + ' ') for r in commands) + assert row('Newcol root') and row('Save arg pane') and row('Edit arg both'), commands + # word, `arg` if it takes one, its ctl, a setting's values, then `-- ` what it does + assert all(r.split()[2 if r.split()[1] == 'arg' else 1] in ('root', 'pane', 'both') for r in commands), commands + assert row('Placement arg root acme,pardes'), commands + assert client.stat('/commands')['length'] == len('\n'.join(commands).encode()) + 1 # bytes: a description may end in … client.write(f'/pane/{first}/ctl', b'Msg said through ctl\n') assert 'said through ctl' in ''.join(cell[0] for cell in client.screen()['cells']) assert set(client.list('/pane')) == {'new', str(fixture), str(first), str(second)} @@ -416,12 +435,20 @@ def run_file(binary): assert run(client, term, b'fi\n') == b'error not run\n' # a syntax error runs nothing assert run(client, term, b"echo 'open\n") == b'error not run\n' # nor does an open quote assert run(client, term, b'true\n') == b'exit 0\n' # ...and the shell is clear after + # A pty asked for one row gets two: one loses the prompt's input + # mark, and the shell would read busy for ever. + client.write(f'/pane/{term}/pty/ctl', b'winsize 20 1\n') + time.sleep(.5) + assert run(client, term, b'true\n') == b'exit 0\n' + assert client.read(f'/pane/{term}/pty/ctl').split()[2] == b'2', client.read(f'/pane/{term}/pty/ctl') + client.write(f'/pane/{term}/pty/ctl', b'winsize 80 24\n') with Client(address) as other: slow = [] waiter = threading.Thread(target=lambda: slow.append(run(other, term, b'sleep 1\n')), daemon=True) waiter.start() time.sleep(.3) - assert run(client, term, b'true\n') == b'busy\n' + # The job the other client ran holds the tty, and says so. + assert run(client, term, b'true\n') == b'busy: sleep is running\n' waiter.join(5) assert slow == [b'exit 0\n'], slow closing = [] @@ -508,6 +535,26 @@ def python_repl_runs_one_line(binary): record = b'MX0 0 0 10 print(6*7)\n' client.rpc(118, struct.pack('<IQI', fid, 0, len(record)) + record) assert wait_for('\n42'), body() + # After the session has sat idle, the Enter still waits for + # the paste: core time is as old as the last frame then. + for n in range(5): + time.sleep(2) + line = f'print({1000 + n})'.encode() + record = b'MX0 0 0 %d %s\n' % (len(line), line) + client.rpc(118, struct.pack('<IQI', fid, 0, len(record)) + record) + assert wait_for(f'\n{1000 + n}'), (n, body()) + # More than one line, with and without a newline at the end, + # and a block with a statement after it: each runs whole, + # and the REPL is back at its prompt, not at `...`. + for code, outputs in [(b'print(11)\nprint(22)', ['11', '22']), + (b'print(33)\nprint(44)\n', ['33', '44']), + (b'for i in range(2):\n print(55 + i)\nprint(77)\n', ['55', '56', '77'])]: + record = b'MX0 0 0 %d %s\n' % (len(code), code) + client.rpc(118, struct.pack('<IQI', fid, 0, len(record)) + record) + for out in outputs: + assert wait_for(f'\n{out}\n'), (code, out, body()) + time.sleep(.5) + assert body().rstrip().endswith('>>>'), (code, body()[-200:]) finally: client.close(fid) @@ -528,8 +575,38 @@ def signal_cleans_up(binary): assert not address.exists() and not detached.exists(), (sig, list(root.iterdir())) +def cut_writes(binary): + """A write bigger than a 9P message comes as several, cut anywhere: each + command line runs once, whole, an Edit block once it ends, and a last + line with no newline when the open closes. Never a fragment.""" + with tempfile.TemporaryDirectory(prefix='pardes-cut-') as directory: + with session(binary, Path(directory), 'cut') as (client, _): + burst = b''.join(b'Msg burst-%04d\n' % i for i in range(1000)) + assert len(burst) > client.msize + client.write('/exec', burst) + log = client.read('/log').decode() + said = [line.split(' ', 2)[2] for line in log.splitlines() if line.startswith('msg ') and 'burst-' in line] + assert said == ['burst-%04d' % i for i in range(1000)], said[:3] + said[-3:] + assert '\nrun ' not in log and 'exit 127' not in log, log[-400:] + client.write('/exec', b'Msg no-newline-at-end') + assert 'msg ' in client.read('/log').decode().split('no-newline-at-end')[0][-40:] + serial = new_pane(client, b'') + block = b'Edit ,a\n' + b''.join(b'line %05d of the block\n' % i for i in range(2500)) + b'.\n' + assert len(block) > 50000 + client.write(f'/pane/{serial}/ctl', block) + body = client.read(f'/pane/{serial}/body').decode().splitlines() + assert body == ['line %05d of the block' % i for i in range(2500)], (len(body), body[:2]) + # A detached session with no frontend is 160x50 until told. + screen = client.screen() + assert (screen['cols'], screen['rows']) == (160, 50), (screen['cols'], screen['rows']) + client.write('/ctl', b'size 132 40\n') + screen = client.screen() + assert (screen['cols'], screen['rows']) == (132, 40), (screen['cols'], screen['rows']) + + def test(binary, quic=False): started = time.monotonic() + cut_writes(binary) run_file(binary) exit_answers(binary) unattended_messages_expire(binary) @@ -665,8 +742,9 @@ def test(binary, quic=False): execute(remote, control, 'Mount peer ' + str(address)) opened = look(remote, '/n/peer/pane/1/body', source=control) assert remote.read(f'/pane/{opened}/body') == client.read('/pane/1/body') - execute(remote, control, 'Mount peer ' + str(own_address)) - execute(remote, control, 'Unmount peer') + # A builtin that fails through exec fails the write (one rule). + execute_refused(remote, control, 'Mount peer ' + str(own_address), 'already mounted') + execute_refused(remote, control, 'Unmount peer', 'mount in use') # a pane is open on it remote.write(f'/pane/{opened}/body', b'runtime mounted Save\n', truncate=True) remote.write(f'/pane/{opened}/exec', b'Save\n') assert client.read('/pane/1/body') == b'runtime mounted Save\n' @@ -693,7 +771,7 @@ def test(binary, quic=False): with session(binary, root, name, tty=tty) as (old, address): old.write('/pane/1/body', b'dumped state\n', truncate=True) control = new_pane(old, b'') - execute(old, control, 'Restore ' + str(root / 'missing dump.zon')) + execute_refused(old, control, 'Restore ' + str(root / 'missing dump.zon'), 'no such file') assert old.read('/pane/1/body') == b'dumped state\n' execute(old, control, 'Mount own ' + str(address)) execute(old, control, 'Dump') @@ -735,7 +813,7 @@ def test(binary, quic=False): contents = restored.read(f'/pane/{file_id}/body') assert contents == b'dumped state\n', (contents, saved.read_text(), restored.read('/index')) assert restored.read(f'/pane/{control_id}/body') == b'', 'commands ran through exec, not the body' - assert restored.screen()['cols'] == 80 + assert restored.screen()['cols'] == (80 if tty else 160) # a detached one is 160x50 opened = look(restored, '/n/own/os' + str(root / (name + '.txt')), source=control_id) assert restored.read(f'/pane/{opened}/body') == b'initial\n' @@ -909,6 +987,7 @@ def test(binary, quic=False): assert path.read_bytes() == b'written through long filename\n' opened = look(client, '/n/os' + str(path)) assert client.read(f'/pane/{opened}/body') == path.read_bytes() + client.remove(f'/pane/{opened}') # a long name's tag takes rows opened = look(client, '/n/os' + str(directory)) listing = client.read(f'/pane/{opened}/body') for path in directory.iterdir(): |
