171 lines
7.6 KiB
Python
171 lines
7.6 KiB
Python
"""Reusable Qt adapter tests, runnable with PySide2 or PySide6, without a GUI app repo."""
|
|
import os
|
|
os.environ.setdefault("QT_QPA_PLATFORM", "offscreen")
|
|
import time
|
|
import unittest
|
|
from dataclasses import replace
|
|
from unittest.mock import patch
|
|
|
|
from set_devices.plot_processing import Axis, Series, Snapshot, process
|
|
from set_devices.qt_ports.plot_processing import SignalProcessingPanel, PlotProcessingAttachment, QWidget
|
|
try:
|
|
from PySide6.QtWidgets import QApplication
|
|
except ImportError:
|
|
from PySide2.QtWidgets import QApplication
|
|
|
|
|
|
class ProcessingQtTests(unittest.TestCase):
|
|
@classmethod
|
|
def setUpClass(cls):
|
|
cls.app = QApplication.instance() or QApplication([])
|
|
|
|
def setUp(self):
|
|
self.panel = SignalProcessingPanel()
|
|
self.snapshot = Snapshot([Series("a", "A", [(0, 0), (1, 1), (2, 0)])])
|
|
self.panel.set_snapshot(self.snapshot)
|
|
|
|
def tearDown(self):
|
|
self.panel.close()
|
|
|
|
def calculate(self):
|
|
self.panel.calculate()
|
|
deadline = time.monotonic() + 5
|
|
while self.panel._job is not None and time.monotonic() < deadline:
|
|
self.app.processEvents()
|
|
time.sleep(.002)
|
|
self.assertIsNone(self.panel._job)
|
|
self.assertIsNotNone(self.panel.curve, self.panel.status.text())
|
|
|
|
def test_all_methods_accumulate_and_parameter_changes_keep_results(self):
|
|
for index, method in enumerate(("polynomial", "linear", "pchip", "spline")):
|
|
self.panel.method.setCurrentIndex(self.panel.method.findData(method))
|
|
self.calculate()
|
|
self.assertEqual(method, self.panel.curve.request.method)
|
|
self.panel.count.setValue(self.panel.count.value() + 1)
|
|
self.assertEqual(index + 1, len(self.panel.curves))
|
|
self.assertTrue(self.panel.export_button.isEnabled())
|
|
specs, data = self.panel.overlay()
|
|
self.assertEqual(4, len(specs))
|
|
self.assertEqual(4, len(data))
|
|
self.assertEqual(4, len({spec[3] for spec in specs}))
|
|
self.panel.clear()
|
|
self.assertEqual((), self.panel.curves)
|
|
self.assertIsNone(self.panel.curve)
|
|
|
|
def test_repeated_request_does_not_duplicate_curve(self):
|
|
self.calculate()
|
|
self.calculate()
|
|
self.assertEqual(1, len(self.panel.curves))
|
|
|
|
def test_repeated_fft_workers_keep_gui_owned_signal_emitters(self):
|
|
self.panel.set_snapshot(replace(self.snapshot, axis=Axis("Frequency", "Hz"), source="FFT"))
|
|
for _ in range(5):
|
|
for method in ("linear", "pchip", "spline", "polynomial"):
|
|
self.panel.method.setCurrentIndex(self.panel.method.findData(method))
|
|
self.panel.calculate()
|
|
self.assertIs(self.panel, self.panel._job.signals.parent())
|
|
deadline = time.monotonic() + 5
|
|
while self.panel._job is not None and time.monotonic() < deadline:
|
|
self.app.processEvents()
|
|
time.sleep(.002)
|
|
self.assertIsNone(self.panel._job)
|
|
self.assertEqual(method, self.panel.curve.request.method)
|
|
self.assertEqual(4, len(self.panel.curves))
|
|
|
|
def test_switching_channel_preserves_results_and_invalidates_only_changed_source(self):
|
|
snapshot = replace(self.snapshot, series=self.snapshot.series +
|
|
(Series("b", "B", [(0, 2), (1, 3), (2, 2)]),))
|
|
self.panel.set_snapshot(snapshot)
|
|
self.calculate()
|
|
first = self.panel.curve
|
|
self.panel.channel.setCurrentIndex(1)
|
|
self.panel.set_snapshot(snapshot)
|
|
self.assertEqual((first,), self.panel.curves)
|
|
self.calculate()
|
|
second = self.panel.curve
|
|
self.assertEqual((first, second), self.panel.curves)
|
|
second_spec = self.panel.overlay()[0][1]
|
|
self.panel.set_snapshot(replace(snapshot, series=(
|
|
Series("a", "A", [(0, 5), (1, 6)]), snapshot.series[1])))
|
|
self.assertEqual((second,), self.panel.curves)
|
|
self.assertEqual(second_spec, self.panel.overlay()[0][0])
|
|
|
|
def test_navigation_during_second_calculation_preserves_both_results(self):
|
|
self.panel.preserve_on_view_change = True
|
|
self.calculate()
|
|
first = self.panel.curve
|
|
self.panel.method.setCurrentIndex(self.panel.method.findData("linear"))
|
|
with patch("set_devices.qt_ports.plot_processing.QThreadPool"):
|
|
self.panel.calculate()
|
|
job = self.panel._job
|
|
self.panel.set_snapshot(replace(self.snapshot, x_range=(0, 1)))
|
|
self.panel._finished(job.signature, process(job.signature), "")
|
|
self.assertEqual(2, len(self.panel.curves))
|
|
self.assertIs(first, self.panel.curves[0])
|
|
|
|
def test_failed_calculation_keeps_previous_curve(self):
|
|
self.calculate()
|
|
previous = self.panel.curve
|
|
self.panel.count.setValue(123)
|
|
with patch("set_devices.qt_ports.plot_processing.QThreadPool"):
|
|
self.panel.calculate()
|
|
self.panel._finished(self.panel._job.signature, None, "test error")
|
|
self.assertEqual((previous,), self.panel.curves)
|
|
|
|
def test_hidden_source_preserves_result_but_changed_samples_invalidate_it(self):
|
|
self.calculate()
|
|
curve = self.panel.curve
|
|
hidden = replace(self.snapshot, series=(replace(self.snapshot.series[0], visible=False),))
|
|
self.panel.set_snapshot(hidden)
|
|
self.assertEqual((curve,), self.panel.curves)
|
|
self.assertTrue(self.panel.export_button.isEnabled())
|
|
self.panel.set_snapshot(replace(hidden, series=(replace(hidden.series[0], points=((0, 2), (1, 3))),)))
|
|
self.assertEqual((), self.panel.curves)
|
|
|
|
def test_source_units_and_blocking_invalidate_result(self):
|
|
for changed in (replace(self.snapshot, axis=Axis("Frequency", "Hz")),
|
|
replace(self.snapshot, source="new file"),
|
|
replace(self.snapshot, x_range=(0, 1)),
|
|
replace(self.snapshot, blocked_reason="FFT")):
|
|
self.panel.set_snapshot(self.snapshot)
|
|
self.calculate()
|
|
self.panel.set_snapshot(changed)
|
|
self.assertIsNone(self.panel.curve)
|
|
self.assertFalse(self.panel.export_button.isEnabled())
|
|
self.assertFalse(self.panel.apply_button.isEnabled())
|
|
|
|
def test_late_worker_result_is_rejected_even_after_source_returns(self):
|
|
# Capture, but do not schedule, the real worker. Deliver its answer after
|
|
# the source changes away and back to the same numerical values.
|
|
with patch("set_devices.qt_ports.plot_processing.QThreadPool"):
|
|
self.panel.calculate()
|
|
job = self.panel._job
|
|
self.panel.set_snapshot(replace(self.snapshot, source="another"))
|
|
self.panel.set_snapshot(self.snapshot)
|
|
self.panel._finished(job.signature, process(job.signature), "")
|
|
self.assertIsNone(self.panel.curve)
|
|
self.assertFalse(self.panel.export_button.isEnabled())
|
|
|
|
def test_attachment_is_lazy_and_coalesces_source_notifications(self):
|
|
widget = QWidget()
|
|
calls = []
|
|
attachment = PlotProcessingAttachment(widget, lambda: calls.append(1) or self.snapshot, widget.update)
|
|
try:
|
|
for _ in range(10):
|
|
attachment.source_changed()
|
|
self.app.processEvents()
|
|
self.assertEqual([], calls)
|
|
self.assertIsNone(attachment.panel)
|
|
attachment.open()
|
|
self.assertEqual([1], calls)
|
|
for _ in range(10):
|
|
attachment.source_changed()
|
|
self.app.processEvents()
|
|
self.assertEqual([1, 1], calls)
|
|
finally:
|
|
widget.close()
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|