lazyBoy/tests/runtime-probe.test.py

158 lines
8.3 KiB
Python
Raw Permalink Normal View History

2026-09-11 02:47:43 +00:00
"""Protocol fixtures exercise bounded guest probes, not a real GUI session."""
import base64
import hashlib
import http.server
import json
from pathlib import Path
import threading
from types import SimpleNamespace
import unittest
from unittest.mock import patch
SOURCE = Path(__file__).resolve().parents[1] / 'crates/supervisor/src/runtime_probe.py'
module = {'__name__': 'runtime_probe_test'}
exec(compile(SOURCE.read_text(), str(SOURCE), 'exec'), module)
class ProbeTests(unittest.TestCase):
def setUp(self):
fixture = self
self.mode = 'valid'
self.requests = []
class Handler(http.server.BaseHTTPRequestHandler):
protocol_version = 'HTTP/1.1'
def log_message(self, *_):
pass
def do_GET(self):
self.close_connection = True
fixture.requests.append(self.path)
if self.path == '/websockify':
self.send_response(200 if fixture.mode == 'no-upgrade' else 101)
key = self.headers['Sec-WebSocket-Key']
accepted = base64.b64encode(hashlib.sha1((key + '258EAFA5-E914-47DA-95CA-C5AB0DC85B11').encode()).digest()).decode()
self.send_header('Upgrade', 'websocket')
self.send_header('Connection', 'Upgrade')
self.send_header('Sec-WebSocket-Protocol', 'binary')
self.send_header('Sec-WebSocket-Accept', 'wrong' if fixture.mode == 'wrong-accept' else accepted)
self.end_headers()
data = {
'bad-rfb': b'\x82\x0cNOT A VIEWER',
'oversized-frame': b'\x82\x7e',
'masked-frame': b'\x82\x8c',
'split-frame': b'\x82\x04RFB \x82\x08003.008\n',
}.get(fixture.mode, b'\x82\x0cRFB 003.008\n')
try:
self.wfile.write(data)
self.wfile.flush()
except (BrokenPipeError, ConnectionResetError):
pass
self.close_connection = True
return
if self.path == '/json/version':
body = json.dumps({'Browser': 'Chrome/fixture', 'Protocol-Version': '1.3',
'webSocketDebuggerUrl': f'ws://127.0.0.1:{fixture.port}/devtools/browser/test'}).encode()
if fixture.mode == 'foreign-browser':
body = body.replace(b'127.0.0.1', b'example.com')
if fixture.mode == 'bad-browser':
body = b'not JSON'
if fixture.mode == 'oversized-browser':
body = b' ' * 65537
elif self.path == '/vnc_lite.html':
body = b'<!doctype html><title>viewer fixture</title>'
else:
self.send_error(404)
return
self.send_response(302 if fixture.mode == 'redirect' else 200)
self.send_header('Content-Type', 'text/plain' if fixture.mode == 'not-html' else 'text/html')
self.send_header('Content-Length', str(len(body)))
self.end_headers()
try:
self.wfile.write(body)
except (BrokenPipeError, ConnectionResetError):
pass
self.server = http.server.ThreadingHTTPServer(('127.0.0.1', 0), Handler)
self.port = self.server.server_port
threading.Thread(target=self.server.serve_forever, daemon=True).start()
self.addCleanup(self.server.server_close)
self.addCleanup(self.server.shutdown)
def test_browser_requires_bounded_local_devtools_discovery(self):
for mode, expected in [('valid', True), ('foreign-browser', False), ('bad-browser', False),
('oversized-browser', False), ('redirect', False)]:
self.mode = mode
self.assertEqual(module['browser_probe'](self.port), expected, mode)
self.assertEqual(self.requests, ['/json/version'] * 5)
def test_viewer_requires_websocket_to_rfb_not_just_http(self):
for mode, expected in [('valid', True), ('split-frame', True), ('bad-rfb', False),
('wrong-accept', False), ('no-upgrade', False), ('oversized-frame', False),
('masked-frame', False), ('not-html', False), ('redirect', False)]:
self.mode = mode
self.assertEqual(module['viewer_probe'](self.port), expected, mode)
self.assertNotIn('/json/version', self.requests)
def test_desktop_needs_a_window_manager_as_well_as_x11(self):
run = module['subprocess'].run
with patch.object(module['subprocess'], 'run', side_effect=[
SimpleNamespace(returncode=0), SimpleNamespace(returncode=0, stdout=b'_NET_SUPPORTING_WM_CHECK(WINDOW): window id # 0x200001'),
SimpleNamespace(returncode=0, stdout=b'_NET_WM_NAME(UTF8_STRING) = "Xfwm4"')]) as call:
self.assertTrue(module['desktop_probe'](2))
self.assertEqual(call.call_args_list[0].args[0], ['xdpyinfo', '-display', ':3'])
self.assertIn('0x200001', call.call_args_list[2].args[0])
with patch.object(module['subprocess'], 'run', side_effect=[
SimpleNamespace(returncode=0), SimpleNamespace(returncode=0, stdout=b'not found')]):
self.assertFalse(module['desktop_probe'](2))
with patch.object(module['subprocess'], 'run', side_effect=FileNotFoundError):
self.assertFalse(module['desktop_probe'](2))
self.assertIs(module['subprocess'].run, run)
def test_no_assignment_never_probes_a_peer_and_rejects_invalid_slots(self):
original = {key: module[key] for key in ('desktop_probe', 'browser_probe', 'viewer_probe')}
try:
for key in original:
module[key] = lambda *_: self.fail('unassigned slot must not be probed')
result = module['probe'](None)
self.assertIsNone(result['desktop'])
self.assertIsNone(result['browser'])
self.assertIsNone(result['viewer'])
for slot in (True, False, -1, 8, '1', [], {}):
with self.assertRaisesRegex(ValueError, 'INVALID_SCREEN_SLOT'):
module['probe'](slot)
finally:
module.update(original)
def test_root_timeout_errors_do_not_echo_the_helper_or_create_a_job(self):
scope = {'__name__': 'root_health_test'}
for filename in ('runner_files.py', 'runner_jobs.py'):
source = SOURCE.parent / filename
exec(compile(source.read_text(), str(source), 'exec'), scope)
scope.update(connect=lambda: object(), RUNTIME_PROBE_SOURCE='PRIVATE_HELPER_CANARY')
request = {'action': 'runtime_health', 'identity': {'computer_id': 'computer', 'bot_id': 'bot', 'generation': 1},
'input': '{"slot":null}'}
errors = [scope['subprocess'].TimeoutExpired(['PRIVATE_HELPER_CANARY'], 8), OSError('PRIVATE_HELPER_CANARY')]
for error in errors:
with patch.object(scope['subprocess'], 'run', side_effect=error) as run:
with self.assertRaises(ValueError) as caught:
scope['handle'](request)
self.assertEqual(str(caught.exception), 'RUNTIME_HEALTH_UNAVAILABLE')
self.assertEqual(run.call_args.kwargs['timeout'], 8)
self.assertEqual(run.call_args.kwargs['user'], 1000)
def test_components_fail_independently_and_ports_follow_the_slot(self):
original = {key: module[key] for key in ('desktop_probe', 'browser_probe', 'viewer_probe')}
seen = []
try:
module['desktop_probe'] = lambda slot: seen.append(('desktop', slot)) or False
module['browser_probe'] = lambda port: seen.append(('browser', port)) or True
module['viewer_probe'] = lambda port: seen.append(('viewer', port)) or False
with patch.object(module['os'], 'getuid', return_value=1000):
result = module['probe'](3)
self.assertEqual(result, {'version': 1, 'slot': 3, 'runner': True, 'desktop': False, 'browser': True, 'viewer': False})
self.assertEqual(seen, [('desktop', 3), ('browser', 9225), ('viewer', 6083)])
finally:
module.update(original)
if __name__ == '__main__':
unittest.main()