summaryrefslogtreecommitdiff
path: root/test
diff options
context:
space:
mode:
authorGabriel Schneider <[email protected]>2026-09-29 18:16:13 -0300
committerGabriel Schneider <[email protected]>2026-10-01 00:12:17 -0300
commit66500ac6e4f002a673e95e9788a4992178bf989a (patch)
tree0816815c8865670269f9923b351bb25a4c45d1f5 /test
parent893e12345c2458e70b333faedaaaaf8fbffb6932 (diff)
downloadpardes-66500ac6e4f002a673e95e9788a4992178bf989a.tar.gz
pardes-66500ac6e4f002a673e95e9788a4992178bf989a.zip
The 9P monkey counts the lines a write runs as the tree does: a short write's unended tail is a line of its own
Since round 24 (uwzuqzxp), a write to a lines file shorter than a Twrite holds is whole, as acme takes each write, so a look line written a byte a Twrite is as many looks as bytes, each missing with its own err. one_failure_rule still split the data on newlines whole, allowed one err, and failed the fixed smoke seed (20260929 step 607, size 20 6 written in bytes to look: 7 errs). served_lines models the tree's write (tree.zig) and has a self-check run at start. Co-Authored-By: Claude Opus 5.5 <[email protected]>
Diffstat (limited to 'test')
-rw-r--r--test/monkey9p.py41
1 files changed, 36 insertions, 5 deletions
diff --git a/test/monkey9p.py b/test/monkey9p.py
index 4c1732e5..6a8f27c1 100644
--- a/test/monkey9p.py
+++ b/test/monkey9p.py
@@ -640,12 +640,13 @@ def op_write(sess, op):
timeout = SLOW_TIMEOUT if slow(data) else TIMEOUT
ok, body = w.call(TOPEN, struct.pack('<IB', fid, mode), timeout)
res.add(open_kind(path, mode), path, ok, None if ok else body)
- allowances(res, path, data)
+ sizes = chunking(op.get('chunks', 'whole'), len(data), w.msize - 23)
+ allowances(res, path, data, sizes, w.msize - 24)
opened = ok
sent = data
if ok:
offset = 0
- for size in chunking(op.get('chunks', 'whole'), len(data), w.msize - 23):
+ for size in sizes:
piece = data[offset:offset + size]
ok, body = w.call(TWRITE, struct.pack('<IQI', fid, op.get('offset', offset), len(piece)) + piece, timeout)
# A write on a fid opened for reading is 9P misuse, not a write
@@ -663,13 +664,35 @@ def op_write(sess, op):
return res
-def allowances(res, path, data):
+def served_lines(data, sizes, room):
+ """The lines a lines file runs for `data` written in pieces of `sizes`
+ (fs.md, tree.zig write): each ended line, and the unended tail of a piece
+ shorter than a Twrite holds (`room`, msize less 24), which is whole in
+ itself, as acme takes each write; a piece that fills its Twrite may go
+ on, and its tail waits for the next. A line written a byte a Twrite is
+ so as many lines as bytes."""
+ lines, pending, at = [], b'', 0
+ for size in sizes:
+ piece = data[at:at + size]
+ at += size
+ pending += piece
+ *done, pending = pending.split(b'\n')
+ lines += done
+ if pending and len(piece) < room:
+ lines.append(pending)
+ pending = b''
+ if pending:
+ lines.append(pending)
+ return lines
+
+
+def allowances(res, path, data, sizes, room):
"""Errs a successful write may log (fs.md): a look that finds nothing logs
one, per line, whether written to look, written back as an event record
(acme's xfid.c:842) or run as the Look builtin; an Edit block whose text
never ended runs, and fails, at the close."""
base = path.rsplit('/', 1)[-1]
- lines = [line for line in data.split(b'\n') if line.strip()]
+ lines = [line for line in served_lines(data, sizes, room) if line.strip()]
if base in ('look', 'event'):
n = len(lines)
elif base in ('exec', 'ctl', 'tagexec'):
@@ -692,6 +715,13 @@ def open_kind(path, mode):
return 'open'
+def check_served_lines():
+ assert served_lines(b'a\nb\n', [4], 8000) == [b'a', b'b']
+ assert served_lines(b'ab\n', [1, 1, 1], 8000) == [b'a', b'b', b'']
+ assert served_lines(b'abcd', [2, 2], 2) == [b'abcd']
+ assert served_lines(b'ab\ncd', [3, 2], 8000) == [b'ab', b'cd']
+
+
def chunking(spec, n, cap):
if n == 0:
return [0]
@@ -820,7 +850,7 @@ def op_hwrite(sess, op):
fid, path, mode, off, _ = h
data = dec(op['data'])[:sess.wire.msize - 23] # one Twrite
h[4] = data
- allowances(res, path, data)
+ allowances(res, path, data, [len(data)], sess.wire.msize - 24)
ok, body = sess.wire.call(TWRITE, struct.pack('<IQI', fid, off[0], len(data)) + data,
SLOW_TIMEOUT if slow(data) else TIMEOUT)
res.add('write' if mode & 3 else 'write-badfid', path, ok, None if ok else body)
@@ -1919,6 +1949,7 @@ def main():
ap.add_argument('--verbose', '-v', action='store_true', help='replay: print each request and log record')
ap.add_argument('--out', default=DEFAULT_OUT)
args = ap.parse_args()
+ check_served_lines()
args.binary = os.path.abspath(args.binary)
for key in [k for k in os.environ if k.startswith('PARDES_') or k in ('NINE_MOUNT', 'NAMESPACE')]:
del os.environ[key]