"""Production lazyboy-screen service repair in an isolated Computer.""" import json from concurrent.futures import ThreadPoolExecutor import os from pathlib import Path import signal import shlex import subprocess import time root = Path('/tmp/lazyboy') def ensure(slot=7): result = subprocess.run(['bash', '/fixture/lazyboy-screen', 'ensure', str(slot), f'/home/lazyboy/.browser-profiles/recovery-{slot}'], capture_output=True, text=True, timeout=60) if result.returncode: logs = {p.name: p.read_text(errors='replace')[-3000:] for p in root.glob('screen-8*.log')} raise AssertionError((result.returncode, result.stdout, result.stderr, logs)) assert json.loads(result.stdout)['ok'] is True def pids(slot=7): return {name: int((root / f'screen-{slot + 1}-{name}.pid').read_text()) for name in ['wm', 'desktop', 'panel', 'cua', 'xvfb', 'dbus', 'browser']} def main(): assert os.getuid() == 1000 with ThreadPoolExecutor(max_workers=2) as workers: pending = [workers.submit(ensure, slot) for slot in [6, 7]] for result in pending: result.result() peer = pids(6) peer_bus = (root / 'screen-7.dbus').read_text() initial = pids() bus = (root / 'screen-8.dbus').read_text() ensure() assert pids() == initial, 'warm ensure must reuse existing services' for name in ['wm', 'panel', 'desktop', 'cua', 'xvfb', 'dbus', 'browser']: before = pids() os.kill(before[name], signal.SIGTERM) deadline = time.monotonic() + 10 while True: try: state = Path(f'/proc/{before[name]}/stat').read_text().rsplit(')', 1)[1].split()[0] except FileNotFoundError: break if state == 'Z': break assert time.monotonic() < deadline, f'{name} did not stop' time.sleep(.1) with ThreadPoolExecutor(max_workers=2) as workers: pending = [workers.submit(ensure) for _ in range(2)] for result in pending: result.result() after = pids() assert after[name] != before[name], (name, before, after) if name not in ('xvfb', 'dbus'): for other in before.keys() - {name}: assert after[other] == before[other], (name, other, before, after) if name == 'dbus': assert (root / 'screen-8.dbus').read_text() != bus bus = (root / 'screen-8.dbus').read_text() else: assert (root / 'screen-8.dbus').read_text() == bus deadline = time.monotonic() + 15 while True: probe = subprocess.run(['python3', '-I', '/fixture/runtime_probe.py', '7'], capture_output=True, text=True, check=True, timeout=8) observation = json.loads(probe.stdout) if all(observation[component] is True for component in ['desktop', 'viewer', 'browser']): break assert time.monotonic() < deadline, observation time.sleep(.1) assert pids(6) == peer, (name, 'other display services changed') assert (root / 'screen-7.dbus').read_text() == peer_bus peer_probe = subprocess.run(['python3', '-I', '/fixture/runtime_probe.py', '6'], capture_output=True, text=True, check=True, timeout=8) peer_observation = json.loads(peer_probe.stdout) assert all(peer_observation[c] is True for c in ['desktop', 'browser', 'viewer']), peer_observation for slot in [6, 7]: browser_pid = pids(slot)['browser'] # This Chromium build rewrites argv into a single process-title string. # Fixture paths contain no spaces; normalize that representation. raw = Path(f'/proc/{browser_pid}/cmdline').read_bytes().replace(b'\0', b' ').decode() argv = shlex.split(raw) assert f'--user-data-dir=/home/lazyboy/.browser-profiles/recovery-{slot}' in argv, (slot, browser_pid, argv) assert f'--remote-debugging-port={9222 + slot}' in argv, (slot, browser_pid, argv) env = dict(os.environ, DISPLAY=':8', CUA_DRIVER_RS_HOME='/tmp/lazyboy/cua-home-8', DBUS_SESSION_BUS_ADDRESS=bus.strip()) for tool, payload, extra in [ ('start_session', {'session': 'recovery-fixture'}, []), ('get_desktop_state', {'session': 'recovery-fixture', 'screenshot_out_file': '/tmp/recovery.png'}, ['--screenshot-out-file', '/tmp/recovery.png']), ]: Path('/tmp/recovery.png').unlink(missing_ok=True) result = subprocess.run(['cua-driver', 'call', '--socket', '/tmp/lazyboy/cua-8.sock', *extra, tool], input=json.dumps(payload), capture_output=True, text=True, env=env, timeout=15) assert result.returncode == 0, (name, tool, result.stdout, result.stderr) assert Path('/tmp/recovery.png').read_bytes().startswith(b'\x89PNG\r\n\x1a\n') print(json.dumps({'repaired': name, 'observation': observation, 'cuaScreenshot': True, 'peerUnchanged': True}), flush=True) if __name__ == '__main__': main()