Skip to content

Commit

Permalink
Support for pytest 8.0
Browse files Browse the repository at this point in the history
  • Loading branch information
Erotemic committed Jan 29, 2024
1 parent 44fe181 commit 5bdc1a4
Show file tree
Hide file tree
Showing 4 changed files with 95 additions and 36 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm

### Fixed
* `modname_to_modpath` now handles cases where editable packages have modules where the name is different than the package.
* Update `xdoctest.plugin` to support pytest 8.0


## Version 1.1.2 - Released 2023-010-25
Expand Down
2 changes: 1 addition & 1 deletion src/xdoctest/directive.py
Original file line number Diff line number Diff line change
Expand Up @@ -453,7 +453,7 @@ def extract(cls, text):
>>> # xdoctest: +REQUIRES(module:pytest)
>>> text = '# xdoctest: does_not_exist, skip'
>>> import pytest
>>> with pytest.warns(None) as record:
>>> with pytest.warns(Warning) as record:
>>> print(', '.join(list(map(str, Directive.extract(text)))))
<Directive(+SKIP)>
Expand Down
7 changes: 7 additions & 0 deletions src/xdoctest/doctest_example.py
Original file line number Diff line number Diff line change
Expand Up @@ -970,6 +970,13 @@ def run(self, verbose=None, on_error=None):

return summary

@property
def globs(self):
"""
Alias for ``global_namespace`` for pytest 8.0 compatability
"""
return self.global_namespace

Check warning on line 978 in src/xdoctest/doctest_example.py

View check run for this annotation

Codecov / codecov/patch

src/xdoctest/doctest_example.py#L978

Added line #L978 was not covered by tests

@property
def cmdline(self):
"""
Expand Down
121 changes: 86 additions & 35 deletions src/xdoctest/plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,18 @@
from _pytest import fixtures

try:
from packaging.version import parse as parse_version
_PYTEST_IS_GE_620 = parse_version(pytest.__version__) >= parse_version('6.2.0')
from packaging.version import parse as Version
except ImportError: # nocover
from distutils.version import LooseVersion
_PYTEST_IS_GE_620 = LooseVersion(pytest.__version__) >= LooseVersion('6.2.0')
from distutils.version import LooseVersion as Version

_PYTEST_IS_GE_620 = Version(pytest.__version__) >= Version('6.2.0')
_PYTEST_IS_GE_800 = Version(pytest.__version__) >= Version('8.0.0')


if _PYTEST_IS_GE_800:
from typing import Dict
from _pytest.fixtures import TopRequest

Check warning on line 32 in src/xdoctest/plugin.py

View check run for this annotation

Codecov / codecov/patch

src/xdoctest/plugin.py#L31-L32

Added lines #L31 - L32 were not covered by tests


# def print(text):
# """ Hack so we can get stdout when debugging the plugin file """
Expand Down Expand Up @@ -185,33 +192,77 @@ def toterminal(self, tw):


class XDoctestItem(pytest.Item):
def __init__(self, name, parent, example=None):
def __init__(self,
name,
parent,
runner=None,
dtest=None):
"""
Args:
name (str):
parent (Any | None):
example (xdoctest.doctest_example.DocTest):
dtest (xdoctest.doctest_example.DocTest):
"""
super(XDoctestItem, self).__init__(name, parent)
self.cls = XDoctestItem
self.example = example
self.dtest = dtest
self.obj = None
self.fixture_request = None
if _PYTEST_IS_GE_800:
# Stuff needed for fixture support in pytest > 8.0.
fm = self.session._fixturemanager
fixtureinfo = fm.getfixtureinfo(node=self, func=None, cls=None)
self._fixtureinfo = fixtureinfo
self.fixturenames = fixtureinfo.names_closure
self._initrequest()

Check warning on line 216 in src/xdoctest/plugin.py

View check run for this annotation

Codecov / codecov/patch

src/xdoctest/plugin.py#L212-L216

Added lines #L212 - L216 were not covered by tests
else:
self.fixture_request = None

if _PYTEST_IS_GE_800:
@classmethod

Check warning on line 221 in src/xdoctest/plugin.py

View check run for this annotation

Codecov / codecov/patch

src/xdoctest/plugin.py#L221

Added line #L221 was not covered by tests
def from_parent( # type: ignore
cls,
parent,
name,
runner=None,
dtest=None,
):
# incompatible signature due to imposed limits on subclass
"""The public named constructor."""
return super().from_parent(name=name, parent=parent, runner=runner, dtest=dtest)

Check warning on line 231 in src/xdoctest/plugin.py

View check run for this annotation

Codecov / codecov/patch

src/xdoctest/plugin.py#L231

Added line #L231 was not covered by tests

@property
def example(self):
"""
Backwards compatability with older pytest versions
"""
return self.dtest

Check warning on line 238 in src/xdoctest/plugin.py

View check run for this annotation

Codecov / codecov/patch

src/xdoctest/plugin.py#L238

Added line #L238 was not covered by tests

def _initrequest(self) -> None:
assert _PYTEST_IS_GE_800
self.funcargs: Dict[str, object] = {}
self._request = TopRequest(self, _ispytest=True) # type: ignore[arg-type]

Check warning on line 243 in src/xdoctest/plugin.py

View check run for this annotation

Codecov / codecov/patch

src/xdoctest/plugin.py#L241-L243

Added lines #L241 - L243 were not covered by tests

def setup(self):
if self.example is not None:
self.fixture_request = _setup_fixtures(self)
global_namespace = dict(getfixture=self.fixture_request.getfixturevalue)
for name, value in self.fixture_request.getfixturevalue('xdoctest_namespace').items():
global_namespace[name] = value
self.example.global_namespace.update(global_namespace)
if _PYTEST_IS_GE_800:
self._request._fillfixtures()
globs = dict(getfixture=self._request.getfixturevalue)

Check warning on line 248 in src/xdoctest/plugin.py

View check run for this annotation

Codecov / codecov/patch

src/xdoctest/plugin.py#L247-L248

Added lines #L247 - L248 were not covered by tests
for name, value in self._request.getfixturevalue("xdoctest_namespace").items():
globs[name] = value
self.dtest.globs.update(globs)

Check warning on line 251 in src/xdoctest/plugin.py

View check run for this annotation

Codecov / codecov/patch

src/xdoctest/plugin.py#L250-L251

Added lines #L250 - L251 were not covered by tests
else:
if self.dtest is not None:
self.fixture_request = _setup_fixtures(self)
global_namespace = dict(getfixture=self.fixture_request.getfixturevalue)
for name, value in self.fixture_request.getfixturevalue('xdoctest_namespace').items():
global_namespace[name] = value

Check warning on line 257 in src/xdoctest/plugin.py

View check run for this annotation

Codecov / codecov/patch

src/xdoctest/plugin.py#L257

Added line #L257 was not covered by tests
self.dtest.global_namespace.update(global_namespace)

def runtest(self):
if self.example.is_disabled(pytest=True):
if self.dtest.is_disabled(pytest=True):
pytest.skip('doctest encountered global skip directive')
# verbose = self.example.config['verbose']
self.example.run(on_error='raise')
if not self.example.anything_ran():
# verbose = self.dtest.config['verbose']
self.dtest.run(on_error='raise')
if not self.dtest.anything_ran():
pytest.skip('doctest is empty or all parts were skipped')

def repr_failure(self, excinfo):
Expand All @@ -222,13 +273,13 @@ def repr_failure(self, excinfo):
# Returns:
# ReprFailXDoctest | str | _pytest._code.code.TerminalRepr:
"""
example = self.example
if example.exc_info is not None:
lineno = example.failed_lineno()
type = example.exc_info[0]
dtest = self.dtest
if dtest.exc_info is not None:
lineno = dtest.failed_lineno()
type = dtest.exc_info[0]
message = type.__name__
reprlocation = code.ReprFileLocation(example.fpath, lineno, message)
lines = example.repr_failure()
reprlocation = code.ReprFileLocation(dtest.fpath, lineno, message)
lines = dtest.repr_failure()

