96 lines
5.3 KiB
Python
96 lines
5.3 KiB
Python
"""Prevent invisible-page work and unbounded UI event processing."""
|
|
import os,queue
|
|
os.environ.setdefault('QT_QPA_PLATFORM','offscreen')
|
|
from types import SimpleNamespace
|
|
from unittest import TestCase,mock
|
|
from wechat_gui_qt import MainWindow,SettingsPage
|
|
|
|
|
|
def snapshot_host(view):
|
|
text=lambda value:SimpleNamespace(text=lambda:value)
|
|
host=SimpleNamespace(_running=False,_status_key='stopped',_console_view=view,runtime_settings={},
|
|
business_page=SimpleNamespace(archive_search=text(''),active_archive_filter='全部'),
|
|
queue_page=SimpleNamespace(pending_states={},active_task_filter='全部'),
|
|
log_page=SimpleNamespace(event_search=text(''),active_event_filter='全部'),
|
|
_console_wecom_info=mock.Mock(return_value={'connected':False}),
|
|
_console_dpi_label=mock.Mock(return_value='100%'),
|
|
_console_idle_label=mock.Mock(return_value='5秒'),
|
|
_console_runtime_label=mock.Mock(return_value='00:00:00'),
|
|
_console_archive_filter_label=mock.Mock(return_value='全部会话'))
|
|
defaults={'people':[{'key':'one'}],'logs':[],'activities':[],'metrics':{},'journey':[],'task':None,
|
|
'journey_total':'','chain':[],'window_stats':[],'archives':[{'key':'archive'}],
|
|
'chat':[],'memory':{},'settle':{},'ai_state':{'name':'客服'},'automation_state':{'poll':2},
|
|
'system_state':{'scale':'自动','serverUrl':'http://example.invalid'},'log_detail':{}}
|
|
for name,value in defaults.items():setattr(host,'_console_'+name,mock.Mock(return_value=value))
|
|
host._console_sort_people=mock.Mock(side_effect=lambda people:people)
|
|
host._wechat_database_console_state=mock.Mock(return_value={'accounts':[]})
|
|
return host
|
|
|
|
|
|
class SnapshotPerformanceTest(TestCase):
|
|
def test_editing_pages_do_not_read_queues_archives_or_logs(self):
|
|
for view,key in [('ai','ai'),('automation','automation'),('system','system'),('wechatdb','wechatDb')]:
|
|
with self.subTest(view=view):
|
|
host=snapshot_host(view);data=MainWindow._console_snapshot(host)
|
|
self.assertIn(key,data)
|
|
for name in ['people','task','archives','chat','memory','logs','metrics']:
|
|
getattr(host,'_console_'+name).assert_not_called()
|
|
if view!='system':host._console_system_state.assert_not_called()
|
|
|
|
def test_each_page_receives_its_required_state(self):
|
|
required={'auto':['people','task','journey','windowStats','metrics'],
|
|
'queue':['people','task','chain','metrics'],
|
|
'archive':['archives','chat','memory','settle'],
|
|
'logs':['logs','logDetail','exceptions','chain','metrics'],
|
|
'ai':['ai'],'automation':['automation'],'system':['system'],'wechatdb':['wechatDb']}
|
|
for view,keys in required.items():
|
|
data=MainWindow._console_snapshot(snapshot_host(view))
|
|
for key in keys:self.assertIn(key,data,(view,key))
|
|
|
|
def test_system_cache_preserves_connection_fields_and_updates_scale(self):
|
|
host=snapshot_host('system');MainWindow._console_snapshot(host)
|
|
host._console_view='ai';host.runtime_settings={'scale_ratio':'200%'}
|
|
result=MainWindow._console_snapshot(host)
|
|
self.assertEqual(result['system']['serverUrl'],'http://example.invalid')
|
|
self.assertEqual(result['system']['scale'],'200%')
|
|
host._console_system_state.assert_called_once()
|
|
|
|
def test_live_queue_does_not_build_archive_or_settings_views(self):
|
|
host=snapshot_host('queue');data=MainWindow._console_snapshot(host)
|
|
self.assertEqual(data['selectedKey'],'one');self.assertTrue(data['people'][0]['active'])
|
|
for name in ['archives','chat','memory','ai_state','system_state','automation_state']:
|
|
getattr(host,'_console_'+name).assert_not_called()
|
|
host._wechat_database_console_state.assert_not_called()
|
|
|
|
|
|
class EventLoopPerformanceTest(TestCase):
|
|
def host(self,count):
|
|
events=queue.Queue()
|
|
for i in range(count):events.put(('log',str(i)))
|
|
return SimpleNamespace(_queue=events,_start_time=None,_running=False,
|
|
append_log=mock.Mock(),queue_timer=mock.Mock())
|
|
|
|
def test_large_event_burst_yields_after_64_and_keeps_order(self):
|
|
host=self.host(200)
|
|
with mock.patch('wechat_gui_qt.time.perf_counter',return_value=0):
|
|
MainWindow._process_queue(host)
|
|
self.assertEqual(host._queue.qsize(),136)
|
|
host.queue_timer.setInterval.assert_called_with(16)
|
|
for _ in range(3):MainWindow._process_queue(host)
|
|
self.assertTrue(host._queue.empty())
|
|
self.assertEqual([c.args[0] for c in host.append_log.call_args_list],[str(i) for i in range(200)])
|
|
host.queue_timer.setInterval.assert_called_with(140)
|
|
|
|
def test_time_budget_yields_even_when_message_limit_not_reached(self):
|
|
host=self.host(20)
|
|
with mock.patch('wechat_gui_qt.time.perf_counter',side_effect=[0,0.001,0.010]):
|
|
MainWindow._process_queue(host)
|
|
self.assertEqual(host.append_log.call_count,1)
|
|
self.assertEqual(host._queue.qsize(),19)
|
|
|
|
def test_manual_save_consumes_pending_native_autosave(self):
|
|
host=SimpleNamespace(save_timer=mock.Mock(),_set_save_status=mock.Mock(),saved=mock.Mock(),values=lambda:{'poll_interval':7})
|
|
SettingsPage._emit_save(host)
|
|
host.save_timer.stop.assert_called_once()
|
|
host.saved.emit.assert_called_once_with({'poll_interval':7})
|