Merge pull request #9184 from bluetech/reportinfo-pathlike

[7.0] Change `Node.reportinfo()` return value from `py.path` to `str|os.PathLike[str]`
This commit is contained in:
Ran Benita 2021-10-11 14:33:03 +03:00 committed by GitHub
commit da3b3012b1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 28 additions and 27 deletions

View File

@ -0,0 +1,8 @@
The :ref:`Node.reportinfo() <non-python tests>` function first return value type has been expanded from `py.path.local | str` to `os.PathLike[str] | str`.
Most plugins which refer to `reportinfo()` only define it as part of a custom :class:`pytest.Item` implementation.
Since `py.path.local` is a `os.PathLike[str]`, these plugins are unaffacted.
Plugins and users which call `reportinfo()`, use the first return value and interact with it as a `py.path.local`, would need to adjust by calling `py.path.local(fspath)`.
Although preferably, avoid the legacy `py.path.local` and use `pathlib.Path`, or use `item.location` or `item.path`, instead.
Note: pytest was not able to provide a deprecation period for this change.

View File

@ -40,7 +40,7 @@ class YamlItem(pytest.Item):
) )
def reportinfo(self): def reportinfo(self):
return self.fspath, 0, f"usecase: {self.name}" return self.path, 0, f"usecase: {self.name}"
class YamlException(Exception): class YamlException(Exception):

View File

@ -1,6 +1,7 @@
"""Discover and run doctests in modules and test files.""" """Discover and run doctests in modules and test files."""
import bdb import bdb
import inspect import inspect
import os
import platform import platform
import sys import sys
import traceback import traceback
@ -28,7 +29,6 @@ from _pytest._code.code import ExceptionInfo
from _pytest._code.code import ReprFileLocation from _pytest._code.code import ReprFileLocation
from _pytest._code.code import TerminalRepr from _pytest._code.code import TerminalRepr
from _pytest._io import TerminalWriter from _pytest._io import TerminalWriter
from _pytest.compat import legacy_path
from _pytest.compat import safe_getattr from _pytest.compat import safe_getattr
from _pytest.config import Config from _pytest.config import Config
from _pytest.config.argparsing import Parser from _pytest.config.argparsing import Parser
@ -371,9 +371,9 @@ class DoctestItem(pytest.Item):
reprlocation_lines.append((reprlocation, lines)) reprlocation_lines.append((reprlocation, lines))
return ReprFailDoctest(reprlocation_lines) return ReprFailDoctest(reprlocation_lines)
def reportinfo(self): def reportinfo(self) -> Tuple[Union["os.PathLike[str]", str], Optional[int], str]:
assert self.dtest is not None assert self.dtest is not None
return legacy_path(self.path), self.dtest.lineno, "[doctest] %s" % self.name return self.path, self.dtest.lineno, "[doctest] %s" % self.name
def _get_flag_lookup() -> Dict[str, int]: def _get_flag_lookup() -> Dict[str, int]:

View File

@ -718,15 +718,13 @@ class Item(Node):
if content: if content:
self._report_sections.append((when, key, content)) self._report_sections.append((when, key, content))
def reportinfo(self) -> Tuple[Union[LEGACY_PATH, str], Optional[int], str]: def reportinfo(self) -> Tuple[Union["os.PathLike[str]", str], Optional[int], str]:
return self.path, None, ""
# TODO: enable Path objects in reportinfo
return legacy_path(self.path), None, ""
@cached_property @cached_property
def location(self) -> Tuple[str, Optional[int], str]: def location(self) -> Tuple[str, Optional[int], str]:
location = self.reportinfo() location = self.reportinfo()
fspath = absolutepath(str(location[0])) path = absolutepath(os.fspath(location[0]))
relfspath = self.session._node_location_to_relpath(fspath) relfspath = self.session._node_location_to_relpath(path)
assert type(location[2]) is str assert type(location[2]) is str
return (relfspath, location[1], location[2]) return (relfspath, location[1], location[2])

View File

