修复 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。
This commit is contained in:
@@ -7,16 +7,16 @@ from pathlib import Path
|
||||
import numpy as np
|
||||
import pytest
|
||||
import ui_check
|
||||
from tests.conftest import (
|
||||
from _studio_helpers import (
|
||||
finish_run as _finish,
|
||||
)
|
||||
from tests.conftest import (
|
||||
from _studio_helpers import (
|
||||
running_window as _running_window,
|
||||
)
|
||||
from tests.conftest import (
|
||||
from _studio_helpers import (
|
||||
show_window as _show,
|
||||
)
|
||||
from tests.conftest import (
|
||||
from _studio_helpers import (
|
||||
visible_descendants,
|
||||
)
|
||||
|
||||
@@ -193,13 +193,27 @@ def test_status_chip_tracks_phase(make_window):
|
||||
|
||||
|
||||
def test_run_tooltip_explains_disabled_reason(make_window):
|
||||
"""A video source never starts a live run, and the rail says why.
|
||||
|
||||
The button used to become enabled when a video file was chosen here; the live view
|
||||
is a camera only (``test_live_run_refuses_a_video_source`` pins the same rule from
|
||||
the other side), so choosing a file leaves Run unavailable and the reason names the
|
||||
camera instead of a missing file.
|
||||
"""
|
||||
from orisys_app_shared.i18n import t
|
||||
|
||||
window = _show(make_window(source_mode="video", video_path=""), (1400, 880))
|
||||
assert not window.toolbar.btn_run.isEnabled()
|
||||
assert window.toolbar.btn_run.toolTip()
|
||||
|
||||
window.toolbar.set_video_path("C:/tmp/demo.mp4")
|
||||
window._update_run_enabled()
|
||||
assert window.toolbar.btn_run.isEnabled()
|
||||
reason = t("toolbar.run_video_in_replay")
|
||||
assert not window.toolbar.btn_run.isEnabled(), (
|
||||
"a video source still starts a live run"
|
||||
)
|
||||
assert window.toolbar.btn_run.toolTip() == reason, window.toolbar.btn_run.toolTip()
|
||||
assert window.toolbar.run_hint.text() == reason, window.toolbar.run_hint.text()
|
||||
|
||||
|
||||
def test_toolbar_run_button_is_labelled(make_window):
|
||||
@@ -1501,7 +1515,18 @@ def test_the_sensor_summary_opens_the_dialog(make_window):
|
||||
assert dialog.calib_title.text() == t("sensor.calib_section")
|
||||
assert dialog.version_label.text() and dialog.axes_label.text()
|
||||
assert dialog.export_label.text() == t("sensor.export_none")
|
||||
assert not dialog.btn_apply.isEnabled(), "applying with no export"
|
||||
# 应用 is the *live calibration* action, not "apply the last export" (``3d2fb0c``
|
||||
# moved it here): it is offered with no export because there is always a choice to
|
||||
# apply — the profile's declared file, a browsed one, or raw force — and with no
|
||||
# session running it only remembers that choice (``sensor_dialog.py:324`` enables it
|
||||
# unconditionally; ``apply_live_calibration_choice`` reports a problem instead of
|
||||
# raising, so an offered button cannot fail).
|
||||
assert dialog.btn_apply.isEnabled(), "the live calibration cannot be applied"
|
||||
assert dialog.btn_apply.text() == t("cal.apply")
|
||||
assert dialog.btn_apply.toolTip() == t("sensor.live_calibration_apply_tip")
|
||||
assert dialog.calibration_combo.currentData() in ("default", "none"), (
|
||||
dialog.calibration_combo.currentData()
|
||||
)
|
||||
|
||||
# Changing the product in the dialog reaches the window and the rail.
|
||||
picker.type_combo.setCurrentIndex(picker.type_combo.findData("finger"))
|
||||
@@ -1659,11 +1684,17 @@ def test_the_2d_area_keeps_the_output_aspect_ratio(make_window):
|
||||
|
||||
|
||||
def test_the_replay_view_is_the_live_view_with_a_video_source(make_window):
|
||||
"""One layout for both views; the only difference is the source slot.
|
||||
"""The same panes and the same transport as the live view; two stated differences.
|
||||
|
||||
The replay used to carry its own bar (a progress slider plus a recording
|
||||
picker). It is gone: the chart's cursor is the playhead, its header carries
|
||||
the transport, and the rail shows the video instead of the camera.
|
||||
|
||||
What differs is deliberate (``321e52f``: "keep each preset's panel priority"):
|
||||
the replay gives the **charts** the room, so the 2D panel is narrower than in the
|
||||
live view and the charts are wider. This used to assert the splitter widths were
|
||||
*equal*, which was true before that change and is the opposite of what the
|
||||
layout now promises.
|
||||
"""
|
||||
from orisys_app_shared.i18n import t
|
||||
|
||||
@@ -1673,13 +1704,23 @@ def test_the_replay_view_is_the_live_view_with_a_video_source(make_window):
|
||||
|
||||
QApplication.processEvents()
|
||||
live_sizes = tuple(window.content_splitter.sizes())
|
||||
# Slot 1 is the 2D panel and slot 2 the charts (0 and 3 are the mesh and calibration
|
||||
# panels, both hidden here, hence their zero width).
|
||||
assert live_sizes[1] > 0 and live_sizes[2] > 0, live_sizes
|
||||
|
||||
window.brand_bar.btn_playback.click()
|
||||
QApplication.processEvents()
|
||||
assert not hasattr(window, "replay_bar"), "the replay still has a separate bar"
|
||||
assert tuple(window.content_splitter.sizes()) == live_sizes, (
|
||||
"the replay view is not laid out like the live view"
|
||||
replay_sizes = tuple(window.content_splitter.sizes())
|
||||
assert replay_sizes[1] < live_sizes[1], (
|
||||
f"the replay 2D panel is not narrower than the live one: {replay_sizes} vs {live_sizes}"
|
||||
)
|
||||
assert replay_sizes[2] > live_sizes[2], (
|
||||
f"the replay charts did not take that room: {replay_sizes} vs {live_sizes}"
|
||||
)
|
||||
# The priority behind those numbers is ``view_layout.py``'s per-preset stretch
|
||||
# factors (arrow 4, charts 5 in replay). The splitter exposes no getter for them in
|
||||
# PySide6, so the widths above are what this test can read.
|
||||
assert window.toolbar.btn_replay_video.isVisible(), "no video picker while replaying"
|
||||
assert window.toolbar.btn_replay_video.text() == t("toolbar.choose_video")
|
||||
assert window.toolbar.btn_replay_video.toolTip() == t("toolbar.choose_video_tip")
|
||||
@@ -2949,6 +2990,14 @@ def test_the_rail_puts_result_identity_before_a_long_video_name(
|
||||
|
||||
|
||||
def test_old_result_depth_is_not_sent_to_the_new_sensor_scene(make_window, monkeypatch):
|
||||
"""A result from the previous sensor must not reach the new sensor's 3D scene.
|
||||
|
||||
Driven through the window's public packet entry (``show_analysis_packet``, which
|
||||
``cf49b08``/``28e955a`` made the replay path; the private ``_show_analysis_frame``
|
||||
it replaced no longer exists). The claim is unchanged: a Finger window refuses a
|
||||
packet whose result says it came from a Box, and accepts the same packet once the
|
||||
result names the sensor on screen.
|
||||
"""
|
||||
from types import SimpleNamespace
|
||||
|
||||
window = make_window(sensor_type="finger")
|
||||
@@ -2968,10 +3017,10 @@ def test_old_result_depth_is_not_sent_to_the_new_sensor_scene(make_window, monke
|
||||
monkeypatch.setattr(window, "_connect_analysis_display", lambda _panel: True)
|
||||
monkeypatch.setattr(window, "_tick_analysis_3d", lambda: None)
|
||||
try:
|
||||
window._show_analysis_frame("old box packet")
|
||||
assert submitted == []
|
||||
window.show_analysis_packet("old box packet")
|
||||
assert submitted == [], "an old sensor's depth reached the new sensor's scene"
|
||||
session.reader.provenance.sensor_type = "finger"
|
||||
window._show_analysis_frame("finger packet")
|
||||
window.show_analysis_packet("finger packet")
|
||||
assert submitted == ["finger packet"]
|
||||
finally:
|
||||
panel._view = real_view
|
||||
|
||||
Reference in New Issue
Block a user