diff --git a/changelog/8456.bugfix.rst b/changelog/8456.bugfix.rst new file mode 100644 index 000000000..da9370b7b --- /dev/null +++ b/changelog/8456.bugfix.rst @@ -0,0 +1 @@ +The :confval:`required_plugins` config option now works correctly when pre-releases of plugins are installed, rather than falsely claiming that those plugins aren't installed at all. diff --git a/src/_pytest/config/__init__.py b/src/_pytest/config/__init__.py index 624fbd02f..12e73859d 100644 --- a/src/_pytest/config/__init__.py +++ b/src/_pytest/config/__init__.py @@ -1270,14 +1270,16 @@ class Config: missing_plugins = [] for required_plugin in required_plugins: try: - spec = Requirement(required_plugin) + req = Requirement(required_plugin) except InvalidRequirement: missing_plugins.append(required_plugin) continue - if spec.name not in plugin_dist_info: + if req.name not in plugin_dist_info: missing_plugins.append(required_plugin) - elif Version(plugin_dist_info[spec.name]) not in spec.specifier: + elif not req.specifier.contains( + Version(plugin_dist_info[req.name]), prereleases=True + ): missing_plugins.append(required_plugin) if missing_plugins: diff --git a/testing/test_config.py b/testing/test_config.py index fbeabaff6..61fea3643 100644 --- a/testing/test_config.py +++ b/testing/test_config.py @@ -309,13 +309,14 @@ class TestParseIni: result.stdout.no_fnmatch_line("*PytestConfigWarning*") @pytest.mark.parametrize( - "ini_file_text, exception_text", + "ini_file_text, plugin_version, exception_text", [ pytest.param( """ [pytest] required_plugins = a z """, + "1.5", "Missing required plugins: a, z", id="2-missing", ), @@ -324,6 +325,7 @@ class TestParseIni: [pytest] required_plugins = a z myplugin """, + "1.5", "Missing required plugins: a, z", id="2-missing-1-ok", ), @@ -332,6 +334,7 @@ class TestParseIni: [pytest] required_plugins = myplugin """, + "1.5", None, id="1-ok", ), @@ -340,6 +343,7 @@ class TestParseIni: [pytest] required_plugins = myplugin==1.5 """, + "1.5", None, id="1-ok-pin-exact", ), @@ -348,23 +352,35 @@ class TestParseIni: [pytest] required_plugins = myplugin>1.0,<2.0 """, + "1.5", None, id="1-ok-pin-loose", ), pytest.param( """ [pytest] - required_plugins = pyplugin==1.6 + required_plugins = myplugin """, - "Missing required plugins: pyplugin==1.6", + "1.5a1", + None, + id="1-ok-prerelease", + ), + pytest.param( + """ + [pytest] + required_plugins = myplugin==1.6 + """, + "1.5", + "Missing required plugins: myplugin==1.6", id="missing-version", ), pytest.param( """ [pytest] - required_plugins = pyplugin==1.6 other==1.0 + required_plugins = myplugin==1.6 other==1.0 """, - "Missing required plugins: other==1.0, pyplugin==1.6", + "1.5", + "Missing required plugins: myplugin==1.6, other==1.0", id="missing-versions", ), pytest.param( @@ -373,6 +389,7 @@ class TestParseIni: required_plugins = wont be triggered [pytest] """, + "1.5", None, id="invalid-header", ), @@ -383,6 +400,7 @@ class TestParseIni: pytester: Pytester, monkeypatch: MonkeyPatch, ini_file_text: str, + plugin_version: str, exception_text: str, ) -> None: """Check 'required_plugins' option with various settings. @@ -408,7 +426,7 @@ class TestParseIni: class DummyDist: entry_points = attr.ib() files = () - version = "1.5" + version = plugin_version @property def metadata(self):