diff options
| author | Gabriel Schneider <[email protected]> | 2026-09-20 00:36:50 -0300 |
|---|---|---|
| committer | Gabriel Schneider <[email protected]> | 2026-10-01 00:12:14 -0300 |
| commit | c1990b3e6e196ad41379aa432bf3ccca8a65d9f0 (patch) | |
| tree | d7ad5afe559f7f56464b14c3122242fa3f16399b /test/fs.py | |
| parent | 717afaf3177a2e0925b18ae3445119efe808b129 (diff) | |
| download | pardes-c1990b3e6e196ad41379aa432bf3ccca8a65d9f0.tar.gz pardes-c1990b3e6e196ad41379aa432bf3ccca8a65d9f0.zip | |
Flatten the 9P control tree and move it out of fs.zig
The served tree loses the self/ level: /index /ctl /new /log /screen
/listeners /pane/<n>/... /os, with /src only in -Dembed-sources=true builds
(default off, on for esp32p4). ctl speaks the editor's own language with two
lowercase verbs, look TEXT and exec TEXT, plus acme's addr verbs; the new/
factory directory becomes one clone file; cons is gone (exec Msg); name and
sel are files; stats report real lengths, modes and mtimes; /log streams
pane new/del/rename/save events. The tree code lives in src/ninep/
(tree, pane, ctl, addr, pty, events, screen, sources); fs.zig keeps host
access, mounts, resolution and find/grep. Same engine and transports.
README (fs-help.txt) and docs rewritten; tests updated and extended.
Co-Authored-By: Claude Fable 5.1 <[email protected]>
Diffstat (limited to 'test/fs.py')
| -rw-r--r-- | test/fs.py | 367 |
1 files changed, 222 insertions, 145 deletions
@@ -72,7 +72,7 @@ def session(binary, root, name, *options, socket_name=None, file=None, tty=False time.sleep(.005) with Client(address) as client: if network is not None: - for dial in client.read('/self/listeners').decode().splitlines(): + for dial in client.read('/listeners').decode().splitlines(): parts = dial.split('!') if parts[0] in ['tcp', 'quic']: assert len(parts) == 3 and 0 < int(parts[2]) < 65536, dial @@ -101,60 +101,136 @@ def session(binary, root, name, *options, socket_name=None, file=None, tty=False def new_pane(client, contents): - serial = int(client.read('/self/new/ctl').split()[0]) - client.write(f'/self/pane/{serial}/body', contents) + serial = int(client.read('/new').split()[0]) + if contents: + client.write(f'/pane/{serial}/body', contents) return serial def look(client, path, source=None): + """A right click on `path` from a pane (a fresh scratch by default).""" 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 + client.write(f'/pane/{serial}/ctl', f'look {path}\n'.encode()) + results = [int(row) for row in client.read('/ctl').split()] + assert len(results) == 1 and results[0] != serial, (path, results) + return results[0] 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()) + """A middle click on `command` from a pane.""" + client.write(f'/pane/{serial}/ctl', f'exec {command}\n'.encode()) -def discovery(binary): +def walk_tree(client, path='/', skip=('/new', '/log', '/os', '/screen')): + """Stat every entry below `path`, as `find` does, without opening anything.""" + seen = [] + for name in client.list(path): + child = (path.rstrip('/') + '/' + name) + info = client.stat(child) + seen.append((child, info)) + if info['dir'] and child not in skip: + seen.extend(walk_tree(client, child, skip)) + return seen + + +def discovery(binary, embedded=False): with tempfile.TemporaryDirectory(prefix='pardes-discovery-') as directory: - with session(binary, Path(directory), 'discovery') as (client, _): - before = client.read('/self/index') - entries = client.list('/self/new') - assert {'README', 'ctl', 'body', 'addr', 'data', 'event'} <= set(entries) - guide = client.read('/self/README') - assert guide == client.read('/self/new/README') - assert b'Each open creates a separate pane' in guide - # Walk and stat each entry, as a filesystem browser does, without open. - from ninep import string - for name in entries: - client.fid += 1 - fid = client.fid - client.rpc(110, struct.pack('<IIH', 1, fid, 3) + - string('self') + string('new') + string(name)) + root = Path(directory) + with session(binary, root, 'discovery') as (client, _): + top = client.list('/') + assert top[:9] == ['README', 'index', 'ctl', 'new', 'log', 'screen', 'listeners', 'pane', 'os'], top + assert 'self' not in top and 'cons' not in top, top + assert ('src' in top) == embedded, (top, embedded) + before = client.read('/index') + guide = client.read('/README') + assert guide.count(b'\n') <= 45 and b'exec Msg' in guide and b'/new' in guide, guide + assert client.stat('/README')['length'] == len(guide) + # ls/stat/find over the whole tree, without opening, creates nothing. + seen = walk_tree(client) + names = {path for path, _ in seen} + assert {'/index', '/ctl', '/new', '/log', '/pane/1', '/pane/1/body', '/pane/1/name', + '/pane/1/sel', '/pane/1/ctl', '/pane/1/tag', '/pane/1/event'} <= names, sorted(names) + assert '/pane/1/pty' not in names, 'the fixture file pane grew a pty/' + assert not any(name.startswith('/self') or name.endswith('/rdsel') or name.endswith('/wrsel') + for name in names), sorted(names) + for path, info in seen: + assert info['mode'] in (0o755, 0o644, 0o666, 0o444, 0o222, 0o555), (path, info) + assert info['mtime'] > 0, (path, info) + assert client.read('/index') == before, 'browsing created a pane' + assert client.stat('/index')['length'] == len(before) > 0 + index = client.stat('/index') + assert index['mode'] == 0o444 and client.stat('/ctl')['mode'] == 0o666 + assert client.stat('/pane/1/body')['mode'] == 0o666 and client.stat('/pane')['mode'] == 0o755 + fixture = int(before.split()[0]) + assert before.split()[1:3] == [b'text', b'0'] and before.endswith(b'/discovery.txt\n'), before + # Before any command, /ctl reports the editor. + status = dict(line.split(maxsplit=1) for line in client.read('/ctl').decode().splitlines()) + assert int(status['pid']) > 0 and status['version'] and int(status['panes']) == 1, status + # /new: one pane per open, its serial is the file's content. + log = client.open('/log') + first = int(client.read('/new').split()[0]) + second = int(client.read('/new').split()[0]) + assert first != second and first != fixture, (first, second) + assert client.read_fid(log) == f'new {first} {root}/+New\n'.encode() + assert client.read_fid(log) == f'new {second} {root}/+New\n'.encode() + assert set(client.list('/pane')) == {str(fixture), str(first), str(second)} + client.write(f'/pane/{first}/body', b'first pane', truncate=True) + assert client.read(f'/pane/{first}/body') == b'first pane' + assert client.read(f'/pane/{second}/body') == b'' + assert client.stat(f'/pane/{first}/body')['length'] == len(b'first pane') + rows = {int(row.split()[0]): row.split() for row in client.read('/index').splitlines()} + assert rows[first][1:3] == [b'text', b'1'] and rows[second][1:3] == [b'text', b'0'], rows + # name: read the file name, write to rename; sel: the selection. + assert client.read(f'/pane/{first}/name') == f'{root}/+New\n'.encode() + client.write(f'/pane/{first}/name', b'notes.txt\n') + assert client.read(f'/pane/{first}/name') == f'{root}/notes.txt\n'.encode() + assert client.read_fid(log) == f'rename {first} {root}/notes.txt\n'.encode() + assert client.stat(f'/pane/{first}/name')['length'] == len(f'{root}/notes.txt\n') + client.write(f'/pane/{first}/addr', b'#0,#5') + client.write(f'/pane/{first}/ctl', b'dot=addr\n') + assert client.read(f'/pane/{first}/sel') == b'first' + assert client.stat(f'/pane/{first}/sel')['length'] == 5 + client.write(f'/pane/{first}/sel', b'FIRST') + assert client.read(f'/pane/{first}/body') == b'FIRST pane' + assert client.read(f'/pane/{first}/sel') == b'' + # exec Msg shows text; exec Save writes the named file; exec Del closes. + client.write(f'/pane/{first}/ctl', b'exec Msg hello from 9P\n') + assert 'hello from 9P' in ''.join(cell[0] for cell in client.screen()['cells']) + client.write(f'/pane/{first}/ctl', b'exec Save\n') + assert (root / 'notes.txt').read_bytes() == b'FIRST pane' + assert client.read_fid(log) == f'save {first} {root}/notes.txt\n'.encode() + assert client.read('/ctl') == f'{first}\n'.encode() + client.write('/ctl', b'exec Del\n') + assert client.read_fid(log) == f'del {second} {root}/+New\n'.encode() + assert str(second) not in client.list('/pane') + # look through /ctl opens a file; the serial reads back. + (root / 'opened.txt').write_bytes(b'one\ntwo\nthree\n') + client.write('/ctl', f'look {root}/opened.txt:2\n'.encode()) + opened = int(client.read('/ctl')) + assert opened not in (fixture, first, second), opened + assert client.read(f'/pane/{opened}/body') == b'one\ntwo\nthree\n' + assert client.read(f'/pane/{opened}/name') == f'{root}/opened.txt\n'.encode() + assert client.read_fid(log) == f'new {opened} {root}/opened.txt\n'.encode() + client.write(f'/pane/{first}/ctl', f'look {root}/opened.txt:3\n'.encode()) + assert client.read('/ctl') == f'{opened}\n'.encode(), 'a repeated look targets the open pane' + assert set(client.list('/pane')) == {str(fixture), str(first), str(opened)} + # Writing an event record back still performs the action it names. + client.write(f'/pane/{first}/body', b'Msg via event', truncate=True) + client.write(f'/pane/{first}/event', b'FX0 13\n') + assert 'via event' in ''.join(cell[0] for cell in client.screen()['cells']) + for bad in [b'Look x\n', b'Exec Msg x\n', b'put\n', b'name x\n', b'del\n']: try: - client.rpc(124, struct.pack('<I', fid)) - finally: - client.close(fid) - assert client.read('/self/index') == before, 'browsing created a pane' - first = int(client.read('/self/new/ctl').split()[0]) - second = int(client.read('/self/new/ctl').split()[0]) - assert first != second - client.write(f'/self/pane/{first}/body', b'first pane', truncate=True) - assert client.read(f'/self/pane/{first}/body') == b'first pane' - assert client.read(f'/self/pane/{second}/body') == b'' - before_ids = set(client.list('/self/pane')) - client.write('/self/new/body', b'created with text', truncate=True) - made = set(client.list('/self/pane')) - before_ids - assert len(made) == 1 - assert client.read('/self/pane/' + made.pop() + '/body') == b'created with text' - print('9P discovery: listing/stat/README are inert; opens create independent panes') + client.write('/ctl', bad) + except OSError: + pass + else: + raise AssertionError(f'{bad!r} was accepted') + client.close(log) + if embedded: + assert client.read('/src/pardes.zig').startswith(b'const std') + assert client.stat('/src/pardes.zig')['mode'] == 0o444 + assert b'pub const Pardes' in client.read(f'/pane/{look(client, "/virtual/src/pardes.zig")}/body') + print('9P discovery: listing/stat/find are inert; new, ctl, name, sel and log behave') def test(binary, quic=False): @@ -188,14 +264,14 @@ def test(binary, quic=False): 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' + assert named.read('/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 {'pane', 'index', 'new', 'ctl', 'log', 'os'} <= set(client.list('/')) + assert 'self' not in client.list('/') + assert client.read('/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('/pane/1/../1/body') == b'initial\n' assert client.read('/os' + str(root) + '/../' + root.name + '/first.txt') == b'initial\n' listing = root / 'listing' @@ -207,9 +283,9 @@ def test(binary, quic=False): assert set(small.list('/os' + str(listing))) == { f'entry-{number:02}' for number in range(24)} - frozen = client.open('/self/screen') + frozen = client.open('/screen') before = bytearray(client.read_fid(frozen, count=31)) - client.write('/self/pane/1/body', b'screen changed\n') + client.write('/pane/1/body', b'screen changed\n') while chunk := client.read_fid(frozen, len(before)): before.extend(chunk) client.close(frozen) @@ -220,12 +296,12 @@ def test(binary, quic=False): payload = ('const value = "λ";\n' * 3000).encode() serial = new_pane(client, payload) - body = f'/self/pane/{serial}/body' + body = f'/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') + event = client.open(f'/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 @@ -242,82 +318,83 @@ def test(binary, quic=False): missing = False try: - client.read('/self/pane/4294967295/body') + client.read('/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') + if 'src' in client.list('/'): + source = look(client, '/virtual/src/pardes.zig') + assert b'pub const Pardes' in client.read(f'/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' + assert client.read(f'/pane/{opened}/body') == b'OS takes precedence\n' opened = look(client, '/n/self/index') - index = client.read(f'/self/pane/{opened}/body') + index = client.read(f'/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' + assert client.read(f'/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') + opened = look(remote, '/n/own/pane/1/body') + assert remote.read(f'/pane/{opened}/body') == b'initial\n' + opened = look(remote, '/n/peer/pane/' + str(serial) + '/body') + assert remote.read(f'/pane/{opened}/body') == b'replaced\nevent\n' + remote.write(f'/pane/{opened}/body', b'saved to peer\n', truncate=True) + remote.write(f'/pane/{opened}/ctl', b'exec Save\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 + assert remote.read(f'/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') + remote.write(f'/pane/{opened}/body', edited, truncate=True) + remote.write(f'/pane/{opened}/ctl', b'exec Save\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') + 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') - 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' + remote.write(f'/pane/{opened}/body', b'runtime mounted Save\n', truncate=True) + remote.write(f'/pane/{opened}/ctl', b'exec Save\n') + assert client.read('/pane/1/body') == b'runtime mounted Save\n' + assert remote.read('/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') + assert b'/n/peer/\n' in remote.read(f'/pane/{roots}/body') + remote.write(f'/pane/{opened}/ctl', b'exec Del\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' + remote.write(f'/pane/{roots}/ctl', b'get\n') + assert remote.read(f'/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' + opened = look(remote, '/n/peer/pane/1/body', source=control) + assert remote.read(f'/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' + file='/n/peer/pane/1/body:1') as (remote, _): + assert remote.read('/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') + client.write(f'/pane/{serial}/ctl', b'exec Del\n') + assert str(serial) not in client.list('/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) + 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')) - assert old.read('/self/pane/1/body') == b'dumped state\n' + assert old.read('/pane/1/body') == b'dumped state\n' execute(old, control, 'Mount own ' + str(address)) execute(old, control, 'Dump') saved = root / (name + '.dump.zon') @@ -325,12 +402,12 @@ def test(binary, quic=False): 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()} + held_body = old.open('/pane/1/body') + held_screen = old.open('/screen') + old_ids = {int(row.split()[0]) for row in old.read('/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) + old.write('/pane/1/body', b'changed after dump\n', truncate=True) try: execute(old, control, 'Restore ' + str(saved)) except (EOFError, ConnectionResetError, BrokenPipeError): @@ -342,15 +419,15 @@ def test(binary, quic=False): 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()] + restored_ids = [int(row.split()[0]) for row in restored.read('/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' + 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 ctl, not the body' 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' + assert restored.read(f'/pane/{opened}/body') == b'initial\n' with session(binary, root, 'forwarding') as (client, address): child_dir = root / 'child-working-directory' @@ -362,25 +439,25 @@ def test(binary, quic=False): 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()} + before = {int(row.split()[0]) for row in client.read('/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()} + after = {int(row.split()[0]) for row in client.read('/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' + assert client.read(f'/pane/{opened}/body') == expected + assert client.read('/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') + addr = client.open(f'/pane/{opened}/addr') + client.write(f'/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) + addr = client.open('/pane/1/addr', 2) + ctl = client.open('/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: @@ -398,7 +475,7 @@ def test(binary, quic=False): finally: client.close(addr) client.close(ctl) - client.read('/self/pane/1/ctl') + client.read('/pane/1/ctl') control = new_pane(client, b'') execute(client, control, 'Dump') @@ -407,10 +484,10 @@ def test(binary, quic=False): while not saved.exists(): assert time.monotonic() < deadline, 'forwarding fixture Dump did not finish' time.sleep(.005) - before = client.read('/self/index') + before = client.read('/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), + ('mount', ['--mount=peer=' + str(address), '/n/peer/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), @@ -425,19 +502,19 @@ def test(binary, quic=False): launch=[*([] if name == 'shells' else ['--tty']), *options], inherited=inherited) as (local, local_address): assert local_address != address - index = local.read('/self/index').splitlines() + index = local.read('/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' + assert local.read('/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' + assert local.read('/index') == before + assert local.read('/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' + assert (name + '!127.0.0.1!').encode() in local.read('/listeners') + assert client.read('/index') == before + assert client.read('/pane/1/body') == b'initial\n' for name, identity, word in [ ('stale-file', dict(inherited, PARDES_9P=str(root / 'absent.sock')), str(spaced)), @@ -450,11 +527,11 @@ def test(binary, quic=False): 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() + assert local.read('/pane/1/body') == spaced.read_bytes() elif word: - assert word.encode() in local.read('/self/index') or word in ''.join( + assert word.encode() in local.read('/index') or word in ''.join( cell[0] for cell in local.screen()['cells']) - assert client.read('/self/index') == before + assert client.read('/index') == before for tty in [False, True]: name = 'lsp-tty' if tty else 'lsp-detached' @@ -463,23 +540,23 @@ def test(binary, quic=False): execute(client, control, 'Lspinfo') deadline = time.monotonic() + 3 while True: - matches = [int(row.split()[0]) for row in client.read('/self/index').splitlines() + matches = [int(row.split()[0]) for row in client.read('/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') + result = client.read(f'/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 client.read('/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') + client.write('/pane/1/name', b'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) + client.write('/pane/1/body', source, truncate=True) screen = client.screen() assert len(screen['cells']) == screen['cols'] * screen['rows'] found = set() @@ -508,9 +585,9 @@ def test(binary, quic=False): 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() + assert client.read(f'/pane/{opened}/body') == path.read_bytes() opened = look(client, '/n/os' + str(directory)) - listing = client.read(f'/self/pane/{opened}/body') + listing = client.read(f'/pane/{opened}/body') for path in directory.iterdir(): assert ('/n/os' + str(path)).encode() in listing if quic: @@ -526,66 +603,66 @@ def quic_test(binary): 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()) == { + assert set(unix.read('/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') + unix_body = unix.open('/pane/1/body') + tcp_body = tcp.open('/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' + opened = look(remote, '/n/peer/pane/1/body', source=control) + body = f'/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') + remote.write(f'/pane/{opened}/ctl', b'exec Save\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 tcp.read('/pane/1/body') == payload + tcp.write('/pane/1/body', b'TCP to QUIC Get\n', truncate=True) + remote.write(f'/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')) + screen_pane = look(remote, '/n/peer/screen', source=control) + screen = json.loads(remote.read(f'/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 + assert remote.read(f'/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') + remote.write(f'/pane/{large_pane}/body', edited, truncate=True) + remote.write(f'/pane/{large_pane}/ctl', b'exec Save\n') assert fixture.read_bytes() == edited for pane in [opened, screen_pane, large_pane]: - remote.write(f'/self/pane/{pane}/ctl', b'delete\n') + remote.write(f'/pane/{pane}/ctl', b'exec Del\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 remote.read(f'/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') + own = look(unix, '/n/own/pane/1/body', source=control) + assert unix.read(f'/pane/{own}/body') == b'TCP to QUIC Get\n' + unix.write(f'/pane/{own}/body', b'same-session QUIC Save\n', truncate=True) + unix.write(f'/pane/{own}/ctl', b'exec Save\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') + unix.write(f'/pane/{own}/ctl', b'exec Del\n') execute(unix, control, 'Unmount own') unix.close(unix_body) tcp.close(tcp_body) if __name__ == '__main__': - if len(sys.argv) == 3 and sys.argv[2] == '--discovery': - discovery(str(Path(sys.argv[1]).resolve())) + if len(sys.argv) in [3, 4] and sys.argv[2] == '--discovery': + discovery(str(Path(sys.argv[1]).resolve()), embedded=sys.argv[3:] == ['--embedded']) raise SystemExit(0) 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 | --discovery]') |
