From 0dfa060f06124f5892a23b9ff3170ba2a19e76c3 Mon Sep 17 00:00:00 2001 From: Thomas Rausch Date: Wed, 15 May 2024 18:23:09 +0200 Subject: [PATCH] fix null pointer errors in metadata plugin finder --- plux/runtime/resolve.py | 7 +++- tests/plugins/invalid_module.py | 6 +++ tests/runtime/test_resolve.py | 67 +++++++++++++++++++++++++++++++++ 3 files changed, 78 insertions(+), 2 deletions(-) create mode 100644 tests/runtime/test_resolve.py diff --git a/plux/runtime/resolve.py b/plux/runtime/resolve.py index 8e7df42..eb304ed 100644 --- a/plux/runtime/resolve.py +++ b/plux/runtime/resolve.py @@ -35,7 +35,9 @@ def find_plugins(self) -> t.List[PluginSpec]: specs = [] finds = self.entry_points_resolver.get_entry_points().get(self.namespace, []) for ep in finds: - specs.append(self.to_plugin_spec(ep)) + spec = self.to_plugin_spec(ep) + if spec: + specs.append(spec) return specs def to_plugin_spec(self, entry_point: EntryPoint) -> PluginSpec: @@ -51,4 +53,5 @@ def to_plugin_spec(self, entry_point: EntryPoint) -> PluginSpec: "error resolving PluginSpec for plugin %s.%s", self.namespace, entry_point.name ) - self.on_resolve_exception_callback(self.namespace, entry_point, e) + if self.on_resolve_exception_callback: + self.on_resolve_exception_callback(self.namespace, entry_point, e) diff --git a/tests/plugins/invalid_module.py b/tests/plugins/invalid_module.py index a218776..7cbe278 100644 --- a/tests/plugins/invalid_module.py +++ b/tests/plugins/invalid_module.py @@ -1,8 +1,14 @@ # this module fails when importing to test the fault tolerance of the plugin discovery mechanism +from plux import Plugin def fail(): raise ValueError("this is an expected exception") +class CannotBeLoadedPlugin(Plugin): + namespace = "namespace_2" + name = "cannot-be-loaded" + + fail() diff --git a/tests/runtime/test_resolve.py b/tests/runtime/test_resolve.py new file mode 100644 index 0000000..c8113d1 --- /dev/null +++ b/tests/runtime/test_resolve.py @@ -0,0 +1,67 @@ +import typing as t +from importlib import metadata +from importlib.metadata import EntryPoint +from unittest.mock import MagicMock + +import pytest + +from plux.runtime.metadata import EntryPointsResolver, build_entry_point_index +from plux.runtime.resolve import MetadataPluginFinder + + +class DummyEntryPointsResolver(EntryPointsResolver): + entry_points: t.Dict[str, t.List[metadata.EntryPoint]] + + def __init__(self, entry_points: t.List[metadata.EntryPoint]): + self.entry_points = build_entry_point_index(entry_points) + + def get_entry_points(self) -> t.Dict[str, t.List[metadata.EntryPoint]]: + return self.entry_points + + +@pytest.fixture +def dummy_entry_point_resolver(): + return DummyEntryPointsResolver( + [ + EntryPoint( + group="namespace_1", + name="plugin_1", + value="tests.plugins.sample_plugins:plugin_spec_1", + ), + EntryPoint( + group="namespace_1", + name="plugin_2", + value="tests.plugins.sample_plugins:plugin_spec_2", + ), + EntryPoint( + group="namespace_2", + name="cannot-be-loaded", + value="tests.plugins.invalid_module:CannotBeLoadedPlugin", + ), + EntryPoint( + group="namespace_2", + name="simple", + value="tests.plugins.sample_plugins:SimplePlugin", + ), + ] + ) + + +def test_resolve_error(dummy_entry_point_resolver): + mock = MagicMock() + finder = MetadataPluginFinder( + "namespace_2", + on_resolve_exception_callback=mock, + entry_points_resolver=dummy_entry_point_resolver, + ) + plugins = finder.find_plugins() + assert len(plugins) == 1 + assert plugins[0].name == "simple" + assert mock.call_count == 1 + assert mock.call_args[0][0] == "namespace_2" + assert mock.call_args[0][1] == EntryPoint( + "cannot-be-loaded", + "tests.plugins.invalid_module:CannotBeLoadedPlugin", + "namespace_2", + ) + assert str(mock.call_args[0][2]) == "this is an expected exception"