diff --git a/CHANGES.rst b/CHANGES.rst index 0a806e1..c1b8b90 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -1,7 +1,9 @@ 1.8.0 (unreleased) ================== -- No changes yet. +- Fixing bug where ``__doctest_requires__`` with version specifiers (e.g., + ``numpy>=2.0``) incorrectly skipped tests even when dependencies were + satisfied. [#319] 1.7.0 (2026-01-07) ================== diff --git a/pytest_doctestplus/plugin.py b/pytest_doctestplus/plugin.py index b8b70aa..23393d6 100644 --- a/pytest_doctestplus/plugin.py +++ b/pytest_doctestplus/plugin.py @@ -874,7 +874,7 @@ def conditionally_insert_skip(test): continue # The pattern does not apply for mod in mods: - self._prepend_importorskip(test, module=mod) + self._prepend_module_check(test, module=mod) return True for _test in tests: @@ -893,17 +893,20 @@ def _prepend_skip(self, test): importorskip = doctest.Example(source=source, want="") test.examples.insert(0, importorskip) - def _prepend_importorskip(self, test, *, module): - """Prepends `pytest.importorskip` before the doctest.""" + def _prepend_module_check(self, test, *, module): + """Prepends module checker before the doctest.""" + escaped_module = module.replace("'", "\\'") source = ( + "from pytest_doctestplus.utils import ModuleChecker; " "import pytest; " # Hide output of this statement in `___`, otherwise doctests fail - f"___ = pytest.importorskip({module!r}); " + f"___ = ModuleChecker().check('{escaped_module}') or " + f"pytest.skip('could not import {escaped_module}'); " # Don't impact what's available in the namespace - "del pytest; del ___" + "del ModuleChecker, pytest, ___" ) - importorskip = doctest.Example(source=source, want="") - test.examples.insert(0, importorskip) + module_check = doctest.Example(source=source, want="") + test.examples.insert(0, module_check) def write_modified_file(fname, new_fname, changes, encoding=None): diff --git a/tests/test_doctestplus.py b/tests/test_doctestplus.py index 9476494..d36e012 100644 --- a/tests/test_doctestplus.py +++ b/tests/test_doctestplus.py @@ -1565,6 +1565,9 @@ def test_requires_module_variable(testdir): __doctest_requires__ = { ("f",): ["module_that_is_not_availabe"], ("g",): ["pytest"], + ("h",): ["pytest>=1.0"], + ("i",): ["pytest<1.0"], + ("j",): ["module_that_is_not_availabe>=1.0"], } def f(): @@ -1575,13 +1578,38 @@ def f(): def g(): ''' - Test that call to `pytest.importorskip` is not visible + Make sure out internal variables are not visible. + >>> assert "ModuleChecker" not in locals() >>> assert "pytest" not in locals() >>> assert "___" not in locals() >>> 1 + 1 2 ''' pass + + def h(): + ''' + Make sure out internal variables are not visible. + + >>> assert "ModuleChecker" not in locals() + >>> assert "pytest" not in locals() + >>> assert "___" not in locals() + >>> 1 + 1 + 2 + ''' + pass + + def i(): + ''' + >>> assert False, "This should be skipped due to version requirement not being met" + ''' + pass + + def j(): + ''' + >>> import module_that_is_not_availabe + ''' + pass """) - testdir.inline_run(p, '--doctest-plus').assertoutcome(passed=1, skipped=1) + testdir.inline_run(p, '--doctest-plus').assertoutcome(passed=2, skipped=3)