test: add comprehensive test suite for visualization and comparison modules

This commit is contained in:
jack
2026-06-25 13:49:25 -07:00
parent cfadbdf1c3
commit 831457941a
4 changed files with 274 additions and 0 deletions
+12
View File
@@ -0,0 +1,12 @@
"""Shared pytest fixtures."""
import pytest
from PySide6.QtWidgets import QApplication
@pytest.fixture(scope="session")
def qapp():
"""Provide a QApplication instance for the test session."""
app = QApplication.instance()
if app is None:
app = QApplication([])
yield app
+63
View File
@@ -0,0 +1,63 @@
"""Tests for src/pygui/backend/comparison.py."""
import numpy as np
from pygui.backend.comparison import compare_runs
def test_identical_series():
a = [20.0, 30.0, 40.0, 50.0, 60.0]
result = compare_runs(a, a)
assert result["distance"] == 0.0
assert len(result["index_a"]) == len(a)
assert result["index_a"] == result["index_b"]
def test_empty_series():
result = compare_runs([], [])
assert result["distance"] == float("inf")
assert result["index_a"] == []
assert result["index_b"] == []
assert result["warping_path"] == []
def test_one_empty():
result = compare_runs([10.0, 20.0], [])
assert result["distance"] == float("inf")
def test_aligned_series():
a = [10.0, 20.0, 30.0]
b = [15.0, 25.0, 35.0]
result = compare_runs(a, b)
assert result["distance"] >= 0.0
assert len(result["index_a"]) > 0
assert len(result["index_b"]) > 0
def test_single_element():
result = compare_runs([42.0], [42.0])
assert result["distance"] == 0.0
def test_different_lengths():
short = [10.0, 20.0]
long_ = [10.0, 15.0, 20.0, 25.0, 30.0]
result = compare_runs(short, long_)
assert result["distance"] >= 0.0
assert len(result["index_a"]) > 0
assert len(result["index_b"]) > 0
assert len(result["index_a"]) == len(result["index_b"])
def test_return_structure():
a = [1.0, 2.0, 3.0, 4.0, 5.0]
b = [2.0, 3.0, 4.0, 5.0, 6.0]
result = compare_runs(a, b)
assert "distance" in result
assert "index_a" in result
assert "index_b" in result
assert "warping_path" in result
assert isinstance(result["distance"], float)
assert isinstance(result["index_a"], list)
assert isinstance(result["index_b"], list)
assert isinstance(result["warping_path"], list)
+108
View File
@@ -0,0 +1,108 @@
"""Tests for src/pygui/backend/visualization/graph_view.py."""
import json
from unittest.mock import MagicMock, PropertyMock, patch
import pytest
def _make_plot_widget():
"""Create a mock PlotWidget with the interface GraphView expects."""
widget = MagicMock()
widget.removeItem = MagicMock()
widget.plot = MagicMock(return_value=MagicMock())
widget.setYRange = MagicMock()
widget.clear = MagicMock()
return widget
class TestUpdateTrend:
def test_parses_valid_json(self):
from pygui.backend.visualization.graph_view import GraphView
gv = GraphView()
gv._plot_widget = _make_plot_widget()
gv.updateTrend(json.dumps([20.0, 25.0, 30.0]))
assert gv._plot_widget.plot.called
gv._plot_widget.setYRange.assert_called_once()
def test_rejects_invalid_json(self):
from pygui.backend.visualization.graph_view import GraphView
gv = GraphView()
gv._plot_widget = _make_plot_widget()
gv.updateTrend("not json")
assert not gv._plot_widget.plot.called
def test_rejects_none(self):
from pygui.backend.visualization.graph_view import GraphView
gv = GraphView()
gv._plot_widget = _make_plot_widget()
gv.updateTrend(None)
assert not gv._plot_widget.plot.called
def test_empty_list(self):
from pygui.backend.visualization.graph_view import GraphView
gv = GraphView()
gv._plot_widget = _make_plot_widget()
gv.updateTrend("[]")
# Empty list means nothing to plot, but clear IS called
assert gv._plot_widget.plot.called is False
def test_no_plot_widget(self):
from pygui.backend.visualization.graph_view import GraphView
gv = GraphView()
assert gv._plot_widget is None
# Should not crash
gv.updateTrend("[1, 2, 3]")
class TestUpdateComparison:
def test_parses_valid_json(self):
from pygui.backend.visualization.graph_view import GraphView
gv = GraphView()
gv._plot_widget = _make_plot_widget()
a = [10.0, 20.0, 30.0]
b = [12.0, 22.0, 32.0]
path = [[0, 0], [1, 1], [2, 2]]
gv.updateComparison(json.dumps(a), json.dumps(b), json.dumps(path))
assert gv._plot_widget.clear.called
assert gv._plot_widget.plot.call_count == 2
def test_rejects_invalid_json(self):
"""clear() is called before parsing, so it always fires on valid widget."""
from pygui.backend.visualization.graph_view import GraphView
gv = GraphView()
gv._plot_widget = _make_plot_widget()
gv.updateComparison("bad", json.dumps([1]), json.dumps([]))
# clear() is called before parsing — it always fires
assert gv._plot_widget.clear.called
# But plot() should NOT be called since parsing fails
assert not gv._plot_widget.plot.called
def test_no_plot_widget(self):
from pygui.backend.visualization.graph_view import GraphView
gv = GraphView()
assert gv._plot_widget is None
# Should not crash
gv.updateComparison("[1,2]", "[3,4]", "[[0,0]]")
+91
View File
@@ -0,0 +1,91 @@
"""Tests for src/pygui/backend/visualization/wafer_map_item.py."""
from unittest.mock import MagicMock, patch
import pytest
# Use the session-scoped qapp fixture from conftest.py so Qt Property/Signal
# descriptors work properly.
pytestmark = pytest.mark.usefixtures("qapp")
def test_export_image_valid_path():
"""export_image saves a PNG when grabToImage succeeds."""
from pygui.backend.visualization.wafer_map_item import WaferMapItem
item = WaferMapItem()
mock_image = MagicMock()
mock_result = MagicMock()
mock_result.image.return_value = mock_image
item.grabToImage = MagicMock(return_value=mock_result)
assert item.export_image("/tmp/test_wafer.png") is True
def test_export_image_empty_path():
"""export_image returns False for empty/None path."""
from pygui.backend.visualization.wafer_map_item import WaferMapItem
item = WaferMapItem()
assert item.export_image("") is False
assert item.export_image(None) is False
def test_export_image_grab_fails():
"""export_image returns False when grabToImage raises."""
from pygui.backend.visualization.wafer_map_item import WaferMapItem
item = WaferMapItem()
item.grabToImage = MagicMock(side_effect=RuntimeError("grab failed"))
assert item.export_image("/tmp/test_wafer.png") is False
def test_export_image_save_fails():
"""export_image returns False when image.save raises."""
from pygui.backend.visualization.wafer_map_item import WaferMapItem
mock_image = MagicMock()
mock_image.save.side_effect = IOError("disk full")
mock_result = MagicMock()
mock_result.image.return_value = mock_image
item = WaferMapItem()
item.grabToImage = MagicMock(return_value=mock_result)
assert item.export_image("/tmp/test_wafer.png") is False
def test_thickness_properties():
"""Thickness data properties exist with correct defaults."""
from pygui.backend.visualization.wafer_map_item import WaferMapItem
item = WaferMapItem()
assert item._thickness_data == []
assert item._show_thickness is False
def test_thickness_setter_updates_data():
"""Setting thicknessData updates _thickness_data."""
from pygui.backend.visualization.wafer_map_item import WaferMapItem
item = WaferMapItem()
item._rebuild_thickness = MagicMock()
item.thicknessData = [1.0, 2.0, 3.0]
assert item._thickness_data == [1.0, 2.0, 3.0]
assert item._rebuild_thickness.called
def test_show_thickness_setter():
"""Setting showThickness updates _show_thickness."""
from pygui.backend.visualization.wafer_map_item import WaferMapItem
item = WaferMapItem()
assert item._show_thickness is False
item.showThickness = True
assert item._show_thickness is True