Retain live processing timings across UI reruns
This commit is contained in:
@@ -0,0 +1,88 @@
|
||||
"""Exercise the real executor and Streamlit reruns without model calls."""
|
||||
|
||||
from pathlib import Path
|
||||
from threading import current_thread
|
||||
from types import SimpleNamespace
|
||||
|
||||
import pytest
|
||||
import streamlit
|
||||
from streamlit.testing.v1 import AppTest
|
||||
|
||||
from mka.application.meeting_service import AppProgressEvent, SpeakerMappingReview, SpeakerReview
|
||||
from mka.application.progress_timing import ProcessingTimer
|
||||
from mka.ui import streamlit_app as ui
|
||||
|
||||
|
||||
class GuardedStreamlit:
|
||||
"""Fail if a worker touches the UI session proxy, including callback reads."""
|
||||
|
||||
def __getattr__(self, name):
|
||||
if name == "session_state" and current_thread().name.startswith("ThreadPoolExecutor"):
|
||||
raise AssertionError("Worker accessed Streamlit session state")
|
||||
return getattr(streamlit, name)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("fails", [False, True])
|
||||
@pytest.mark.parametrize("mapped", [False, True])
|
||||
def test_regeneration_captures_ui_values_before_real_worker_and_retains_timing(
|
||||
monkeypatch, fails, mapped
|
||||
):
|
||||
outcome = SimpleNamespace(
|
||||
succeeded=True,
|
||||
run_dir=Path("/tmp/alpha-mapping-test"),
|
||||
speaker_attribution_available=True,
|
||||
original_protocol="Existing protocol",
|
||||
)
|
||||
review = SpeakerMappingReview((SpeakerReview("SPEAKER_00", ()),), (("a", "A"),), {})
|
||||
calls = []
|
||||
|
||||
class Service:
|
||||
def __init__(self, *args):
|
||||
self.timer = ProcessingTimer()
|
||||
|
||||
def load_speaker_mapping_review(self, run_dir):
|
||||
return review
|
||||
|
||||
def regenerate_protocol(self, run_dir, selections, *, progress_sink, performance_profile):
|
||||
assert current_thread().name.startswith("ThreadPoolExecutor")
|
||||
calls.append((run_dir, selections, performance_profile))
|
||||
self.timer.start_run()
|
||||
self.timer.start_stage("protocol_generation")
|
||||
progress_sink(AppProgressEvent("protocol_generation", "started", 0.0))
|
||||
if fails:
|
||||
raise RuntimeError("model unavailable")
|
||||
self.timer.finish_stage("protocol_generation")
|
||||
self.timer.finish_run()
|
||||
progress_sink(AppProgressEvent("protocol_generation", "completed", 0.0))
|
||||
return outcome
|
||||
|
||||
monkeypatch.setattr(ui, "st", GuardedStreamlit())
|
||||
monkeypatch.setattr(ui, "MeetingProcessingService", Service)
|
||||
monkeypatch.setattr(ui.AppSettings, "from_environment", lambda: None)
|
||||
monkeypatch.setattr(ui, "MeetingLabGateway", lambda: None)
|
||||
|
||||
def result_app(outcome):
|
||||
import streamlit as st
|
||||
|
||||
from mka.ui.streamlit_app import _render_result
|
||||
|
||||
st.session_state.outcome = outcome
|
||||
st.session_state.performance_profile = "fast"
|
||||
_render_result()
|
||||
|
||||
app = AppTest.from_function(result_app, args=(outcome,)).run()
|
||||
if mapped:
|
||||
app.selectbox[0].select("a").run()
|
||||
button = next(button for button in app.button if "Regenerate" in button.label)
|
||||
assert not button.disabled
|
||||
button.click().run()
|
||||
assert not app.exception
|
||||
assert calls == [(outcome.run_dir, {"SPEAKER_00": "a"} if mapped else {}, "fast")]
|
||||
states, message, timing = app.session_state["processing_progress"]
|
||||
assert states["protocol_generation"] == ("failed" if fails else "completed")
|
||||
assert not timing.running
|
||||
assert timing.total_duration >= timing.stage_durations["protocol_generation"] >= 0
|
||||
app.run()
|
||||
assert not app.exception
|
||||
assert app.session_state["processing_progress"][2] == timing
|
||||
assert any("total runtime" in item.value for item in app.info)
|
||||
Reference in New Issue
Block a user