fix stop button disabled during connection
This commit is contained in:
@@ -15,6 +15,8 @@ from __future__ import annotations
|
||||
|
||||
import os
|
||||
import sys
|
||||
import threading
|
||||
import time
|
||||
from pathlib import Path
|
||||
|
||||
APP_ROOT = Path(__file__).resolve().parents[1]
|
||||
@@ -45,6 +47,55 @@ def section(name: str) -> None:
|
||||
print(f"\n== {name} ==")
|
||||
|
||||
|
||||
class _StuckWorker:
|
||||
"""A worker whose device open never returns.
|
||||
|
||||
Mirrors the real worker's non-blocking ``stop()`` contract so the window can
|
||||
be exercised against the failure mode the Stop button exists to survive.
|
||||
"""
|
||||
|
||||
def __init__(self) -> None:
|
||||
self._thread: threading.Thread | None = None
|
||||
self._end_reason: str | None = None
|
||||
self.thread_still_owned = False
|
||||
self.stop_blocked_for = 0.0
|
||||
|
||||
def start(self, _source, *, config_ref=None) -> None:
|
||||
self._thread = threading.Thread(target=self._hang, daemon=True)
|
||||
self._thread.start()
|
||||
|
||||
def _hang(self) -> None:
|
||||
time.sleep(600.0)
|
||||
|
||||
@property
|
||||
def is_running(self) -> bool:
|
||||
return self._thread is not None and self._thread.is_alive()
|
||||
|
||||
@property
|
||||
def phase(self) -> str:
|
||||
return "connecting"
|
||||
|
||||
def latest(self):
|
||||
return None
|
||||
|
||||
def consume_end_reason(self):
|
||||
reason, self._end_reason = self._end_reason, None
|
||||
return reason
|
||||
|
||||
def reset(self) -> None:
|
||||
return None
|
||||
|
||||
def stop(self, *, wait: bool = False) -> None:
|
||||
self._end_reason = self._end_reason or "stopped"
|
||||
started = time.perf_counter()
|
||||
if wait and self._thread is not None and self._thread.is_alive():
|
||||
self._thread.join(timeout=5.0)
|
||||
if self._thread is None or not self._thread.is_alive():
|
||||
self._thread = None
|
||||
self.thread_still_owned = self._thread is not None
|
||||
self.stop_blocked_for = time.perf_counter() - started
|
||||
|
||||
|
||||
# --------------------------------------------------------------------------
|
||||
# Palette rules
|
||||
#
|
||||
@@ -440,6 +491,74 @@ def main() -> int:
|
||||
window.close()
|
||||
app.processEvents()
|
||||
|
||||
section("stopping a hung connect")
|
||||
stuck = _StuckWorker()
|
||||
window = StudioMainWindow(
|
||||
prefs={
|
||||
"language": "zh_CN",
|
||||
"theme": "dark",
|
||||
"sensor_type": "box",
|
||||
"source_mode": "camera",
|
||||
}
|
||||
)
|
||||
window.resize(1440, 880)
|
||||
window.show()
|
||||
window._ensure_workers = lambda: None
|
||||
window._box_worker = stuck
|
||||
window._finger_worker = stuck
|
||||
app.processEvents()
|
||||
|
||||
check(not window.act_stop.isEnabled(), "Stop shortcut is live before a session")
|
||||
window.toolbar.btn_run.setChecked(True)
|
||||
app.processEvents()
|
||||
|
||||
check(window._session_phase == "connecting", "run did not enter the connecting phase")
|
||||
check(
|
||||
window.toolbar.btn_run.isEnabled(),
|
||||
"Stop is disabled while connecting — a hung connect could not be cancelled",
|
||||
)
|
||||
check(window.toolbar.btn_run.isChecked(), "run button did not latch into the stop state")
|
||||
check(
|
||||
window.toolbar.btn_run.text() == t("toolbar.cancel_label"),
|
||||
f"connecting label is not the cancel label: {window.toolbar.btn_run.text()!r}",
|
||||
)
|
||||
check(
|
||||
window.toolbar.btn_run.toolTip() == t("toolbar.cancel_tip"),
|
||||
"connecting tooltip does not explain how to cancel",
|
||||
)
|
||||
check(window.act_stop.isEnabled(), "Stop shortcut is disabled while connecting")
|
||||
|
||||
started = time.perf_counter()
|
||||
window.toolbar.btn_run.setChecked(False)
|
||||
elapsed = time.perf_counter() - started
|
||||
check(elapsed < 0.5, f"Stop blocked the GUI for {elapsed:.2f}s on a hung connect")
|
||||
check(not window._running, "session stayed active after Stop")
|
||||
check(window._session_phase == "idle", "phase did not return to idle after Stop")
|
||||
check(
|
||||
stuck.thread_still_owned,
|
||||
"Stop abandoned the still-running worker thread",
|
||||
)
|
||||
|
||||
# The slow-connect hint must point at the escape hatch.
|
||||
window.toolbar.btn_run.setChecked(True)
|
||||
app.processEvents()
|
||||
window._connect_started_at = time.perf_counter() - 3600.0
|
||||
window._on_run_tick()
|
||||
check(
|
||||
window.statusBar().currentMessage() == t("status.connect_slow"),
|
||||
f"no slow-connect hint: {window.statusBar().currentMessage()!r}",
|
||||
)
|
||||
|
||||
escaped = time.perf_counter()
|
||||
window._on_stop_shortcut()
|
||||
check(
|
||||
time.perf_counter() - escaped < 0.5,
|
||||
"Esc did not cancel the hung connect immediately",
|
||||
)
|
||||
check(not window._running, "Esc left the session running")
|
||||
window.close()
|
||||
app.processEvents()
|
||||
|
||||
print(f"\nscenes: {scene_count} checks: {CHECKS} failures: {len(FAILURES)}")
|
||||
for failure in FAILURES:
|
||||
print(f" FAIL: {failure}")
|
||||
|
||||
Reference in New Issue
Block a user