Files
radar_system/python_app/tests/test_runtime_history.py
T
2026-10-06 12:32:26 +03:00

121 lines
6.5 KiB
Python

"""Deleting an acquisition and changing startup settings respect stage ownership."""
from __future__ import annotations
from collections import deque
from dataclasses import fields
import unittest
from unittest import mock
from python_app.gui.controllers.history_controller import HistoryController
from python_app.gui.runtime.history import (
build_processor_run_signature, build_run_history_signature, record_result_history,
)
from python_app.models.dataset_model import ResultCollection, SweepCollection
from python_app.models.run_config_schema import RunConfigModel
def sweep(cid: int, ns: int, window: tuple[int, int] = (0, 0)) -> SweepCollection:
return SweepCollection(cid, ns, capture_start_ns=window[0], capture_end_ns=window[1])
class RemoveLastHistoryTest(unittest.TestCase):
def test_newest_capture_is_removed_across_stages_despite_late_processing(self) -> None:
old, fresh = sweep(1, 900, (10, 20)), sweep(1, 100, (30, 40))
pre_old, pre_fresh = sweep(1, 950, (10, 20)), sweep(1, 150, (30, 40))
late_old = ResultCollection(1, 1000, capture_start_ns=10, capture_end_ns=20)
fresh_result = ResultCollection(1, 200, capture_start_ns=30, capture_end_ns=40)
history = HistoryController()
history.raw.extend([fresh, old])
history.preprocessed.extend([pre_old, pre_fresh])
history.results.extend([fresh_result, late_old])
history.delete(remove_last=True)
self.assertEqual((list(history.raw), list(history.preprocessed), list(history.results)), ([old], [pre_old], [late_old]))
self.assertEqual(history.removed, [{"collection_id": 1, "capture_start_ns": 30}])
self.assertFalse(history.record_result(fresh_result))
def test_missing_stages_do_not_delete_another_acquisition_with_reused_id(self) -> None:
old, fresh = sweep(1, 100, (10, 20)), sweep(1, 200, (30, 40))
late = ResultCollection(1, 900, capture_start_ns=10, capture_end_ns=20)
for raw, pre in (([old], [fresh]), ([old, fresh], [])):
history = HistoryController()
history.raw.extend(raw)
history.preprocessed.extend(pre)
history.results.append(late)
history.delete(remove_last=True)
self.assertEqual((list(history.raw), list(history.preprocessed), list(history.results)), ([old], [], [late]))
def test_results_only_and_empty_histories(self) -> None:
result = ResultCollection(1, 100, capture_start_ns=10, capture_end_ns=20)
history = HistoryController()
history.results.append(result)
history.replay_results.append(result)
history.delete(remove_last=True)
history.delete(remove_last=True)
self.assertFalse(history.results or history.replay_results)
self.assertEqual(history.removed, [{"collection_id": 1, "capture_start_ns": 10}])
with mock.patch("python_app.gui.controllers.history_controller.time.monotonic_ns", return_value=50):
history.delete(remove_last=False)
self.assertEqual(history.clear_before_ns, 50)
self.assertEqual(history.removed, [])
self.assertFalse(history.record_result(result))
def test_results_sort_by_capture_and_reprocessed_identity_is_replaced(self) -> None:
old = ResultCollection(1, 300, capture_start_ns=10, capture_end_ns=20)
middle = ResultCollection(2, 500, capture_start_ns=30, capture_end_ns=40)
fresh = ResultCollection(1, 100, capture_start_ns=50, capture_end_ns=60)
history = deque([old, fresh], maxlen=3)
self.assertTrue(record_result_history(history, middle))
self.assertEqual(list(history), [old, middle, fresh])
reprocessed = ResultCollection(1, 900, capture_start_ns=10, capture_end_ns=20)
self.assertFalse(record_result_history(history, reprocessed))
self.assertIs(history[0], reprocessed)
self.assertFalse(record_result_history(history, ResultCollection(3, 999, capture_start_ns=1)))
self.assertFalse(record_result_history(deque(maxlen=0), fresh))
class RuntimeSignatureTest(unittest.TestCase):
def test_every_ring_endpoint_setting_requires_restart(self) -> None:
original = RunConfigModel()
signature = build_processor_run_signature(original)
for ring_field in fields(original.rings):
for field, value in (("name", "/other"), ("capacity", 7), ("slot_size_bytes", 8192)):
with self.subTest(ring=ring_field.name, field=field):
changed = original.clone()
setattr(getattr(changed.rings, ring_field.name), field, value)
self.assertNotEqual(build_processor_run_signature(changed), signature)
def test_locator_and_live_reader_settings_require_restart(self) -> None:
original = RunConfigModel()
signature = build_processor_run_signature(original)
for field in fields(original.runtime.locator_server):
with self.subTest(field=field.name):
changed = original.clone()
value = getattr(changed.runtime.locator_server, field.name)
setattr(changed.runtime.locator_server, field.name, value + "x" if isinstance(value, str) else value + 1)
self.assertNotEqual(build_processor_run_signature(changed), signature)
for field, value in (("processing_live_config_path", "other.json"), ("idle_sleep_ms", 8)):
changed = original.clone()
setattr(changed.runtime, field, value)
self.assertNotEqual(build_processor_run_signature(changed), signature)
def test_notch_and_preprocess_assets_reset_history_without_restarting_processor(self) -> None:
original = RunConfigModel()
for section, field, value in (
("notch", "enabled", True), ("notch", "bands_hz", [(1e8, 2e8)]),
("notch", "taper_width_hz", 1e7), ("notch", "taper_type", "linear"),
("s21.calibration", "set_name", "other"), ("s21.reference", "bundle_path", "other.bin"),
):
with self.subTest(section=section, field=field):
changed = original.clone()
target = changed.preprocess
for part in section.split("."):
target = getattr(target, part)
setattr(target, field, value)
self.assertEqual(build_processor_run_signature(changed), build_processor_run_signature(original))
self.assertNotEqual(build_run_history_signature(changed), build_run_history_signature(original))
if __name__ == "__main__":
unittest.main()