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