diff options
| author | Gabriel Schneider <[email protected]> | 2026-09-29 02:11:15 -0300 |
|---|---|---|
| committer | Gabriel Schneider <[email protected]> | 2026-10-01 00:12:15 -0300 |
| commit | 3db73735e9a8d88878bc9ee343f226c033320913 (patch) | |
| tree | 93c52cd0d4d2008d765c862651464684e15006d6 /test | |
| parent | b29de1d4e78466a15f5d3dc0916410cba1445c5f (diff) | |
| download | pardes-3db73735e9a8d88878bc9ee343f226c033320913.tar.gz pardes-3db73735e9a8d88878bc9ee343f226c033320913.zip | |
A command line cut across writes runs once whole, and look/exec answer per open
A mount cuts a write at its message size, and each piece ran on its own: a
line cut at the boundary ran as shell commands and a 50 KB Edit block
broke. An open of look, exec, tagexec, any ctl or a column's exec now keeps
an unfinished last line, or an Edit block still open, until its next write
or its release, and never runs a fragment. The same open record holds what
its last write touched, which a read on it answers (as /net/tcp/clone
does); an open that never wrote reads the session's last as of its open.
A release that runs a held line waits for quiet and counts as a change;
a clicked line with a control character is still refused at its write.
Tested in unit tests, fs.py (python client, 8 KiB) and selfmount.py (the
kernel mount, a 1000-line seq burst and a 50 KB Edit block).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Diffstat (limited to 'test')
| -rw-r--r-- | test/fs.py | 24 | ||||
| -rw-r--r-- | test/selfmount.py | 33 |
2 files changed, 57 insertions, 0 deletions
@@ -531,8 +531,32 @@ 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]) + + def test(binary, quic=False): started = time.monotonic() + cut_writes(binary) run_file(binary) exit_answers(binary) unattended_messages_expire(binary) diff --git a/test/selfmount.py b/test/selfmount.py index 90960bed..55809740 100644 --- a/test/selfmount.py +++ b/test/selfmount.py @@ -105,6 +105,39 @@ def run(binary): check(client.read(f'/pane/{first}/body') == b'HELLO initial\n', f'Save wrote back through the mount into pane {first}') text = ''.join(cell[0] for cell in client.screen()['cells']) check('saved ' + mount in text, 'the editor reported the save') + # Through the kernel mount a write is cut at its msize: a + # 1000-line burst and a 50 KB Edit block each still run whole, + # line by line, and no piece of a line runs as a shell command. + # sh: bash's and fish's prompt files are not reachable in 9ns's + # namespace here, and a command pane needs no prompt marks. + client.write('/ctl', b'Shell sh\n') + # From the fixture's directory: a pane on the mount has one a + # shell cannot enter. + client.write('/focus', f'{first}\n'.encode()) + + def run(line): + """A command pane runs the line; answers its exit status.""" + seen = client.read('/log').decode().count('\nexit ') + client.write('/exec', line) + deadline = time.monotonic() + 20 + while time.monotonic() < deadline: + drain() + exits = [row for row in client.read('/log').decode().splitlines() if row.startswith('exit ')] + if len(exits) > seen: + return exits[-1].split()[2] + time.sleep(.1) + return 'timeout' + got = run(f"seq -f 'Msg mnt-%04g' 0 999 > {mount}/exec\n".encode()) + check(got == '0', f'a 1000-line burst written through the mount ({got!r})') + log = client.read('/log').decode() + said = [line.split(' ', 2)[2] for line in log.splitlines() if line.startswith('msg ') and 'mnt-' in line] + check(said == ['mnt-%04d' % i for i in range(1000)], f'each line of it ran once, whole ({len(said)} said)') + check('exit 127' not in log, 'no piece of a line ran as a command') + scratch = int(client.read('/pane/new')) + maker = "import sys; sys.stdout.write('Edit ,a\\n' + ''.join('line %05d of the block\\n' % i for i in range(2500)) + '.\\n')" + check(run(f'python3 -c "{maker}" > {mount}/pane/{scratch}/ctl\n'.encode()) == '0', 'a 50 KB Edit block written through the mount') + body = client.read(f'/pane/{scratch}/body').decode().splitlines() + check(body == ['line %05d of the block' % i for i in range(2500)], f'it ran once, whole ({len(body)} lines)') client.write('/exec', b'Msg still here\n') text = ''.join(cell[0] for cell in client.screen()['cells']) check('still here' in text, 'the editor answers after all of that') |
