Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion CHANGES.rst
Original file line number Diff line number Diff line change
@@ -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. [#318]

1.7.0 (2026-01-07)
==================
Expand Down
17 changes: 10 additions & 7 deletions pytest_doctestplus/plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand All @@ -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):
Expand Down
32 changes: 30 additions & 2 deletions tests/test_doctestplus.py
Original file line number Diff line number Diff line change
Expand Up @@ -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():
Expand All @@ -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"
'''
skip

def j():
'''
>>> import module_that_is_not_availabe
'''
skip
""")
testdir.inline_run(p, '--doctest-plus').assertoutcome(passed=1, skipped=1)
testdir.inline_run(p, '--doctest-plus').assertoutcome(passed=2, skipped=3)