From c42511dd44cc13577e108a6cef3166376e594f54 Mon Sep 17 00:00:00 2001 From: wakonig_k Date: Fri, 28 Jun 2024 13:49:12 +0200 Subject: [PATCH] fix(plugins): fixes and tests for auto-gen plugins --- bec_widgets/utils/generate_designer_plugin.py | 29 ++-- tests/unit_tests/test_generate_plugin.py | 155 ++++++++++++++++++ 2 files changed, 170 insertions(+), 14 deletions(-) create mode 100644 tests/unit_tests/test_generate_plugin.py diff --git a/bec_widgets/utils/generate_designer_plugin.py b/bec_widgets/utils/generate_designer_plugin.py index 84d30bdc..af6d8595 100644 --- a/bec_widgets/utils/generate_designer_plugin.py +++ b/bec_widgets/utils/generate_designer_plugin.py @@ -47,13 +47,12 @@ class DesignerPluginGenerator: def __init__(self, widget: type): self._excluded = False self.widget = widget + self.info = DesignerPluginInfo(widget) if widget.__name__ in EXCLUDED_PLUGINS: self._excluded = True return - self.info = DesignerPluginInfo(widget) - self.templates = {} self.template_path = os.path.join( os.path.dirname(os.path.abspath(__file__)), "plugin_templates" @@ -75,7 +74,7 @@ class DesignerPluginGenerator: # Check if the widget class has parent as the first argument. This is a strict requirement of Qt! signature = list(inspect.signature(self.widget.__init__).parameters.values()) - if signature[1].name != "parent": + if len(signature) == 1 or signature[1].name != "parent": raise ValueError( f"Widget class {self.widget.__name__} must have parent as the first argument." ) @@ -89,20 +88,22 @@ class DesignerPluginGenerator: # Check if the widget class calls the super constructor with parent argument init_source = inspect.getsource(self.widget.__init__) cls_init_found = ( - bool(init_source.find(f"{base_cls[0].__name__}.__init__(self, parent=parent")) - or bool(init_source.find(f"{base_cls[0].__name__}.__init__(self, parent)")) - or bool(init_source.find(f"{base_cls[0].__name__}.__init__(self, parent,")) + bool(init_source.find(f"{base_cls[0].__name__}.__init__(self, parent=parent") > 0) + or bool(init_source.find(f"{base_cls[0].__name__}.__init__(self, parent)") > 0) + or bool(init_source.find(f"{base_cls[0].__name__}.__init__(self, parent,") > 0) ) super_init_found = ( - bool(init_source.find(f"super({self.widget.__name__}, self).__init__(parent=parent")) - or bool(init_source.find(f"super({self.widget.__name__}, self).__init__(parent,")) - or bool(init_source.find(f"super({self.widget.__name__}, self).__init__(parent)")) + bool( + init_source.find(f"super({base_cls[0].__name__}, self).__init__(parent=parent") > 0 + ) + or bool(init_source.find(f"super({base_cls[0].__name__}, self).__init__(parent,") > 0) + or bool(init_source.find(f"super({base_cls[0].__name__}, self).__init__(parent)") > 0) ) - if issubclass(self.widget.__bases__[0], QObject) and super_init_found == -1: + if issubclass(self.widget.__bases__[0], QObject) and not super_init_found: super_init_found = ( - bool(init_source.find("super().__init__(parent=parent")) - or bool(init_source.find("super().__init__(parent,")) - or bool(init_source.find("super().__init__(parent)")) + bool(init_source.find("super().__init__(parent=parent") > 0) + or bool(init_source.find("super().__init__(parent,") > 0) + or bool(init_source.find("super().__init__(parent)") > 0) ) if not cls_init_found and not super_init_found: @@ -139,7 +140,7 @@ class DesignerPluginGenerator: self.templates[file.split(".")[0]] = f.read() -if __name__ == "__main__": +if __name__ == "__main__": # pragma: no cover # from bec_widgets.widgets.bec_queue.bec_queue import BECQueue from bec_widgets.widgets.dock import BECDockArea diff --git a/tests/unit_tests/test_generate_plugin.py b/tests/unit_tests/test_generate_plugin.py new file mode 100644 index 00000000..8b55ed00 --- /dev/null +++ b/tests/unit_tests/test_generate_plugin.py @@ -0,0 +1,155 @@ +import importlib +import inspect +import os +import sys + +import pytest + +from bec_widgets.utils.generate_designer_plugin import DesignerPluginGenerator + + +def load_plugin(dir_path, content, plugin_name="MyWidget"): + plugin_path = dir_path.mkdir("plugin").join("plugin.py") + plugin_path.write(content) + sys.path.append(str(dir_path)) + plugin = importlib.import_module("plugin.plugin") + importlib.reload(plugin) + yield getattr(plugin, plugin_name) + sys.path.pop() + + +@pytest.fixture( + params=[ + """ +from qtpy.QtWidgets import QWidget +class MyWidget(QWidget): + def __init__(self, parent=None): + QWidget.__init__(self, parent) +""", + """ +from qtpy.QtWidgets import QWidget +class MyWidget(QWidget): + def __init__(self, parent=None): + QWidget.__init__(self, parent=parent) +""", + """ +from qtpy.QtWidgets import QWidget +class MyWidget(QWidget): + def __init__(self, parent=None): + super().__init__(parent) +""", + """ +from qtpy.QtWidgets import QWidget +class MyWidget(QWidget): + def __init__(self, parent=None): + super().__init__(parent=parent) +""", + """ +from qtpy.QtWidgets import QWidget +class MyWidget(QWidget): + def __init__(self, parent=None): + super(QWidget, self).__init__(parent)""" + """ +from qtpy.QtWidgets import QWidget +class MyWidget(QWidget): + def __init__(self, parent=None): + super(QWidget, self).__init__(parent=parent) +""", + ] +) +def plugin_with_correct_parent(tmpdir, request): + yield from load_plugin(tmpdir, request.param) + + +@pytest.fixture( + params=[ + """ +from qtpy.QtWidgets import QWidget +class MyWidget(QWidget): + def __init__(self, parent=None): + QWidget.__init__(self) +""", + """ +from qtpy.QtWidgets import QWidget +class MyWidget(QWidget): + def __init__(self, parent=None): + super().__init__() +""", + """ +from qtpy.QtWidgets import QWidget +class MyWidget(QWidget): + def __init__(self, parent=None): + super(QWidget, self).__init__() + """, + ] +) +def plugin_with_missing_parent(tmpdir, request): + yield from load_plugin(tmpdir, request.param) + + +def test_generate_plugin(plugin_with_correct_parent): + generator = DesignerPluginGenerator(plugin_with_correct_parent) + generator.run() + assert os.path.exists(f"{generator.info.base_path}/register_my_widget.py") + assert os.path.exists(f"{generator.info.base_path}/my_widget_plugin.py") + assert os.path.exists(f"{generator.info.base_path}/my_widget.pyproject") + + +def test_generate_plugin_with_missing_parent(plugin_with_missing_parent): + with pytest.raises(ValueError) as excinfo: + generator = DesignerPluginGenerator(plugin_with_missing_parent) + generator.run() + assert "Widget class MyWidget must call the super constructor with parent." in str( + excinfo.value + ) + + +@pytest.fixture() +def plugin_with_excluded_widget(tmpdir): + content = """ +from qtpy.QtWidgets import QWidget +class BECDock(QWidget): + def __init__(self, parent=None): + QWidget.__init__(self, parent) +""" + yield from load_plugin(tmpdir, content, plugin_name="BECDock") + + +def test_generate_plugin_with_excluded_widget(plugin_with_excluded_widget, capsys): + generator = DesignerPluginGenerator(plugin_with_excluded_widget) + generator.run() + captured = capsys.readouterr() + + assert "Plugin BECDock is excluded from generation." in captured.out + assert not os.path.exists(f"{generator.info.base_path}/register_bec_dock.py") + assert not os.path.exists(f"{generator.info.base_path}/bec_dock_plugin.py") + assert not os.path.exists(f"{generator.info.base_path}/bec_dock.pyproject") + + +@pytest.fixture( + params=[ + """ +from qtpy.QtWidgets import QWidget +class MyWidget(QWidget): + def __init__(self): + QWidget.__init__(self) +""", + """ +from qtpy.QtWidgets import QWidget +class MyWidget(QWidget): + def __init__(self, config, parent=None): + super().__init__() +""", + ] +) +def plugin_with_no_parent_as_first_arg(tmpdir, request): + yield from load_plugin(tmpdir, request.param) + + +def test_generate_plugin_raises_exception_when_first_argument_is_not_parent( + plugin_with_no_parent_as_first_arg, +): + with pytest.raises(ValueError) as excinfo: + generator = DesignerPluginGenerator(plugin_with_no_parent_as_first_arg) + generator.run() + assert "Widget class MyWidget must have parent as the first argument." in str(excinfo.value)