加快门禁:删除测试窗口、释放桩线程并并行运行三个阶段
关闭后未删除的测试窗口让每次主题切换重排全部残留控件,ui_check 每次关窗还要等桩线程 5 秒。Linux 上结论不变,test_ui_smoke 75→64 s、ui_check 52→16 s;Windows 门禁结果见 .scratch/gate-speed/。
This commit is contained in:
@@ -97,6 +97,8 @@ def make_window(qt_app, tmp_path, monkeypatch):
|
||||
|
||||
yield _make
|
||||
|
||||
from PySide6.QtCore import QCoreApplication, QEvent
|
||||
|
||||
for window in created:
|
||||
try:
|
||||
window.close()
|
||||
@@ -104,6 +106,14 @@ def make_window(qt_app, tmp_path, monkeypatch):
|
||||
# Reported rather than swallowed: a window that cannot close is a Qt lifetime
|
||||
# problem worth seeing, but it must not turn a green test red.
|
||||
logging.getLogger(__name__).debug("could not close a test window", exc_info=True)
|
||||
# ``close()`` only hides a window: something in the window keeps it referenced,
|
||||
# so it stayed alive for the rest of the session. Every leaked window's ~600
|
||||
# widgets are then re-polished by each later ``app.setStyleSheet`` — measured on
|
||||
# Linux, one theme switch takes 0.07 s with one window and 0.85 s after ten closed
|
||||
# ones, and three theme tests took 76 s of a 231 s Windows suite. Delete it.
|
||||
window.deleteLater()
|
||||
created.clear()
|
||||
QCoreApplication.sendPostedEvents(None, QEvent.Type.DeferredDelete)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
|
||||
@@ -1048,7 +1048,9 @@ def test_the_domain_boundary_is_latched_for_the_session(make_window):
|
||||
"the previous session's island survived into a new run"
|
||||
)
|
||||
window._run_timer.stop()
|
||||
window._stop_run(reason="user_stop")
|
||||
# Release the stand-in worker as well: the second run re-armed its hang, and a
|
||||
# window closed on a hanging worker waits out its 5 s join in teardown.
|
||||
_finish(window, stuck)
|
||||
|
||||
|
||||
# ------------------------------------------------------------- experiment record
|
||||
|
||||
@@ -414,6 +414,22 @@ DECORATIVE_PAIRS: tuple[tuple[str, tuple[str, ...]], ...] = (
|
||||
)
|
||||
|
||||
|
||||
def _close_window(window) -> None:
|
||||
"""Close a check window, releasing its stand-in worker first.
|
||||
|
||||
A ``_StuckWorker`` hangs on purpose, and the window's shutdown path stops its
|
||||
worker with ``wait=True``, which joins that thread for up to 5 s. Every scene that
|
||||
closed a window on a still-hanging stub paid those 5 s (measured: 7 joins, 35 s of
|
||||
a 52 s run). What those scenes check happens before the close, so the stub is let
|
||||
go here; the shutdown itself is checked separately in a subprocess.
|
||||
"""
|
||||
for name in ("_box_worker", "_finger_worker"):
|
||||
worker = getattr(window, name, None)
|
||||
if isinstance(worker, _StuckWorker):
|
||||
worker.release()
|
||||
window.close()
|
||||
|
||||
|
||||
def _srgb_channel(value: int) -> float:
|
||||
channel = value / 255.0
|
||||
if channel <= 0.03928:
|
||||
@@ -836,7 +852,7 @@ def main() -> int:
|
||||
len(window.prefs.get("splitter_sizes", [])) == 4,
|
||||
"splitter sizes not persisted",
|
||||
)
|
||||
window.close()
|
||||
_close_window(window)
|
||||
app.processEvents()
|
||||
|
||||
section("small window")
|
||||
@@ -885,7 +901,7 @@ def main() -> int:
|
||||
f" in {type(parent).__name__} {parent.width()}x{parent.height()}"
|
||||
)
|
||||
check(not oversize, f"widgets overflow their parent: {oversize}")
|
||||
window.close()
|
||||
_close_window(window)
|
||||
app.processEvents()
|
||||
|
||||
section("top area")
|
||||
@@ -1072,7 +1088,7 @@ def main() -> int:
|
||||
not window.toolbar.settings_expanded(),
|
||||
f"{theme}: the settings area could not be collapsed again",
|
||||
)
|
||||
window.close()
|
||||
_close_window(window)
|
||||
app.processEvents()
|
||||
|
||||
# English labels are the widest case ("Start recording", "Sensor config"),
|
||||
@@ -1096,7 +1112,7 @@ def main() -> int:
|
||||
f"the sensor summary is not stated in full: "
|
||||
f"{english_window.toolbar.btn_sensor.text()!r}",
|
||||
)
|
||||
english_window.close()
|
||||
_close_window(english_window)
|
||||
app.processEvents()
|
||||
set_locale("zh_CN")
|
||||
|
||||
@@ -1153,7 +1169,7 @@ def main() -> int:
|
||||
f" reset label at 1280x720, {language}: live {live_width}px, "
|
||||
f"replay {replay_width}px (button width {button.width()}px)"
|
||||
)
|
||||
measured.close()
|
||||
_close_window(measured)
|
||||
app.processEvents()
|
||||
set_locale("zh_CN")
|
||||
|
||||
@@ -1320,7 +1336,7 @@ def main() -> int:
|
||||
window.toolbar.set_record_checked(False)
|
||||
window._stop_run(reason="user_stop")
|
||||
stuck.release()
|
||||
window.close()
|
||||
_close_window(window)
|
||||
app.processEvents()
|
||||
|
||||
section("stopping a hung connect")
|
||||
@@ -1388,7 +1404,7 @@ def main() -> int:
|
||||
"Esc did not cancel the hung connect immediately",
|
||||
)
|
||||
check(not window._running, "Esc left the session running")
|
||||
window.close()
|
||||
_close_window(window)
|
||||
app.processEvents()
|
||||
|
||||
# Let the deliberately-stuck thread finish before the interpreter tears
|
||||
@@ -1491,7 +1507,7 @@ def main() -> int:
|
||||
"the depth colour scale re-fitted per frame instead of staying fixed",
|
||||
)
|
||||
|
||||
window.close()
|
||||
_close_window(window)
|
||||
app.processEvents()
|
||||
stuck.release()
|
||||
|
||||
@@ -1797,14 +1813,14 @@ def main() -> int:
|
||||
"the 2D image area does not follow the output image's aspect ratio: "
|
||||
f"{image.width()}x{image.height()}",
|
||||
)
|
||||
finger_window.close()
|
||||
_close_window(finger_window)
|
||||
app.processEvents()
|
||||
check(
|
||||
window.toolbar.record_chip.text() == t("record.state_idle"),
|
||||
f"the record strip did not fall back to idle: "
|
||||
f"{window.toolbar.record_chip.text()!r}",
|
||||
)
|
||||
window.close()
|
||||
_close_window(window)
|
||||
app.processEvents()
|
||||
|
||||
section("chart header and unit chip")
|
||||
@@ -1892,7 +1908,7 @@ def main() -> int:
|
||||
# The locale is process-wide: leave it where the other sections expect it.
|
||||
header_window._set_language("zh_CN")
|
||||
app.processEvents()
|
||||
header_window.close()
|
||||
_close_window(header_window)
|
||||
app.processEvents()
|
||||
|
||||
section("acquisition correctness")
|
||||
@@ -2156,7 +2172,7 @@ def main() -> int:
|
||||
pane_window.replay.close()
|
||||
pane_window._stop_run(reason="user_stop")
|
||||
stuck.release()
|
||||
pane_window.close()
|
||||
_close_window(pane_window)
|
||||
app.processEvents()
|
||||
_shutil.rmtree(replay_root, ignore_errors=True)
|
||||
|
||||
@@ -2323,7 +2339,7 @@ def main() -> int:
|
||||
)
|
||||
check(meta["stats"]["frames"] == 31, f"unexpected frame count: {meta['stats']['frames']}")
|
||||
|
||||
window.close()
|
||||
_close_window(window)
|
||||
app.processEvents()
|
||||
stuck.release()
|
||||
_shutil.rmtree(scratch, ignore_errors=True)
|
||||
@@ -2430,7 +2446,7 @@ def main() -> int:
|
||||
f"the session phase is {fail_window._session_phase!r} after a failed recording",
|
||||
)
|
||||
|
||||
fail_window.close()
|
||||
_close_window(fail_window)
|
||||
app.processEvents()
|
||||
stuck_fail.release()
|
||||
_app_module.recordings_dir = lambda: scratch
|
||||
@@ -2605,7 +2621,7 @@ def main() -> int:
|
||||
f"a matching calibration was not requested: {stuck_identity.calibration_requests}",
|
||||
)
|
||||
|
||||
identity_window.close()
|
||||
_close_window(identity_window)
|
||||
app.processEvents()
|
||||
_shutil.rmtree(record_dir, ignore_errors=True)
|
||||
|
||||
@@ -2657,7 +2673,7 @@ def main() -> int:
|
||||
units_plots.history.sample_count > 0,
|
||||
"the rebuilt history lost every sample",
|
||||
)
|
||||
units_window.close()
|
||||
_close_window(units_window)
|
||||
units_window._box_worker = None
|
||||
stuck_units.release()
|
||||
app.processEvents()
|
||||
@@ -2716,7 +2732,7 @@ def main() -> int:
|
||||
and not any(_math.isfinite(v) for v in one_state.values[:, 1]),
|
||||
"FX carried a value without a calibration curve",
|
||||
)
|
||||
one_window.close()
|
||||
_close_window(one_window)
|
||||
one_window._box_worker = None
|
||||
stuck_one.release()
|
||||
app.processEvents()
|
||||
@@ -2903,7 +2919,7 @@ def main() -> int:
|
||||
check(plots._source_ready, "switching units detached the curve source")
|
||||
check(plots._unit == "N", f"the unit switch did not reach the charts: {plots._unit}")
|
||||
|
||||
window.close()
|
||||
_close_window(window)
|
||||
app.processEvents()
|
||||
stuck.release()
|
||||
|
||||
@@ -2996,7 +3012,7 @@ def main() -> int:
|
||||
"the way back to the camera did not take effect",
|
||||
)
|
||||
|
||||
window.close()
|
||||
_close_window(window)
|
||||
app.processEvents()
|
||||
stuck.release()
|
||||
|
||||
@@ -3141,7 +3157,7 @@ def main() -> int:
|
||||
pane.close_view()
|
||||
check(True, "closing the 3D pane twice is safe")
|
||||
finally:
|
||||
pane_window.close()
|
||||
_close_window(pane_window)
|
||||
app.processEvents()
|
||||
|
||||
section("replay")
|
||||
@@ -3325,7 +3341,7 @@ def main() -> int:
|
||||
)
|
||||
other_window.replay_ctl.stop_activity()
|
||||
app.processEvents()
|
||||
other_window.close()
|
||||
_close_window(other_window)
|
||||
app.processEvents()
|
||||
replay_window.activateWindow()
|
||||
app.processEvents()
|
||||
@@ -3706,7 +3722,7 @@ def main() -> int:
|
||||
)
|
||||
|
||||
replay_window.replay.close()
|
||||
replay_window.close()
|
||||
_close_window(replay_window)
|
||||
app.processEvents()
|
||||
|
||||
_shutil.rmtree(replay_root, ignore_errors=True)
|
||||
|
||||
Reference in New Issue
Block a user