return ReprFailXDoctest(reprlocation, lines)
else:
Expand All @@ -239,7 +290,7 @@ def reportinfo(self):
Returns:
Tuple[str, int, str]
"""
return self.fspath, self.example.lineno, "[xdoctest] %s" % self.name
return self.fspath, self.dtest.lineno, "[xdoctest] %s" % self.name


class _XDoctestBase(pytest.Module):
Expand Down Expand Up @@ -282,15 +333,15 @@ def collect(self):
_example_iter = core.parse_docstr_examples(
text, name, fpath=filename, style=style)

for example in _example_iter:
example.global_namespace.update(global_namespace)
example.config.update(self._examp_conf)
for dtest in _example_iter:
dtest.global_namespace.update(global_namespace)
dtest.config.update(self._examp_conf)
if hasattr(XDoctestItem, 'from_parent'):
yield XDoctestItem.from_parent(
self, name=name, example=example)
self, name=name, dtest=dtest)
else:
# direct construction is deprecated
yield XDoctestItem(name, self, example)
yield XDoctestItem(name, self, dtest=dtest)


class XDoctestModule(_XDoctestBase):
Expand All @@ -311,15 +362,15 @@ def collect(self):
else:
raise

for example in examples:
example.config.update(self._examp_conf)
name = example.unique_callname
for dtest in examples:
dtest.config.update(self._examp_conf)
name = dtest.unique_callname
if hasattr(XDoctestItem, 'from_parent'):
yield XDoctestItem.from_parent(
self, name=name, example=example)
self, name=name, dtest=dtest)
else:
# direct construction is deprecated
yield XDoctestItem(name, self, example)
yield XDoctestItem(name, self, dtest=dtest)


def _setup_fixtures(xdoctest_item):
Expand Down

0 comments on commit 5bdc1a4

Please sign in to comment.