1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
|
#!/usr/bin/env python3
"""Check local Collapse height transfers through an owned SDL or TTY 9P session.
python3 -B test/collapse_layout.py zig-out/column-review/bin/pardes-gui
python3 -B test/collapse_layout.py zig-out/column-review/bin/pardes --tty
Measures tagline boundaries in the rendered core grid, not host pixel geometry.
No user session is contacted, and all fixture files live in a temporary directory.
"""
import argparse
from pathlib import Path
import tempfile
from fs import new_pane, session
from ui_review import panes, require, wait_for
def geometry(client, names):
frame = client.screen()
rows = [''.join(cell[0] for cell in frame['cells'][row * frame['cols']:(row + 1) * frame['cols']])
for row in range(frame['rows'])]
starts = [next((row for row, text in enumerate(rows) if '/' + name + ' ' in text), None)
for name in names]
if any(row is None for row in starts) or starts != sorted(set(starts)):
return None
ends = starts[1:] + [frame['rows']]
return tuple((start, end - start) for start, end in zip(starts, ends))
def toggle(client, serial):
"""Execute the default tag's Collapse word without changing any body or tag."""
pane = f'/self/pane/{serial}'
tag = client.read(pane + '/tag')
at = tag.rfind(b' Collapse')
require(at >= 0, 'default pane tag lacks Collapse')
at += 1
client.write(pane + '/event', f'Mx{at} {at + len("Collapse")}\n'.encode())
def run(args):
binary = str(args.binary.resolve())
with tempfile.TemporaryDirectory(prefix='pardes-collapse-') as directory:
root = Path(directory)
name = 'collapse-layout'
inherited = {} if args.tty else {
'PARDES_TEST': '1', 'PARDES_TEST_COLS': '120', 'PARDES_TEST_ROWS': '48',
'PARDES_TEST_CAPTURE_DIR': str(root),
'SDL_VIDEODRIVER': 'x11',
}
launch = ['-c', 'stty cols 120 rows 48; exec "$@"', name, binary,
'--9p=' + name, str(root / (name + '.txt'))]
with session('/bin/sh', root, name, socket_name=name, tty=True,
launch=launch, inherited=inherited) as (client, _):
serials = [next(iter(panes(client)))]
names = [f'collapse-{letter}' for letter in 'ABCD']
contents = b'owned collapse fixture\n' * 60
for index, label in enumerate(names):
if index:
serials.append(new_pane(client, b''))
pane = f'/self/pane/{serials[index]}'
client.write(pane + '/ctl', ('name ' + label + '\n').encode())
client.write(pane + '/body', contents, truncate=True)
original = require(wait_for(lambda: geometry(client, names)), 'four pane tags not visible')
require(all(height > 1 for _, height in original), f'fixture has no body room: {original}')
for target in [1, 3, 0, 2, 1, 3, 0]:
recipient = target - 1 if target else 1
heights = [height for _, height in original]
heights[recipient] += heights[target] - 1
heights[target] = 1
expected = []
row = original[0][0]
for height in heights:
expected.append((row, height))
row += height
toggle(client, serials[target])
require(wait_for(lambda: geometry(client, names) == tuple(expected)),
f'Collapse {names[target]} changed unrelated geometry: {geometry(client, names)} != {expected}')
for index in range(4):
if index not in [target, recipient]:
require(expected[index] == original[index], 'unrelated pane moved')
toggle(client, serials[target])
require(wait_for(lambda: geometry(client, names) == original),
f'expansion did not restore original geometry: {geometry(client, names)}')
for serial in serials:
require(client.read(f'/self/pane/{serial}/body') == contents, 'Collapse changed pane contents')
require((root / (name + '.txt')).read_bytes() == b'initial\n', 'fixture source file changed')
print(f'{"TTY" if args.tty else "SDL"}: 7 local collapse/expand pairs, exact unrelated pane geometry, and content preservation passed')
if __name__ == '__main__':
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument('binary', type=Path)
parser.add_argument('--tty', action='store_true')
run(parser.parse_args())
|