diff --git a/src/pluggy/_manager.py b/src/pluggy/_manager.py index 4994c867..6db75187 100644 --- a/src/pluggy/_manager.py +++ b/src/pluggy/_manager.py @@ -317,6 +317,14 @@ def unregister( if self._name2plugin.get(name) is not None: del self._name2plugin[name] + # Drop any entry-point bookkeeping for this plugin so that + # list_plugin_distinfo()/list_plugin_distributions() no longer report + # a plugin that has been unregistered (and re-registration through + # entry points does not leave a duplicate entry behind). + self._plugin_distinfo[:] = [ + (p, dist) for p, dist in self._plugin_distinfo if p is not plugin + ] + return plugin def set_blocked(self, name: str) -> None: diff --git a/testing/test_pluginmanager.py b/testing/test_pluginmanager.py index 06d645d7..db436161 100644 --- a/testing/test_pluginmanager.py +++ b/testing/test_pluginmanager.py @@ -960,6 +960,49 @@ def my_distributions(): assert ret_distributions == [(plugin, dist)] +def test_unregister_removes_plugin_distinfo( + monkeypatch: pytest.MonkeyPatch, pm: PluginManager +) -> None: + """Unregistering drops entry-point bookkeeping (#763). + + ``list_plugin_distinfo()`` / ``list_plugin_distributions()`` must not + report a plugin after it has been unregistered, and re-registering + through entry points must not leave a duplicate entry behind. + """ + + class EntryPoint: + name = "myname" + group = "hello" + value = "myname:foo" + + def load(self): + class PseudoPlugin: + x = 42 + + return PseudoPlugin() + + class Distribution: + entry_points = (EntryPoint(),) + + dist = cast(importlib.metadata.Distribution, Distribution()) + + monkeypatch.setattr(importlib.metadata, "distributions", lambda: (dist,)) + + assert pm.load_setuptools_entrypoints("hello") == 1 + assert len(pm.list_plugin_distinfo()) == 1 + assert pm.list_plugin_distributions() == [(pm.get_plugin("myname"), dist)] + + pm.unregister(name="myname") + + assert pm.list_plugin_distinfo() == [] + assert pm.list_plugin_distributions() == [] + + # Re-registering through entry points starts from a clean slate: exactly + # one entry, not a stale leftover plus a new one. + assert pm.load_setuptools_entrypoints("hello") == 1 + assert len(pm.list_plugin_distributions()) == 1 + + def test_add_tracefuncs(he_pm: PluginManager) -> None: out: list[Any] = []