fix: verify work during continuously hidden guest intervals

This commit is contained in:
Codex
2026-10-13 04:41:07 +03:00
parent 7353c4ec92
commit adfce75973
5 changed files with 147 additions and 15 deletions
+64 -9
View File
@@ -35,6 +35,51 @@ def counter(manager, name):
return values[0]
def focus_events(log):
"""Only keyboard focus, excluding pointer-enter and old window titles."""
return re.findall(r'wl_keyboard#\d+\.(enter|leave)\(', log.read_text())
def fresh_focus_transition(source_before, target_before, source_after, target_after):
return (len(source_after) > len(source_before) and source_after[-1] == 'leave'
and len(target_after) > len(target_before) and target_after[-1] == 'enter')
def measure_hidden_interval(read_counter, hidden_log, visible_log, duration=.5):
hidden_focus, visible_focus = focus_events(hidden_log), focus_events(visible_log)
assert hidden_focus and hidden_focus[-1] == 'leave', 'sampled guest is not hidden'
assert visible_focus and visible_focus[-1] == 'enter', 'other guest is not focused'
def unchanged():
assert (focus_events(hidden_log) == hidden_focus and
focus_events(visible_log) == visible_focus), 'focus changed during hidden interval'
start = time.monotonic()
before = read_counter()
unchanged()
while time.monotonic() - start < duration:
time.sleep(min(.05, duration))
unchanged()
after = read_counter()
unchanged()
assert after != before, 'hidden guest stopped working during continuously hidden interval'
return {'before': before, 'after': after,
'elapsed_seconds': time.monotonic() - start, 'focus_unchanged': True}
def classify_compositor_errors(log):
expected = []
for line in log.splitlines():
if '[ERROR]' not in line:
continue
if re.search(r'\[xwayland/sockets\.c:\d+\] Failed to bind socket '
r'@?/tmp/\.X11-unix/X\d+: Address already in use$', line):
expected.append(line)
else:
raise AssertionError('Unexpected compositor error: ' + line)
return expected
def main():
cli = argparse.ArgumentParser(description=__doc__)
cli.add_argument('--viewer', default=os.environ.get('HYDRA_VIEWER', 'remote-viewer'))
@@ -74,7 +119,7 @@ def main():
(config / 'environment').touch()
env = dict(os.environ, XDG_RUNTIME_DIR=str(xdg), WLR_BACKENDS='headless',
WLR_RENDERER='pixman', WLR_XWAYLAND='/bin/false', XDG_DATA_DIRS='/nonexistent',
GSETTINGS_BACKEND='memory')
GSETTINGS_BACKEND='memory', LABWC_UPDATE_ACTIVATION_ENV='0')
for key in ('DISPLAY', 'WAYLAND_DISPLAY'):
env.pop(key, None)
manager = Runtime(root / 'config', root / 'state', root / 'run')
@@ -111,6 +156,15 @@ def main():
def chord(code):
keys(f'down 29\ndown 56\ndown 42\ndown {code}\nup {code}\nup 42\nup 56\nup 29')
def switch(key, source_log, target_log):
source_before, target_before = focus_events(source_log), focus_events(target_log)
chord(key)
wait_for(lambda: fresh_focus_transition(source_before, target_before,
focus_events(source_log), focus_events(target_log)),
'reserved switch did not produce fresh keyboard leave/enter')
return {'from_events': len(focus_events(source_log)),
'to_events': len(focus_events(target_log)), 'keycode': key}
def attach(name, number):
vl = (root / f'{name}-{number}.log').open('w'); streams.append(vl)
previous = dict(os.environ)
@@ -150,11 +204,10 @@ def main():
chord(23) # I: test-only ToggleKeybinds.
keys('down 88\nup 88')
assert not unreserved.exists(), 'keybinding inhibition did not suppress ordinary F12'
a_leave = alog.read_text().count('.leave(')
chord(106) # Right: production reserved host switch.
wait_for(lambda: alog.read_text().count('.leave(') > a_leave and '.enter(' in blog.read_text(),
'reserved switch failed with inhibition')
chord(105) # Left: return to inhibited alpha.
switch(106, alog, blog) # Right: beta focused, alpha continuously hidden.
hidden_intervals = {'alpha': measure_hidden_interval(lambda: counter(manager, 'alpha'), alog, blog)}
switch(105, blog, alog) # Left: alpha focused, beta continuously hidden.
hidden_intervals['beta'] = measure_hidden_interval(lambda: counter(manager, 'beta'), blog, alog)
chord(1) # Escape: production recovery binding still works.
wait_for(recovery.exists, 'reserved recovery failed with inhibition')
dialog = 'set_title("Select USB devices for redirection")'
@@ -164,8 +217,7 @@ def main():
wait_for(lambda: dialog in alog.read_text(), 'manual USB selector did not open', timeout=3)
keys('up 22\nup 42\nup 56\nup 29')
keys('down 1\nup 1') # Close selector.
chord(106)
chord(105)
post_usb_switches = [switch(106, alog, blog), switch(105, blog, alog)]
assert alpha.poll() is None and beta.poll() is None
assert identities == {name: manager.paths(name).pid.read_text().strip() for name in names}, 'switch restarted a guest'
assert alog.read_text().count('set_title("HydraOS: alpha")') == 1, 'switch reconnected alpha'
@@ -191,7 +243,10 @@ def main():
time.sleep(1)
disk_boot_counter = counter(manager, 'alpha')
assert disk_boot_counter > 0
result.update(manual_usb_dialog=True, standard_us_keymap=True,
expected_compositor_noise = classify_compositor_errors((root / 'labwc.log').read_text())
result.update(hidden_guest_intervals=hidden_intervals, post_usb_switches=post_usb_switches,
expected_compositor_noise=expected_compositor_noise,
manual_usb_dialog=True, standard_us_keymap=True,
native_labwc_inhibition=True, reserved_switch_and_recovery=True,
qemu_survives_viewer_and_compositor=True, independent_lifecycle=True,
guest_counters_before=before, guest_counters_after=after,