Files
orisys-app/apps/studio/tests/test_transport_review.py
T
ykcheungab 433b76e43c 修复 Studio 失败测试并新增合并门禁
把丢失的 box 录像放回 apps/studio/tests/data/,修掉第 3 节 3–10 号与两处过时测试桩,
注册 timing 标记并让根目录收集零错误;新增 tools/gate.ps1 与可选的 pre-push 钩子。
验证:studio 524 通过 19 跳过、shared 467 通过 18 跳过、ui_check 632 项 0 失败、gate.ps1 退出码 0。
细节见 docs/test-repair-plan.zh.md。
2026-10-18 19:24:31 +08:00

163 lines
7.5 KiB
Python

"""Review of the rail's unified play control: play/pause, stop, reset — and nothing else.
Written against the requirement rather than the implementation:
* the play-control group is exactly **play/pause**, **stop**, **reset** (no extra pause
button, no role switch that turns the third button into a stop);
* the primary button is a play/pause toggle in **both** views, and pressing it while a
session runs pauses without stopping — the device stays connected;
* stop ends the session (live) or leaves the replay (playback);
* reset is one control with one meaning *per view*: the sensor tracker's in the live
view (which owns a device session), the display's in the replay view (which owns
none).
"""
from __future__ import annotations
import ui_check
from orisys_app_shared.i18n import t
from _studio_helpers import finish_run, running_window, show_window
def test_the_group_is_exactly_play_pause_stop_reset(make_window):
window = show_window(make_window(sensor_type="box", source_mode="camera"), (1440, 880))
toolbar = window.toolbar
assert not hasattr(toolbar, "btn_pause"), "an extra pause button is still in the rail"
assert hasattr(toolbar, "btn_stop"), "there is no dedicated stop button"
assert not hasattr(toolbar, "set_reset_role"), (
"the reset button still switches role to stop; one control cannot be two"
)
assert toolbar.btn_run.isCheckable(), "the play/pause control must be a toggle"
window.close()
def test_the_primary_button_pauses_and_does_not_stop(make_window):
"""The heart of the unification: one toggle, three states, and stop is not the toggle."""
window, stuck = running_window(make_window)
try:
assert window._session_active, "the fixture did not start a session"
assert window.toolbar.btn_run.isChecked(), (
"a running session must show the transport as engaged (pause available)"
)
assert "暂停" in window.toolbar.btn_run.text() or "Paus" in window.toolbar.btn_run.text(), (
f"a running session reads {window.toolbar.btn_run.text()!r}, not pause"
)
# Press it: pause, not stop.
window.toolbar.btn_run.setChecked(False)
assert window._session_active, "pressing the transport ended the session"
assert window._display_paused is True, "the window did not record the pause"
assert stuck.is_paused is True, "the worker was not paused"
assert "继续" in window.toolbar.btn_run.text() or "Resume" in window.toolbar.btn_run.text(), (
f"a paused session reads {window.toolbar.btn_run.text()!r}, not resume"
)
# Press it again: resume.
window.toolbar.btn_run.setChecked(True)
assert window._display_paused is False
assert stuck.is_paused is False, "the worker was not resumed"
assert window._session_active, "resuming ended the session"
finally:
finish_run(window, stuck)
def test_stop_ends_the_session(make_window):
window, stuck = running_window(make_window)
try:
# A first frame is what makes the session a *running* one (the rail's reset and
# the phase both key off it), so drive one before pressing stop.
stuck.emit(ui_check._fake_packet(1))
window._on_run_tick()
assert window.toolbar.btn_stop.isEnabled(), "stop is not reachable while running"
window.toolbar.btn_stop.click()
assert window._session_phase != "running", (
f"the stop button left the session in {window._session_phase!r}"
)
assert not window.toolbar.btn_run.isChecked(), "the transport stayed engaged after stop"
assert window._display_paused is False, "the pause flag survived the stop"
finally:
stuck.release()
def test_a_paused_session_still_offers_both_stop_and_reset(make_window):
"""Pause holds the session; the device is still open, so both controls stay offered.
This is the state the two rules are easiest to get wrong in: the transport is
unchecked, which is not "stopped", so Stop must still end the session and the reset
must still reach the device it is holding.
"""
window = show_window(make_window(sensor_type="box", source_mode="camera"), (1440, 880))
stuck = ui_check._StuckWorker()
window._ensure_workers = lambda: None
window._box_worker = stuck
window._finger_worker = stuck
window._start_run()
window._run_timer.stop()
try:
# A first frame is the application's own path to ``running``, and the moment
# the rail recomputes what the session can do (the reset keys off it), so the
# session is reached through that path rather than stated directly.
stuck.emit(ui_check._fake_packet(1))
window._on_run_tick()
assert window._session_phase == "running"
assert window.toolbar.btn_reset.isEnabled(), (
"a running session must offer the tracker reset before it is paused"
)
window.toolbar.btn_run.setChecked(False)
assert window._display_paused is True and window._running
assert window.toolbar.btn_stop.isEnabled(), "no way out of the paused session"
assert window.toolbar.btn_reset.isEnabled(), (
"pausing took the device's reset away: the device is still open"
)
assert window.toolbar.reset_target() == "sensor"
assert window.toolbar.btn_reset.text() == t("toolbar.reset_sensor")
assert window.toolbar.btn_reset.toolTip() == t("toolbar.reset_sensor_tip")
window.toolbar.btn_reset.click()
assert stuck.reset_requests == 1, (
"the paused session's reset did not reach the device"
)
window.toolbar.btn_stop.click()
assert not window._running, "Stop did not end the paused session"
finally:
stuck.release()
def test_reset_is_the_sensor_in_live_and_the_display_in_the_replay_view(make_window):
"""Reset stays one control, and each view gets the meaning it can act on.
The enabled state of reset once frames are flowing is covered by
``test_ui_smoke.py::test_a_zeroing_is_recorded_only_after_the_worker_confirms_it``
(which drives ``_on_reset``); this test pins what the unification is about: reset and
stop are two different controls, and the view decides which of the two things the
reset acts on — the sensor tracker where a session owns a device, the display where
there is nothing of the device to act on.
"""
window, stuck = running_window(make_window)
try:
assert window.toolbar.btn_reset.text() != window.toolbar.btn_stop.text(), (
"reset and stop must be distinguishable controls"
)
assert window.toolbar.reset_target() == "sensor"
assert window.toolbar.btn_stop.isEnabled(), "stop is not reachable during a session"
finally:
finish_run(window, stuck)
replay = show_window(make_window(sensor_type="box", source_mode="camera"), (1440, 880))
replay.brand_bar.btn_playback.click()
assert replay.toolbar.reset_target() == "display", (
"the replay view owns no device: its reset is the display's"
)
assert replay.toolbar.btn_reset.isEnabled(), (
"the display reset is unavailable in the replay view"
)
assert replay.toolbar.btn_reset.text() == t("toolbar.reset_display")
assert hasattr(replay.toolbar, "btn_stop"), "the replay view has no stop control"
assert replay.toolbar.btn_run.text().startswith("▶"), (
f"the replay transport idle reads {replay.toolbar.btn_run.text()!r}, not play"
)
replay.close()