@ -48,7 +48,6 @@ from _pytest.compat import getlocation
from _pytest.compat import is_async_function from _pytest.compat import is_async_function
from _pytest.compat import is_generator from _pytest.compat import is_generator
from _pytest.compat import LEGACY_PATH from _pytest.compat import LEGACY_PATH
from _pytest.compat import legacy_path
from _pytest.compat import NOTSET from _pytest.compat import NOTSET
from _pytest.compat import safe_getattr from _pytest.compat import safe_getattr
from _pytest.compat import safe_isclass from _pytest.compat import safe_isclass
@ -321,7 +320,7 @@ class PyobjMixin(nodes.Node):
parts.reverse() parts.reverse()
return ".".join(parts) return ".".join(parts)
def reportinfo(self) -> Tuple[Union[LEGACY_PATH, str], int, str]: def reportinfo(self) -> Tuple[Union["os.PathLike[str]", str], Optional[int], str]:
# XXX caching? # XXX caching?
obj = self.obj obj = self.obj
compat_co_firstlineno = getattr(obj, "compat_co_firstlineno", None) compat_co_firstlineno = getattr(obj, "compat_co_firstlineno", None)
@ -330,17 +329,13 @@ class PyobjMixin(nodes.Node):
file_path = sys.modules[obj.__module__].__file__ file_path = sys.modules[obj.__module__].__file__
if file_path.endswith(".pyc"): if file_path.endswith(".pyc"):
file_path = file_path[:-1] file_path = file_path[:-1]
fspath: Union[LEGACY_PATH, str] = file_path path: Union["os.PathLike[str]", str] = file_path
lineno = compat_co_firstlineno lineno = compat_co_firstlineno
else: else:
path, lineno = getfslineno(obj) path, lineno = getfslineno(obj)
if isinstance(path, Path):
fspath = legacy_path(path)
else:
fspath = path
modpath = self.getmodpath() modpath = self.getmodpath()
assert isinstance(lineno, int) assert isinstance(lineno, int)
return fspath, lineno, modpath return path, lineno, modpath
# As an optimization, these builtin attribute names are pre-ignored when # As an optimization, these builtin attribute names are pre-ignored when

View File

@ -324,9 +324,9 @@ class TestReport(BaseReport):
outcome = "skipped" outcome = "skipped"
r = excinfo._getreprcrash() r = excinfo._getreprcrash()
if excinfo.value._use_item_location: if excinfo.value._use_item_location:
filename, line = item.reportinfo()[:2] path, line = item.reportinfo()[:2]
assert line is not None assert line is not None
longrepr = str(filename), line + 1, r.message longrepr = os.fspath(path), line + 1, r.message
else: else:
longrepr = (str(r.path), r.lineno, r.message) longrepr = (str(r.path), r.lineno, r.message)
else: else:

View File

@ -1154,8 +1154,8 @@ class TestReportInfo:
def test_func_reportinfo(self, pytester: Pytester) -> None: def test_func_reportinfo(self, pytester: Pytester) -> None:
item = pytester.getitem("def test_func(): pass") item = pytester.getitem("def test_func(): pass")
fspath, lineno, modpath = item.reportinfo() path, lineno, modpath = item.reportinfo()
assert str(fspath) == str(item.path) assert os.fspath(path) == str(item.path)
assert lineno == 0 assert lineno == 0
assert modpath == "test_func" assert modpath == "test_func"
@ -1169,8 +1169,8 @@ class TestReportInfo:
) )
classcol = pytester.collect_by_name(modcol, "TestClass") classcol = pytester.collect_by_name(modcol, "TestClass")
assert isinstance(classcol, Class) assert isinstance(classcol, Class)
fspath, lineno, msg = classcol.reportinfo() path, lineno, msg = classcol.reportinfo()
assert str(fspath) == str(modcol.path) assert os.fspath(path) == str(modcol.path)
assert lineno == 1 assert lineno == 1
assert msg == "TestClass" assert msg == "TestClass"
@ -1194,7 +1194,7 @@ class TestReportInfo:
assert isinstance(classcol, Class) assert isinstance(classcol, Class)
instance = list(classcol.collect())[0] instance = list(classcol.collect())[0]
assert isinstance(instance, Instance) assert isinstance(instance, Instance)
fspath, lineno, msg = instance.reportinfo() path, lineno, msg = instance.reportinfo()
def test_customized_python_discovery(pytester: Pytester) -> None: def test_customized_python_discovery(pytester: Pytester) -> None:

View File

@ -19,7 +19,7 @@ class TestOEJSKITSpecials:
return MyCollector.from_parent(collector, name=name) return MyCollector.from_parent(collector, name=name)
class MyCollector(pytest.Collector): class MyCollector(pytest.Collector):
def reportinfo(self): def reportinfo(self):
return self.fspath, 3, "xyz" return self.path, 3, "xyz"
""" """
) )
modcol = pytester.getmodulecol( modcol = pytester.getmodulecol(
@ -52,7 +52,7 @@ class TestOEJSKITSpecials:
return MyCollector.from_parent(collector, name=name) return MyCollector.from_parent(collector, name=name)
class MyCollector(pytest.Collector): class MyCollector(pytest.Collector):
def reportinfo(self): def reportinfo(self):
return self.fspath, 3, "xyz" return self.path, 3, "xyz"
""" """
) )
modcol = pytester.getmodulecol( modcol = pytester.getmodulecol(