summaryrefslogtreecommitdiff
path: root/test
diff options
context:
space:
mode:
Diffstat (limited to 'test')
-rw-r--r--test/agent_session.py14
-rw-r--r--test/appearance_gallery.py26
-rw-r--r--test/collapse_layout.py8
-rw-r--r--test/column_tags.py32
-rw-r--r--test/font_size.py2
-rw-r--r--test/fs.py367
-rw-r--r--test/fs_bench.zig43
-rw-r--r--test/fs_namespace.zig12
-rw-r--r--test/fs_soak.py29
-rw-r--r--test/look_placement.py8
-rw-r--r--test/ninep.py39
-rw-r--r--test/output.zig12
-rw-r--r--test/panes.zig8
-rw-r--r--test/pdf_scroll_bench.zig18
-rw-r--r--test/snapshot.zig2
-rw-r--r--test/tag_cleanup.py10
-rw-r--r--test/ui_review.py50
-rw-r--r--test/v9fs.py33
-rw-r--r--test/v9fs_terminal.py6
-rw-r--r--test/window_opacity.py12
20 files changed, 430 insertions, 301 deletions
diff --git a/test/agent_session.py b/test/agent_session.py
index b920b006..aa87c845 100644
--- a/test/agent_session.py
+++ b/test/agent_session.py
@@ -52,21 +52,21 @@ def run(args, command, status):
client, _ = context.__enter__()
try:
try:
- while not client.read('/self/pane/1/body').strip():
+ while not client.read('/pane/1/body').strip():
time.sleep(.01)
started = time.monotonic()
- client.write('/self/pane/1/pty/data', shlex.join(['/bin/sh', str(script)]).encode() + b'\r')
+ client.write('/pane/1/pty/data', shlex.join(['/bin/sh', str(script)]).encode() + b'\r')
status['phase'] = 'readiness'
wait_ready(client, args.ready)
if args.prepare_key is not None:
status['phase'] = 'preparation'
- client.write('/self/pane/1/pty/data', args.prepare_key)
+ client.write('/pane/1/pty/data', args.prepare_key)
wait_ready(client, args.ready)
ready_ms = (time.monotonic() - started) * 1000
status.update(phase='history', required_body_rows=args.min_rows)
while True:
started = time.monotonic()
- body = client.read('/self/pane/1/body')
+ body = client.read('/pane/1/body')
body_read_ms = (time.monotonic() - started) * 1000
rows = body.count(b'\n') + int(bool(body) and not body.endswith(b'\n'))
status.update(body_bytes=len(body), body_rows=rows)
@@ -81,13 +81,13 @@ def run(args, command, status):
raise AssertionError('probe is already visible')
status['phase'] = 'probe-visible'
started = time.monotonic()
- client.write('/self/pane/1/pty/data', probe.encode())
+ client.write('/pane/1/pty/data', probe.encode())
while probe not in screen_text(client).replace('\n', ''):
time.sleep(.01)
write_ms = (time.monotonic() - started) * 1000
status['phase'] = 'probe-cleared'
started = time.monotonic()
- client.write('/self/pane/1/pty/data', b'\x15')
+ client.write('/pane/1/pty/data', b'\x15')
while True:
observed = screen_text(client)
status['observed_visible_text_sha256'] = hashlib.sha256(observed.encode()).hexdigest()
@@ -109,7 +109,7 @@ def run(args, command, status):
client.socket.settimeout(.25)
for _ in range(2):
try:
- client.write('/self/pane/1/pty/data', b'\x03')
+ client.write('/pane/1/pty/data', b'\x03')
except (OSError, EOFError):
pass
time.sleep(.05)
diff --git a/test/appearance_gallery.py b/test/appearance_gallery.py
index 7779efa9..0fa12136 100644
--- a/test/appearance_gallery.py
+++ b/test/appearance_gallery.py
@@ -80,21 +80,21 @@ def run(args):
before = set(panes(client))
command(client, source, 'Newcol')
right = require(wait_for(lambda: next((s for s in panes(client) if s not in before), None)), 'right column missing')
- client.write(f'/self/pane/{right}/ctl', f'name {root / "Review"}\n'.encode())
- client.write(f'/self/pane/{right}/body', PREFERENCE_NOTES if args.theme_selector else NOTES, truncate=True)
- client.write(f'/self/pane/{right}/ctl', b'clean\n')
+ client.write(f'/pane/{right}/name', f'{root / "Review"}\n'.encode())
+ client.write(f'/pane/{right}/body', PREFERENCE_NOTES if args.theme_selector else NOTES, truncate=True)
+ client.write(f'/pane/{right}/ctl', b'clean\n')
before = set(panes(client))
command(client, right, 'Tty')
terminal = require(wait_for(lambda: next((s for s in panes(client) if s not in before), None)), 'terminal missing')
- client.write(f'/self/pane/{terminal}/pty/data', b"PS1='$ '; printf '\\033[2J\\033[H\\033[1;36mPardes workspace\\033[0m\\n\\n'; printf 'Agave source: read-only\\nTerminal: ready\\n\\n'; printf '\\033[32mSmall touches. Same workflow.\\033[0m\\n'\r")
- require(wait_for(lambda: b'Small touches. Same workflow.' in client.read(f'/self/pane/{terminal}/body')), 'terminal output missing')
+ client.write(f'/pane/{terminal}/pty/data', b"PS1='$ '; printf '\\033[2J\\033[H\\033[1;36mPardes workspace\\033[0m\\n\\n'; printf 'Agave source: read-only\\nTerminal: ready\\n\\n'; printf '\\033[32mSmall touches. Same workflow.\\033[0m\\n'\r")
+ require(wait_for(lambda: b'Small touches. Same workflow.' in client.read(f'/pane/{terminal}/body')), 'terminal output missing')
def config_report():
before = set(panes(client))
command(client, right, 'Config')
pane = require(wait_for(lambda: next((s for s in panes(client) if s not in before), None)), 'Config output missing')
- text = client.read(f'/self/pane/{pane}/body').decode()
- client.write(f'/self/pane/{pane}/ctl', b'del\n')
+ text = client.read(f'/pane/{pane}/body').decode()
+ client.write(f'/pane/{pane}/ctl', b'exec Del\n')
return text
settings = config_report()
@@ -135,7 +135,7 @@ def run(args):
command(client, right, 'Theme ' + theme)
if args.theme_selector:
# Keep both quiet active gutter ink and vivid comments in view.
- client.write(f'/self/pane/{source}/ctl', f'look {source_path}:117\n'.encode())
+ client.write(f'/pane/{source}/ctl', f'look {source_path}:117\n'.encode())
take_frame('classic-' + theme)
if args.theme_selector:
frame = client.screen()
@@ -162,7 +162,7 @@ def run(args):
command(client, right, 'ThemeSel')
selector = require(wait_for(lambda: next((s for s in panes(client) if s not in before), None)),
'ThemeSel missing')
- body = client.read(f'/self/pane/{selector}/body').decode()
+ body = client.read(f'/pane/{selector}/body').decode()
native, legacy = body.split('# Legacy and imported themes', 1)
for theme in ('forge_black', 'forge_soft', 'orchard_black'):
require('Theme ' + theme + '\n' in native, 'native contrast variant below legacy heading')
@@ -171,14 +171,14 @@ def run(args):
'ThemeSel did not open at native first entry')
take_frame('theme-selector')
report['native_themes_first'] = True
- client.write(f'/self/pane/{selector}/ctl', b'del\n')
+ client.write(f'/pane/{selector}/ctl', b'exec Del\n')
command(client, terminal, 'Collapse')
for pet in (() if args.skip_pets else ('cat', 'frog', 'off')):
command(client, right, 'Pet ' + pet)
settings = config_report()
require('Pet: ' + pet + '\n' in settings, 'pet setting not applied')
# Returning focus to the source closes all transient report state.
- client.write(f'/self/pane/{source}/ctl', f'look {source_path}:1\n'.encode())
+ client.write(f'/pane/{source}/ctl', f'look {source_path}:1\n'.encode())
take_frame('pet-' + pet)
if pet != 'off':
frame = client.screen()
@@ -194,9 +194,9 @@ def run(args):
require(band_digest('pet-' + pet) != band_digest('pet-' + pet + '-later'),
'pet did not animate in workspace tag')
report['pets'].append(pet)
- require(client.read(f'/self/pane/{source}/body') == original == source_path.read_bytes(), 'Agave source changed')
+ require(client.read(f'/pane/{source}/body') == original == source_path.read_bytes(), 'Agave source changed')
report['source_unchanged'] = True
- client.write(f'/self/pane/{terminal}/pty/data', b'exit\r')
+ client.write(f'/pane/{terminal}/pty/data', b'exit\r')
(root / 'report.json').write_text(json.dumps(report, indent=2))
print(json.dumps(report, indent=2))
diff --git a/test/collapse_layout.py b/test/collapse_layout.py
index e549bd37..244ee57a 100644
--- a/test/collapse_layout.py
+++ b/test/collapse_layout.py
@@ -29,7 +29,7 @@ def geometry(client, names):
def toggle(client, serial):
"""Execute the default tag's Collapse word without changing any body or tag."""
- pane = f'/self/pane/{serial}'
+ pane = f'/pane/{serial}'
tag = client.read(pane + '/tag')
at = tag.rfind(b' Collapse')
require(at >= 0, 'default pane tag lacks Collapse')
@@ -57,8 +57,8 @@ def run(args):
for index, label in enumerate(names):
if index:
serials.append(new_pane(client, b''))
- pane = f'/self/pane/{serials[index]}'
- client.write(pane + '/ctl', ('name ' + label + '\n').encode())
+ pane = f'/pane/{serials[index]}'
+ client.write(pane + '/name', (label + '\n').encode())
client.write(pane + '/body', contents, truncate=True)
original = require(wait_for(lambda: geometry(client, names)), 'four pane tags not visible')
require(all(height > 1 for _, height in original), f'fixture has no body room: {original}')
@@ -82,7 +82,7 @@ def run(args):
require(wait_for(lambda: geometry(client, names) == original),
f'expansion did not restore original geometry: {geometry(client, names)}')
for serial in serials:
- require(client.read(f'/self/pane/{serial}/body') == contents, 'Collapse changed pane contents')
+ require(client.read(f'/pane/{serial}/body') == contents, 'Collapse changed pane contents')
require((root / (name + '.txt')).read_bytes() == b'initial\n', 'fixture source file changed')
print(f'{"TTY" if args.tty else "SDL"}: 7 local collapse/expand pairs, exact unrelated pane geometry, and content preservation passed')
diff --git a/test/column_tags.py b/test/column_tags.py
index 6173d38a..80b623bd 100644
--- a/test/column_tags.py
+++ b/test/column_tags.py
@@ -70,7 +70,7 @@ def await_text(client, row, text):
def tag(client, serial):
- return client.read(f'/self/pane/{serial}/tag').decode().rstrip('\n')
+ return client.read(f'/pane/{serial}/tag').decode().rstrip('\n')
def new_serial(client, previous):
@@ -79,10 +79,10 @@ def new_serial(client, previous):
def exec_tag_without_cleanup(client, serial, text):
"""Restore can replace the whole core, so never touch the old pane after it."""
- client.write(f'/self/pane/{serial}/tag', (' ' + text).encode())
- whole = client.read(f'/self/pane/{serial}/tag')
+ client.write(f'/pane/{serial}/tag', (' ' + text).encode())
+ whole = client.read(f'/pane/{serial}/tag')
start = whole.rfind(text.encode())
- client.write(f'/self/pane/{serial}/event', f'Mx{start} {start + len(text.encode())}\n'.encode())
+ client.write(f'/pane/{serial}/event', f'Mx{start} {start + len(text.encode())}\n'.encode())
def reconnect(address):
@@ -140,10 +140,10 @@ def run(args):
keys.click(*require(locate(client, 'Newcol', row=0), 'Newcol missing'), button=1)
right = require(wait_for(lambda: new_serial(client, before)), 'Newcol did not create its pane')
right_name = root / 'right.rs'
- client.write(f'/self/pane/{right}/ctl', f'name {right_name}\n'.encode())
+ client.write(f'/pane/{right}/name', f'{right_name}\n'.encode())
right_body = b'// Column-local scratch\nfn main() {\n let ready = true;\n}\n'
- client.write(f'/self/pane/{right}/body', right_body)
- client.write(f'/self/pane/{right}/ctl', b'clean\n')
+ client.write(f'/pane/{right}/body', right_body)
+ client.write(f'/pane/{right}/ctl', b'clean\n')
require(wait_for(lambda: locate(client, 'New Tty', row=1, minimum_col=75)), 'right column tag missing')
require(locate(client, 'New Tty', row=1, minimum_col=75) == (75, 1),
'right column tag retains an inset')
@@ -171,9 +171,9 @@ def run(args):
before = panes(client)
keys.click(col_start, 1, button=1)
extra = require(wait_for(lambda: new_serial(client, before)), 'right-column New did not create a pane')
- client.write(f'/self/pane/{extra}/body', b'RIGHT-COLUMN-NEW\n')
+ client.write(f'/pane/{extra}/body', b'RIGHT-COLUMN-NEW\n')
require(wait_for(lambda: locate(client, 'RIGHT-COLUMN-NEW', minimum_col=75)), 'right-column command targeted another column')
- client.write(f'/self/pane/{extra}/ctl', b'delete\n')
+ client.write(f'/pane/{extra}/ctl', b'exec Del\n')
checks.append('middle-click New targets the inactive right column')
# An owned short-path file makes its complete tag visible. Pane-tag
@@ -197,7 +197,7 @@ def run(args):
keys.escape()
require(wait_for(lambda: tag(client, right).startswith(str(right_name) + ' ')), 'Escape did not cancel filename draft')
require(not (root / 'Xright.rs').exists(), 'editing a filename wrote a file')
- require(client.read(f'/self/pane/{right}/body') == right_body, 'tag editing changed file body')
+ require(client.read(f'/pane/{right}/body') == right_body, 'tag editing changed file body')
checks.append('filename draft cancels without disk writes or body changes')
# Keep a real embedded shell in the right column for useful captures.
@@ -205,9 +205,9 @@ def run(args):
before = panes(client)
keys.click(*require(locate(client, 'Tty', row=1, minimum_col=75), 'Tty missing'), button=1)
terminal = require(wait_for(lambda: new_serial(client, before)), 'column Tty did not create a pane')
- require('pty' in client.list(f'/self/pane/{terminal}'), 'Tty created a non-terminal pane')
- client.write(f'/self/pane/{terminal}/pty/data', b"printf 'Column-local terminal ready\\n'; pwd\r")
- require(wait_for(lambda: b'\nColumn-local terminal ready\n' in client.read(f'/self/pane/{terminal}/body').replace(b'\r', b'')), 'terminal command output missing')
+ require('pty' in client.list(f'/pane/{terminal}'), 'Tty created a non-terminal pane')
+ client.write(f'/pane/{terminal}/pty/data', b"printf 'Column-local terminal ready\\n'; pwd\r")
+ require(wait_for(lambda: b'\nColumn-local terminal ready\n' in client.read(f'/pane/{terminal}/body').replace(b'\r', b'')), 'terminal command output missing')
checks.append('right-column embedded terminal executes harmless commands')
# Persistence is checked through real Dump/Restore dispatch and the
@@ -285,11 +285,11 @@ def run(args):
capture(client, root, 'columns-tag-reveal', not args.tty)
keys.escape()
checks.append('long-path pane tag reveals its command tail when the caret moves to End')
- require(client.read(f'/self/pane/{source_serial}/body') == original == source.read_bytes(), 'Agave source changed')
+ require(client.read(f'/pane/{source_serial}/body') == original == source.read_bytes(), 'Agave source changed')
checks.append('Agave source bytes unchanged')
for serial in panes(client):
- if 'pty' in client.list(f'/self/pane/{serial}'):
- client.write(f'/self/pane/{serial}/pty/data', b'exit\r')
+ if 'pty' in client.list(f'/pane/{serial}'):
+ client.write(f'/pane/{serial}/pty/data', b'exit\r')
report = {'checks': checks, 'cols': 150, 'rows': 48, 'host': 'tty' if args.tty else 'sdl',
'input': 'keyboard and SGR mouse through the host PTY parser',
'source': str(source), 'artifacts': str(root)}
diff --git a/test/font_size.py b/test/font_size.py
index e40b7b3a..3a0a9a28 100644
--- a/test/font_size.py
+++ b/test/font_size.py
@@ -29,7 +29,7 @@ def run(args):
def ready():
for serial, tag in panes(client).items():
if '/+Config ' in tag:
- body = client.read(f'/self/pane/{serial}/body').decode()
+ body = client.read(f'/pane/{serial}/body').decode()
if f'Font effective size: {size:.2f} pixels' in body:
return body
return None
diff --git a/test/fs.py b/test/fs.py
index 2ffd5098..819a78d6 100644
--- a/test/fs.py
+++ b/test/fs.py
@@ -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]')
diff --git a/test/fs_bench.zig b/test/fs_bench.zig
index 7fe49734..f9735ee2 100644
--- a/test/fs_bench.zig
+++ b/test/fs_bench.zig
@@ -1,6 +1,7 @@
const std = @import("std");
const pardes = @import("pardes");
const filesystem = pardes.filesystem;
+const ctlfs = pardes.ctlfs;
pub const std_options: std.Options = .{ .log_level = .err };
@@ -73,8 +74,8 @@ const Session = struct {
s.core.deinit();
}
- fn node(s: *const Session, file: filesystem.PaneFile) u64 {
- return filesystem.Node.of(s.file_serial, file);
+ fn node(s: *const Session, file: ctlfs.PaneFile) u64 {
+ return ctlfs.Node.of(s.file_serial, file);
}
};
@@ -90,9 +91,9 @@ fn checkNumbers(bytes: []const u8, expected: []const usize) ![]const u8 {
return std.mem.trimStart(u8, rest, " ");
}
-fn checkReply(s: *Session, req: filesystem.Req, reply: filesystem.Reply) !void {
+fn checkReply(s: *Session, req: ctlfs.Req, reply: ctlfs.Reply) !void {
const event_read = req.op == .read and req.node == s.node(.event);
- const expected_status: filesystem.Status = if (event_read) .again else .ok;
+ const expected_status: ctlfs.Status = if (event_read) .again else .ok;
if (reply.tag != req.tag or reply.errno != 0 or reply.status != expected_status)
return error.IncorrectReply;
const body = s.core.panes[s.file_id].?.file.?.content;
@@ -112,16 +113,18 @@ fn checkReply(s: *Session, req: filesystem.Req, reply: filesystem.Reply) !void {
if (reply.payload != .staged or reply.payload.staged != bytes.len or bytes.len > req.size)
return error.IncorrectReply;
const pane = s.core.panes[s.file_id].?;
+ if (req.node == @intFromEnum(ctlfs.TopFile.index)) {
+ var expected_buf: [4200]u8 = undefined;
+ const expected = try std.fmt.bufPrint(&expected_buf, "{d} text {d} {s}\n", .{ s.file_serial, @intFromBool(pane.file.?.revision != pane.file.?.saved_revision), pane.file.?.path });
+ if (!std.mem.eql(u8, expected, bytes)) return error.IncorrectReply;
+ return;
+ }
const rest = try checkNumbers(bytes, &.{ s.file_serial, s.tag.len, body.len, 0, @intFromBool(pane.file.?.revision != pane.file.?.saved_revision) });
if (req.node == s.node(.ctl)) {
const tail = try checkNumbers(rest, &.{pane.cols});
if (!std.mem.startsWith(u8, tail, "default ")) return error.IncorrectReply;
if ((try checkNumbers(tail[8..], &.{pardes.config.tab_width})).len != 0)
return error.IncorrectReply;
- } else if (req.node == @intFromEnum(filesystem.SelfFile.index)) {
- const tag = s.tag[0 .. std.mem.indexOfScalar(u8, s.tag, '\n') orelse s.tag.len];
- if (rest.len != tag.len + 1 or rest[rest.len - 1] != '\n' or !std.mem.eql(u8, rest[0..tag.len], tag))
- return error.IncorrectReply;
} else return error.UnmeasuredOperation;
}
},
@@ -129,8 +132,8 @@ fn checkReply(s: *Session, req: filesystem.Req, reply: filesystem.Reply) !void {
const bytes = s.core.fs.out.items;
if (reply.payload != .staged or reply.payload.staged != bytes.len or bytes.len > req.size)
return error.IncorrectReply;
- const names = [_][]const u8{ "index", "cons", "new", "pane", "screen", "listeners" };
- const nodes = [_]u64{ @intFromEnum(filesystem.SelfFile.index), @intFromEnum(filesystem.SelfFile.cons), @intFromEnum(filesystem.SelfFile.new), filesystem.namespace_panes, @intFromEnum(filesystem.SelfFile.screen), @intFromEnum(filesystem.SelfFile.listeners) };
+ const names = [_][]const u8{ "README", "index", "ctl", "new", "log", "screen", "listeners", "pane", "os" };
+ const nodes = [_]u64{ @intFromEnum(ctlfs.TopFile.README), @intFromEnum(ctlfs.TopFile.index), @intFromEnum(ctlfs.TopFile.ctl), @intFromEnum(ctlfs.TopFile.new), @intFromEnum(ctlfs.TopFile.log), @intFromEnum(ctlfs.TopFile.screen), @intFromEnum(ctlfs.TopFile.listeners), @intFromEnum(ctlfs.TopFile.pane), filesystem.os_root };
var off: usize = 0;
var entry: usize = 0;
while (off < bytes.len) : (entry += 1) {
@@ -140,7 +143,7 @@ fn checkReply(s: *Session, req: filesystem.Req, reply: filesystem.Reply) !void {
const size: usize = bytes[off + 9];
off += 10;
if (dir > 1 or size == 0 or size > bytes.len - off) return error.IncorrectReply;
- if (entry < names.len and (node != nodes[entry] or dir != @intFromBool(entry == 2 or entry == 3) or
+ if (entry < names.len and (node != nodes[entry] or dir != @intFromBool(entry >= 7) or
!std.mem.eql(u8, names[entry], bytes[off..][0..size]))) return error.IncorrectReply;
off += size;
}
@@ -151,21 +154,21 @@ fn checkReply(s: *Session, req: filesystem.Req, reply: filesystem.Reply) !void {
}
}
-fn bench(s: *Session, name: []const u8, reps: usize, req: filesystem.Req, note: []const u8) !void {
+fn bench(s: *Session, name: []const u8, reps: usize, req: ctlfs.Req, note: []const u8) !void {
std.debug.assert(reps > 0);
const initial_bytes = s.core.panes[s.file_id].?.file.?.content.len;
const initial_hash = if (req.op == .write) std.hash.Wyhash.hash(0, s.core.panes[s.file_id].?.file.?.content) else 0;
for (0..64) |_| {
- try checkReply(s, req, filesystem.handle(s.core, req));
+ try checkReply(s, req, ctlfs.handle(s.core, req));
_ = s.core.scratch.reset(.retain_capacity);
}
s.counting.allocations = 0;
- const expected_status: filesystem.Status = if (req.op == .read and req.node == s.node(.event)) .again else .ok;
+ const expected_status: ctlfs.Status = if (req.op == .read and req.node == s.node(.event)) .again else .ok;
var valid = true;
- var reply: filesystem.Reply = undefined;
+ var reply: ctlfs.Reply = undefined;
const start = nowNs();
for (0..reps) |_| {
- reply = filesystem.handle(s.core, req);
+ reply = ctlfs.handle(s.core, req);
valid = valid and reply.tag == req.tag and reply.status == expected_status and reply.errno == 0;
std.mem.doNotOptimizeAway(reply);
_ = s.core.scratch.reset(.retain_capacity);
@@ -223,7 +226,7 @@ pub fn main(init: std.process.Init) !void {
try bench(&s, "lookup ctl", reps, .{
.tag = 2,
.op = .lookup,
- .node = filesystem.Node.of(s.file_serial, .dir),
+ .node = ctlfs.Node.of(s.file_serial, .dir),
.data = "ctl",
}, "name -> node");
try bench(&s, "read body 4K", reps, .{
@@ -249,13 +252,13 @@ pub fn main(init: std.process.Init) !void {
try bench(&s, "read index", reps, .{
.tag = 6,
.op = .read,
- .node = @intFromEnum(filesystem.SelfFile.index),
+ .node = @intFromEnum(ctlfs.TopFile.index),
.size = 4096,
}, "one line per pane");
- try bench(&s, "readdir self", reps, .{
+ try bench(&s, "readdir root", reps, .{
.tag = 7,
.op = .readdir,
- .node = @intFromEnum(filesystem.SelfFile.root),
+ .node = ctlfs.root,
.size = 4096,
}, "staged dirents");
try bench(&s, "read event (empty)", reps, .{
diff --git a/test/fs_namespace.zig b/test/fs_namespace.zig
index c856148d..8b6f245e 100644
--- a/test/fs_namespace.zig
+++ b/test/fs_namespace.zig
@@ -13,13 +13,13 @@ test "runtime Mount and Unmount copy command arguments and change Look resolutio
try std.testing.expectEqualStrings("peer", p.fs.mounts.items[0].name);
try std.testing.expectEqualStrings("/tmp/a socket.sock", p.fs.mounts.items[0].dial);
var path: [4096]u8 = undefined;
- try std.testing.expectEqualStrings("/n/peer/self/index", filesystem.resolve(p, "/n/peer/self/index", "/", &path).?.path);
+ try std.testing.expectEqualStrings("/n/peer/index", filesystem.resolve(p, "/n/peer/index", "/", &path).?.path);
try std.testing.expect(p.executeBuiltinLine(0, "Mount peer /tmp/other.sock"));
try std.testing.expectEqual(@as(usize, 1), p.fs.mounts.items.len);
try std.testing.expectEqualStrings("/tmp/a socket.sock", p.fs.mounts.items[0].dial);
try std.testing.expect(p.executeBuiltinLine(0, "Unmount peer"));
try std.testing.expectEqual(@as(usize, 0), p.fs.mounts.items.len);
- try std.testing.expect(filesystem.resolve(p, "/n/peer/self/index", "/", &path) == null);
+ try std.testing.expect(filesystem.resolve(p, "/n/peer/index", "/", &path) == null);
for ([_][]const u8{ "Mount", "Mount name", "Mount os /tmp/peer.sock", "Mount peer tcp!127.0.0.1!0", "Unmount", "Unmount name extra" }) |invalid| {
try std.testing.expect(p.executeBuiltinLine(0, invalid));
try std.testing.expectEqual(@as(usize, 0), p.fs.mounts.items.len);
@@ -185,6 +185,7 @@ test "filesystem resolves OS first then virtual with explicit mounts and parent
const fallback = filesystem.resolve(p, "index", directory, &resolved_buffer) orelse return error.MissingVirtualFallback;
try std.testing.expectEqualStrings("/virtual/index", fallback.path);
+ if (!pardes.memory.limits.embedded_sources) return;
for ([_][]const u8{ "src/pardes.zig", "/virtual/src/pardes.zig", "/virtual/src/../src/pardes.zig" }) |word| {
const found = filesystem.resolve(p, word, directory, &resolved_buffer) orelse return error.MissingEmbeddedSource;
const bytes = try filesystem.read(p, found.path);
@@ -235,6 +236,7 @@ test "explicit OS file opens and restores keep their names and request watches"
}
test "virtual file opens do not request OS watches" {
+ if (!pardes.memory.limits.embedded_sources) return error.SkipZigTest;
const p = try pardes.Pardes.init(std.testing.allocator, .{ .tty_only = true });
defer p.deinit();
while (p.nextEffect()) |_| {}
@@ -243,16 +245,16 @@ test "virtual file opens do not request OS watches" {
while (p.nextEffect()) |effect| if (effect == .watch) return error.VirtualFileWatch;
}
-test "Look at the self factory reserves its own pane and failed opens release it" {
+test "Look at the clone file reserves its own pane and failed opens release it" {
const p = try pardes.Pardes.init(std.testing.allocator, .{ .tty_only = true });
defer p.deinit();
- p.lookAt(0, "/virtual/new/ctl");
+ p.lookAt(0, "/virtual/new");
var control_count: usize = 0;
var scratch_count: usize = 0;
for (p.panes) |slot| {
const pane = slot orelse continue;
const file = pane.file orelse continue;
- if (std.mem.eql(u8, file.path, "/virtual/new/ctl")) control_count += 1;
+ if (std.mem.eql(u8, file.path, "/virtual/new")) control_count += 1;
if (file.output) |output| if (output.from == .cmd and output.from.cmd == .New) {
scratch_count += 1;
};
diff --git a/test/fs_soak.py b/test/fs_soak.py
index 1201fda2..5569a7c7 100644
--- a/test/fs_soak.py
+++ b/test/fs_soak.py
@@ -56,7 +56,7 @@ def read_fid(client, fid, limit, count):
def check_body(client, pane, count=4096):
- fid = client.open(f'/self/pane/{pane["serial"]}/body')
+ fid = client.open(f'/pane/{pane["serial"]}/body')
try:
actual = read_fid(client, fid, BODY_LIMIT, count)
finally:
@@ -127,7 +127,7 @@ def run(args):
address = unix_address
if args.transport == 'tcp':
listeners = [line.split('!') for line in
- control.read('/self/listeners').decode().splitlines()
+ control.read('/listeners').decode().splitlines()
if line.startswith('tcp!')]
assert len(listeners) == 1, listeners
transport, host, port = listeners[0]
@@ -140,11 +140,12 @@ def run(args):
for slot in range(4):
body = payload(rng)
serial = 1 if slot == 0 else new_pane(client, body)
- base = f'/self/pane/{serial}'
+ base = f'/pane/{serial}'
if slot == 0:
client.write(base + '/body', body, truncate=True)
path = root / f'pane-{slot}-0.zig'
- client.write(base + '/ctl', f'name {path}\nput\n'.encode())
+ client.write(base + '/name', f'{path}\n'.encode())
+ client.write(base + '/ctl', b'exec Save\n')
panes.append({'serial': serial, 'body': body, 'path': path, 'variant': 0})
emit(kind='start', seed=args.seed, session_pid=pid,
transport=args.transport, host='tty' if args.tty else 'detached',
@@ -161,7 +162,7 @@ def run(args):
for operation in operations:
slot = rng.randrange(len(panes))
pane = panes[slot]
- base = f'/self/pane/{pane["serial"]}'
+ base = f'/pane/{pane["serial"]}'
if operation == 'append':
addition = payload(rng, min(256, BODY_LIMIT - len(pane['body'])))
client.write(base + '/body', addition)
@@ -185,7 +186,8 @@ def run(args):
elif operation == 'save':
pane['variant'] ^= 1
pane['path'] = root / f'pane-{slot}-{pane["variant"]}.zig'
- client.write(base + '/ctl', f'name {pane["path"]}\nput\n'.encode())
+ client.write(base + '/name', f'{pane["path"]}\n'.encode())
+ client.write(base + '/ctl', b'exec Save\n')
check_bytes(pane['path'].read_bytes(), pane['body'], 'saved file')
check_bytes(client.read('/os' + str(pane['path'])), pane['body'], 'OS mount')
elif operation == 'reload':
@@ -194,19 +196,20 @@ def run(args):
client.write(base + '/ctl', b'get\n')
elif operation == 'cycle':
old_serial = pane['serial']
- client.write(base + '/ctl', b'delete\n')
+ client.write(base + '/ctl', b'exec Del\n')
pane['body'] = payload(rng)
pane['serial'] = new_pane(client, pane['body'])
assert pane['serial'] != old_serial
- base = f'/self/pane/{pane["serial"]}'
- client.write(base + '/ctl', f'name {pane["path"]}\nput\n'.encode())
+ base = f'/pane/{pane["serial"]}'
+ client.write(base + '/name', f'{pane["path"]}\n'.encode())
+ client.write(base + '/ctl', b'exec Save\n')
elif operation == 'reconnect':
client.socket.close()
client = Client(address)
check_session(client, control, pid)
check_body(control, pane)
elif operation == 'screen':
- frozen = client.open('/self/screen')
+ frozen = client.open('/screen')
try:
before = read_fid(client, frozen, SCREEN_LIMIT, 4096)
check_screen(before)
@@ -217,7 +220,7 @@ def run(args):
check_bytes(after, before, 'frozen screen')
finally:
client.close(frozen)
- fresh = client.open('/self/screen')
+ fresh = client.open('/screen')
try:
check_screen(read_fid(client, fresh, SCREEN_LIMIT, 4096))
finally:
@@ -225,8 +228,8 @@ def run(args):
operation_count += 1
check_body(client, pane)
expected = {str(pane['serial']) for pane in panes}
- assert set(client.list('/self/pane')) == expected
- assert set(control.list('/self/pane')) == expected
+ assert set(client.list('/pane')) == expected
+ assert set(control.list('/pane')) == expected
for pane in panes:
check_body(client, pane)
check_body(control, pane)
diff --git a/test/look_placement.py b/test/look_placement.py
index e9918c5d..1341e776 100644
--- a/test/look_placement.py
+++ b/test/look_placement.py
@@ -70,7 +70,7 @@ def run_case(args, root, source_index):
require(len(serials) == 3, 'classic fixture did not boot three terminals')
markers = {}
for index, serial in enumerate(serials):
- pane = f'/self/pane/{serial}'
+ pane = f'/pane/{serial}'
require('pty' in client.list(pane), 'fixture contains an existing document')
marker = f'LP_{index}'
markers[serial] = marker
@@ -89,7 +89,7 @@ def run_case(args, root, source_index):
require(before[serials[0]][:2] == (0, 2) and before[serials[1]][0] == 0 and
before[serials[1]][1] > 2 and before[serials[2]][:2] == (75, 2),
f'unexpected initial classic layout: {before}')
- client.write(f'/self/pane/{source}/ctl', f'look {target}\n'.encode())
+ client.write(f'/pane/{source}/ctl', f'look {target}\n'.encode())
opened = require(wait_for(lambda: next((serial for serial in panes(client)
if serial not in serials), None)),
'Look did not open the first document')
@@ -111,9 +111,9 @@ def run_case(args, root, source_index):
require(rectangle_text(before_frame, before[serial]) ==
rectangle_text(after_frame, after[serial]),
f'Look changed unrelated terminal {serial} rendered text')
- require(client.read(f'/self/pane/{opened}/body') == contents,
+ require(client.read(f'/pane/{opened}/body') == contents,
'opened document contents differ')
- client.write(f'/self/pane/{source}/ctl', f'look {target}:2\n'.encode())
+ client.write(f'/pane/{source}/ctl', f'look {target}:2\n'.encode())
require(list(panes(client)) == serials + [opened], 'repeated Look created a duplicate pane')
require(wait_for(lambda: geometry(client, markers)[1] == after),
'repeated Look changed layout')
diff --git a/test/ninep.py b/test/ninep.py
index b128cc8a..6c625951 100644
--- a/test/ninep.py
+++ b/test/ninep.py
@@ -116,7 +116,44 @@ class Client:
self.close(fid)
def screen(self):
- return json.loads(self.read('/self/screen'))
+ return json.loads(self.read('/screen'))
+
+ def walk(self, path):
+ names = [part for part in os.fspath(path).split('/') if part]
+ self.fid += 1
+ fid = self.fid
+ oldfid = 1
+ try:
+ for start in range(0, max(1, len(names)), 16):
+ part = names[start:start + 16]
+ reply = self.rpc(110, struct.pack('<IIH', oldfid, fid, len(part)) +
+ b''.join(string(name) for name in part))
+ if struct.unpack_from('<H', reply)[0] != len(part):
+ raise FileNotFoundError(path)
+ oldfid = fid
+ return fid
+ except BaseException:
+ try:
+ self.close(fid)
+ except OSError:
+ pass
+ raise
+
+ def stat(self, path):
+ """Tstat without an open: {'name', 'mode', 'length', 'mtime', 'qid'}."""
+ fid = self.walk(path)
+ try:
+ reply = self.rpc(124, struct.pack('<I', fid))
+ finally:
+ self.close(fid)
+ size = struct.unpack_from('<H', reply, 2)[0]
+ record = reply[4:4 + size]
+ qid = record[6:19]
+ mode, _, mtime, length = struct.unpack_from('<IIIQ', record, 19)
+ name_len = struct.unpack_from('<H', record, 39)[0]
+ name = record[41:41 + name_len].decode()
+ return {'name': name, 'mode': mode & 0o777, 'dir': bool(mode & 0x80000000),
+ 'length': length, 'mtime': mtime, 'qid': qid}
def write(self, path, data, truncate=False):
fid = self.open(path, 1 | (16 if truncate else 0))
diff --git a/test/output.zig b/test/output.zig
index 21e9f04c..07a1ce23 100644
--- a/test/output.zig
+++ b/test/output.zig
@@ -575,6 +575,12 @@ test "EffectCode lists readable virtual sources used by this backend" {
const p = try Pardes.init(std.testing.allocator, .{ .tty_only = true });
defer p.deinit();
+ if (!pardes.memory.limits.embedded_sources) {
+ const before = p.active;
+ try std.testing.expect(p.executeBuiltinLine(0, "EffectCode PanelSlide"));
+ try std.testing.expectEqual(before, p.active);
+ return error.SkipZigTest;
+ }
try std.testing.expect(p.executeBuiltinLine(0, "EffectCode PanelSlide"));
const out = p.panes[p.active].?.file.?;
try std.testing.expectEqualStrings(config.effect_code_buffer, std.fs.path.basename(out.path));
@@ -1035,10 +1041,10 @@ test "identical result refresh keeps warm content caches without allocation" {
const style_ptr = file.highlights.ptr;
const highlight_start = file.highlight_start;
const selected = result.vsel;
- const event_node = pardes.filesystem.Node.of(result.serial, .event);
- const opened = pardes.filesystem.handle(p, .{ .tag = 0, .op = .open, .node = event_node });
+ const event_node = pardes.ctlfs.Node.of(result.serial, .event);
+ const opened = pardes.ctlfs.handle(p, .{ .tag = 0, .op = .open, .node = event_node });
try std.testing.expectEqual(.ok, opened.status);
- defer _ = pardes.filesystem.handle(p, .{ .tag = 0, .op = .release, .node = event_node, .handle = opened.handle });
+ defer _ = pardes.ctlfs.handle(p, .{ .tag = 0, .op = .release, .node = event_node, .handle = opened.handle });
for (0..32) |_| {
const incoming = try p.gpa.dupe(u8, text);
diff --git a/test/panes.zig b/test/panes.zig
index 81015356..d3c29856 100644
--- a/test/panes.zig
+++ b/test/panes.zig
@@ -834,10 +834,10 @@ const ClickCursorTests = struct {
for (0..2) |_| p.update(.{ .mouse = .{ .button = .wheel_down, .kind = .press, .col = x + 2, .row = y + 4 } });
try std.testing.expectEqual(@as(usize, 2), pane.file.?.scroll);
_ = try p.render(frame.allocator());
- const event_node = filesystem.Node.of(pane.serial, .event);
- const opened = filesystem.handle(p, .{ .tag = 0, .op = .open, .node = event_node });
+ const event_node = pardes.ctlfs.Node.of(pane.serial, .event);
+ const opened = pardes.ctlfs.handle(p, .{ .tag = 0, .op = .open, .node = event_node });
try std.testing.expectEqual(.ok, opened.status);
- defer _ = filesystem.handle(p, .{ .tag = 0, .op = .release, .node = event_node, .handle = opened.handle });
+ defer _ = pardes.ctlfs.handle(p, .{ .tag = 0, .op = .release, .node = event_node, .handle = opened.handle });
for ([_]pardes.Mouse.Button{ .middle, .right }) |button| {
const events = &p.fs.panes[0].events;
@@ -1907,7 +1907,7 @@ test "file history allocation precedes a filesystem read with side effects" {
const original = p.panes[0].?;
const serial = p.next_serial;
failing.fail_index = failing.alloc_index + 2;
- try std.testing.expectError(error.OutOfMemory, panes.File.open(p, 1, "/n/self/new/ctl", 0));
+ try std.testing.expectError(error.OutOfMemory, panes.File.open(p, 1, "/n/self/new", 0));
try std.testing.expect(failing.has_induced_failure);
try std.testing.expectEqual(original, p.panes[0].?);
try std.testing.expectEqual(serial, p.next_serial);
diff --git a/test/pdf_scroll_bench.zig b/test/pdf_scroll_bench.zig
index 31aab270..33dfe70a 100644
--- a/test/pdf_scroll_bench.zig
+++ b/test/pdf_scroll_bench.zig
@@ -547,9 +547,9 @@ fn verifyIdentity(
" expected frames={d} images={d} visual={x:0>16} transport={x:0>16}\n" ++
" got frames={d} images={d} visual={x:0>16} transport={x:0>16}\n",
.{
- name, round, want.frames, want.images,
- want.visual, want.transport, got.frames, got.images,
- got.visual, got.transport,
+ name, round, want.frames, want.images,
+ want.visual, want.transport, got.frames, got.images,
+ got.visual, got.transport,
},
);
return error.UnstableIdentity;
@@ -569,8 +569,8 @@ fn reportText(config: Config, travel: Travel, results: []const Result) void {
std.debug.print(
"{s}: {d} pages, {d}px strip at {d}x{d} cells of {d}x{d}px\n",
.{
- config.path, travel.pages, travel.strip_px, bench_cols,
- bench_rows, bench_cell_pixels.w, bench_cell_pixels.h,
+ config.path, travel.pages, travel.strip_px, bench_cols,
+ bench_rows, bench_cell_pixels.w, bench_cell_pixels.h,
},
);
std.debug.print(
@@ -578,9 +578,9 @@ fn reportText(config: Config, travel: Travel, results: []const Result) void {
.{ config.warmup, config.reps },
);
std.debug.print("{s:<16} {s:>6} {s:>7} {s:>10} {s:>10} {s:>10} {s:>10} {s:>10} {s:>11} {s:>7} {s:>5} {s:<16} {s}\n", .{
- "scenario", "frames", "notches", "min",
- "median", "p90", "p99", "max",
- "total_us", "rasters", "kept", "visual",
+ "scenario", "frames", "notches", "min",
+ "median", "p90", "p99", "max",
+ "total_us", "rasters", "kept", "visual",
"transport",
});
std.debug.print("{s}\n", .{"-" ** 160});
@@ -618,7 +618,7 @@ fn reportJson(io: std.Io, config: Config, travel: Travel, results: []const Resul
"{{\"benchmark\":\"pardes-pdf-scroll\",\"build\":\"{s}\",\"path\":\"{s}\"," ++
"\"pages\":{d},\"strip_px\":{d},\"warmup\":{d},\"reps\":{d},\"results\":[",
.{
- bench_config.core_optimize, config.path, travel.pages,
+ bench_config.core_optimize, config.path, travel.pages,
travel.strip_px, config.warmup, config.reps,
},
) catch return;
diff --git a/test/snapshot.zig b/test/snapshot.zig
index a7a1ba48..7fa84a86 100644
--- a/test/snapshot.zig
+++ b/test/snapshot.zig
@@ -1151,7 +1151,7 @@ const Screen9p = struct {
fn snap9p(arena: std.mem.Allocator, h: *eh.Harness, label: []const u8, prev: *Prev) ![]u8 {
var dial_buf: [24]u8 = undefined;
const dial = try std.fmt.bufPrint(&dial_buf, "{d}", .{h.pid});
- const bytes = try Client.read(gpa, dial, "/self/screen", "/self/screen");
+ const bytes = try Client.read(gpa, dial, "/screen", "/screen");
defer gpa.free(bytes);
const parsed = try std.json.parseFromSlice(Screen9p, gpa, bytes, .{});
defer parsed.deinit();
diff --git a/test/tag_cleanup.py b/test/tag_cleanup.py
index 52ee8e9d..7b7d8bbf 100644
--- a/test/tag_cleanup.py
+++ b/test/tag_cleanup.py
@@ -28,7 +28,7 @@ def run(args):
launch=launch, inherited=inherited, terminal=terminal) as (client, _):
keys = HostInput(terminal['input_fd'])
pdf = next(serial for serial, tag in panes(client).items() if str(source) in tag)
- pdf_tag = client.read(f'/self/pane/{pdf}/tag').decode().strip()
+ pdf_tag = client.read(f'/pane/{pdf}/tag').decode().strip()
require(pdf_tag.startswith(str(source) + ' [1/'), 'PDF name/page prefix missing')
require(pdf_tag.endswith('] Tty Del PdfSections PdfTint Collapse'), 'PDF commands missing')
require(all(word not in pdf_tag for word in ['PdfFit', 'filtered', 'full', 'disabled']), 'PDF tag contains fit or tint-state clutter')
@@ -36,7 +36,7 @@ def run(args):
before = set(panes(client))
execute(client, control, 'Tty')
tty = require(wait_for(lambda: next((s for s in panes(client) if s not in before), None)), 'terminal missing')
- client.write(f'/self/pane/{control}/ctl', b'delete\n') # owned command-only scratch
+ client.write(f'/pane/{control}/ctl', b'exec Del\n') # owned command-only scratch
def tag_position():
frame = client.screen()
@@ -64,11 +64,11 @@ def run(args):
keys.send(b'\t')
require(wait_for(lambda: (p := tag_position()) and p[2] == ' '), 'Mode from edited tag ignored parked raw mode')
keys.send(b'\x02')
- client.write(f'/self/pane/{tty}/pty/data', b"printf 'Mode toggle ready\\n'\r")
- require(wait_for(lambda: b'Mode toggle ready' in client.read(f'/self/pane/{tty}/body')), 'terminal input failed')
+ client.write(f'/pane/{tty}/pty/data', b"printf 'Mode toggle ready\\n'\r")
+ require(wait_for(lambda: b'Mode toggle ready' in client.read(f'/pane/{tty}/body')), 'terminal input failed')
capture(client, root, 'pdf-and-tty-tags', not args.tty)
require(source.read_bytes() == original, 'PDF file changed')
- client.write(f'/self/pane/{tty}/pty/data', b'exit\r')
+ client.write(f'/pane/{tty}/pty/data', b'exit\r')
print('PDF tag, Mode click, Ctrl-B, edited-tag execution and source preservation passed')
diff --git a/test/ui_review.py b/test/ui_review.py
index df492eac..2b6faa29 100644
--- a/test/ui_review.py
+++ b/test/ui_review.py
@@ -21,7 +21,7 @@ from fs import session
def panes(client):
return {int(row.split(maxsplit=5)[0]): row.split(maxsplit=5)[5]
- for row in client.read('/self/index').decode().splitlines()}
+ for row in client.read('/index').decode().splitlines()}
def wait_for(fn, seconds=12):
deadline = time.monotonic() + seconds
@@ -34,7 +34,7 @@ def wait_for(fn, seconds=12):
def command(client, serial, text):
"""Execute a temporary tag command without changing source contents."""
- tag = f'/self/pane/{serial}/tag'
+ tag = f'/pane/{serial}/tag'
before = client.read(tag).rstrip(b'\n')
tail_start = before.index(b'Save Tty Del')
while tail_start and before[tail_start - 1:tail_start] == b' ':
@@ -42,13 +42,13 @@ def command(client, serial, text):
client.write(tag, b' ' + text.encode())
current = client.read(tag)
start = current.rfind(text.encode())
- client.write(f'/self/pane/{serial}/event', f'Mx{start} {start + len(text.encode())}\n'.encode())
- client.write(f'/self/pane/{serial}/ctl', b'cleartag\n')
+ client.write(f'/pane/{serial}/event', f'Mx{start} {start + len(text.encode())}\n'.encode())
+ client.write(f'/pane/{serial}/ctl', b'cleartag\n')
client.write(tag, before[tail_start:])
def new_output(client, before):
return next((serial for serial in panes(client) if serial not in before and
- client.read(f'/self/pane/{serial}/body').strip()), None)
+ client.read(f'/pane/{serial}/body').strip()), None)
def require(value, description):
if not value:
@@ -63,7 +63,7 @@ def capture(client, root, label, gui):
text = '\n'.join(''.join(cell[0] for cell in screen['cells'][at:at+cols])
for at in range(0, len(screen['cells']), cols))
(root / (label + '.txt')).write_text(text)
- (root / (label + '-index.txt')).write_text(client.read('/self/index').decode())
+ (root / (label + '-index.txt')).write_text(client.read('/index').decode())
if gui:
require(wait_for(lambda: (root / 'latest.ppm').exists(), 8), 'GPU capture missing')
def convert_frame():
@@ -106,28 +106,28 @@ def run(args):
before = panes(client)
command(client, source, 'Find crds_value')
find_pane = require(wait_for(lambda: new_output(client, before)), 'Find did not return results')
- require(b'crds_value.rs' in client.read(f'/self/pane/{find_pane}/body'), 'Find result missing source')
+ require(b'crds_value.rs' in client.read(f'/pane/{find_pane}/body'), 'Find result missing source')
report['find'] = True
capture(client, root, '02-find', not args.tty)
if find_pane:
- (root / 'find-results.txt').write_bytes(client.read(f'/self/pane/{find_pane}/body'))
- client.write(f'/self/pane/{find_pane}/ctl', b'del\n')
+ (root / 'find-results.txt').write_bytes(client.read(f'/pane/{find_pane}/body'))
+ client.write(f'/pane/{find_pane}/ctl', b'exec Del\n')
before = panes(client)
command(client, source, 'Grep CrdsValueLabel')
grep_pane = require(wait_for(lambda: new_output(client, before)), 'Grep did not return results')
- require(b'CrdsValueLabel' in client.read(f'/self/pane/{grep_pane}/body'), 'Grep result missing match')
+ require(b'CrdsValueLabel' in client.read(f'/pane/{grep_pane}/body'), 'Grep result missing match')
report['grep'] = True
if grep_pane:
- (root / 'grep-results.txt').write_bytes(client.read(f'/self/pane/{grep_pane}/body'))
+ (root / 'grep-results.txt').write_bytes(client.read(f'/pane/{grep_pane}/body'))
capture(client, root, '03-grep', not args.tty)
command(client, source, 'Tty')
terminal = require(wait_for(lambda: next((serial for serial in panes(client)
- if 'pty' in client.list(f'/self/pane/{serial}')), None)),
+ if 'pty' in client.list(f'/pane/{serial}')), None)),
'Tty did not create a terminal')
if terminal:
- client.write(f'/self/pane/{terminal}/pty/data', b"printf '\\033[1;36mAgave workspace\\033[0m\\n'; pwd; printf '\\033[32mRead-only UI review ready\\033[0m\\n'\r")
+ client.write(f'/pane/{terminal}/pty/data', b"printf '\\033[1;36mAgave workspace\\033[0m\\n'; pwd; printf '\\033[32mRead-only UI review ready\\033[0m\\n'\r")
require(wait_for(lambda: b'\nRead-only UI review ready\n' in
- client.read(f'/self/pane/{terminal}/body').replace(b'\r', b'')),
+ client.read(f'/pane/{terminal}/body').replace(b'\r', b'')),
'Terminal command output was not observed')
report['terminal'] = True
capture(client, root, '04-terminal', not args.tty)
@@ -137,46 +137,46 @@ def run(args):
symbols = wait_for(lambda: new_output(client, before), args.lsp_wait)
report['symbols'] = 'returned results' if symbols else 'no output before deadline'
if symbols:
- (root / 'symbols-results.txt').write_bytes(client.read(f'/self/pane/{symbols}/body'))
+ (root / 'symbols-results.txt').write_bytes(client.read(f'/pane/{symbols}/body'))
capture(client, root, '05-symbols', not args.tty)
if symbols:
- client.write(f'/self/pane/{symbols}/ctl', b'del\n')
+ client.write(f'/pane/{symbols}/ctl', b'exec Del\n')
offset = original.index(b'CrdsValue {')
- client.write(f'/self/pane/{source}/addr', f'#{offset},#{offset}'.encode())
- client.write(f'/self/pane/{source}/ctl', b'dot=addr\nshow\n')
+ client.write(f'/pane/{source}/addr', f'#{offset},#{offset}'.encode())
+ client.write(f'/pane/{source}/ctl', b'dot=addr\nshow\n')
before = panes(client)
command(client, source, 'Hover')
hover = wait_for(lambda: new_output(client, before), args.lsp_wait)
report['hover'] = 'returned results' if hover else 'no output before deadline'
if hover:
- (root / 'hover-results.txt').write_bytes(client.read(f'/self/pane/{hover}/body'))
+ (root / 'hover-results.txt').write_bytes(client.read(f'/pane/{hover}/body'))
capture(client, root, '06-hover', not args.tty)
# Look focuses the source without changing it; gd then travels through
# the host's real input parser, unlike tag builtin dispatch above.
line = original[:original.index(b'impl Sanitize for CrdsValue')].count(b'\n') + 1
- client.write(f'/self/pane/{source}/ctl', f'look {source_path}:{line}:19\n'.encode())
+ client.write(f'/pane/{source}/ctl', f'look {source_path}:{line}:19\n'.encode())
os.write(terminal_handle['input_fd'], b'gd')
time.sleep(1)
capture(client, root, '07-definition', not args.tty)
command(client, source, 'Lspinfo')
lsp = wait_for(lambda: next((serial for serial, tag in panes(client).items() if '+Lsp' in tag), None))
if lsp:
- info = client.read(f'/self/pane/{lsp}/body')
+ info = client.read(f'/pane/{lsp}/body')
(root / 'lsp-info.txt').write_bytes(info)
report['lsp'] = info.decode()
capture(client, root, '08-lsp-info', not args.tty)
if lsp:
- client.write(f'/self/pane/{lsp}/ctl', b'del\n')
+ client.write(f'/pane/{lsp}/ctl', b'exec Del\n')
for theme in args.themes:
command(client, source, 'Theme ' + theme)
capture(client, root, 'theme-' + theme, not args.tty)
for serial, tag in panes(client).items():
if any(word in tag for word in ['+Hover', '+Search']):
- (root / f'pane-{serial}.txt').write_bytes(client.read(f'/self/pane/{serial}/body'))
- assert client.read(f'/self/pane/{source}/body') == original == source_path.read_bytes()
+ (root / f'pane-{serial}.txt').write_bytes(client.read(f'/pane/{serial}/body'))
+ assert client.read(f'/pane/{source}/body') == original == source_path.read_bytes()
report['source_unchanged'] = True
if terminal:
- client.write(f'/self/pane/{terminal}/pty/data', b'exit\r')
+ client.write(f'/pane/{terminal}/pty/data', b'exit\r')
(root / 'report.json').write_text(json.dumps(report, indent=2))
print('artifacts', root, flush=True)
diff --git a/test/v9fs.py b/test/v9fs.py
index 7dc88195..16916d0b 100644
--- a/test/v9fs.py
+++ b/test/v9fs.py
@@ -42,25 +42,26 @@ def worker(mountpoint, socket, uid, gid, original_namespace):
assert len(mounted) == 1 and ' - 9p ' in mounted[0], mounted
assert not any(field.startswith(('shared:', 'master:')) for field in mounted[0].split()[6:])
- assert set(os.listdir(mountpoint)) == {'os', 'self'}
- tree = mountpoint / 'self'
- assert {'index', 'pane', 'new', 'screen'} <= set(os.listdir(tree))
+ tree = mountpoint
+ assert {'os', 'index', 'pane', 'new', 'ctl', 'log', 'screen', 'README'} <= set(os.listdir(tree))
+ assert 'self' not in os.listdir(tree)
# A direct connection provides independent evidence for VFS reads/writes.
with Client(socket) as client:
- assert (tree / 'index').read_bytes() == client.read('/self/index')
+ assert (tree / 'index').read_bytes() == client.read('/index')
assert (tree / 'pane/1/body').read_bytes() == b'initial\n'
- before = client.read('/self/index')
- subprocess.run(['ls', '-l', str(tree / 'new')], check=True, capture_output=True, timeout=5)
- assert {'README', 'ctl', 'body'} <= set(os.listdir(tree / 'new'))
- assert (tree / 'new/README').read_bytes() == (tree / 'README').read_bytes()
- assert client.read('/self/index') == before, 'browsing created panes'
- serial = int((tree / 'new/ctl').read_bytes().split()[0])
- another = int((tree / 'new/ctl').read_bytes().split()[0])
+ before = client.read('/index')
+ subprocess.run(['ls', '-l', str(tree), str(tree / 'pane' / '1')], check=True, capture_output=True, timeout=5)
+ subprocess.run(['find', str(tree / 'pane'), str(tree / 'new'), '-ls'], check=True, capture_output=True, timeout=5)
+ assert (tree / 'README').read_bytes() == client.read('/README')
+ assert (tree / 'new').stat().st_size == 0
+ assert client.read('/index') == before, 'browsing created panes'
+ serial = int((tree / 'new').read_bytes().split()[0])
+ another = int((tree / 'new').read_bytes().split()[0])
assert serial != another, 'cached factory reused a pane'
- client.write(f'/self/pane/{another}/ctl', b'delete\n')
+ client.write(f'/pane/{another}/ctl', b'exec Del\n')
pane = tree / 'pane' / str(serial)
- wire = f'/self/pane/{serial}'
+ wire = f'/pane/{serial}'
assert str(serial) in os.listdir(tree / 'pane')
assert serial in [int(row.split()[0]) for row in (tree / 'index').read_bytes().splitlines()]
@@ -76,7 +77,7 @@ def worker(mountpoint, socket, uid, gid, original_namespace):
assert client.read(wire + '/body') == b'v9fs update\nappended\n'
# Exercise an actual shell redirection, including its O_TRUNC open.
- subprocess.run(['/bin/sh', '-c', 'printf "name kernel-probe\\n" > "$1/ctl"',
+ subprocess.run(['/bin/sh', '-c', 'printf "kernel-probe\\n" > "$1/name"',
'v9fs-probe', str(pane)], check=True, timeout=5)
tag = client.read(wire + '/tag')
assert tag.split(maxsplit=1)[0] == str(socket.parent / 'kernel-probe').encode(), tag
@@ -100,7 +101,7 @@ def worker(mountpoint, socket, uid, gid, original_namespace):
# Reading OS files through the exported tree does not recurse through
# the mount: the core still lives in the supervisor's namespace.
assert (mountpoint / 'os' / str(socket.parent).lstrip('/') / 'kernel.txt').read_bytes() == b'initial\n'
- write_existing(pane / 'ctl', b'delete\n')
+ write_existing(pane / 'ctl', b'exec Del\n')
assert serial not in [int(row.split()[0]) for row in (tree / 'index').read_bytes().splitlines()]
print('v9fs: namespace isolation, privilege drop, inherited mount, directory refresh, '
@@ -154,7 +155,7 @@ def run(binary, helper):
print(run_helper(command), end='')
assert os.readlink('/proc/self/ns/mnt') == namespace
assert list(target.iterdir()) == [], 'mount escaped its private namespace'
- assert client.read('/self/pane/1/body') == b'initial\n', 'core stopped serving after probe exit'
+ assert client.read('/pane/1/body') == b'initial\n', 'core stopped serving after probe exit'
print('v9fs: supervisor namespace unchanged and session cleanup passed')
diff --git a/test/v9fs_terminal.py b/test/v9fs_terminal.py
index cc273a40..b49246d1 100644
--- a/test/v9fs_terminal.py
+++ b/test/v9fs_terminal.py
@@ -74,7 +74,7 @@ sys.exit(1)
execute(client, control, 'Tty9p')
until(report_path.exists, 'Tty9p did not reach the sudo stand-in')
report = json.loads(report_path.read_text())
- pane = f"/self/pane/{report['pane']}"
+ pane = f"/pane/{report['pane']}"
until(lambda: b'V9FS_AUTH_READY' in client.read(pane + '/body'), 'prompt not visible')
assert report['socket'] == str(address)
assert report['argv'][:2] == ['-E', '--'], report
@@ -85,7 +85,7 @@ sys.exit(1)
assert report['path'] == path
assert report['shell'] == str(Path(shell).resolve()), report
# The detached core remains responsive during authentication.
- assert client.read('/self/pane/1/body') == b'initial\n'
+ assert client.read('/pane/1/body') == b'initial\n'
target = Path(report['mount'])
assert target.is_dir() and list(target.iterdir()) == []
if interrupted:
@@ -100,7 +100,7 @@ sys.exit(1)
client.write(pane + '/pty/data', b'printf "OUTER_READY:%s\\n" "$PARDES_PANE"\r')
expected = ('OUTER_READY:' + report['pane']).encode()
until(lambda: expected in client.read(pane + '/body'), 'original shell did not remain usable')
- assert client.read('/self/pane/1/body') == b'initial\n'
+ assert client.read('/pane/1/body') == b'initial\n'
print('Tty9p: shell-first startup, quoted paths, hidden password input, failure recovery and cleanup passed')
diff --git a/test/window_opacity.py b/test/window_opacity.py
index 8abe8677..b9529373 100644
--- a/test/window_opacity.py
+++ b/test/window_opacity.py
@@ -49,9 +49,9 @@ def run(args):
command(client, source, 'Tty')
terminal = require(wait_for(lambda: next((serial for serial in panes(client)
if serial not in before), None)), 'terminal missing')
- client.write(f'/self/pane/{terminal}/pty/data',
+ client.write(f'/pane/{terminal}/pty/data',
b"PS1='$ '; printf '\\033[2J\\033[H\\033[36mTerminal opacity sample\\033[0m\\n'\r")
- require(wait_for(lambda: b'Terminal opacity sample' in client.read(f'/self/pane/{terminal}/body')),
+ require(wait_for(lambda: b'Terminal opacity sample' in client.read(f'/pane/{terminal}/body')),
'terminal output missing')
def output(builtin):
@@ -59,8 +59,8 @@ def run(args):
command(client, source, builtin)
pane = require(wait_for(lambda: next((serial for serial in panes(client)
if serial not in before), None)), builtin + ' output missing')
- text = client.read(f'/self/pane/{pane}/body').decode()
- client.write(f'/self/pane/{pane}/ctl', b'del\n')
+ text = client.read(f'/pane/{pane}/body').decode()
+ client.write(f'/pane/{pane}/ctl', b'exec Del\n')
return text
help_text = output('Help')
@@ -208,9 +208,9 @@ def run(args):
require(all(int(value) == 0xffffffff for value in multipliers),
'whole-window alpha modifier would fade foreground text')
require('WindowOpacity: 100%\n' in output('Config'), '100% opacity not restored')
- require(source_path.read_bytes() == original == client.read(f'/self/pane/{source}/body'),
+ require(source_path.read_bytes() == original == client.read(f'/pane/{source}/body'),
'source contents changed')
- client.write(f'/self/pane/{terminal}/pty/data', b'exit\r')
+ client.write(f'/pane/{terminal}/pty/data', b'exit\r')
report.update(source_unchanged=True, core_rgb_and_text_unchanged=True,
invalid_value_ignored=True, help_updated=True, restored_opaque=True)
(root / 'report.json').write_text(json.dumps(report, indent=2) + '\n')