From b83f7df0bace48d1d947008dd8f259f47f041a56 Mon Sep 17 00:00:00 2001 From: Nick Moore Date: Thu, 9 Nov 2023 15:05:07 +1100 Subject: [PATCH] more gui tests --- countess/gui/main.py | 1 + tests/gui/test_main.py | 60 +++++++++++++++++++++++++++++ tests/plugins/test_expression.py | 65 ++++++++++++++++++++++++++++++++ tests/test_plugins.py | 33 ++++++++++++++++ 4 files changed, 159 insertions(+) create mode 100644 tests/gui/test_main.py create mode 100644 tests/plugins/test_expression.py create mode 100644 tests/test_plugins.py diff --git a/countess/gui/main.py b/countess/gui/main.py index 80d5976..2f65912 100644 --- a/countess/gui/main.py +++ b/countess/gui/main.py @@ -314,6 +314,7 @@ def on_button(self): else: self.toplevel.destroy() + self.toplevel = None class MainWindow: diff --git a/tests/gui/test_main.py b/tests/gui/test_main.py new file mode 100644 index 0000000..ec9c52a --- /dev/null +++ b/tests/gui/test_main.py @@ -0,0 +1,60 @@ +import tkinter as tk +from unittest.mock import MagicMock +import time + +from countess.core.config import read_config +from countess.gui.main import make_root, ConfiguratorWrapper, PluginChooserFrame, RunWindow +from countess.core.pipeline import PipelineNode +from countess.plugins.data_table import DataTablePlugin + + +def descendants(widget): + for w in widget.winfo_children(): + yield w + yield from descendants(w) + +def test_chooser(): + + callback = MagicMock() + + root = make_root() + choose = PluginChooserFrame(root, 'X', callback) + + for x in descendants(choose): + if isinstance(x, tk.Button): + x.invoke() + break + + callback.assert_called_once() + + root.destroy() + +def test_main(): + + root = make_root() + node = PipelineNode(name='NEW', plugin=DataTablePlugin()) + callback = MagicMock() + wrap = ConfiguratorWrapper(root, node, callback) + wrap.on_add_notes() + + root.update() + + root.destroy() + +def test_run(): + + graph = read_config("tests/simple.ini") + + runner = RunWindow(graph) + for _ in range(0,20): + time.sleep(0.1) + runner.toplevel.update() + runner.on_button() + time.sleep(1) + + assert runner.process is None + + runner.on_button() + time.sleep(0.1) + + assert runner.toplevel is None diff --git a/tests/plugins/test_expression.py b/tests/plugins/test_expression.py new file mode 100644 index 0000000..4c45997 --- /dev/null +++ b/tests/plugins/test_expression.py @@ -0,0 +1,65 @@ +import time + +import pandas as pd + +from countess.core.logger import MultiprocessLogger +from countess.plugins.expression import ExpressionPlugin + +logger = MultiprocessLogger() + +df1 = pd.DataFrame( + [ + {"foo": 1, "bar": 2, "baz": 3}, + {"foo": 4, "bar": 5, "baz": 6}, + {"foo": 7, "bar": 8, "baz": 9}, + ], +).set_index("foo") + +code_1 = "qux = bar + baz\n\nquux = bar * baz\n" + +code_2 = "bar + baz != 11" + + +def test_expr_0(): + plugin = ExpressionPlugin() + plugin.set_parameter("code", "1/0") + plugin.prepare(["x"]) + + df = plugin.process_dataframe(df1, logger) + + time.sleep(0.1) + assert "ZeroDivisionError" in logger.dump() + + +def test_expr_1(): + plugin = ExpressionPlugin() + plugin.set_parameter("code", code_1) + plugin.prepare(["x"]) + + df = plugin.process_dataframe(df1, logger) + assert len(df) == 3 + assert set(df.columns) == {"foo", "bar", "baz", "qux", "quux"} + + +def test_expr_2(): + plugin = ExpressionPlugin() + plugin.set_parameter("code", code_2) + plugin.prepare(["x"]) + + df = plugin.process_dataframe(df1, logger) + assert len(df) == 2 + assert set(df.columns) == {"foo", "bar", "baz"} + + +def test_expr_3(): + plugin = ExpressionPlugin() + plugin.set_parameter("code", code_1) + plugin.set_parameter("drop.0._label", "foo") + plugin.set_parameter("drop.0", True) + plugin.set_parameter("drop.1._label", "baz") + plugin.set_parameter("drop.1", True) + plugin.prepare(["x"]) + + df = next(plugin.process(df1, "x", logger)) + assert len(df) == 3 + assert set(df.columns) == {"bar", "qux", "quux"} diff --git a/tests/test_plugins.py b/tests/test_plugins.py new file mode 100644 index 0000000..0bbb3fb --- /dev/null +++ b/tests/test_plugins.py @@ -0,0 +1,33 @@ +import importlib.metadata +from unittest.mock import patch + +import countess +from countess.core.plugins import BasePlugin, get_plugin_classes + +empty_entry_points_dict = {"countess_plugins": []} + +invalid_entry_points_dict = { + "countess_plugins": [importlib.metadata.EntryPoint(name="test", value="mockplugin", group="countess_plugins")] +} + + +class NoParentPlugin: + pass + + +noparent_entry_points_dict = { + "countess_plugins": [importlib.metadata.EntryPoint(name="test", value="NoParentPlugin", group="countess_plugins")] +} + + +def test_get_plugin_classes_invalid(caplog): + with patch("importlib.metadata.entry_points", lambda: invalid_entry_points_dict): + get_plugin_classes() + assert "could not be loaded" in caplog.text + + +def test_get_plugin_classes_wrongparent(caplog): + with patch("importlib.metadata.entry_points", lambda: noparent_entry_points_dict): + with patch("importlib.metadata.EntryPoint.load", lambda x: NoParentPlugin): + get_plugin_classes() + assert "not a valid CountESS plugin" in caplog.text