test: add comprehensive test suite for visualization and comparison modules
This commit is contained in:
@@ -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
|
||||
@@ -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)
|
||||
@@ -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]]")
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user