修复 Autocal 录像故障处理、复核回放与汇总统计

This commit is contained in:
ykcheungab
2026-10-17 12:30:15 +08:00
parent 19426c3c4a
commit 7607653c18
18 changed files with 15042 additions and 23 deletions
@@ -193,6 +193,40 @@ def test_a_stopped_source_keeps_the_repeat_incomplete(tmp_path):
assert session["status"] == "incomplete"
def test_a_failed_writer_aborts_while_the_camera_keeps_running(tmp_path, monkeypatch):
from orisys_app_shared.record.video_writer import VideoSegments
def fail_write(self, frame):
raise OSError("encoder or disk failed")
monkeypatch.setattr(VideoSegments, "write", fail_write)
campaign = _campaign(repeats=1)
root = _start(campaign, tmp_path)
worker = FakeWorker()
campaign.attach_capture(worker, fps=10)
recorder = campaign._recorder
try:
for _ in range(8):
worker.advance()
worker.emit()
time.sleep(0.02)
deadline = time.monotonic() + 3
while recorder.is_recording and time.monotonic() < deadline:
time.sleep(0.005)
assert recorder.stats().failed
assert worker.is_running
assert not campaign.capture_ready
campaign.poll()
assert campaign.phase in {"closing", "aborted"}
_drain(campaign)
trial = _session(root)["trials"][0]
assert trial["capture_status"] == "incomplete"
assert trial["capture_reason"] == "write_error"
finally:
campaign.stop("test finished")
campaign.flush_close()
def test_a_simulated_repeat_is_steps_complete_but_not_recorded_evidence(tmp_path):
campaign = _campaign(repeats=1)
root = campaign.start(
+16
View File
@@ -164,3 +164,19 @@ def test_confirmed_and_candidate_windows_are_never_fitted_together():
assert by_review["candidate"]["slope_raw_per_N"] == pytest.approx(40.0)
assert by_review["confirmed"]["window_count"] == 2
assert by_review["confirmed"]["slope_raw_per_N"] == pytest.approx(40.0)
def test_site_aggregation_keeps_review_sets_and_trial_counts_separate():
from orisys_autocal.summary import aggregate_sensitivity, build_sensitivity
windows = [
_window(0, 0, branch="baseline"), _window(5, 500),
_window(10, 400, verified=True), _window(15, 600, verified=True),
]
rows = [row for row in aggregate_sensitivity(build_sensitivity(windows))
if row["component"] == "FN"]
assert len(rows) == 2
by_review = {row["review_status"]: row for row in rows}
assert by_review["candidate"]["slope_mean_raw_per_N"] == pytest.approx(100)
assert by_review["confirmed"]["slope_mean_raw_per_N"] == pytest.approx(40)
assert all(row["trials"] == 1 and row["slope_std_raw_per_N"] is None for row in rows)
+33
View File
@@ -205,6 +205,39 @@ def test_the_frame_list_is_bounded_by_the_hold(tmp_path):
assert all(frame.in_video for frame in frames)
def test_confirmed_playback_uses_the_reviewers_selected_interval(tmp_path):
review = _review(tmp_path)
trial_id, step = _capture(review)
window = review["sites"][0]["windows"][1]
selected_start = window["trace"][1][0]
selected_end = window["hold_end_host_mono_ns"]
review = review_session(tmp_path / "rec", selections={f"{trial_id}:{step}": {
"t_start_host_mono_ns": selected_start,
"t_end_host_mono_ns": selected_end,
"stability_verified": True,
}})
with open_window_video(review, trial_id=trial_id, step_index=step) as player:
assert len(player.frames) == 2
assert all(selected_start <= frame.t_host_mono_ns < selected_end for frame in player.frames)
def test_playback_does_not_retain_previously_decoded_images(tmp_path):
import gc
import weakref
review = _review(tmp_path)
trial_id, step = _capture(review)
refs = []
with open_window_video(review, trial_id=trial_id, step_index=step) as player:
for index in range(player.frame_count):
image = player.frame(index)
assert image is not None
refs.append(weakref.ref(image))
del image
gc.collect()
assert sum(ref() is not None for ref in refs) <= 1
def test_decode_failure_is_reported_rather_than_showing_a_neighbour(tmp_path):
review = _review(tmp_path)
root = tmp_path / "rec"
@@ -147,3 +147,33 @@ def test_an_unclosed_trial_does_not_offer_the_video(qt_app, tmp_path):
assert dialog.message.text()
finally:
dialog.close()
def test_a_closed_aborted_trial_can_be_replayed_for_diagnosis(qt_app, tmp_path):
import json
from orisys_autocal.ui.record_overview_dialog import RecordOverviewDialog
from orisys_autocal.ui.session_review_dialog import SessionReviewDialog
root = multipoint_session(tmp_path / "rec", trial_status="incomplete")
path = root / "session.json"
manifest = json.loads(path.read_text(encoding="utf-8"))
manifest["trials"][0].update(
capture_status="incomplete", capture_report={"completed": True})
path.write_text(json.dumps(manifest), encoding="utf-8")
review = review_session(root)
for kind in (SessionReviewDialog, RecordOverviewDialog):
dialog = kind(root, review=review)
opened = None
try:
if isinstance(dialog, RecordOverviewDialog):
dialog.table.selectRow(0)
opened = dialog._video()
assert opened is not None
wait_review(opened)
assert opened.video is not None
assert opened.image.pixmap() is not None
finally:
if opened is not None:
opened.close()
dialog.close()