重构 Autocal 采集页:单一队列与控制器驱动的主按钮
选点、对准、加载与录像分散在两个面板,状态靠隐藏相位判断。现改由不依赖 Qt 的采集控制器驱动,并加入录像选项、汇总平铺副本、保持到时自动结束、会话结束回到空闲,以及无设备时的模拟采集。已运行 Autocal 非 Qt 测试并通过范围内门禁;界面测试待 Windows 复核,未做实机验收。
This commit is contained in:
@@ -3,7 +3,9 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import csv
|
||||
import hashlib
|
||||
import json
|
||||
import logging
|
||||
|
||||
import pytest
|
||||
|
||||
@@ -14,11 +16,16 @@ from orisys_autocal.summary_export import (
|
||||
POINTER_NAME,
|
||||
SummaryExportError,
|
||||
load_summary,
|
||||
published_summary_directory,
|
||||
publish_summary,
|
||||
render_html,
|
||||
)
|
||||
|
||||
|
||||
def _sha256(path):
|
||||
return hashlib.sha256(path.read_bytes()).hexdigest()
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def session(tmp_path):
|
||||
root = multipoint_session(
|
||||
@@ -173,3 +180,85 @@ def test_the_sensitivity_csv_has_one_row_per_point_trial_axis(session):
|
||||
assert {row["review_status"] for row in rows} == {"candidate"}
|
||||
fn = [row for row in rows if row["component"] == "FN"]
|
||||
assert all(float(row["slope_raw_per_N"]) == pytest.approx(40.0) for row in fn)
|
||||
|
||||
|
||||
def test_the_published_files_are_mirrored_under_the_session_root(session):
|
||||
"""The folder the operator opens is the byte-identical copy the pointer names."""
|
||||
summary = build_summary(session)
|
||||
|
||||
published = publish_summary(session, summary)
|
||||
|
||||
pointer = _pointer(session)
|
||||
flat = session / "summary"
|
||||
assert flat.is_dir()
|
||||
assert sorted(path.name for path in flat.iterdir()) == sorted(pointer["files"])
|
||||
for name, digest in pointer["files"].items():
|
||||
assert _sha256(flat / name) == digest, name
|
||||
assert (flat / name).read_bytes() == (published / name).read_bytes(), name
|
||||
# The readable copy is not a second pointer: the authoritative path is derived/.
|
||||
assert pointer["path"] == f"derived/{published.name}"
|
||||
assert published_summary_directory(session) == flat
|
||||
assert load_summary(session)[1]["summary_id"] == summary["summary_id"]
|
||||
|
||||
|
||||
def test_a_failed_copy_reports_without_moving_the_pointer_back(session, monkeypatch, caplog):
|
||||
"""Losing the readable copy must not lose the published evidence or the pointer."""
|
||||
import orisys_autocal.summary_export as module
|
||||
|
||||
first = build_summary(session)
|
||||
publish_summary(session, first)
|
||||
flat = session / "summary"
|
||||
before = {path.name: _sha256(path) for path in flat.iterdir()}
|
||||
first_pointer = _pointer(session)
|
||||
|
||||
second = build_summary(session)
|
||||
second["generated_at"] = "2999-01-01T00:00:00+00:00"
|
||||
reports: list[str] = []
|
||||
|
||||
def explode(*args, **kwargs):
|
||||
raise OSError("no space left on device")
|
||||
|
||||
monkeypatch.setattr(module, "_write_flat_summary", explode)
|
||||
with caplog.at_level(logging.WARNING, logger="orisys_autocal.summary_export"):
|
||||
published = publish_summary(session, second, on_warning=reports.append)
|
||||
|
||||
pointer = _pointer(session)
|
||||
assert pointer["summary_id"] == second["summary_id"]
|
||||
assert pointer["path"] == f"derived/{published.name}"
|
||||
assert first_pointer["path"] != pointer["path"]
|
||||
for name, digest in pointer["files"].items():
|
||||
assert _sha256(published / name) == digest, name
|
||||
# The previous readable copy still stands, unchanged.
|
||||
assert {path.name: _sha256(path) for path in flat.iterdir()} == before
|
||||
assert reports and "no space left on device" in reports[0]
|
||||
assert any("no space left on device" in record.message for record in caplog.records)
|
||||
|
||||
|
||||
def test_a_stale_readable_copy_is_not_offered_as_the_current_summary(session, monkeypatch):
|
||||
"""A failed mirror keeps the older copy; opening it must not pass it off as current.
|
||||
|
||||
The pointer has moved on, so ``summary/`` describes a superseded version. The
|
||||
operator must be sent to the directory the pointer names, not to numbers the
|
||||
pointer no longer claims.
|
||||
"""
|
||||
import orisys_autocal.summary_export as module
|
||||
|
||||
first = build_summary(session)
|
||||
publish_summary(session, first)
|
||||
|
||||
second = build_summary(session)
|
||||
second["generated_at"] = "2999-01-01T00:00:00+00:00"
|
||||
|
||||
def explode(*args, **kwargs):
|
||||
raise OSError("no space left on device")
|
||||
|
||||
monkeypatch.setattr(module, "_write_flat_summary", explode)
|
||||
published = publish_summary(session, second)
|
||||
|
||||
pointer = _pointer(session)
|
||||
assert pointer["summary_id"] == second["summary_id"]
|
||||
opened = published_summary_directory(session)
|
||||
assert opened is not None
|
||||
assert opened == published, "the stale summary/ was offered as the current summary"
|
||||
stored = json.loads((opened / "summary.json").read_text(encoding="utf-8"))
|
||||
assert stored["summary_id"] == second["summary_id"]
|
||||
|
||||
Reference in New Issue
Block a user