"""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.sections.processing_section import build_processing_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.legacy_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", "legacy_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_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)