summaryrefslogtreecommitdiff
path: root/test
diff options
context:
space:
mode:
authorGabriel Schneider <[email protected]>2026-10-02 00:50:41 -0300
committerGabriel Schneider <[email protected]>2026-10-02 01:43:04 -0300
commit97c939b981bc7fbf1a58511a9d83d9f1e12a8f16 (patch)
tree168f1c1cb7005d4b684be312a36be205e642f7df /test
parentd65ec3394ff619118d9208718477469c673a5598 (diff)
downloadpardes-97c939b981bc7fbf1a58511a9d83d9f1e12a8f16.tar.gz
pardes-97c939b981bc7fbf1a58511a9d83d9f1e12a8f16.zip
An Edit filter that can never be reaped no longer holds the Edit's answer: at the limit the command's group is killed and reaped off the answer's path, the stdin writer owning its input, so a filter writing its own ctl with > or >> answers EIO at 10 s instead of hanging; a running Edit keeps its own copies of the names it reports, and the panes it changes refuse Del, delete, rmdir, Undo, Redo, Get and Zerox (busy, EBUSY); e and r say unreadable files in Get's words; the reference says both sides of a filter's own ctl write need <>
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Diffstat (limited to 'test')
-rw-r--r--test/selfmount.py35
1 files changed, 34 insertions, 1 deletions
diff --git a/test/selfmount.py b/test/selfmount.py
index 5aa9749a..61d32523 100644
--- a/test/selfmount.py
+++ b/test/selfmount.py
@@ -372,8 +372,41 @@ def run(binary):
check(codes == ['124', '0'] and client.read(f'/pane/{edited}/body') == b'aPPle\npear\napple\npear\n',
f'an interrupted Edit changes nothing, and the next runs ({codes!r}, {client.read(f"/pane/{edited}/body")!r})')
client.write('/exec', b'Msg still here\n')
- text = ''.join(cell[0] for cell in client.screen()['cells'])
+ # The terminal is read as it is waited on: a frame the editor
+ # cannot write out (its pty full) is a frame /screen does not have.
+ deadline = time.monotonic() + 8
+ while 'still here' not in (text := ''.join(cell[0] for cell in client.screen()['cells'])) and time.monotonic() < deadline:
+ drain()
+ time.sleep(.1)
check('still here' in text, 'the editor answers after all of that')
+ # A filter that writes its own pane's ctl through the mount. Both
+ # sides with `<>` run at once; a `>` or `>>` open on either side
+ # waits in the kernel for the other write, and the command is
+ # stopped at its 10 s limit: the Edit's write is answered then,
+ # never held by a command it cannot kill.
+ loop_pane = env_pane # the screen has no room for another
+ client.write(f'/pane/{loop_pane}/body', b'x\n', truncate=True)
+ inner = Path(f'/tmp/{name}.inner')
+ timed = Path(f'/tmp/{name}.timed')
+ for outer_open, inner_open, quick in [('3<>', '1<>', True), ('>', '>', False), ('>>', '>', False),
+ ('3<>', '>>', False), ('>', '>>', False)]:
+ # `clean`, which says nothing: a stopped filter's write still
+ # lands once the Edit's is answered.
+ inner.write_text(f'echo clean {inner_open} "$PARDES_MOUNT/pane/$winid/ctl"\n')
+ edit = f'Edit 1 > sh {inner}'
+ write = (f"exec 3<>{mount}/pane/{loop_pane}/ctl; echo '{edit}' >&3; rc=$?; exec 3>&-"
+ if outer_open == '3<>' else f"echo '{edit}' {outer_open} {mount}/pane/{loop_pane}/ctl; rc=$?")
+ ran = run((f"s=$(date +%s%N); {write}; e=$(date +%s%N); "
+ f"echo $rc $(( (e - s) / 1000000 )) > {timed}\n").encode())
+ rc_ms = timed.read_text().split() if timed.exists() else ['?', '-1']
+ timed.unlink(missing_ok=True)
+ ms = int(rc_ms[1])
+ ok = ms >= 0 and (ms < 2000 if quick else ms < 13000)
+ check(ran == '0' and ok, f'outer {outer_open}, filter {inner_open} on its own ctl: answered in {ms} ms (rc {rc_ms[0]})')
+ inner.unlink(missing_ok=True)
+ client.write('/exec', b'Msg after the stopped filters\n')
+ check('msg ' in client.read('/log').decode().split('after the stopped filters')[0][-40:],
+ 'and the editor answers after them')
finally:
os.kill(pid, signal.SIGTERM)
time.sleep(.2)