Files
radar_system/python_app/tests/test_live_settings_regressions.py
2026-10-08 16:24:01 +03:00

117 lines
7.2 KiB
Python

"""Settings reach the processor atomically and preserve the saved frequency band."""
from copy import deepcopy
import unittest
from unittest import mock
from PyQt6.QtWidgets import QApplication, QComboBox, QDoubleSpinBox, QStackedWidget
from python_app.gui.antenna_geometry_editor import AntennaGeometryEditor
from python_app.gui.controllers.radar_settings_controller import RadarSettingsController
from python_app.gui.controllers.sections.processing_section import build_processing_group
from python_app.gui.controllers.sections.radar_section import build_radar_group
from python_app.models.gui_profile_schema import GuiStateModel
from python_app.models.dataset_model import SweepCollection
from python_app.models.run_config_schema import RunConfigModel
from python_app.tests.gui_test_support import isolated_window, widget
class LiveSettingsRegressionTest(unittest.TestCase):
@classmethod
def setUpClass(cls) -> None:
cls.app = QApplication.instance() or QApplication([])
def test_initial_processing_band_uses_sweep_limits_before_profile_values(self) -> None:
config, gui = RunConfigModel(), GuiStateModel()
config.radar.sweep.start_hz, config.radar.sweep.stop_hz = 1e6, 9.5e9
for settings in (gui.processing.bscan, gui.processing.gpr,
gui.processing.sar3d):
settings.start_freq_mhz, settings.stop_freq_mhz = 20, 9200
section = build_processing_group(config, gui)
self.addCleanup(section.group.deleteLater)
for mode in ("bscan", "gpr", "sar3d"):
for bound, expected in (("start", 20), ("stop", 9200)):
with self.subTest(mode=mode, bound=bound):
editor = widget(section, f"_{mode}_{bound}_freq_mhz", QDoubleSpinBox)
self.assertEqual((editor.minimum(), editor.maximum(), editor.value()), (1, 9500, expected))
def test_rph_processing_limits_use_band_instead_of_hidden_vna_fields(self) -> None:
config, gui = RunConfigModel(), GuiStateModel()
config.radar.model = "rph_radar"
config.radar.sweep.start_hz, config.radar.sweep.stop_hz = 1e6, 2e6
config.radar.rph_radar.band.start_hz, config.radar.rph_radar.band.stop_hz = 2.1e9, 5.5e9
gui.processing.bscan.start_freq_mhz, gui.processing.bscan.stop_freq_mhz = 2200, 5400
radar, processing = build_radar_group(config), build_processing_group(config, gui)
for group in (radar.group, processing.group):
self.addCleanup(group.deleteLater)
controller = RadarSettingsController(radar, mock.Mock(), processing, lambda: config, mock.Mock(),
reset_preprocess=lambda: None, live_changed=lambda: None)
for stage in ("initial", "resync"):
if stage == "resync":
controller.sync_frequency_limits()
with self.subTest(stage=stage):
for editor in processing.frequency_controls:
self.assertEqual((editor.minimum(), editor.maximum()), (2100, 5500))
self.assertEqual(widget(processing, "_bscan_start_freq_mhz", QDoubleSpinBox).value(), 2200)
self.assertEqual(widget(processing, "_bscan_stop_freq_mhz", QDoubleSpinBox).value(), 5400)
def test_failed_live_write_restores_web_batch_and_desktop_changes(self) -> None:
window = isolated_window(self.addCleanup)
window._pipeline.timer.stop()
mode = window._view.processing.mode
gain = widget(window._view.processing, "_bscan_gain", QDoubleSpinBox)
pages = window._view.processing.pages
live = window._live
before = (window._profile.build_gui_state(), window._config.geometry(),
live.active_mode, pages.currentIndex(), window._plots.stack.currentWidget(),
live.revision, deepcopy(live.processing_settings), window._plots.required_revision,
live.writer.path.read_bytes())
for source in ("web", "web_geometry", "desktop_mode", "desktop_value", "desktop_geometry"):
with self.subTest(source=source), \
mock.patch.object(live.writer, "write", side_effect=OSError("disk full")), \
mock.patch.object(window._preprocess, "invalidate_object_calibration_after_geometry_change") as invalidate, \
mock.patch.object(window._log, "show_exception") as error:
if source == "web":
live.apply_web({"processor_mode": "bscan", "bscan_gain": gain.value() + .1})
elif source in {"web_geometry", "desktop_geometry"}:
editor = window._view.processing.geometries[0]
records = editor.records()
records[0]["x_m"] += .125
if source == "web_geometry":
live.apply_web({"tx_geometry": records})
else:
editor.set_records(records)
elif source == "desktop_mode":
mode.setCurrentText("bscan")
else:
widget(window._view.processing, "_gpr_range_comp_power", QDoubleSpinBox).setValue(.4)
error.assert_called_once()
invalidate.assert_not_called()
self.assertEqual((window._profile.build_gui_state(), window._config.geometry(),
live.active_mode, pages.currentIndex(), window._plots.stack.currentWidget(),
live.revision, live.processing_settings, window._plots.required_revision,
live.writer.path.read_bytes()), before)
with mock.patch.object(window._plots, "draw_bscan", side_effect=ValueError("invalid plot")), \
mock.patch.object(window._log, "show_exception") as error:
live.apply_web({"processor_mode": "bscan", "bscan_gain": gain.value() + .1})
error.assert_called_once()
self.assertEqual((mode.currentText(), live.active_mode, pages.currentIndex()), ("bscan", "bscan", 1))
self.assertEqual(live.processing_settings[live.revision]["processor_mode"], "bscan")
self.assertNotEqual(live.writer.path.read_bytes(), before[-1])
def test_mode_publication_selects_replay_from_new_mode_before_changing_the_plot(self) -> None:
window = isolated_window(self.addCleanup)
window._pipeline.timer.stop()
window._history.preprocessed.append(SweepCollection(1, 1, [], capture_start_ns=1, capture_end_ns=2))
with mock.patch.object(window._pipeline.supervisor, "is_processor_running", return_value=True), \
mock.patch.object(window._pipeline, "_result_reader"), \
mock.patch.object(window._pipeline, "drain_results", return_value=None), \
mock.patch.object(window._pipeline, "schedule_replay"), \
mock.patch.object(window._plots, "draw_bscan"):
for mode, reprocess in (("bscan", False), ("pass_through", True)):
with self.subTest(mode=mode):
window._live.apply_web({"processor_mode": mode})
published = window._live.processing_settings[window._live.revision]
self.assertEqual(published["processor_mode"], mode)
self.assertEqual(published["reprocess_current_result"], reprocess)