summaryrefslogtreecommitdiff
path: root/test/look_placement.py
diff options
context:
space:
mode:
authorGabriel Schneider <[email protected]>2026-09-11 12:38:56 -0300
committerGabriel Schneider <[email protected]>2026-09-15 17:24:42 -0300
commit682e237df7e8b22f820d14a4adee58e6e2f84268 (patch)
tree5d888cb46f1a49e89fc55e2a4c232d3af0265b1d /test/look_placement.py
parent6878e1c309172d624c0d7a3555f6f0217ae6770e (diff)
downloadpardes-682e237df7e8b22f820d14a4adee58e6e2f84268.tar.gz
pardes-682e237df7e8b22f820d14a4adee58e6e2f84268.zip
trunk: resume before the Reload experiment
Empty marker on the last pre-Reload change. Keep the Reload experiment on reload (3801914), its first change on reload-start (200a1fc), and the unfinished performance investigation on reload-perf-wip.
Diffstat (limited to 'test/look_placement.py')
-rw-r--r--test/look_placement.py143
1 files changed, 143 insertions, 0 deletions
diff --git a/test/look_placement.py b/test/look_placement.py
new file mode 100644
index 00000000..e9918c5d
--- /dev/null
+++ b/test/look_placement.py
@@ -0,0 +1,143 @@
+#!/usr/bin/env python3
+"""Check narrow-window first-document Look placement in owned SDL/TTY sessions.
+
+python3 -B test/look_placement.py zig-out/column-review/bin/pardes-gui
+python3 -B test/look_placement.py zig-out/column-review/bin/pardes --tty
+
+Starts the classic three-terminal layout, then opens the first document from
+the lower-left or right terminal. Screen-grid geometry must split only that
+terminal, immediately below it, and a repeated Look must reuse the file pane.
+Use --output DIR to retain grid observations and SDL GPU screenshots.
+"""
+import argparse
+from contextlib import nullcontext
+from pathlib import Path
+import tempfile
+
+from fs import session
+from ui_review import capture, panes, require, wait_for
+
+
+def geometry(client, markers):
+ frame = client.screen()
+ width = frame['cols']
+ lines = [''.join(cell[0] for cell in frame['cells'][row * width:(row + 1) * width])
+ for row in range(frame['rows'])]
+ positions = {}
+ for serial, marker in markers.items():
+ hits = [(row, line.index(marker)) for row, line in enumerate(lines) if marker in line]
+ if len(hits) != 1:
+ return None
+ row, offset = hits[0]
+ column_x = 0 if offset < width // 2 else width // 2
+ positions[serial] = (column_x, row)
+ rectangles = {}
+ for serial, (column_x, row) in positions.items():
+ following = [other_y for other_x, other_y in positions.values()
+ if other_x == column_x and other_y > row]
+ end = min(following, default=frame['rows'])
+ rectangles[serial] = (column_x, row, width // 2, end - row)
+ return frame, rectangles
+
+
+def rectangle_text(frame, rect):
+ x, y, width, height = rect
+ return tuple(tuple(cell[0] for cell in frame['cells'][row * frame['cols'] + x:
+ row * frame['cols'] + x + width])
+ for row in range(y, y + height))
+
+
+def run_case(args, root, source_index):
+ root.mkdir(parents=True, exist_ok=True, mode=0o700)
+ root.chmod(0o700)
+ config = root / 'config/pardes'
+ config.mkdir(parents=True)
+ (config / 'init').write_text('Shell /bin/sh\nTheme forge\n')
+ target = root / 'opened.txt'
+ contents = b'First document opened from this terminal.\n' * 40
+ target.write_bytes(contents)
+ name = 'look-placement'
+ inherited = {} if args.tty else {
+ 'PARDES_TEST': '1', 'PARDES_TEST_COLS': '150', 'PARDES_TEST_ROWS': '48',
+ 'PARDES_TEST_CAPTURE_DIR': str(root), 'SDL_VIDEODRIVER': 'x11',
+ }
+ # No document argument: the regression concerns the very first document.
+ launch = ['-c', 'stty cols 150 rows 48; exec "$@"', name,
+ str(args.binary.resolve()), '--9p=' + name, '-n', '3']
+ with session('/bin/sh', root, name, socket_name=name, tty=True,
+ launch=launch, inherited=inherited) as (client, _):
+ serials = list(panes(client))
+ require(len(serials) == 3, 'classic fixture did not boot three terminals')
+ markers = {}
+ for index, serial in enumerate(serials):
+ pane = f'/self/pane/{serial}'
+ require('pty' in client.list(pane), 'fixture contains an existing document')
+ marker = f'LP_{index}'
+ markers[serial] = marker
+ client.write(pane + '/ctl', b'cleartag\n')
+ client.write(pane + '/tag', f' {marker} Tty Del Collapse'.encode())
+ # Establish non-pristine shells and a stable prompt before measuring.
+ client.write(pane + '/pty/data', f"printf 'ready-{index}\\n'\r".encode())
+ require(wait_for(lambda: f'\nready-{index}\n'.encode() in
+ client.read(pane + '/body').replace(b'\r', b'')),
+ 'terminal did not execute readiness command')
+ before_frame, before = require(wait_for(lambda: geometry(client, markers)),
+ 'terminal tags not visible in fixture')
+ source = serials[source_index]
+ source_rect = before[source]
+ require(source_rect[3] >= 8, 'source terminal too short for a local split')
+ require(before[serials[0]][:2] == (0, 2) and before[serials[1]][0] == 0 and
+ before[serials[1]][1] > 2 and before[serials[2]][:2] == (75, 2),
+ f'unexpected initial classic layout: {before}')
+ client.write(f'/self/pane/{source}/ctl', f'look {target}\n'.encode())
+ opened = require(wait_for(lambda: next((serial for serial in panes(client)
+ if serial not in serials), None)),
+ 'Look did not open the first document')
+ markers[opened] = 'opened.txt'
+ after_frame, after = require(wait_for(lambda: geometry(client, markers)),
+ 'Look pane tags not visible')
+ require(len(panes(client)) == 4, 'Look removed a terminal or added extra panes')
+ x, y, width, height = source_rect
+ require(after[source][:3] == (x, y, width),
+ f'Look moved its originating terminal: {source_rect} -> {after[source]}')
+ require(after[opened][:3] == (x, y + after[source][3], width) and
+ after[source][3] + after[opened][3] == height,
+ f'Look did not split directly below its caller: {after}')
+ for serial in serials:
+ if serial == source:
+ continue
+ require(after[serial] == before[serial],
+ f'Look resized or moved unrelated terminal {serial}: {before} -> {after}')
+ require(rectangle_text(before_frame, before[serial]) ==
+ rectangle_text(after_frame, after[serial]),
+ f'Look changed unrelated terminal {serial} rendered text')
+ require(client.read(f'/self/pane/{opened}/body') == contents,
+ 'opened document contents differ')
+ client.write(f'/self/pane/{source}/ctl', f'look {target}:2\n'.encode())
+ require(list(panes(client)) == serials + [opened], 'repeated Look created a duplicate pane')
+ require(wait_for(lambda: geometry(client, markers)[1] == after),
+ 'repeated Look changed layout')
+ require(target.read_bytes() == contents, 'Look modified the fixture on disk')
+ if args.output:
+ capture(client, root, 'look-below-caller', not args.tty)
+ print(f'{"TTY" if args.tty else "SDL"} source {"lower-left" if source_index == 1 else "right"}: '
+ 'local split, unchanged other terminals, first-file contents and re-Look reuse passed', flush=True)
+
+
+def run(args):
+ # Cache-rooted fixtures avoid relying on /tmp quota in developer sessions.
+ cache = Path.home() / '.cache'
+ cache.mkdir(exist_ok=True)
+ context = nullcontext(str(args.output.resolve())) if args.output else tempfile.TemporaryDirectory(
+ prefix='lp-', dir=cache)
+ with context as directory:
+ for source_index, label in [(1, 'left'), (2, 'right')]:
+ run_case(args, Path(directory) / label, source_index)
+
+
+if __name__ == '__main__':
+ parser = argparse.ArgumentParser(description=__doc__)
+ parser.add_argument('binary', type=Path)
+ parser.add_argument('--tty', action='store_true')
+ parser.add_argument('--output', type=Path)
+ run(parser.parse_args())