summaryrefslogtreecommitdiff
path: root/test/fs.py
diff options
context:
space:
mode:
Diffstat (limited to 'test/fs.py')
-rw-r--r--test/fs.py134
1 files changed, 82 insertions, 52 deletions
diff --git a/test/fs.py b/test/fs.py
index 819a78d6..c3ffa440 100644
--- a/test/fs.py
+++ b/test/fs.py
@@ -100,8 +100,15 @@ def session(binary, root, name, *options, socket_name=None, file=None, tty=False
assert not reader.is_alive(), 'terminal reader did not stop'
+def newest(client):
+ """The newest pane's serial: /index is ordered by serial."""
+ return int(client.read('/index').splitlines()[-1].split()[0])
+
+
def new_pane(client, contents):
- serial = int(client.read('/new').split()[0])
+ """mkdir in /pane; the editor names the directory after the new serial."""
+ client.mkdir('/pane', 'scratch')
+ serial = newest(client)
if contents:
client.write(f'/pane/{serial}/body', contents)
return serial
@@ -110,18 +117,18 @@ def new_pane(client, contents):
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'/pane/{serial}/ctl', f'look {path}\n'.encode())
- results = [int(row) for row in client.read('/ctl').split()]
+ client.write(f'/pane/{serial}/look', f'{path}\n'.encode())
+ results = [int(row) for row in client.read('/look').split()]
assert len(results) == 1 and results[0] != serial, (path, results)
return results[0]
def execute(client, serial, command):
"""A middle click on `command` from a pane."""
- client.write(f'/pane/{serial}/ctl', f'exec {command}\n'.encode())
+ client.write(f'/pane/{serial}/exec', f'{command}\n'.encode())
-def walk_tree(client, path='/', skip=('/new', '/log', '/os', '/screen')):
+def walk_tree(client, path='/', skip=('/log', '/os', '/screen')):
"""Stat every entry below `path`, as `find` does, without opening anything."""
seen = []
for name in client.list(path):
@@ -138,18 +145,21 @@ def discovery(binary, embedded=False):
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 top[:10] == ['README', 'index', 'status', 'look', 'exec', 'log', 'screen',
+ 'listeners', 'pane', 'os'], top
+ assert 'self' not in top and 'cons' not in top and 'new' not in top, top
assert ('src' in top) == embedded, (top, embedded)
before = client.read('/index')
guide = client.read('/README')
- assert guide.count(b'\n') <= 45 and b'exec Msg' in guide and b'/new' in guide, guide
+ assert guide.count(b'\n') <= 45 and b'Msg hello' in guide and b'mkdir' 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 {'/index', '/status', '/look', '/exec', '/log', '/pane/1', '/pane/1/body',
+ '/pane/1/name', '/pane/1/sel', '/pane/1/ctl', '/pane/1/tag', '/pane/1/event',
+ '/pane/1/dot', '/pane/1/limit', '/pane/1/dirty', '/pane/1/mark',
+ '/pane/1/scroll', '/pane/1/look', '/pane/1/exec'} <= 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)
@@ -159,18 +169,28 @@ def discovery(binary, embedded=False):
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 index['mode'] == 0o444 and client.stat('/status')['mode'] == 0o444
+ assert client.stat('/exec')['mode'] == 0o666
assert client.stat('/pane/1/body')['mode'] == 0o666 and client.stat('/pane')['mode'] == 0o755
+ # A range file reads the pair it takes; a flag file reads 0 or 1.
+ assert client.stat('/pane/1/addr')['length'] == 24
+ assert client.stat('/pane/1/limit')['length'] == 0
+ assert client.read('/pane/1/dirty') == b'0\n'
+ assert client.read('/pane/1/scroll') == b'1\n'
fixture = int(before.split()[0])
assert before.split()[1:3] == [b'text', b'0'] and before.endswith(b'/discovery.txt\n'), before
- # 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.
+ # /status reports the editor; /exec has nothing to report yet.
+ status = dict(line.split(maxsplit=1) for line in client.read('/status').decode().splitlines())
+ assert int(status['pid']) > 0 and status['version'] and int(status['panes']) >= 1, status
+ assert client.read('/exec') == b''
+ # mkdir makes a pane, named by the serial the editor gives it.
log = client.open('/log')
- first = int(client.read('/new').split()[0])
- second = int(client.read('/new').split()[0])
+ client.mkdir('/pane', 'one')
+ first = newest(client)
+ client.mkdir('/pane', 'two')
+ second = newest(client)
assert first != second and first != fixture, (first, second)
+ assert 'one' not in client.list('/pane') and 'two' not in client.list('/pane')
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)}
@@ -186,51 +206,63 @@ def discovery(binary, embedded=False):
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')
+ # Copying addr onto dot selects that range; sel reads and replaces it.
client.write(f'/pane/{first}/addr', b'#0,#5')
- client.write(f'/pane/{first}/ctl', b'dot=addr\n')
+ client.write(f'/pane/{first}/dot', client.read(f'/pane/{first}/addr'))
+ assert client.read(f'/pane/{first}/dot') == client.read(f'/pane/{first}/addr')
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')
+ # exec Msg shows text; exec Save writes the named file; rmdir closes.
+ client.write(f'/pane/{first}/exec', b'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 client.read(f'/pane/{first}/dirty') == b'1\n'
+ client.write(f'/pane/{first}/exec', b'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('/exec') == f'{first}\n'.encode()
+ assert client.read(f'/pane/{first}/dirty') == b'0\n'
+ client.remove(f'/pane/{second}')
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.
+ # look 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'))
+ client.write('/look', f'{root}/opened.txt:2\n'.encode())
+ opened = int(client.read('/look'))
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)}
+ client.write(f'/pane/{first}/look', f'{root}/opened.txt:3\n'.encode())
+ assert client.read('/look') == f'{opened}\n'.encode(), 'a repeated look targets the open pane'
+ assert {str(fixture), str(first), str(opened)} <= set(client.list('/pane'))
# 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']:
+ # A command line may not carry control characters, and nothing else
+ # in the tree may be created or removed.
+ refused = [lambda: client.write('/exec', b'Msg a\x00b'),
+ lambda: client.write('/status', b'anything\n'),
+ lambda: client.mkdir('/', 'x'),
+ lambda: client.create('/pane', 'plain-file'),
+ lambda: client.remove('/pane/1/body'),
+ lambda: client.remove('/index')]
+ for index, attempt in enumerate(refused):
try:
- client.write('/ctl', bad)
+ attempt()
except OSError:
pass
else:
- raise AssertionError(f'{bad!r} was accepted')
+ raise AssertionError(f'refusal {index} 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')
+ print('9P discovery: listing/stat/find are inert; create, remove, look, exec, name, sel and log behave')
def test(binary, quic=False):
@@ -266,7 +298,7 @@ def test(binary, quic=False):
assert not (root / 'pardes-9p-renamed.sock').exists()
assert named.read('/pane/1/body') == b'initial\n'
with session(binary, root, 'first') as (client, address):
- assert {'pane', 'index', 'new', 'ctl', 'log', 'os'} <= set(client.list('/'))
+ assert {'pane', 'index', 'status', 'look', 'exec', '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'
@@ -346,7 +378,7 @@ def test(binary, quic=False):
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')
+ remote.write(f'/pane/{opened}/exec', b'Save\n')
assert client.read(body) == b'saved to peer\n'
large = b'mounted file contents\n' * 60000
@@ -356,7 +388,7 @@ def test(binary, quic=False):
assert remote.read(f'/pane/{opened}/body') == large
edited = large[:-3] + b'edited\n'
remote.write(f'/pane/{opened}/body', edited, truncate=True)
- remote.write(f'/pane/{opened}/ctl', b'exec Save\n')
+ remote.write(f'/pane/{opened}/exec', b'Save\n')
assert large_path.read_bytes() == edited
assert client.read('/os' + str(large_path)) == edited
@@ -368,12 +400,12 @@ def test(binary, quic=False):
execute(remote, control, 'Mount peer ' + str(own_address))
execute(remote, control, 'Unmount peer')
remote.write(f'/pane/{opened}/body', b'runtime mounted Save\n', truncate=True)
- remote.write(f'/pane/{opened}/ctl', b'exec Save\n')
+ remote.write(f'/pane/{opened}/exec', b'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'/pane/{roots}/body')
- remote.write(f'/pane/{opened}/ctl', b'exec Del\n')
+ remote.write(f'/pane/{opened}/exec', b'Del\n')
execute(remote, control, 'Unmount peer')
remote.write(f'/pane/{roots}/ctl', b'get\n')
assert remote.read(f'/pane/{roots}/body') == b'/n/os/\n/n/self/\n'
@@ -385,7 +417,7 @@ def test(binary, quic=False):
file='/n/peer/pane/1/body:1') as (remote, _):
assert remote.read('/pane/1/body') == b'runtime mounted Save\n'
- client.write(f'/pane/{serial}/ctl', b'exec Del\n')
+ client.write(f'/pane/{serial}/exec', b'Del\n')
assert str(serial) not in client.list('/pane')
for tty in [False, True]:
@@ -424,7 +456,7 @@ def test(binary, quic=False):
file_id, control_id = restored_ids
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.read(f'/pane/{control_id}/body') == b'', 'commands ran through exec, 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'/pane/{opened}/body') == b'initial\n'
@@ -451,14 +483,12 @@ def test(binary, quic=False):
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'/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)
+ # The selection is dot; reading it takes no command at all.
+ actual = list(map(int, client.read(f'/pane/{opened}/dot').split()))
assert actual == selected, (word, actual, selected)
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')]:
+ limit = client.open('/pane/1/limit', 2)
+ for fid, text in [(addr, b'#0,#3'), (limit, b'#0,#3'), (addr, b'#0')]:
client.rpc(118, struct.pack('<IQI', fid, 0, len(text)) + text)
try:
for _ in range(8):
@@ -474,7 +504,7 @@ def test(binary, quic=False):
raise AssertionError('no-argument child cleared the parent address limit')
finally:
client.close(addr)
- client.close(ctl)
+ client.close(limit)
client.read('/pane/1/ctl')
control = new_pane(client, b'')
@@ -618,7 +648,7 @@ def quic_test(binary):
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'/pane/{opened}/ctl', b'exec Save\n')
+ remote.write(f'/pane/{opened}/exec', b'Save\n')
assert unix.read_fid(unix_body) == payload[:unix.msize - 11]
assert tcp.read('/pane/1/body') == payload
tcp.write('/pane/1/body', b'TCP to QUIC Get\n', truncate=True)
@@ -637,10 +667,10 @@ def quic_test(binary):
assert remote.read(f'/pane/{large_pane}/body') == large
edited = b'QUIC file saved\n'
remote.write(f'/pane/{large_pane}/body', edited, truncate=True)
- remote.write(f'/pane/{large_pane}/ctl', b'exec Save\n')
+ remote.write(f'/pane/{large_pane}/exec', b'Save\n')
assert fixture.read_bytes() == edited
for pane in [opened, screen_pane, large_pane]:
- remote.write(f'/pane/{pane}/ctl', b'exec Del\n')
+ remote.write(f'/pane/{pane}/exec', b'Del\n')
execute(remote, control, 'Unmount peer')
roots = look(remote, '/n', source=control)
assert remote.read(f'/pane/{roots}/body') == b'/n/os/\n/n/self/\n'
@@ -651,10 +681,10 @@ def quic_test(binary):
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')
+ unix.write(f'/pane/{own}/exec', b'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'/pane/{own}/ctl', b'exec Del\n')
+ unix.write(f'/pane/{own}/exec', b'Del\n')
execute(unix, control, 'Unmount own')
unix.close(unix_body)
tcp.close(tcp_body)