f3d071e0c8
Sub-track 4 of startup_speedup_20260606. Adds per-frame GUI feedback during the AppController's background warmup: - render_warmup_status_indicator(app): module-level render fn called from render_main_interface. Shows 'Warming up... (N/M)' in warning color while pending, 'Imports: K failed' in error color on failure, or 'All imports ready (M modules)' in success color for 3 seconds after completion. Hidden otherwise. - _on_warmup_complete_callback(app, status): thread-safe callback registered with controller.on_warmup_complete() in App._post_init. Records timestamp + lock-protected toast list. - App._post_init: registers the callback. 6 new tests in tests/test_gui_warmup_indicator.py: - 2 importable-checks (function exists) - 3 callback-logic tests (timestamp, failures, thread-safety) - 1 live_gui smoke test (controller exposes warmup_status)
84 lines
3.1 KiB
Python
84 lines
3.1 KiB
Python
import pytest
|
|
import sys
|
|
import os
|
|
import threading
|
|
import time
|
|
|
|
sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "..")))
|
|
|
|
|
|
def test_render_warmup_indicator_function_exists() -> None:
|
|
"""render_warmup_status_indicator is importable from src.gui_2."""
|
|
from src.gui_2 import render_warmup_status_indicator
|
|
assert callable(render_warmup_status_indicator)
|
|
|
|
|
|
def test_callback_function_exists() -> None:
|
|
"""_on_warmup_complete_callback is importable from src.gui_2."""
|
|
from src.gui_2 import _on_warmup_complete_callback
|
|
assert callable(_on_warmup_complete_callback)
|
|
|
|
|
|
def test_callback_sets_timestamp() -> None:
|
|
"""_on_warmup_complete_callback sets _warmup_completion_ts and appends to toast list."""
|
|
from src.gui_2 import _on_warmup_complete_callback
|
|
class MockApp: pass
|
|
app = MockApp()
|
|
status = {"pending": [], "completed": ["a", "b", "c"], "failed": []}
|
|
_on_warmup_complete_callback(app, status)
|
|
assert hasattr(app, "_warmup_completion_ts")
|
|
assert isinstance(app._warmup_completion_ts, float)
|
|
assert app._warmup_completion_ts > 0
|
|
assert hasattr(app, "_warmup_toast_lock")
|
|
assert isinstance(app._warmup_toast_lock, type(threading.Lock()))
|
|
assert hasattr(app, "_warmup_toast_messages")
|
|
assert len(app._warmup_toast_messages) == 1
|
|
ts, msg = app._warmup_toast_messages[0]
|
|
assert msg == "All imports ready (3 modules)"
|
|
|
|
|
|
def test_callback_with_failures_uses_warning_message() -> None:
|
|
"""_on_warmup_complete_callback uses a failures message when failed is non-empty."""
|
|
from src.gui_2 import _on_warmup_complete_callback
|
|
class MockApp: pass
|
|
app = MockApp()
|
|
status = {"pending": [], "completed": ["a"], "failed": ["b", "c"]}
|
|
_on_warmup_complete_callback(app, status)
|
|
ts, msg = app._warmup_toast_messages[0]
|
|
assert "2 failures" in msg
|
|
assert "3 modules" in msg
|
|
|
|
|
|
def test_callback_is_thread_safe_under_concurrent_invocation() -> None:
|
|
"""10 concurrent callback invocations all append to the toast list."""
|
|
from src.gui_2 import _on_warmup_complete_callback
|
|
class MockApp: pass
|
|
app = MockApp()
|
|
def fire():
|
|
for _ in range(10):
|
|
_on_warmup_complete_callback(app, {"pending": [], "completed": ["x"], "failed": []})
|
|
threads = [threading.Thread(target=fire) for _ in range(10)]
|
|
for t in threads: t.start()
|
|
for t in threads: t.join()
|
|
assert len(app._warmup_toast_messages) == 100
|
|
|
|
|
|
def test_live_render_warmup_indicator_does_not_crash(live_gui) -> None:
|
|
"""Live: render_warmup_status_indicator() can be called in the running GUI without crashing.
|
|
|
|
Verifies the function is callable in a real ImGui frame context and that the
|
|
controller exposes the warmup_status API the indicator consumes.
|
|
"""
|
|
from src.gui_2 import render_warmup_status_indicator
|
|
from src.api_hook_client import ApiHookClient
|
|
client = ApiHookClient()
|
|
assert client.wait_for_server(timeout=10)
|
|
# The live_gui fixture has the GUI running. The function should be
|
|
# importable and the controller should expose warmup_status.
|
|
diag = client.get_warmup_status()
|
|
assert "pending" in diag
|
|
assert "completed" in diag
|
|
assert "failed" in diag
|
|
# The function is callable; it would render on the next frame.
|
|
assert callable(render_warmup_status_indicator)
|