From 4f5bd90c818a8a8f2c93ee66bd8f9567a1b0bb30 Mon Sep 17 00:00:00 2001 From: nstelter-slac Date: Tue, 7 Jan 2025 17:50:39 -0800 Subject: [PATCH 1/2] TST: add tests for objects the use PyDMPrimitiveWidget's RULE_PROPERTIES Also adding these b/c RULE_PROPERTIES system is going to get reworked to work with qt6/pyside6. (these tests should remain same while implementation changes) --- pydm/tests/widgets/test_drawing.py | 14 +++++++ pydm/tests/widgets/test_label.py | 6 +++ .../widgets/test_related_display_button.py | 6 ++- pydm/tests/widgets/test_slider.py | 5 +++ pydm/tests/widgets/test_symbol_editor.py | 6 +++ pydm/utilities/__init__.py | 37 +++++++++++++++++++ pydm/widgets/related_display_button.py | 3 +- 7 files changed, 74 insertions(+), 3 deletions(-) diff --git a/pydm/tests/widgets/test_drawing.py b/pydm/tests/widgets/test_drawing.py index f1b2de8c5..8b2c754ed 100644 --- a/pydm/tests/widgets/test_drawing.py +++ b/pydm/tests/widgets/test_drawing.py @@ -27,6 +27,7 @@ PyDMDrawingPolyline, PyDMDrawingIrregularPolygon, ) +from ...utilities import checkPyDMWidgetDerivedObjectProperties # -------------------- @@ -37,6 +38,8 @@ # # ------------- # # PyDMDrawing # # ------------- + + @pytest.mark.parametrize( "deg, expected_qt_deg", [ @@ -84,6 +87,14 @@ def test_qt_to_deg(qt_deg, expected_deg): assert qt_to_deg(qt_deg) == expected_deg +expected_drawing_properties = { + "Set Pen Color": ["penColor", QColor], + "Set Pen Style": ["penStyle", int], + "Set Pen Width": ["penWidth", float], + "Set Brush Color": ["brush", QBrush], +} + + def test_pydmdrawing_construct(qtbot): """ Test the construction of a PyDM base object. @@ -97,6 +108,8 @@ def test_pydmdrawing_construct(qtbot): Window for widget testing """ pydm_drawing = PyDMDrawing() + # should be ok to just test this on a single instance of the created object + assert checkPyDMWidgetDerivedObjectProperties(pydm_drawing, expected_drawing_properties) qtbot.addWidget(pydm_drawing) assert pydm_drawing.alarmSensitiveBorder is False @@ -482,6 +495,7 @@ def test_pydmdrawingline_draw_item(qtbot, signals, alarm_sensitive_content): True if the widget will be redraw with a different color if an alarm is triggered; False otherwise """ pydm_drawingline = PyDMDrawingLine(init_channel="fake://tst") + assert checkPyDMWidgetDerivedObjectProperties(pydm_drawingline, expected_drawing_properties) qtbot.addWidget(pydm_drawingline) pydm_drawingline.alarmSensitiveContent = alarm_sensitive_content diff --git a/pydm/tests/widgets/test_label.py b/pydm/tests/widgets/test_label.py index 665a6bbd7..dbcc7342e 100644 --- a/pydm/tests/widgets/test_label.py +++ b/pydm/tests/widgets/test_label.py @@ -9,6 +9,7 @@ from ...widgets.label import PyDMLabel from ...widgets.base import PyDMWidget from ...widgets.display_format import parse_value_for_display, DisplayFormat +from ...utilities import checkPyDMWidgetDerivedObjectProperties from qtpy.QtWidgets import QApplication, QStyleOption from qtpy.QtCore import Qt @@ -18,6 +19,9 @@ # -------------------- +expected_label_properties = {"Text": ["value_changed", str]} + + def test_construct(qtbot): """ Test the basic instantiation of the widget. @@ -33,6 +37,8 @@ def test_construct(qtbot): Window for widget testing """ pydm_label = PyDMLabel() + assert checkPyDMWidgetDerivedObjectProperties(pydm_label, expected_label_properties) + qtbot.addWidget(pydm_label) display_format_type = pydm_label.displayFormat diff --git a/pydm/tests/widgets/test_related_display_button.py b/pydm/tests/widgets/test_related_display_button.py index 19be7f6ae..6123b8b02 100644 --- a/pydm/tests/widgets/test_related_display_button.py +++ b/pydm/tests/widgets/test_related_display_button.py @@ -6,7 +6,7 @@ from qtpy.QtWidgets import QApplication from ...utilities.stylesheet import global_style from ...widgets.related_display_button import PyDMRelatedDisplayButton -from ...utilities import IconFont +from ...utilities import IconFont, checkPyDMWidgetDerivedObjectProperties test_ui_path = os.path.join(os.path.dirname(os.path.realpath(__file__)), "../test_data", "test.ui") test_ui_path_with_stylesheet = os.path.join( @@ -17,6 +17,9 @@ ) +expected_related_display_button_properties = {"Text": ["setText", str], "Filenames": ["filenames", list]} + + def test_old_display_filename_property(qtbot): # This test is mostly only checking that the related display button # doesn't totally explode when the old 'displayFilename' property is used. @@ -25,6 +28,7 @@ def test_old_display_filename_property(qtbot): main_window.setWindowTitle("Related Display Button Test") qtbot.addWidget(main_window) button = PyDMRelatedDisplayButton(parent=main_window) + assert checkPyDMWidgetDerivedObjectProperties(button, expected_related_display_button_properties) with warnings.catch_warnings(record=True) as record: button.displayFilename = test_ui_path assert len(record) >= 1 diff --git a/pydm/tests/widgets/test_slider.py b/pydm/tests/widgets/test_slider.py index 1cea688bd..a23335b12 100644 --- a/pydm/tests/widgets/test_slider.py +++ b/pydm/tests/widgets/test_slider.py @@ -7,6 +7,7 @@ from qtpy.QtGui import QMouseEvent from ...widgets.slider import PyDMSlider, PyDMPrimitiveSlider from ...widgets.base import PyDMWidget +from ...utilities import checkPyDMWidgetDerivedObjectProperties # Unit Tests for the PyDMPrimitiveSlider class @@ -199,6 +200,9 @@ def test_getSliderPosition(slider_fixture, request): # Unit Tests for the PyDMSlider Widget +expected_slider_properties = {"Set Step Size ": ["step_size", float]} + + def test_construct(qtbot): """ Test the construction of the widget. @@ -212,6 +216,7 @@ def test_construct(qtbot): Window for widget testing """ pydm_slider = PyDMSlider() + assert checkPyDMWidgetDerivedObjectProperties(pydm_slider, expected_slider_properties) qtbot.addWidget(pydm_slider) assert pydm_slider.alarmSensitiveContent is True diff --git a/pydm/tests/widgets/test_symbol_editor.py b/pydm/tests/widgets/test_symbol_editor.py index a2ac43a8e..56c3abab7 100644 --- a/pydm/tests/widgets/test_symbol_editor.py +++ b/pydm/tests/widgets/test_symbol_editor.py @@ -6,6 +6,10 @@ from ...widgets.symbol import PyDMSymbol from ...widgets.symbol_editor import SymbolEditor +from ...utilities import checkPyDMWidgetDerivedObjectProperties + + +expected_symbol_properties = {"Index": ["set_current_key", int]} def test_symbol_editor(qtbot, monkeypatch): @@ -21,6 +25,8 @@ def test_symbol_editor(qtbot, monkeypatch): """ # Create the base widget widget = PyDMSymbol() + assert checkPyDMWidgetDerivedObjectProperties(widget, expected_symbol_properties) + qtbot.addWidget(widget) # Ensure that no rules are set diff --git a/pydm/utilities/__init__.py b/pydm/utilities/__init__.py index 1a5a2594d..189484fe3 100644 --- a/pydm/utilities/__init__.py +++ b/pydm/utilities/__init__.py @@ -560,3 +560,40 @@ def get_clipboard_text() -> str: if text: return text return "" + + +def checkPyDMWidgetDerivedObjectProperties(class_object, extra_properties): + """ + Check that an object derived from PyDMWidget has the expected properties, + these being the base properties applied to PyDMWidget and any extra ones applied to the subclass. + + Parameters + ---------- + class_object : Type[PyDMWidget] + The object to check the properties of. + + extra_properties : dict + Map of the additional properties applied to the class_object. + """ + + # properties that all PyDMWidget derived objects should all have + pydm_widget_props = { + "Enable": ["setEnabled", bool], + "Visible": ["setVisible", bool], + "Opacity": ["set_opacity", float], + "Position - X": ["setX", int], + "Position - Y": ["setY", int], + } + + # combine the properties into one map + pydm_widget_props.update(extra_properties) + + # check if object's properties are as expected + for key, value in pydm_widget_props.items(): + if key not in class_object.RULE_PROPERTIES: + print("Missing property: ", key) + return False + if class_object.RULE_PROPERTIES[key] != value: + print(f"Mismatch for property '{key}': expected {value}, got {class_object.RULE_PROPERTIES[key]}") + return False + return True diff --git a/pydm/widgets/related_display_button.py b/pydm/widgets/related_display_button.py index 06505d095..201e512fc 100644 --- a/pydm/widgets/related_display_button.py +++ b/pydm/widgets/related_display_button.py @@ -22,8 +22,7 @@ class PyDMRelatedDisplayButton(QPushButton, PyDMWidget, new_properties=_relatedDisplayRuleProperties): """ - A QPushButton capable of opening a new Display at the same of at a - new window. + A QPushButton capable of opening a new Display in the same or a new window. Parameters ---------- From 4b94724d1d6169ab13e91a803c0a21dfd0e0c168 Mon Sep 17 00:00:00 2001 From: nstelter-slac Date: Wed, 8 Jan 2025 10:17:51 -0800 Subject: [PATCH 2/2] MNT: use better naming for util func that checks expected RULE_PROPERTIES in tests --- pydm/tests/widgets/test_drawing.py | 9 ++++----- pydm/tests/widgets/test_label.py | 5 +++-- .../widgets/test_related_display_button.py | 5 +++-- pydm/tests/widgets/test_slider.py | 5 +++-- pydm/tests/widgets/test_symbol_editor.py | 5 +++-- pydm/utilities/__init__.py | 19 ++++++++++++------- 6 files changed, 28 insertions(+), 20 deletions(-) diff --git a/pydm/tests/widgets/test_drawing.py b/pydm/tests/widgets/test_drawing.py index 8b2c754ed..3fa09de9c 100644 --- a/pydm/tests/widgets/test_drawing.py +++ b/pydm/tests/widgets/test_drawing.py @@ -27,7 +27,7 @@ PyDMDrawingPolyline, PyDMDrawingIrregularPolygon, ) -from ...utilities import checkPyDMWidgetDerivedObjectProperties +from ...utilities import checkObjectProperties # -------------------- @@ -38,8 +38,6 @@ # # ------------- # # PyDMDrawing # # ------------- - - @pytest.mark.parametrize( "deg, expected_qt_deg", [ @@ -87,6 +85,7 @@ def test_qt_to_deg(qt_deg, expected_deg): assert qt_to_deg(qt_deg) == expected_deg +# additional props we expect to get added to PyDMDrawing class RULE_PROPERTIES expected_drawing_properties = { "Set Pen Color": ["penColor", QColor], "Set Pen Style": ["penStyle", int], @@ -109,7 +108,7 @@ def test_pydmdrawing_construct(qtbot): """ pydm_drawing = PyDMDrawing() # should be ok to just test this on a single instance of the created object - assert checkPyDMWidgetDerivedObjectProperties(pydm_drawing, expected_drawing_properties) + assert checkObjectProperties(pydm_drawing, expected_drawing_properties) is True qtbot.addWidget(pydm_drawing) assert pydm_drawing.alarmSensitiveBorder is False @@ -495,7 +494,7 @@ def test_pydmdrawingline_draw_item(qtbot, signals, alarm_sensitive_content): True if the widget will be redraw with a different color if an alarm is triggered; False otherwise """ pydm_drawingline = PyDMDrawingLine(init_channel="fake://tst") - assert checkPyDMWidgetDerivedObjectProperties(pydm_drawingline, expected_drawing_properties) + assert checkObjectProperties(pydm_drawingline, expected_drawing_properties) is True qtbot.addWidget(pydm_drawingline) pydm_drawingline.alarmSensitiveContent = alarm_sensitive_content diff --git a/pydm/tests/widgets/test_label.py b/pydm/tests/widgets/test_label.py index dbcc7342e..21fe7004b 100644 --- a/pydm/tests/widgets/test_label.py +++ b/pydm/tests/widgets/test_label.py @@ -9,7 +9,7 @@ from ...widgets.label import PyDMLabel from ...widgets.base import PyDMWidget from ...widgets.display_format import parse_value_for_display, DisplayFormat -from ...utilities import checkPyDMWidgetDerivedObjectProperties +from ...utilities import checkObjectProperties from qtpy.QtWidgets import QApplication, QStyleOption from qtpy.QtCore import Qt @@ -19,6 +19,7 @@ # -------------------- +# additional props we expect to get added to PyDMLabel class RULE_PROPERTIES expected_label_properties = {"Text": ["value_changed", str]} @@ -37,7 +38,7 @@ def test_construct(qtbot): Window for widget testing """ pydm_label = PyDMLabel() - assert checkPyDMWidgetDerivedObjectProperties(pydm_label, expected_label_properties) + assert checkObjectProperties(pydm_label, expected_label_properties) is True qtbot.addWidget(pydm_label) diff --git a/pydm/tests/widgets/test_related_display_button.py b/pydm/tests/widgets/test_related_display_button.py index 6123b8b02..efbeefadf 100644 --- a/pydm/tests/widgets/test_related_display_button.py +++ b/pydm/tests/widgets/test_related_display_button.py @@ -6,7 +6,7 @@ from qtpy.QtWidgets import QApplication from ...utilities.stylesheet import global_style from ...widgets.related_display_button import PyDMRelatedDisplayButton -from ...utilities import IconFont, checkPyDMWidgetDerivedObjectProperties +from ...utilities import IconFont, checkObjectProperties test_ui_path = os.path.join(os.path.dirname(os.path.realpath(__file__)), "../test_data", "test.ui") test_ui_path_with_stylesheet = os.path.join( @@ -17,6 +17,7 @@ ) +# additional props we expect to get added to PyDMRelatedDisplayButton class RULE_PROPERTIES expected_related_display_button_properties = {"Text": ["setText", str], "Filenames": ["filenames", list]} @@ -28,7 +29,7 @@ def test_old_display_filename_property(qtbot): main_window.setWindowTitle("Related Display Button Test") qtbot.addWidget(main_window) button = PyDMRelatedDisplayButton(parent=main_window) - assert checkPyDMWidgetDerivedObjectProperties(button, expected_related_display_button_properties) + assert checkObjectProperties(button, expected_related_display_button_properties) is True with warnings.catch_warnings(record=True) as record: button.displayFilename = test_ui_path assert len(record) >= 1 diff --git a/pydm/tests/widgets/test_slider.py b/pydm/tests/widgets/test_slider.py index a23335b12..138640713 100644 --- a/pydm/tests/widgets/test_slider.py +++ b/pydm/tests/widgets/test_slider.py @@ -7,7 +7,7 @@ from qtpy.QtGui import QMouseEvent from ...widgets.slider import PyDMSlider, PyDMPrimitiveSlider from ...widgets.base import PyDMWidget -from ...utilities import checkPyDMWidgetDerivedObjectProperties +from ...utilities import checkObjectProperties # Unit Tests for the PyDMPrimitiveSlider class @@ -200,6 +200,7 @@ def test_getSliderPosition(slider_fixture, request): # Unit Tests for the PyDMSlider Widget +# additional props we expect to get added to PyDMSlider class RULE_PROPERTIES expected_slider_properties = {"Set Step Size ": ["step_size", float]} @@ -216,7 +217,7 @@ def test_construct(qtbot): Window for widget testing """ pydm_slider = PyDMSlider() - assert checkPyDMWidgetDerivedObjectProperties(pydm_slider, expected_slider_properties) + assert checkObjectProperties(pydm_slider, expected_slider_properties) is True qtbot.addWidget(pydm_slider) assert pydm_slider.alarmSensitiveContent is True diff --git a/pydm/tests/widgets/test_symbol_editor.py b/pydm/tests/widgets/test_symbol_editor.py index 56c3abab7..41bc996ad 100644 --- a/pydm/tests/widgets/test_symbol_editor.py +++ b/pydm/tests/widgets/test_symbol_editor.py @@ -6,9 +6,10 @@ from ...widgets.symbol import PyDMSymbol from ...widgets.symbol_editor import SymbolEditor -from ...utilities import checkPyDMWidgetDerivedObjectProperties +from ...utilities import checkObjectProperties +# additional props we expect to get added to PyDMSymbol class RULE_PROPERTIES expected_symbol_properties = {"Index": ["set_current_key", int]} @@ -25,7 +26,7 @@ def test_symbol_editor(qtbot, monkeypatch): """ # Create the base widget widget = PyDMSymbol() - assert checkPyDMWidgetDerivedObjectProperties(widget, expected_symbol_properties) + assert checkObjectProperties(widget, expected_symbol_properties) is True qtbot.addWidget(widget) diff --git a/pydm/utilities/__init__.py b/pydm/utilities/__init__.py index 189484fe3..c0f2c8de3 100644 --- a/pydm/utilities/__init__.py +++ b/pydm/utilities/__init__.py @@ -562,10 +562,12 @@ def get_clipboard_text() -> str: return "" -def checkPyDMWidgetDerivedObjectProperties(class_object, extra_properties): +def checkObjectProperties(class_object, extra_properties): """ - Check that an object derived from PyDMWidget has the expected properties, - these being the base properties applied to PyDMWidget and any extra ones applied to the subclass. + Check that an object has the expected RULE_PROPERTIES map. + This function should only be used on objects derived from PyDMWidget and + therefore PyDMPrimitiveWidget (which defines the RULE_PROPERTIES). + The expected properties are the base properties of PyDMWidget and any extra ones applied to the subclass. Parameters ---------- @@ -573,10 +575,13 @@ def checkPyDMWidgetDerivedObjectProperties(class_object, extra_properties): The object to check the properties of. extra_properties : dict - Map of the additional properties applied to the class_object. + Map of the additional properties we expect applied to the class_object. + These should be only the extra props applied to the derived class itself, + not including the props applied to PyDMWidget and PyDMPrimitiveWidget. """ - # properties that all PyDMWidget derived objects should all have + # Properties that all PyDMWidget derived objects should all have, since they are applied + # at the definition of PyDMWidget and PyDMPrimitiveWidget classes. pydm_widget_props = { "Enable": ["setEnabled", bool], "Visible": ["setVisible", bool], @@ -585,10 +590,10 @@ def checkPyDMWidgetDerivedObjectProperties(class_object, extra_properties): "Position - Y": ["setY", int], } - # combine the properties into one map + # Combine the properties into one map pydm_widget_props.update(extra_properties) - # check if object's properties are as expected + # Check if object's properties are as expected for key, value in pydm_widget_props.items(): if key not in class_object.RULE_PROPERTIES: print("Missing property: ", key)