Files
sshclient/test_monitor_nonblock.py
Your Name 5d83348db3 fix: monitor worker non-blocking + test fixes
- core/monitor.py: add fast metrics script (0.3s vs 1.4s sleep),
  collect() now defaults to fast=True for responsive UI
- ui/workers.py: MonitorWorker changed from loop mode to single-shot
  (QTimer re-arms next cycle on finished signal, avoids paramiko
  blocking in infinite loop)
- ui/widgets.py: remove debug print from _kick_one_sample
- test_monitor_nonblock.py: fix sample counting by wrapping _on_sample
  instead of disconnecting signals (old approach missed new workers)
- test_process_monitor.py: assertions account for MAX_RENDER_ROWS=200,
  fix MEM% column index (4 not 3) in sort test
2026-07-28 22:57:15 +08:00

143 lines
5.1 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""
监控 UI 不卡死测试:
- 启动监控
- 5 秒内应该完成 ≥2 次采集(说明没卡死)
- worker 不会重复启动
- 关闭/重新打开工作正常
- _on_refresh_now 不打乱 timer
"""
import os
import sys
import time
import threading
os.environ["QT_QPA_PLATFORM"] = "offscreen"
sys.path.insert(0, os.path.dirname(__file__))
from PyQt5.QtCore import QTimer
from PyQt5.QtWidgets import QApplication
from core.ssh_client import SSHConnection
from core.monitor import SystemMonitor
from core.manager import ConnectionManager
from ui.widgets import MonitorPanel
def find_local_ssh():
for pwd in ("sshclient_test_pwd_2026", "testpass", ""):
c = SSHConnection("127.0.0.1", 22, "root", pwd, timeout=5)
ok, _ = c.connect()
if ok:
return c
return None
def main():
app = QApplication(sys.argv)
print("[1/6] 连接本机 SSH")
conn = find_local_ssh()
if not conn:
print(" ⚠ 本机 SSH 不可用,跳过")
return
print(f"{conn.username}@{conn.host}")
print("[2/6] 创建 MonitorPanel + 启动监控")
mgr = ConnectionManager()
mgr.connections["fake"] = conn
panel = MonitorPanel(mgr)
panel.set_host("fake")
panel.btn_toggle.setChecked(True) # 等价于点「开始监控」
app.processEvents()
assert panel.btn_toggle.isChecked()
assert panel._monitor_conn is not None
print(f" ✓ 监控启动, interval={panel._monitor_interval_ms}ms")
print("[3/6] 5 秒内累计采集次数(要求 ≥2 次且不卡死)")
# 改用更短的 interval 让测试在 5s 内能采到 2 次以上
panel._monitor_interval_ms = 1000
# 用 wrapper 替换 _on_sample 来计数。
# 关键:_kick_one_sample 里每次创建新 worker 时执行
# self.worker.sample_ready.connect(self._on_sample)
# 此处 self._on_sample 在连接时求值,所以新 worker 会连到 wrapper。
# 旧的断开/重连方式只作用于当前 worker,新 worker 不经过 counting。
orig_on_sample = panel._on_sample
samples = [0]
def counting(m):
samples[0] += 1
orig_on_sample(m)
panel._on_sample = counting
# 重启 timer 到 1s(如果 worker #1 还在跑,等它结束后 _on_worker_finished
# 会创建/重启 timer,此时 _monitor_interval_ms 已是 1000
if getattr(panel, "_monitor_timer", None):
panel._monitor_timer.stop()
panel._monitor_timer.start(1000)
# 立即触发一次采集(如果 worker #1 已结束的话)
panel._kick_one_sample()
# 跑 5 秒(interval 1s + ~0.4s 采集耗时,应该能采 ~3 次)
deadline = time.time() + 5.0
while time.time() < deadline:
app.processEvents()
time.sleep(0.05)
wr = panel.worker.isRunning() if panel.worker else 'N/A'
timer = getattr(panel, '_monitor_timer', None)
print(f" 调试: samples={samples[0]} worker.isRunning={wr} timer.isActive={timer.isActive() if timer else 'N/A'}")
assert samples[0] >= 2, f"5s 内只采了 {samples[0]} 次,太少(卡死了?)"
print(f" ✓ 5s 内采集次数: {samples[0]} (期望 ≥2)")
print("[4/6] 验证 worker 不会重复启动")
assert panel.worker is not None
# worker 刚跑完应在 finished 状态
deadline = time.time() + 2
while panel.worker and panel.worker.isRunning() and time.time() < deadline:
app.processEvents()
time.sleep(0.05)
assert not panel.worker.isRunning(), "上次的 worker 应该在 finished 状态"
# 现在再点 refresh_now
samples[0] = 0
panel._on_refresh_now()
app.processEvents()
time.sleep(0.5)
app.processEvents()
assert samples[0] >= 1 or panel.worker.isRunning(), "refresh_now 后没采集到"
print(f" ✓ 立即刷新后采集 1 次(worker isRunning={panel.worker.isRunning() if panel.worker else 'N/A'}")
# 恢复原始 _on_sample
panel._on_sample = orig_on_sample
print("[5/6] 关闭监控(停止 worker")
panel.btn_toggle.setChecked(False)
app.processEvents()
# 等 worker 退出
if panel.worker:
deadline = time.time() + 2
while panel.worker.isRunning() and time.time() < deadline:
app.processEvents()
time.sleep(0.05)
assert not panel.worker.isRunning(), "关闭后 worker 还在跑"
assert panel._monitor_timer is None, "关闭后 timer 还在"
print(f" ✓ 监控关闭 (worker stopped, timer cleared)")
print("[6/6] 重新打开监控应能正常恢复")
panel.btn_toggle.setChecked(True)
app.processEvents()
assert panel._monitor_conn is not None
assert panel._monitor_interval_ms > 0
# 等 worker 跑完
deadline = time.time() + 3
while time.time() < deadline:
app.processEvents()
if panel.worker and not panel.worker.isRunning() and panel._proc_data:
break
time.sleep(0.05)
assert panel._proc_data, "重开后没采到数据"
print(f" ✓ 重开正常,进程数={len(panel._proc_data)}")
# 清理
panel.btn_toggle.setChecked(False)
app.processEvents()
conn.disconnect()
print("\n监控 UI 不卡死测试通过 ✓")
if __name__ == "__main__":
main()