Skip to content

warn on ambiguous -p plugin module usage#14270

Open
kartikdp wants to merge 6 commits intopytest-dev:mainfrom
kartikdp:bugfix/pluginarg-invalid-entrypoint
Open

warn on ambiguous -p plugin module usage#14270
kartikdp wants to merge 6 commits intopytest-dev:mainfrom
kartikdp:bugfix/pluginarg-invalid-entrypoint

Conversation

@kartikdp
Copy link

@kartikdp kartikdp commented Mar 6, 2026

Summary

-p <name> can silently do the wrong thing with --disable-plugin-autoload
when <name> is an importable top-level module that has no pytest hooks,
while the real plugin is exposed via a pytest11 entry-point submodule.

This change adds a focused warning in that case:

  • if the imported module has no pytest hooks
  • and a pytest11 entry-point exists in one of its submodules
  • pytest emits PytestConfigWarning suggesting the entry-point name (for
    example -p recording)

closes #14135

Impact

This improves correctness and UX for plugin selection, especially in autoload-
disabled runs, by surfacing likely misconfiguration instead of failing
silently.

How tested

  • Added regression test:
    • testing/ test_config.py::test_disable_plugin_autoload_warns_for_submodule_entrypoint
  • Ran focused tests:
    • uv run -m pytest testing/test_config.py -k "disable_plugin_autoload_warns_for_submodule_entrypoint or test_disable_plugin_autoload"
    • uv run -m pytest testing/test_pluginmanager.py -k "import_plugin_importname or import_plugin_dotted_name"
  • Ran formatting/lint/hooks on changed files:
    • uv run --with pre-commit pre-commit run --files src/_pytest/config/ __init__.py testing/test_config.py changelog/14135.bugfix.rst

Checklist

  • Include documentation when adding new features. (Not applicable: bugfix,
    no new feature)
  • Include new tests or update existing tests when applicable.
  • Allow maintainers to push and squash when merging my commits.
  • Add text like closes #XYZW to PR description and/or commits.
  • If AI agents were used, they are credited in Co-authored-by commit
    trailers.
  • Create a new changelog file in the changelog directory.
  • Add yourself to AUTHORS in alphabetical order. (Not needed for this
    change)

@psf-chronographer psf-chronographer bot added the bot:chronographer:provided (automation) changelog entry is part of PR label Mar 6, 2026
@kartikdp kartikdp force-pushed the bugfix/pluginarg-invalid-entrypoint branch from 4c67d0b to f0192b9 Compare March 6, 2026 19:19
Comment on lines +910 to +911
if self._plugin_has_pytest_hooks(mod):
return
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's inline this:

Suggested change
if self._plugin_has_pytest_hooks(mod):
return
plugin_has_pytest_hooks = any(
self.parse_hookimpl_opts(plugin, attr) is not None for attr in dir(plugin)
)
if plugin_has_pytest_hooks:
return

Comment on lines +923 to +924
if not suggested:
return
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should still warn about the plugin not having hooks, even if we don't find a suitable suggestion.

Comment on lines +914 to +922
suggested = {
ep.name
for dist in importlib.metadata.distributions()
for ep in dist.entry_points
if ep.group == "pytest11"
and ep.name != modname
and isinstance(getattr(ep, "value", None), str)
and getattr(ep, "value").split(":", 1)[0].startswith(modname_prefix)
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a bit dense, can we break it a bit?

ep_values = ...
suggested = ...

if ep.group == "pytest11"
and ep.name != modname
and isinstance(getattr(ep, "value", None), str)
and getattr(ep, "value").split(":", 1)[0].startswith(modname_prefix)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This warrants a comment: examples of what ep.value might be and what we are trying to match with this split() call here with : (the tests use . as separator).

w
for w in captured
if isinstance(w.message, PytestConfigWarning)
and "contains no pytest hooks" in str(w.message)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This check is a bit brittle in case we tweak the message later. Could we instead just check that there are no warnings at all?

)

assert config.pluginmanager.get_plugin("pytest_recording") is not None
assert not [
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same comment as the other test.

assert config.pluginmanager.get_plugin("pytest_recording") is not None


def test_warn_about_submodule_entrypoint_plugin_direct(pytester: Pytester, monkeypatch):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
def test_warn_about_submodule_entrypoint_plugin_direct(pytester: Pytester, monkeypatch):
def test_warn_about_submodule_entrypoint_plugin_direct(pytester: Pytester, monkeypatch: MonkeyPatch) -> None:

assert config.pluginmanager.get_plugin("pytest_recording") is not None


def test_warn_about_submodule_entrypoint_plugin_direct(pytester: Pytester, monkeypatch):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure I follow the intent behind this test, could you add a docstring please?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bot:chronographer:provided (automation) changelog entry is part of PR

Projects

None yet

Development

Successfully merging this pull request may close these issues.

-p silently fails if parameter is an invalid plugin

2 participants