Files
orisys_arm/station/test/gui_fixture.py
T
2026-10-16 17:27:30 +08:00

68 lines
3.3 KiB
Python

"""Exercise Qt and backend shutdown with no device transport in the UI."""
import sys
import os
import time
from station import runtime_client as runtime
runtime.start(offline=True)
import station.operator_app as ui
app=ui.QApplication.instance();app.setFont(ui.QFont('Noto Sans SC',11));app.setStyleSheet(ui._STYLE)
window=ui.StationApp();window.resize(1440,900);window.show()
window._apply_theme('dark',persist=False)
assert app.property('darkTheme') is True
assert window._theme_selector.currentData()=='dark'
window._apply_theme('light',persist=False)
assert app.property('darkTheme') is False
assert window._theme_selector.currentData()=='light'
assert 'rclpy' not in sys.modules
assert 'serial' not in sys.modules
assert 'station.operator_console' not in sys.modules
assert set(window._pages) == {'main','calibration','settings','ros_monitor'}
# A photo must preserve the current RGB frame; no capture/device operation is needed.
import tempfile
from unittest.mock import patch
sample=bytes([12,84,192])*(32*18)
selected=next(cam for _page,cam,_label in window._camera_labels if cam.get('arm')==window._arm)
with tempfile.TemporaryDirectory() as pictures:
with patch.object(ui.Path,'home',return_value=ui.Path(pictures)), patch.object(ui.camera_view,'previews',return_value={selected['id']+'_color':(sample,32,18)}):
window._take_photo()
photos=list(ui.Path(pictures).rglob('*.png'))
assert len(photos)==1
image=ui.QImage(str(photos[0]))
assert (image.width(),image.height())==(32,18)
assert image.pixelColor(0,0).getRgb()[:3]==(12,84,192)
window._open_camera(selected['id'])
assert selected['id'] in window._camera_windows
window._camera_windows[selected['id']].close()
assert not window._camera_windows
errors=[];ticks=[]
duration=float(os.environ.get("ORISYS_GUI_TEST_SECONDS","2.2"))
started=time.monotonic()
frames=[bytes([v,40,80])*(320*180) for v in (30,70,120)]
def tick():
ticks.append(time.monotonic())
if duration>10:
if len(ticks)%100==0:
window._show_page(("main","calibration")[(len(ticks)//100)%2])
for key in ui.camera_view._PREVIEW_WANT:
ui.camera_view._PREVIEW[key]=(frames[len(ticks)%3],320,180)
ui.camera_view._META[key]=(320,180,20.)
original=sys.excepthook
def fail(*args): errors.append(str(args[1]));original(*args)
sys.excepthook=fail
beat=ui.QTimer();beat.timeout.connect(tick);beat.start(50)
ui.QTimer.singleShot(200,lambda:window._start(lambda:(time.sleep(.7),{'ok':True,'message':'test'})[1],'test'))
ui.QTimer.singleShot(500,lambda:window._show_page('calibration'))
ui.QTimer.singleShot(1000,lambda:window._show_page('calibration'))
ui.QTimer.singleShot(1500,lambda:window._show_page('ros_monitor'))
ui.QTimer.singleShot(1900,lambda:window._show_page('main'))
ui.QTimer.singleShot(round(duration*1000),window.close)
ui.QTimer.singleShot(round((duration+15)*1000),app.quit)
app.exec()
assert len(ticks)>20,len(ticks)
assert not errors,errors
assert runtime._runtime is None
assert not any(job.isRunning() for job in window._jobs.values())
print('Qt responsive, no device modules, async task and graceful shutdown PASS',
{'seconds':round(time.monotonic()-started,2),'ticks':len(ticks),
'max_gap':round(max((b-a for a,b in zip(ticks,ticks[1:])),default=0),3),'errors':errors},flush=True)