"""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)