mirror of
https://github.com/bec-project/bec_widgets.git
synced 2025-07-13 03:01:50 +02:00
tests: add tests for widget creator
This commit is contained in:
@ -22,7 +22,7 @@ def _commit_added_widget(repo: Path, name: str):
|
|||||||
logger.info(f"Committing new widget {name}")
|
logger.info(f"Committing new widget {name}")
|
||||||
|
|
||||||
|
|
||||||
def _widget_exists(widget_list: list[dict[str, str]], name: str):
|
def _widget_exists(widget_list: list[dict[str, str | bool]], name: str):
|
||||||
return name in [w["name"] for w in widget_list]
|
return name in [w["name"] for w in widget_list]
|
||||||
|
|
||||||
|
|
||||||
|
@ -16,6 +16,7 @@ from watchdog.events import (
|
|||||||
)
|
)
|
||||||
from watchdog.observers import Observer
|
from watchdog.observers import Observer
|
||||||
|
|
||||||
|
from bec_widgets.utils.bec_designer import open_designer
|
||||||
from bec_widgets.utils.bec_plugin_helper import get_all_plugin_widgets
|
from bec_widgets.utils.bec_plugin_helper import get_all_plugin_widgets
|
||||||
from bec_widgets.utils.plugin_utils import get_custom_classes
|
from bec_widgets.utils.plugin_utils import get_custom_classes
|
||||||
|
|
||||||
@ -57,6 +58,7 @@ class RecompileHandler(FileSystemEventHandler):
|
|||||||
if code == 0:
|
if code == 0:
|
||||||
logger.success("updating imports...")
|
logger.success("updating imports...")
|
||||||
self._update_imports()
|
self._update_imports()
|
||||||
|
logger.success("done!")
|
||||||
|
|
||||||
def _update_imports(self):
|
def _update_imports(self):
|
||||||
with open(self.out_file, "r+") as f:
|
with open(self.out_file, "r+") as f:
|
||||||
@ -82,13 +84,6 @@ class RecompileHandler(FileSystemEventHandler):
|
|||||||
|
|
||||||
def open_and_watch_ui_editor(widget_name: str):
|
def open_and_watch_ui_editor(widget_name: str):
|
||||||
logger.info(f"Opening the editor for {widget_name}... ")
|
logger.info(f"Opening the editor for {widget_name}... ")
|
||||||
|
|
||||||
try:
|
|
||||||
from bec_widgets.utils.bec_designer import open_designer
|
|
||||||
except ImportError:
|
|
||||||
logger.error("BEC Widgets must be installed to use the UI editor tool")
|
|
||||||
exit(127)
|
|
||||||
|
|
||||||
repo = Path(plugin_repo_path())
|
repo = Path(plugin_repo_path())
|
||||||
widget_dir = repo / repo.name / "bec_widgets" / "widgets" / widget_name
|
widget_dir = repo / repo.name / "bec_widgets" / "widgets" / widget_name
|
||||||
ui_file = widget_dir / f"{widget_name}.ui"
|
ui_file = widget_dir / f"{widget_name}.ui"
|
||||||
|
@ -39,6 +39,7 @@ dev = [
|
|||||||
"pytest~=8.0",
|
"pytest~=8.0",
|
||||||
"pytest-cov~=6.1.1",
|
"pytest-cov~=6.1.1",
|
||||||
"watchdog~=6.0",
|
"watchdog~=6.0",
|
||||||
|
"pre_commit~=4.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
[project.urls]
|
[project.urls]
|
||||||
|
252
tests/unit_tests/test_plugin_creator.py
Normal file
252
tests/unit_tests/test_plugin_creator.py
Normal file
@ -0,0 +1,252 @@
|
|||||||
|
import os
|
||||||
|
import subprocess
|
||||||
|
import time
|
||||||
|
from pathlib import Path
|
||||||
|
from time import sleep
|
||||||
|
from types import SimpleNamespace
|
||||||
|
from unittest.mock import MagicMock, call, patch
|
||||||
|
|
||||||
|
import copier
|
||||||
|
import pytest
|
||||||
|
from bec_lib.utils.plugin_manager import main
|
||||||
|
from bec_lib.utils.plugin_manager._util import _goto_dir
|
||||||
|
from typer.testing import CliRunner
|
||||||
|
|
||||||
|
from bec_widgets.utils.bec_plugin_manager.create.widget import _commit_added_widget, _widget_exists
|
||||||
|
|
||||||
|
PLUGIN_REPO = "https://github.com/bec-project/plugin_copier_template.git"
|
||||||
|
|
||||||
|
REPLACEMENT_UI_CONTENTS = """<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<ui version="4.0">
|
||||||
|
<class>testWidget6</class>
|
||||||
|
<widget class="QWidget" name="testWidget6">
|
||||||
|
<property name="geometry">
|
||||||
|
<rect>
|
||||||
|
<x>0</x>
|
||||||
|
<y>0</y>
|
||||||
|
<width>539</width>
|
||||||
|
<height>287</height>
|
||||||
|
</rect>
|
||||||
|
</property>
|
||||||
|
<widget class="Waveform" name="waveform">
|
||||||
|
<property name="geometry">
|
||||||
|
<rect>
|
||||||
|
<x>30</x>
|
||||||
|
<y>0</y>
|
||||||
|
<width>361</width>
|
||||||
|
<height>125</height>
|
||||||
|
</rect>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</widget>
|
||||||
|
<customwidgets>
|
||||||
|
<customwidget>
|
||||||
|
<class>Waveform</class>
|
||||||
|
<extends>QWidget</extends>
|
||||||
|
<header>waveform</header>
|
||||||
|
</customwidget>
|
||||||
|
</customwidgets>
|
||||||
|
<resources/>
|
||||||
|
<connections/>
|
||||||
|
</ui>
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def runner():
|
||||||
|
return CliRunner()
|
||||||
|
|
||||||
|
|
||||||
|
def test_app_has_widget_commands(runner: CliRunner):
|
||||||
|
result = runner.invoke(main._app, ["create", "--help"])
|
||||||
|
assert "widget" in result.output
|
||||||
|
|
||||||
|
|
||||||
|
def test_create_widget_takes_name(runner: CliRunner):
|
||||||
|
result = runner.invoke(main._app, ["create", "widget"])
|
||||||
|
assert "Missing argument 'NAME'." in result.output
|
||||||
|
|
||||||
|
|
||||||
|
@patch("bec_widgets.utils.bec_plugin_manager.create.widget.logger")
|
||||||
|
@patch("bec_widgets.utils.bec_plugin_manager.create.widget.make_commit")
|
||||||
|
@patch("bec_widgets.utils.bec_plugin_manager.create.widget.git_stage_files")
|
||||||
|
def test_make_commit(stage: MagicMock, commit: MagicMock, logger: MagicMock):
|
||||||
|
repo = Path("test_path")
|
||||||
|
_commit_added_widget(repo, "test")
|
||||||
|
assert stage.call_count == 2
|
||||||
|
stage.assert_has_calls(
|
||||||
|
[
|
||||||
|
call(repo, [".copier-answers.yml"]),
|
||||||
|
call(repo / repo.name / "bec_widgets" / "widgets" / "test", []),
|
||||||
|
]
|
||||||
|
)
|
||||||
|
commit.assert_called_with(repo, "plugin-manager added new widget: test")
|
||||||
|
logger.info.assert_called_with("Committing new widget test")
|
||||||
|
|
||||||
|
|
||||||
|
def test_widget_exists_function():
|
||||||
|
assert not _widget_exists([], "test_widget")
|
||||||
|
assert _widget_exists([{"name": "test_widget", "use_ui": True}], "test_widget")
|
||||||
|
|
||||||
|
|
||||||
|
def test_editor_cb(runner):
|
||||||
|
result = runner.invoke(main._app, ["create", "widget", "test", "--no-use-ui", "--open-editor"])
|
||||||
|
assert result.exit_code == 2
|
||||||
|
assert "Invalid value" in result.output
|
||||||
|
assert "Can only open" in result.output
|
||||||
|
|
||||||
|
|
||||||
|
class TestAddWidgetVariants:
|
||||||
|
@pytest.fixture(scope="class", autouse=True)
|
||||||
|
def setup_env(self, tmp_path_factory: pytest.TempPathFactory):
|
||||||
|
TestAddWidgetVariants._tmp_plugin_dir = tmp_path_factory.mktemp("test_plugin")
|
||||||
|
|
||||||
|
@pytest.fixture(scope="function", autouse=True)
|
||||||
|
def cleanup_repo(self):
|
||||||
|
yield
|
||||||
|
subprocess.run(["git", "reset", "--hard"], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
||||||
|
|
||||||
|
@pytest.fixture(scope="class")
|
||||||
|
def git_repo(self):
|
||||||
|
project = TestAddWidgetVariants._tmp_plugin_dir / "test_plugin"
|
||||||
|
with _goto_dir(TestAddWidgetVariants._tmp_plugin_dir):
|
||||||
|
subprocess.run(["git", "clone", PLUGIN_REPO])
|
||||||
|
os.makedirs(project)
|
||||||
|
with _goto_dir(project):
|
||||||
|
subprocess.run(["git", "init", "-b", "main"])
|
||||||
|
subprocess.run(["git", "config", "user.email", "test"])
|
||||||
|
subprocess.run(["git", "config", "user.name", "test"])
|
||||||
|
copier.run_copy(
|
||||||
|
str(TestAddWidgetVariants._tmp_plugin_dir / "plugin_copier_template"),
|
||||||
|
str(project),
|
||||||
|
defaults=True,
|
||||||
|
data={
|
||||||
|
"project_name": "test_plugin",
|
||||||
|
"widget_plugins_input": [{"name": "test_widget", "use_ui": True}],
|
||||||
|
},
|
||||||
|
unsafe=True,
|
||||||
|
)
|
||||||
|
yield project
|
||||||
|
|
||||||
|
@patch("bec_widgets.utils.bec_plugin_manager.create.widget.plugin_repo_path")
|
||||||
|
def test_add_widget_with_ui(self, plugin_repo_path, runner: CliRunner, git_repo: Path):
|
||||||
|
plugin_repo_path.return_value = str(git_repo)
|
||||||
|
result = runner.invoke(
|
||||||
|
main._app, ["create", "widget", "test_widget_2", "--use-ui", "--no-open-editor"]
|
||||||
|
)
|
||||||
|
assert result.exit_code == 0, result.output
|
||||||
|
widget_dir = git_repo / "test_plugin" / "bec_widgets" / "widgets" / "test_widget_2"
|
||||||
|
assert os.path.isdir(widget_dir)
|
||||||
|
assert os.path.isfile(widget_dir / "test_widget_2.py")
|
||||||
|
assert os.path.isfile(widget_dir / "test_widget_2.ui")
|
||||||
|
assert os.path.isfile(widget_dir / "test_widget_2_ui.py")
|
||||||
|
|
||||||
|
@patch("bec_widgets.utils.bec_plugin_manager.create.widget.plugin_repo_path")
|
||||||
|
def test_add_widget_without_ui(self, plugin_repo_path, runner: CliRunner, git_repo: Path):
|
||||||
|
plugin_repo_path.return_value = str(git_repo)
|
||||||
|
result = runner.invoke(
|
||||||
|
main._app, ["create", "widget", "test_widget_3", "--no-use-ui", "--no-open-editor"]
|
||||||
|
)
|
||||||
|
assert result.exit_code == 0, result.output
|
||||||
|
widget_dir = git_repo / "test_plugin" / "bec_widgets" / "widgets" / "test_widget_3"
|
||||||
|
assert os.path.isdir(widget_dir)
|
||||||
|
assert os.path.isfile(widget_dir / "test_widget_3.py")
|
||||||
|
assert not os.path.isfile(widget_dir / "test_widget_3.ui")
|
||||||
|
assert not os.path.isfile(widget_dir / "test_widget_3_ui.py")
|
||||||
|
|
||||||
|
@patch("bec_widgets.utils.bec_plugin_manager.create.widget.logger")
|
||||||
|
@patch("bec_widgets.utils.bec_plugin_manager.create.widget.plugin_repo_path")
|
||||||
|
def test_no_add_widget_dupe_name(
|
||||||
|
self, plugin_repo_path, logger, runner: CliRunner, git_repo: Path
|
||||||
|
):
|
||||||
|
plugin_repo_path.return_value = str(git_repo)
|
||||||
|
result = runner.invoke(
|
||||||
|
main._app, ["create", "widget", "test_widget", "--no-use-ui", "--no-open-editor"]
|
||||||
|
)
|
||||||
|
assert result.exit_code == -1, result.output
|
||||||
|
assert "already exists!" in logger.error.mock_calls[0].args[0]
|
||||||
|
|
||||||
|
@patch("bec_widgets.utils.bec_plugin_manager.create.widget.logger")
|
||||||
|
@patch("bec_widgets.utils.bec_plugin_manager.create.widget.plugin_repo_path")
|
||||||
|
def test_no_add_widget_bad_name(
|
||||||
|
self, plugin_repo_path, logger, runner: CliRunner, git_repo: Path
|
||||||
|
):
|
||||||
|
plugin_repo_path.return_value = str(git_repo)
|
||||||
|
result = runner.invoke(
|
||||||
|
main._app, ["create", "widget", "12345", "--no-use-ui", "--no-open-editor"]
|
||||||
|
)
|
||||||
|
assert result.exit_code == -1, result.output
|
||||||
|
assert "not a valid name for a widget" in logger.error.mock_calls[0].args[0]
|
||||||
|
|
||||||
|
@patch("bec_widgets.utils.bec_plugin_manager.create.widget.copier")
|
||||||
|
@patch("bec_widgets.utils.bec_plugin_manager.create.widget.logger")
|
||||||
|
@patch("bec_widgets.utils.bec_plugin_manager.create.widget.plugin_repo_path")
|
||||||
|
def test_copier_error_logged(
|
||||||
|
self, plugin_repo_path, logger, copier, runner: CliRunner, git_repo: Path
|
||||||
|
):
|
||||||
|
class CopierFailure(Exception): ...
|
||||||
|
|
||||||
|
copier.run_update.side_effect = CopierFailure
|
||||||
|
plugin_repo_path.return_value = str(git_repo)
|
||||||
|
result = runner.invoke(
|
||||||
|
main._app, ["create", "widget", "test_widget_4", "--no-use-ui", "--no-open-editor"]
|
||||||
|
)
|
||||||
|
assert result.exit_code == -1, result.output
|
||||||
|
assert "CopierFailure" in logger.error.mock_calls[0].args[0]
|
||||||
|
|
||||||
|
@patch("bec_widgets.utils.bec_plugin_manager.create.widget.open_and_watch_ui_editor")
|
||||||
|
@patch("bec_widgets.utils.bec_plugin_manager.create.widget.plugin_repo_path")
|
||||||
|
def test_editor_opened_on_success(
|
||||||
|
self, plugin_repo_path, open_editor, runner: CliRunner, git_repo: Path
|
||||||
|
):
|
||||||
|
plugin_repo_path.return_value = str(git_repo)
|
||||||
|
runner.invoke(main._app, ["create", "widget", "TeSt_wiDgeT_5", "--use-ui", "--open-editor"])
|
||||||
|
open_editor.assert_called_with("test_widget_5")
|
||||||
|
|
||||||
|
@patch("bec_widgets.utils.bec_plugin_manager.edit_ui.logger")
|
||||||
|
@patch("bec_widgets.utils.bec_plugin_manager.edit_ui.open_designer")
|
||||||
|
@patch("bec_widgets.utils.bec_plugin_manager.edit_ui.plugin_repo_path")
|
||||||
|
@patch("bec_widgets.utils.bec_plugin_manager.create.widget.plugin_repo_path")
|
||||||
|
def test_widget_editor_watcher(
|
||||||
|
self,
|
||||||
|
plugin_repo_path,
|
||||||
|
plugin_repo_path_2,
|
||||||
|
open_designer,
|
||||||
|
logger: MagicMock,
|
||||||
|
runner: CliRunner,
|
||||||
|
git_repo: Path,
|
||||||
|
):
|
||||||
|
plugin_repo_path.return_value = str(git_repo)
|
||||||
|
plugin_repo_path_2.return_value = str(git_repo)
|
||||||
|
|
||||||
|
widget_dir = git_repo / "test_plugin" / "bec_widgets" / "widgets" / "test_widget_6"
|
||||||
|
widget_ui_file = widget_dir / "test_widget_6.ui"
|
||||||
|
compiled_widget_ui_file = widget_dir / "test_widget_6_ui.py"
|
||||||
|
|
||||||
|
test_collector = SimpleNamespace()
|
||||||
|
|
||||||
|
def test_function(args: list[str]):
|
||||||
|
test_collector.ui_file = args[0]
|
||||||
|
with open(compiled_widget_ui_file) as f:
|
||||||
|
test_collector.initial_compiled_ui_contents = f.read()
|
||||||
|
with open(args[0], "w") as f:
|
||||||
|
f.write(REPLACEMENT_UI_CONTENTS)
|
||||||
|
start = time.monotonic()
|
||||||
|
while call("done!") not in logger.success.call_args_list:
|
||||||
|
time.sleep(0.05)
|
||||||
|
if time.monotonic() - start > 5:
|
||||||
|
raise TimeoutError("Waiting for recompilation timed out.")
|
||||||
|
with open(compiled_widget_ui_file) as f:
|
||||||
|
test_collector.final_compiled_ui_contents = f.read()
|
||||||
|
|
||||||
|
open_designer.side_effect = test_function
|
||||||
|
result = runner.invoke(
|
||||||
|
main._app, ["create", "widget", "test_widget_6", "--use-ui", "--open-editor"]
|
||||||
|
)
|
||||||
|
|
||||||
|
assert result.exit_code == 0, result.output
|
||||||
|
assert test_collector.ui_file == str(widget_ui_file)
|
||||||
|
assert (
|
||||||
|
test_collector.initial_compiled_ui_contents != test_collector.final_compiled_ui_contents
|
||||||
|
)
|
||||||
|
assert "" in test_collector.final_compiled_ui_contents
|
Reference in New Issue
Block a user