修复 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:
ykcheungab
2026-10-18 19:24:31 +08:00
parent dcabd547fa
commit 433b76e43c
38 changed files with 1036 additions and 171 deletions
+48 -29
View File
@@ -18,7 +18,7 @@ chain — and assert on the picture the panel actually shows, not on a state a h
built.
The recording is copied before use: a pass writes its result into the recording's own
directory, and nothing may write into ``dist/``.
directory, and the tracked copy the tests read must never be written to.
"""
from __future__ import annotations
@@ -34,11 +34,11 @@ from orisys_app_shared.acquisition.image_backend import (
ARROW_PURE,
ARROW_STITCHED,
DEPTH,
DEPTH_ARROWS,
DERIVED_MODES,
RECORDED,
plan_image,
)
from orisys_app_shared.i18n import t
from orisys_app_shared.scene3d.contracts import DisplaySettings
from orisys_app_shared.scene3d.visual_policy import (
select_flow_samples,
@@ -46,14 +46,8 @@ from orisys_app_shared.scene3d.visual_policy import (
from orisys_studio.replay import MODE_RECORDING
from PySide6.QtWidgets import QApplication
REPO = Path(__file__).resolve().parents[3]
STUDIO = REPO / "dist" / "OrisysStudio"
SOURCE_RECORDING = (
STUDIO
/ "outputs"
/ "recordings"
/ "20260930T015028Z_session0002_40cb8e7441434feab48bb646afdba011"
)
#: The tracking-visible test input (``data/README.md`` explains where it came from).
DATA = Path(__file__).resolve().parent / "data" / "box_short_20260930"
#: What makes a directory a recording for the application, and all a replay reads.
RECORDING_FILES = (
"frames.csv",
@@ -61,29 +55,41 @@ RECORDING_FILES = (
"events.csv",
"session.json",
"raw_0001.mp4",
"preroll_0001.mp4",
)
#: Written by a recording and read by nothing a replay needs: ``preroll_segment()``
#: answers ``None`` when the file is absent, so it is copied when it is there and never
#: required. It stays out of the repository because it is a third of the data's size.
OPTIONAL_FILES = ("preroll_0001.mp4",)
def _require_recording() -> None:
"""The recording must be here, and its absence is a failure, not a skip.
The old version skipped: the tests went green on a machine where the footage had
been repackaged away, which is exactly how eleven failing tests went unnoticed.
"""
missing = [
str(DATA / name) for name in RECORDING_FILES if not (DATA / name).is_file()
]
assert not missing, (
"the replay wiring tests need the tracked box test recording at "
f"{DATA}; missing: {', '.join(missing)}. It is committed "
"(apps/studio/tests/data/README.md); restore it rather than skipping."
)
def _require_inputs() -> None:
missing = [
str(SOURCE_RECORDING / name)
for name in ("frames.csv", "raw_0001.mp4", "session.json")
if not (SOURCE_RECORDING / name).exists()
]
if missing:
pytest.skip(
"the replay wiring tests need the evidence recording: " + ", ".join(missing)
)
_require_recording()
pytest.importorskip("orisys", reason="the analysis pass drives the SDK chain")
def _copy_recording(destination: Path) -> Path:
"""The evidence recording, copied: nothing may write into ``dist/``."""
"""The test recording, copied: nothing may write into the tracked data."""
_require_recording()
destination.mkdir(parents=True, exist_ok=True)
for name in RECORDING_FILES:
source = SOURCE_RECORDING / name
if source.exists():
for name in RECORDING_FILES + OPTIONAL_FILES:
source = DATA / name
if source.is_file():
shutil.copy2(source, destination / name)
return destination
@@ -303,11 +309,16 @@ def test_the_operator_sees_the_cached_views_over_the_recording(
"a pass ran over a recording that already had a result"
)
# ``depth_arrows`` joined the derived views in ``091d0fd`` ("二维增加深度叠箭头"), which
# updated ``ui_check`` and the live-view tests but not this one: it was skipping on the
# missing footage at the time, so nothing caught it. The claim is unchanged — the
# recording's own picture stays one step away from *every* derived view.
assert window.arrow_panel.view_modes() == (
RECORDED,
ARROW_PURE,
ARROW_STITCHED,
DEPTH,
DEPTH_ARROWS,
), "the recording's own picture must stay one step away from the derived views"
assert window.arrow_panel.view_mode() in DERIVED_MODES, (
"the operator was left on the camera picture although a result is served"
@@ -343,14 +354,22 @@ def test_the_operator_sees_the_cached_views_over_the_recording(
"the arrow view is the recording's raw picture"
)
# The depth view is the stored map, drawn through the window's own backend. This
# result's map is exactly zero on every frame it holds — the box chain's depth is
# flat on this footage — and it is still a stored array of the result's own shape,
# so the pixels are checked against it rather than against a flat placeholder.
# The depth view is the stored map, drawn through the window's own backend. The
# map is this result's own array of the result's own shape, so the pixels are checked
# against it rather than against a placeholder.
#
# It is **not** all-zero, which is what this asserted when the recording was first
# played (a "flat depth" note): measured on the restored footage with the shipped box
# config, the stored map has 8926 non-zero cells of 365², i.e. the chain does return
# a depth field here. Flatness was a property of an earlier run's result, not a
# product promise; the claim this block exists for — the depth view's pixels are
# ``depth_map_to_bgr`` of the stored frame — is asserted below and is unchanged.
# The colour-scale caption names the range only.
_set_mode(window, DEPTH)
target = active_frame
assert target.depth is not None and target.depth_nonzero == 0
assert target.depth is not None and target.depth_nonzero > 0, (
f"the stored depth map holds no value at all: {target.depth_nonzero} non-zero cells"
)
QApplication.processEvents()
drawn = captured[-1]
assert drawn.frame_id == target.frame_id