diff options
| author | Gabriel Schneider <[email protected]> | 2026-09-06 18:11:36 -0300 |
|---|---|---|
| committer | Gabriel Schneider <[email protected]> | 2026-09-07 13:59:12 -0300 |
| commit | 60367d8fe23f6af98ec28e3cf6c2094dfe332df0 (patch) | |
| tree | 310fc734173cf771881f4691c71909135fadde97 /test/fs.py | |
| parent | fa82cac885cb4738fe36d1e49b4749b5a3e31a4a (diff) | |
| download | pardes-60367d8fe23f6af98ec28e3cf6c2094dfe332df0.tar.gz pardes-60367d8fe23f6af98ec28e3cf6c2094dfe332df0.zip | |
Refactor panes and filesystem; replace FUSE with 9P
Consolidate pane, layout, memory and host code. Serve 9P by default over Unix sockets, with runtime mounts and optional TCP/QUIC transports. Remove FUSE and obsolete proof-of-concept examples.
Fix highlighting and terminal-history performance, expand differential and stress-test infrastructure, sort navigation results while preserving the next occurrence, add syntax-colored Braille minimaps, remove SPC-k, and document 9P interaction as a repository skill.
Diffstat (limited to 'test/fs.py')
| -rw-r--r-- | test/fs.py | 549 |
1 files changed, 549 insertions, 0 deletions
diff --git a/test/fs.py b/test/fs.py new file mode 100644 index 00000000..9fc5bcc6 --- /dev/null +++ b/test/fs.py @@ -0,0 +1,549 @@ +#!/usr/bin/env python3 +import contextlib +import fcntl +import json +import os +from pathlib import Path +import subprocess +import struct +import sys +import tempfile +import termios +import threading +import time + +from ninep import Client + + +def session(binary, root, name, *options, socket_name=None, file=None, tty=False, network=None, + launch=None, inherited=None): + env = {key: value for key, value in os.environ.items() + if not key.startswith('PARDES_')} + env.update(HOME=str(root), XDG_RUNTIME_DIR=str(root), + XDG_CONFIG_HOME=str(root / 'config'), PARDES_NOTIME='1', + PARDES_DUMP=str(root / (name + '.dump.zon'))) + env.update(inherited or {}) + for language in ['RS', 'C', 'GO', 'TS', 'PY']: + env['PARDES_LSP_' + language] = '' + fixture = root / (name + '.txt') + fixture.write_bytes(b'initial\n') + address = root / ('pardes-9p-' + (socket_name or name) + '.sock') + child = None + master = slave = None + reader = None + try: + args = [binary, '--detach=' + name, *options, str(file or fixture)] + preexec = None + if tty: + master, slave = os.openpty() + fcntl.ioctl(slave, termios.TIOCSWINSZ, struct.pack('HHHH', 24, 80, 0, 0)) + env['TERM'] = 'xterm-256color' + args = [binary, '--tty', '--9p=' + (socket_name or name), *options, str(file or fixture)] + preexec = lambda: fcntl.ioctl(0, termios.TIOCSCTTY, 0) + if launch is not None: + args = [binary, *launch] + child = subprocess.Popen(args, cwd=root, env=env, stdin=slave, + stdout=slave if tty else subprocess.DEVNULL, + stderr=subprocess.PIPE, start_new_session=True, + preexec_fn=preexec) + if launch is not None and socket_name is None: + address = root / f'pardes-9p-{child.pid}.sock' + if slave is not None: + os.close(slave) + slave = None + def drain(): + try: + while os.read(master, 65536): + pass + except OSError: + pass + reader = threading.Thread(target=drain, daemon=True) + reader.start() + deadline = time.monotonic() + 10 + while not address.exists(): + if child.poll() is not None: + raise AssertionError((args, child.returncode, child.stderr.read().decode())) + if time.monotonic() > deadline: + raise TimeoutError('default 9P socket did not appear') + time.sleep(.005) + with Client(address) as client: + if network is not None: + for dial in client.read('/self/listeners').decode().splitlines(): + parts = dial.split('!') + if parts[0] in ['tcp', 'quic']: + assert len(parts) == 3 and 0 < int(parts[2]) < 65536, dial + network[parts[0].upper()] = int(parts[2]) + assert set(network) == {'TCP', 'QUIC'}, network + yield client, address + finally: + if child is not None: + if child.poll() is None: + child.terminate() + try: + child.wait(timeout=5) + except subprocess.TimeoutExpired: + child.kill() + child.wait() + child.stderr.close() + if slave is not None: + os.close(slave) + if master is not None: + os.close(master) + if reader is not None: + reader.join(timeout=1) + assert not reader.is_alive(), 'terminal reader did not stop' + + +def new_pane(client, contents): + serial = int(client.read('/self/new/ctl').split()[0]) + client.write(f'/self/pane/{serial}/body', contents) + return serial + + +def look(client, path, source=None): + serial = source if source is not None else new_pane(client, b'') + client.write(f'/self/pane/{serial}/body', path.encode(), truncate=True) + client.write(f'/self/pane/{serial}/event', f'ML0 {len(path.encode())}\n'.encode()) + rows = client.read('/self/index').splitlines() + opened = int(rows[-1].split()[0]) + assert opened != serial, f'Look did not open {path}' + return opened + + +def execute(client, serial, command): + text = command.encode() + client.write(f'/self/pane/{serial}/body', text, truncate=True) + client.write(f'/self/pane/{serial}/event', f'MX0 {len(text)}\n'.encode()) + + +def test(binary, quic=False): + started = time.monotonic() + for options, message in [ + (['--fs'], b'no such option'), + (['--fs9'], b'no such option'), + (['--9p'], b'--9p needs a socket name'), + (['--9p='], b'invalid 9P socket name'), + (['--9p=a/b'], b'invalid 9P socket name'), + (['--attach=work', '--9p=work'], b'--attach has none'), + (['--9p-tcp'], b'--9p-tcp needs'), + (['--9p-tcp='], b'--9p-tcp needs'), + (['--9p-tcp=tcp!localhost!5640'], b'--9p-tcp needs'), + (['--9p-tcp=quic!127.0.0.1!5640'], b'--9p-tcp needs'), + (['--9p-tcp=tcp!127.0.0.1!5640', '--9p-tcp=tcp!127.0.0.1!5641'], b'specified twice'), + (['--attach=work', '--9p-tcp=tcp!127.0.0.1!5640'], b'--attach has none'), + (['--9p-quic'], b'--9p-quic needs'), + (['--9p-quic='], b'--9p-quic needs'), + (['--9p-quic=tcp!127.0.0.1!5640'], b'--9p-quic needs'), + (['--mount=os=work'], b'invalid mount name'), + (['--mount=peer='], b'invalid mount name or dial'), + (['--mount=peer=one', '--mount=peer=two'], b'duplicate mount'), + (['--mount=peer=tcp!127.0.0.1!0'], b'invalid mount dial'), + (['--attach=work', '--mount=peer=one'], b'--attach has none'), + ]: + result = subprocess.run([binary, *options], capture_output=True, timeout=5) + assert result.returncode == 1 and message in result.stderr, (options, result) + with tempfile.TemporaryDirectory(prefix='pardes-fs-') as directory: + root = Path(directory) + with session(binary, root, 'renamed', '--9p=explicit', socket_name='explicit') as (named, named_address): + assert named_address.name == 'pardes-9p-explicit.sock' + assert not (root / 'pardes-9p-renamed.sock').exists() + assert named.read('/self/pane/1/body') == b'initial\n' + with session(binary, root, 'first') as (client, address): + assert set(client.list('/')) == {'os', 'self'} + assert {'pane', 'index', 'new'} <= set(client.list('/self')) + assert client.read('/self/pane/1/body') == b'initial\n' + assert client.read('/os' + str(root / 'first.txt')) == b'initial\n' + assert 'first.txt' in client.list('/os' + str(root)) + assert client.read('/self/pane/1/../1/body') == b'initial\n' + assert client.read('/os' + str(root) + '/../' + root.name + '/first.txt') == b'initial\n' + + listing = root / 'listing' + listing.mkdir() + (listing / 'broken').symlink_to(listing / 'missing') + for number in range(24): + (listing / f'entry-{number:02}').touch() + with Client(address, msize=256) as small: + assert set(small.list('/os' + str(listing))) == { + f'entry-{number:02}' for number in range(24)} + + frozen = client.open('/self/screen') + before = bytearray(client.read_fid(frozen, count=31)) + client.write('/self/pane/1/body', b'screen changed\n') + while chunk := client.read_fid(frozen, len(before)): + before.extend(chunk) + client.close(frozen) + old_screen = json.loads(before) + old_text = ''.join(cell[0] for cell in old_screen['cells']) + assert 'initial' in old_text and 'screen changed' not in old_text + assert 'screen changed' in ''.join(cell[0] for cell in client.screen()['cells']) + + payload = ('const value = "λ";\n' * 3000).encode() + serial = new_pane(client, payload) + body = f'/self/pane/{serial}/body' + assert client.read(body) == payload + client.write(body, b'replaced\n', truncate=True) + assert client.read(body) == b'replaced\n' + + event = client.open(f'/self/pane/{serial}/event') + client.write(body, b'event\n') + record = client.read_fid(event) + assert record.startswith(b'EI') and b'event\n' in record, record + client.close(event) + + os_path = '/os' + str(root / 'first.txt') + client.write(os_path, b'written through 9P\n', truncate=True) + assert (root / 'first.txt').read_bytes() == b'written through 9P\n' + assert client.read(os_path) == b'written through 9P\n' + + for index in range(12): + with Client(address) as other: + assert other.read(body) == b'replaced\nevent\n', index + + missing = False + try: + client.read('/self/pane/4294967295/body') + except OSError: + missing = True + assert missing + assert client.read(body) == b'replaced\nevent\n' + + source = look(client, '/virtual/src/pardes.zig') + assert b'pub const Pardes' in client.read(f'/self/pane/{source}/body') + + (root / 'index').write_bytes(b'OS takes precedence\n') + opened = look(client, 'index') + assert client.read(f'/self/pane/{opened}/body') == b'OS takes precedence\n' + opened = look(client, '/n/self/index') + index = client.read(f'/self/pane/{opened}/body') + assert index.split()[0] == b'1' and b'first.txt' in index, index + (root / 'explicit.txt').write_bytes(b'explicit OS mount\n') + opened = look(client, '/n/os' + str(root / 'explicit.txt')) + assert client.read(f'/self/pane/{opened}/body') == b'explicit OS mount\n' + + own = root / 'pardes-9p-second.sock' + with session(binary, root, 'second', '--mount=peer=' + str(address), + '--mount=own=' + str(own)) as (remote, _): + opened = look(remote, '/n/own/self/pane/1/body') + assert remote.read(f'/self/pane/{opened}/body') == b'initial\n' + opened = look(remote, '/n/peer/self/pane/' + str(serial) + '/body') + assert remote.read(f'/self/pane/{opened}/body') == b'replaced\nevent\n' + remote.write(f'/self/pane/{opened}/body', b'saved to peer\n', truncate=True) + remote.write(f'/self/pane/{opened}/ctl', b'put\n') + assert client.read(body) == b'saved to peer\n' + + large = b'mounted file contents\n' * 60000 + large_path = root / 'large.txt' + large_path.write_bytes(large) + opened = look(remote, '/n/peer/os' + str(large_path)) + assert remote.read(f'/self/pane/{opened}/body') == large + edited = large[:-3] + b'edited\n' + remote.write(f'/self/pane/{opened}/body', edited, truncate=True) + remote.write(f'/self/pane/{opened}/ctl', b'put\n') + assert large_path.read_bytes() == edited + assert client.read('/os' + str(large_path)) == edited + + with session(binary, root, 'runtime') as (remote, own_address): + control = new_pane(remote, b'') + execute(remote, control, 'Mount peer ' + str(address)) + opened = look(remote, '/n/peer/self/pane/1/body', source=control) + assert remote.read(f'/self/pane/{opened}/body') == client.read('/self/pane/1/body') + execute(remote, control, 'Mount peer ' + str(own_address)) + execute(remote, control, 'Unmount peer') + remote.write(f'/self/pane/{opened}/body', b'runtime mounted Save\n', truncate=True) + remote.write(f'/self/pane/{opened}/ctl', b'put\n') + assert client.read('/self/pane/1/body') == b'runtime mounted Save\n' + assert remote.read('/self/pane/1/body') == b'initial\n' + roots = look(remote, '/n', source=control) + assert b'/n/peer/\n' in remote.read(f'/self/pane/{roots}/body') + remote.write(f'/self/pane/{opened}/ctl', b'delete\n') + execute(remote, control, 'Unmount peer') + remote.write(f'/self/pane/{roots}/ctl', b'get\n') + assert remote.read(f'/self/pane/{roots}/body') == b'/n/os/\n/n/self/\n' + execute(remote, control, 'Mount peer ' + str(own_address)) + opened = look(remote, '/n/peer/self/pane/1/body', source=control) + assert remote.read(f'/self/pane/{opened}/body') == b'initial\n' + + with session(binary, root, 'mounted-startup', '--mount=peer=' + str(address), + file='/n/peer/self/pane/1/body:1') as (remote, _): + assert remote.read('/self/pane/1/body') == b'runtime mounted Save\n' + + client.write(f'/self/pane/{serial}/ctl', b'delete\n') + assert str(serial) not in client.list('/self/pane') + + for tty in [False, True]: + name = 'restore tty space' if tty else 'restore detached space' + with session(binary, root, name, tty=tty) as (old, address): + old.write('/self/pane/1/body', b'dumped state\n', truncate=True) + control = new_pane(old, b'') + execute(old, control, 'Restore ' + str(root / 'missing dump.zon')) + assert old.read('/self/pane/1/body') == b'dumped state\n' + execute(old, control, 'Mount own ' + str(address)) + execute(old, control, 'Dump') + saved = root / (name + '.dump.zon') + deadline = time.monotonic() + 5 + while not saved.exists(): + assert time.monotonic() < deadline, 'Dump did not write its state' + time.sleep(.005) + held_body = old.open('/self/pane/1/body') + held_screen = old.open('/self/screen') + old_ids = {int(row.split()[0]) for row in old.read('/self/index').splitlines()} + assert old.read_fid(held_body) == b'dumped state\n' + assert old.read_fid(held_screen, count=1) == b'{' + old.write('/self/pane/1/body', b'changed after dump\n', truncate=True) + try: + execute(old, control, 'Restore ' + str(saved)) + except (EOFError, ConnectionResetError, BrokenPipeError): + pass + try: + old.read_fid(held_body) + except (EOFError, ConnectionResetError, BrokenPipeError): + pass + else: + raise AssertionError('Restore left an old body fid usable') + with Client(address) as restored: + restored_ids = [int(row.split()[0]) for row in restored.read('/self/index').splitlines()] + assert len(restored_ids) == 2 and old_ids.isdisjoint(restored_ids), restored_ids + file_id, control_id = restored_ids + contents = restored.read(f'/self/pane/{file_id}/body') + assert contents == b'dumped state\n', (contents, saved.read_text(), restored.read('/self/index')) + assert restored.read(f'/self/pane/{control_id}/body') == b'Dump' + assert restored.screen()['cols'] == 80 + opened = look(restored, '/n/own/os' + str(root / (name + '.txt')), source=control_id) + assert restored.read(f'/self/pane/{opened}/body') == b'initial\n' + + with session(binary, root, 'forwarding') as (client, address): + child_dir = root / 'child-working-directory' + child_dir.mkdir() + spaced = child_dir / 'space name.txt' + spaced.write_bytes(b'first line\nsecond line\n') + env = os.environ.copy() + env.update(PARDES_9P=str(address), PARDES_PANE='1', PARDES_FORWARD_LOOK='1') + for word, expected, selected, reuse in [('space name.txt:2:4', spaced.read_bytes(), [14, 14], False), + ('/n/self/pane/1/body', b'initial\n', None, False), + ('/virtual/pane/1/body:1:2-4', b'initial\n', [1, 4], True)]: + before = {int(row.split()[0]) for row in client.read('/self/index').splitlines()} + result = subprocess.run([binary, '--tty', word], cwd=child_dir, env=env, + capture_output=True, timeout=3) + assert result.returncode == 0, (word, result.stderr) + after = {int(row.split()[0]) for row in client.read('/self/index').splitlines()} + if reuse: + assert after == before + else: + opened, = after - before + assert client.read(f'/self/pane/{opened}/body') == expected + assert client.read('/self/pane/1/body') == b'initial\n' + if selected is not None: + addr = client.open(f'/self/pane/{opened}/addr') + client.write(f'/self/pane/{opened}/ctl', b'addr=dot\n') + actual = list(map(int, client.read_fid(addr).split())) + client.close(addr) + assert actual == selected, (word, actual, selected) + addr = client.open('/self/pane/1/addr', 2) + ctl = client.open('/self/pane/1/ctl', 1) + for fid, text in [(addr, b'#0,#3'), (ctl, b'limit=addr\n'), (addr, b'#0')]: + client.rpc(118, struct.pack('<IQI', fid, 0, len(text)) + text) + try: + for _ in range(8): + result = subprocess.run([binary], cwd=child_dir, env=env, + capture_output=True, timeout=3) + assert result.returncode == 1 and b'already inside pardes' in result.stderr + text = b'/tial/' + try: + client.rpc(118, struct.pack('<IQI', addr, 0, len(text)) + text) + except OSError: + pass + else: + raise AssertionError('no-argument child cleared the parent address limit') + finally: + client.close(addr) + client.close(ctl) + client.read('/self/pane/1/ctl') + + control = new_pane(client, b'') + execute(client, control, 'Dump') + saved = root / 'forwarding.dump.zon' + deadline = time.monotonic() + 3 + while not saved.exists(): + assert time.monotonic() < deadline, 'forwarding fixture Dump did not finish' + time.sleep(.005) + before = client.read('/self/index') + inherited = {key: env[key] for key in ['PARDES_9P', 'PARDES_PANE', 'PARDES_FORWARD_LOOK']} + cases = [ + ('mount', ['--mount=peer=' + str(address), '/n/peer/self/pane/1/body'], None), + ('name', ['--9p=forwarded-name'], 'forwarded-name'), + ('tcp', ['--9p-tcp=tcp!127.0.0.1!0'], None), + ('load', ['-l', str(saved)], None), + ('shells', ['-n', '3'], None), + ('toggle', ['--tty-toggle=q'], None), + ('toggle-arg', ['--tty-toggle', 'q'], None), + ] + if quic: + cases.append(('quic', ['--9p-quic=quic!127.0.0.1!0'], None)) + for name, options, socket_name in cases: + with session(binary, root, 'forward-' + name, tty=True, socket_name=socket_name, + launch=[*([] if name == 'shells' else ['--tty']), *options], + inherited=inherited) as (local, local_address): + assert local_address != address + index = local.read('/self/index').splitlines() + if name == 'mount': + assert len(index) == 1 and b'/n/peer/' in index[0], index + assert local.read('/self/pane/1/body') == b'initial\n' + elif name == 'load': + assert local.read('/self/index') == before + assert local.read('/self/pane/1/body') == b'initial\n' + else: + assert len(index) == (3 if name == 'shells' else 1), index + if name in ['tcp', 'quic']: + assert (name + '!127.0.0.1!').encode() in local.read('/self/listeners') + assert client.read('/self/index') == before + assert client.read('/self/pane/1/body') == b'initial\n' + + for name, identity, word in [ + ('stale-file', dict(inherited, PARDES_9P=str(root / 'absent.sock')), str(spaced)), + ('stale-missing', dict(inherited, PARDES_9P=str(root / 'absent.sock')), 'missing-child-file.txt'), + ('stale-noarg', dict(inherited, PARDES_9P=str(root / 'absent.sock')), None), + ('stale-pane', dict(inherited, PARDES_PANE='4294967295'), str(spaced)), + ('disabled', dict(inherited, PARDES_FORWARD_LOOK='0'), str(spaced)), + ]: + with session(binary, root, name, tty=True, inherited=identity, + launch=['--tty', *([word] if word else [])]) as (local, local_address): + assert local_address != address + if word == str(spaced): + assert local.read('/self/pane/1/body') == spaced.read_bytes() + elif word: + assert word.encode() in local.read('/self/index') or word in ''.join( + cell[0] for cell in local.screen()['cells']) + assert client.read('/self/index') == before + + for tty in [False, True]: + name = 'lsp-tty' if tty else 'lsp-detached' + with session(binary, root, name, tty=tty) as (client, _): + control = new_pane(client, b'') + execute(client, control, 'Lspinfo') + deadline = time.monotonic() + 3 + while True: + matches = [int(row.split()[0]) for row in client.read('/self/index').splitlines() + if b'+Lsp' in row] + if matches: + break + assert time.monotonic() < deadline, (name, 'Lspinfo did not complete') + time.sleep(.005) + assert len(matches) == 1, matches + result = client.read(f'/self/pane/{matches[0]}/body') + assert result.startswith(b'backend: zls-inproc+lsp-client\n'), result + assert b'protocol servers (lsp-client):' in result, result + assert client.read('/self/pane/1/body') == b'initial\n' + assert '+Lsp' in ''.join(cell[0] for cell in client.screen()['cells']) + + with session(binary, root, 'highlight') as (client, _): + client.write('/self/pane/1/ctl', b'name keywords.zig\n') + source = b'// padding\n' * 9999 + b'fn check() void {\n if (true) return;\n}\n' + client.write('/self/pane/1/body', source, truncate=True) + screen = client.screen() + assert len(screen['cells']) == screen['cols'] * screen['rows'] + found = set() + for row in range(screen['rows']): + cells = screen['cells'][row * screen['cols']:(row + 1) * screen['cols']] + text = ''.join(cell[0] for cell in cells) + for word in ['fn', 'if']: + start = text.find(word + ' ') + if start < 0: + continue + styles = [screen['styles'][cell[1]] for cell in cells[start:start + len(word)]] + assert all(style['bold'] and style['fg'] == styles[0]['fg'] for style in styles), (word, styles) + found.add(word) + assert found == {'fn', 'if'}, screen + + with session(binary, root, 'long-names') as (client, _): + directory = root / ('directory-' + 'd' * 54) + directory.mkdir() + for length in [29, 63, 127, 255]: + name = 'f' * (length - 4) + '.txt' + path = directory / name + path.write_bytes(b'long filename\n') + wire_path = '/os' + str(path) + assert name in client.list('/os' + str(directory)) + assert client.read(wire_path) == b'long filename\n' + client.write(wire_path, b'written through long filename\n', truncate=True) + assert path.read_bytes() == b'written through long filename\n' + opened = look(client, '/n/os' + str(path)) + assert client.read(f'/self/pane/{opened}/body') == path.read_bytes() + opened = look(client, '/n/os' + str(directory)) + listing = client.read(f'/self/pane/{opened}/body') + for path in directory.iterdir(): + assert ('/n/os' + str(path)).encode() in listing + if quic: + quic_test(binary) + print(f'9P integration: passed ({time.monotonic() - started:.3f}s)') + + +def quic_test(binary): + for tty in [False, True]: + for host in ['127.0.0.1', '::1']: + with tempfile.TemporaryDirectory(prefix='pardes-quic-fs-') as directory: + root = Path(directory) + ports = {} + with session(binary, root, 'server', '--9p-tcp=tcp!' + host + '!0', + '--9p-quic=quic!' + host + '!0', tty=tty, network=ports) as (unix, address): + assert set(unix.read('/self/listeners').decode().splitlines()) == { + 'unix!' + str(address), 'tcp!' + host + '!' + str(ports['TCP']), + 'quic!' + host + '!' + str(ports['QUIC'])} + with Client((host, ports['TCP'])) as tcp: + unix_body = unix.open('/self/pane/1/body') + tcp_body = tcp.open('/self/pane/1/body') + with session(binary, root, 'client') as (remote, _): + control = new_pane(remote, b'') + dial = 'quic!' + host + '!' + str(ports['QUIC']) + execute(remote, control, 'Mount peer ' + dial) + opened = look(remote, '/n/peer/self/pane/1/body', source=control) + body = f'/self/pane/{opened}/body' + assert remote.read(body) == b'initial\n' + payload = 'saved café λ\n'.encode() + b'x' * (3 * 8192 + 27) + b'\n' + remote.write(body, payload, truncate=True) + remote.write(f'/self/pane/{opened}/ctl', b'put\n') + assert unix.read_fid(unix_body) == payload[:unix.msize - 11] + assert tcp.read('/self/pane/1/body') == payload + tcp.write('/self/pane/1/body', b'TCP to QUIC Get\n', truncate=True) + remote.write(f'/self/pane/{opened}/ctl', b'get\n') + assert remote.read(body) == b'TCP to QUIC Get\n' + assert unix.read_fid(unix_body) == b'TCP to QUIC Get\n' + assert tcp.read_fid(tcp_body) == b'TCP to QUIC Get\n' + screen_pane = look(remote, '/n/peer/self/screen', source=control) + screen = json.loads(remote.read(f'/self/pane/{screen_pane}/body')) + assert len(screen['cells']) == screen['cols'] * screen['rows'] + assert 'TCP to QUIC Get' in ''.join(cell[0] for cell in screen['cells']) + large = ('normal λ line\n' * 20000).encode() + fixture = root / 'large.txt' + fixture.write_bytes(large) + large_pane = look(remote, '/n/peer/os' + str(fixture), source=control) + assert remote.read(f'/self/pane/{large_pane}/body') == large + edited = b'QUIC file saved\n' + remote.write(f'/self/pane/{large_pane}/body', edited, truncate=True) + remote.write(f'/self/pane/{large_pane}/ctl', b'put\n') + assert fixture.read_bytes() == edited + for pane in [opened, screen_pane, large_pane]: + remote.write(f'/self/pane/{pane}/ctl', b'delete\n') + execute(remote, control, 'Unmount peer') + roots = look(remote, '/n', source=control) + assert remote.read(f'/self/pane/{roots}/body') == b'/n/os/\n/n/self/\n' + assert unix.read_fid(unix_body) == b'TCP to QUIC Get\n' + assert tcp.read_fid(tcp_body) == b'TCP to QUIC Get\n' + control = new_pane(unix, b'') + execute(unix, control, 'Mount own ' + dial) + own = look(unix, '/n/own/self/pane/1/body', source=control) + assert unix.read(f'/self/pane/{own}/body') == b'TCP to QUIC Get\n' + unix.write(f'/self/pane/{own}/body', b'same-session QUIC Save\n', truncate=True) + unix.write(f'/self/pane/{own}/ctl', b'put\n') + assert unix.read_fid(unix_body) == b'same-session QUIC Save\n' + assert tcp.read_fid(tcp_body) == b'same-session QUIC Save\n' + unix.write(f'/self/pane/{own}/ctl', b'delete\n') + execute(unix, control, 'Unmount own') + unix.close(unix_body) + tcp.close(tcp_body) + + +if __name__ == '__main__': + if len(sys.argv) not in [2, 3] or (len(sys.argv) == 3 and sys.argv[2] != '--quic'): + raise SystemExit('usage: fs.py <pardes> [--quic]') + test(str(Path(sys.argv[1]).resolve()), quic=len(sys.argv) == 3) |
