Move testdir to legacypath plugin

This commit is contained in:
Ran Benita 2021-10-16 10:28:45 +03:00
parent c2ece58aa0
commit 1df28a4450
7 changed files with 284 additions and 260 deletions

View File

@ -446,3 +446,7 @@ def setup(app: "sphinx.application.Sphinx") -> None:
) )
sphinx.pycode.parser.VariableCommentPicker.is_final = patched_is_final sphinx.pycode.parser.VariableCommentPicker.is_final = patched_is_final
# legacypath.py monkey-patches pytest.Testdir in. Import the file so
# that autodoc can discover references to it.
import _pytest.legacypath # noqa: F401

View File

@ -1 +1,254 @@
"""Add backward compatibility support for the legacy py path type.""" """Add backward compatibility support for the legacy py path type."""
import subprocess
from typing import List
from typing import Optional
from typing import TYPE_CHECKING
from typing import Union
from iniconfig import SectionWrapper
import pytest
from _pytest.compat import final
from _pytest.compat import LEGACY_PATH
from _pytest.compat import legacy_path
from _pytest.deprecated import check_ispytest
if TYPE_CHECKING:
from typing_extensions import Final
import pexpect
@final
class Testdir:
"""
Similar to :class:`Pytester`, but this class works with legacy legacy_path objects instead.
All methods just forward to an internal :class:`Pytester` instance, converting results
to `legacy_path` objects as necessary.
"""
__test__ = False
CLOSE_STDIN: "Final" = pytest.Pytester.CLOSE_STDIN
TimeoutExpired: "Final" = pytest.Pytester.TimeoutExpired
def __init__(self, pytester: pytest.Pytester, *, _ispytest: bool = False) -> None:
check_ispytest(_ispytest)
self._pytester = pytester
@property
def tmpdir(self) -> LEGACY_PATH:
"""Temporary directory where tests are executed."""
return legacy_path(self._pytester.path)
@property
def test_tmproot(self) -> LEGACY_PATH:
return legacy_path(self._pytester._test_tmproot)
@property
def request(self):
return self._pytester._request
@property
def plugins(self):
return self._pytester.plugins
@plugins.setter
def plugins(self, plugins):
self._pytester.plugins = plugins
@property
def monkeypatch(self) -> pytest.MonkeyPatch:
return self._pytester._monkeypatch
def make_hook_recorder(self, pluginmanager) -> pytest.HookRecorder:
"""See :meth:`Pytester.make_hook_recorder`."""
return self._pytester.make_hook_recorder(pluginmanager)
def chdir(self) -> None:
"""See :meth:`Pytester.chdir`."""
return self._pytester.chdir()
def finalize(self) -> None:
"""See :meth:`Pytester._finalize`."""
return self._pytester._finalize()
def makefile(self, ext, *args, **kwargs) -> LEGACY_PATH:
"""See :meth:`Pytester.makefile`."""
if ext and not ext.startswith("."):
# pytester.makefile is going to throw a ValueError in a way that
# testdir.makefile did not, because
# pathlib.Path is stricter suffixes than py.path
# This ext arguments is likely user error, but since testdir has
# allowed this, we will prepend "." as a workaround to avoid breaking
# testdir usage that worked before
ext = "." + ext
return legacy_path(self._pytester.makefile(ext, *args, **kwargs))
def makeconftest(self, source) -> LEGACY_PATH:
"""See :meth:`Pytester.makeconftest`."""
return legacy_path(self._pytester.makeconftest(source))
def makeini(self, source) -> LEGACY_PATH:
"""See :meth:`Pytester.makeini`."""
return legacy_path(self._pytester.makeini(source))
def getinicfg(self, source: str) -> SectionWrapper:
"""See :meth:`Pytester.getinicfg`."""
return self._pytester.getinicfg(source)
def makepyprojecttoml(self, source) -> LEGACY_PATH:
"""See :meth:`Pytester.makepyprojecttoml`."""
return legacy_path(self._pytester.makepyprojecttoml(source))
def makepyfile(self, *args, **kwargs) -> LEGACY_PATH:
"""See :meth:`Pytester.makepyfile`."""
return legacy_path(self._pytester.makepyfile(*args, **kwargs))
def maketxtfile(self, *args, **kwargs) -> LEGACY_PATH:
"""See :meth:`Pytester.maketxtfile`."""
return legacy_path(self._pytester.maketxtfile(*args, **kwargs))
def syspathinsert(self, path=None) -> None:
"""See :meth:`Pytester.syspathinsert`."""
return self._pytester.syspathinsert(path)
def mkdir(self, name) -> LEGACY_PATH:
"""See :meth:`Pytester.mkdir`."""
return legacy_path(self._pytester.mkdir(name))
def mkpydir(self, name) -> LEGACY_PATH:
"""See :meth:`Pytester.mkpydir`."""
return legacy_path(self._pytester.mkpydir(name))
def copy_example(self, name=None) -> LEGACY_PATH:
"""See :meth:`Pytester.copy_example`."""
return legacy_path(self._pytester.copy_example(name))
def getnode(
self, config: pytest.Config, arg
) -> Optional[Union[pytest.Item, pytest.Collector]]:
"""See :meth:`Pytester.getnode`."""
return self._pytester.getnode(config, arg)
def getpathnode(self, path):
"""See :meth:`Pytester.getpathnode`."""
return self._pytester.getpathnode(path)
def genitems(
self, colitems: List[Union[pytest.Item, pytest.Collector]]
) -> List[pytest.Item]:
"""See :meth:`Pytester.genitems`."""
return self._pytester.genitems(colitems)
def runitem(self, source):
"""See :meth:`Pytester.runitem`."""
return self._pytester.runitem(source)
def inline_runsource(self, source, *cmdlineargs):
"""See :meth:`Pytester.inline_runsource`."""
return self._pytester.inline_runsource(source, *cmdlineargs)
def inline_genitems(self, *args):
"""See :meth:`Pytester.inline_genitems`."""
return self._pytester.inline_genitems(*args)
def inline_run(self, *args, plugins=(), no_reraise_ctrlc: bool = False):
"""See :meth:`Pytester.inline_run`."""
return self._pytester.inline_run(
*args, plugins=plugins, no_reraise_ctrlc=no_reraise_ctrlc
)
def runpytest_inprocess(self, *args, **kwargs) -> pytest.RunResult:
"""See :meth:`Pytester.runpytest_inprocess`."""
return self._pytester.runpytest_inprocess(*args, **kwargs)
def runpytest(self, *args, **kwargs) -> pytest.RunResult:
"""See :meth:`Pytester.runpytest`."""
return self._pytester.runpytest(*args, **kwargs)
def parseconfig(self, *args) -> pytest.Config:
"""See :meth:`Pytester.parseconfig`."""
return self._pytester.parseconfig(*args)
def parseconfigure(self, *args) -> pytest.Config:
"""See :meth:`Pytester.parseconfigure`."""
return self._pytester.parseconfigure(*args)
def getitem(self, source, funcname="test_func"):
"""See :meth:`Pytester.getitem`."""
return self._pytester.getitem(source, funcname)
def getitems(self, source):
"""See :meth:`Pytester.getitems`."""
return self._pytester.getitems(source)
def getmodulecol(self, source, configargs=(), withinit=False):
"""See :meth:`Pytester.getmodulecol`."""
return self._pytester.getmodulecol(
source, configargs=configargs, withinit=withinit
)
def collect_by_name(
self, modcol: pytest.Collector, name: str
) -> Optional[Union[pytest.Item, pytest.Collector]]:
"""See :meth:`Pytester.collect_by_name`."""
return self._pytester.collect_by_name(modcol, name)
def popen(
self,
cmdargs,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
stdin=CLOSE_STDIN,
**kw,
):
"""See :meth:`Pytester.popen`."""
return self._pytester.popen(cmdargs, stdout, stderr, stdin, **kw)
def run(self, *cmdargs, timeout=None, stdin=CLOSE_STDIN) -> pytest.RunResult:
"""See :meth:`Pytester.run`."""
return self._pytester.run(*cmdargs, timeout=timeout, stdin=stdin)
def runpython(self, script) -> pytest.RunResult:
"""See :meth:`Pytester.runpython`."""
return self._pytester.runpython(script)
def runpython_c(self, command):
"""See :meth:`Pytester.runpython_c`."""
return self._pytester.runpython_c(command)
def runpytest_subprocess(self, *args, timeout=None) -> pytest.RunResult:
"""See :meth:`Pytester.runpytest_subprocess`."""
return self._pytester.runpytest_subprocess(*args, timeout=timeout)
def spawn_pytest(
self, string: str, expect_timeout: float = 10.0
) -> "pexpect.spawn":
"""See :meth:`Pytester.spawn_pytest`."""
return self._pytester.spawn_pytest(string, expect_timeout=expect_timeout)
def spawn(self, cmd: str, expect_timeout: float = 10.0) -> "pexpect.spawn":
"""See :meth:`Pytester.spawn`."""
return self._pytester.spawn(cmd, expect_timeout=expect_timeout)
def __repr__(self) -> str:
return f"<Testdir {self.tmpdir!r}>"
def __str__(self) -> str:
return str(self.tmpdir)
pytest.Testdir = Testdir # type: ignore[attr-defined]
@pytest.fixture
def testdir(pytester: pytest.Pytester) -> Testdir:
"""
Identical to :fixture:`pytester`, and provides an instance whose methods return
legacy ``LEGACY_PATH`` objects instead when applicable.
New code should avoid using :fixture:`testdir` in favor of :fixture:`pytester`.
"""
return Testdir(pytester, _ispytest=True)

View File

@ -40,8 +40,6 @@ from _pytest import timing
from _pytest._code import Source from _pytest._code import Source
from _pytest.capture import _get_multicapture from _pytest.capture import _get_multicapture
from _pytest.compat import final from _pytest.compat import final
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 NotSetType from _pytest.compat import NotSetType
from _pytest.config import _PluggyPlugin from _pytest.config import _PluggyPlugin
@ -493,17 +491,6 @@ def pytester(request: FixtureRequest, tmp_path_factory: TempPathFactory) -> "Pyt
return Pytester(request, tmp_path_factory, _ispytest=True) return Pytester(request, tmp_path_factory, _ispytest=True)
@fixture
def testdir(pytester: "Pytester") -> "Testdir":
"""
Identical to :fixture:`pytester`, and provides an instance whose methods return
legacy ``LEGACY_PATH`` objects instead when applicable.
New code should avoid using :fixture:`testdir` in favor of :fixture:`pytester`.
"""
return Testdir(pytester, _ispytest=True)
@fixture @fixture
def _sys_snapshot() -> Generator[None, None, None]: def _sys_snapshot() -> Generator[None, None, None]:
snappaths = SysPathsSnapshot() snappaths = SysPathsSnapshot()
@ -1531,224 +1518,6 @@ class LineComp:
LineMatcher(lines1).fnmatch_lines(lines2) LineMatcher(lines1).fnmatch_lines(lines2)
@final
class Testdir:
"""
Similar to :class:`Pytester`, but this class works with legacy legacy_path objects instead.
All methods just forward to an internal :class:`Pytester` instance, converting results
to `legacy_path` objects as necessary.
"""
__test__ = False
CLOSE_STDIN: "Final" = Pytester.CLOSE_STDIN
TimeoutExpired: "Final" = Pytester.TimeoutExpired
def __init__(self, pytester: Pytester, *, _ispytest: bool = False) -> None:
check_ispytest(_ispytest)
self._pytester = pytester
@property
def tmpdir(self) -> LEGACY_PATH:
"""Temporary directory where tests are executed."""
return legacy_path(self._pytester.path)
@property
def test_tmproot(self) -> LEGACY_PATH:
return legacy_path(self._pytester._test_tmproot)
@property
def request(self):
return self._pytester._request
@property
def plugins(self):
return self._pytester.plugins
@plugins.setter
def plugins(self, plugins):
self._pytester.plugins = plugins
@property
def monkeypatch(self) -> MonkeyPatch:
return self._pytester._monkeypatch
def make_hook_recorder(self, pluginmanager) -> HookRecorder:
"""See :meth:`Pytester.make_hook_recorder`."""
return self._pytester.make_hook_recorder(pluginmanager)
def chdir(self) -> None:
"""See :meth:`Pytester.chdir`."""
return self._pytester.chdir()
def finalize(self) -> None:
"""See :meth:`Pytester._finalize`."""
return self._pytester._finalize()
def makefile(self, ext, *args, **kwargs) -> LEGACY_PATH:
"""See :meth:`Pytester.makefile`."""
if ext and not ext.startswith("."):
# pytester.makefile is going to throw a ValueError in a way that
# testdir.makefile did not, because
# pathlib.Path is stricter suffixes than py.path
# This ext arguments is likely user error, but since testdir has
# allowed this, we will prepend "." as a workaround to avoid breaking
# testdir usage that worked before
ext = "." + ext
return legacy_path(self._pytester.makefile(ext, *args, **kwargs))
def makeconftest(self, source) -> LEGACY_PATH:
"""See :meth:`Pytester.makeconftest`."""
return legacy_path(self._pytester.makeconftest(source))
def makeini(self, source) -> LEGACY_PATH:
"""See :meth:`Pytester.makeini`."""
return legacy_path(self._pytester.makeini(source))
def getinicfg(self, source: str) -> SectionWrapper:
"""See :meth:`Pytester.getinicfg`."""
return self._pytester.getinicfg(source)
def makepyprojecttoml(self, source) -> LEGACY_PATH:
"""See :meth:`Pytester.makepyprojecttoml`."""
return legacy_path(self._pytester.makepyprojecttoml(source))
def makepyfile(self, *args, **kwargs) -> LEGACY_PATH:
"""See :meth:`Pytester.makepyfile`."""
return legacy_path(self._pytester.makepyfile(*args, **kwargs))
def maketxtfile(self, *args, **kwargs) -> LEGACY_PATH:
"""See :meth:`Pytester.maketxtfile`."""
return legacy_path(self._pytester.maketxtfile(*args, **kwargs))
def syspathinsert(self, path=None) -> None:
"""See :meth:`Pytester.syspathinsert`."""
return self._pytester.syspathinsert(path)
def mkdir(self, name) -> LEGACY_PATH:
"""See :meth:`Pytester.mkdir`."""
return legacy_path(self._pytester.mkdir(name))
def mkpydir(self, name) -> LEGACY_PATH:
"""See :meth:`Pytester.mkpydir`."""
return legacy_path(self._pytester.mkpydir(name))
def copy_example(self, name=None) -> LEGACY_PATH:
"""See :meth:`Pytester.copy_example`."""
return legacy_path(self._pytester.copy_example(name))
def getnode(self, config: Config, arg) -> Optional[Union[Item, Collector]]:
"""See :meth:`Pytester.getnode`."""
return self._pytester.getnode(config, arg)
def getpathnode(self, path):
"""See :meth:`Pytester.getpathnode`."""
return self._pytester.getpathnode(path)
def genitems(self, colitems: List[Union[Item, Collector]]) -> List[Item]:
"""See :meth:`Pytester.genitems`."""
return self._pytester.genitems(colitems)
def runitem(self, source):
"""See :meth:`Pytester.runitem`."""
return self._pytester.runitem(source)
def inline_runsource(self, source, *cmdlineargs):
"""See :meth:`Pytester.inline_runsource`."""
return self._pytester.inline_runsource(source, *cmdlineargs)
def inline_genitems(self, *args):
"""See :meth:`Pytester.inline_genitems`."""
return self._pytester.inline_genitems(*args)
def inline_run(self, *args, plugins=(), no_reraise_ctrlc: bool = False):
"""See :meth:`Pytester.inline_run`."""
return self._pytester.inline_run(
*args, plugins=plugins, no_reraise_ctrlc=no_reraise_ctrlc
)
def runpytest_inprocess(self, *args, **kwargs) -> RunResult:
"""See :meth:`Pytester.runpytest_inprocess`."""
return self._pytester.runpytest_inprocess(*args, **kwargs)
def runpytest(self, *args, **kwargs) -> RunResult:
"""See :meth:`Pytester.runpytest`."""
return self._pytester.runpytest(*args, **kwargs)
def parseconfig(self, *args) -> Config:
"""See :meth:`Pytester.parseconfig`."""
return self._pytester.parseconfig(*args)
def parseconfigure(self, *args) -> Config:
"""See :meth:`Pytester.parseconfigure`."""
return self._pytester.parseconfigure(*args)
def getitem(self, source, funcname="test_func"):
"""See :meth:`Pytester.getitem`."""
return self._pytester.getitem(source, funcname)
def getitems(self, source):
"""See :meth:`Pytester.getitems`."""
return self._pytester.getitems(source)
def getmodulecol(self, source, configargs=(), withinit=False):
"""See :meth:`Pytester.getmodulecol`."""
return self._pytester.getmodulecol(
source, configargs=configargs, withinit=withinit
)
def collect_by_name(
self, modcol: Collector, name: str
) -> Optional[Union[Item, Collector]]:
"""See :meth:`Pytester.collect_by_name`."""
return self._pytester.collect_by_name(modcol, name)
def popen(
self,
cmdargs,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
stdin=CLOSE_STDIN,
**kw,
):
"""See :meth:`Pytester.popen`."""
return self._pytester.popen(cmdargs, stdout, stderr, stdin, **kw)
def run(self, *cmdargs, timeout=None, stdin=CLOSE_STDIN) -> RunResult:
"""See :meth:`Pytester.run`."""
return self._pytester.run(*cmdargs, timeout=timeout, stdin=stdin)
def runpython(self, script) -> RunResult:
"""See :meth:`Pytester.runpython`."""
return self._pytester.runpython(script)
def runpython_c(self, command):
"""See :meth:`Pytester.runpython_c`."""
return self._pytester.runpython_c(command)
def runpytest_subprocess(self, *args, timeout=None) -> RunResult:
"""See :meth:`Pytester.runpytest_subprocess`."""
return self._pytester.runpytest_subprocess(*args, timeout=timeout)
def spawn_pytest(
self, string: str, expect_timeout: float = 10.0
) -> "pexpect.spawn":
"""See :meth:`Pytester.spawn_pytest`."""
return self._pytester.spawn_pytest(string, expect_timeout=expect_timeout)
def spawn(self, cmd: str, expect_timeout: float = 10.0) -> "pexpect.spawn":
"""See :meth:`Pytester.spawn`."""
return self._pytester.spawn(cmd, expect_timeout=expect_timeout)
def __repr__(self) -> str:
return f"<Testdir {self.tmpdir!r}>"
def __str__(self) -> str:
return str(self.tmpdir)
@final
class LineMatcher: class LineMatcher:
"""Flexible matching of text. """Flexible matching of text.

View File

@ -46,7 +46,6 @@ from _pytest.pytester import LineMatcher
from _pytest.pytester import Pytester from _pytest.pytester import Pytester
from _pytest.pytester import RecordedHookCall from _pytest.pytester import RecordedHookCall
from _pytest.pytester import RunResult from _pytest.pytester import RunResult
from _pytest.pytester import Testdir
from _pytest.python import Class from _pytest.python import Class
from _pytest.python import Function from _pytest.python import Function
from _pytest.python import Instance from _pytest.python import Instance
@ -145,7 +144,6 @@ __all__ = [
"StashKey", "StashKey",
"version_tuple", "version_tuple",
"TempPathFactory", "TempPathFactory",
"Testdir",
"TempdirFactory", "TempdirFactory",
"UsageError", "UsageError",
"WarningsRecorder", "WarningsRecorder",

View File

@ -614,7 +614,6 @@ class TestSession:
items2, hookrec = pytester.inline_genitems(item.nodeid) items2, hookrec = pytester.inline_genitems(item.nodeid)
(item2,) = items2 (item2,) = items2
assert item2.name == item.name assert item2.name == item.name
assert item2.fspath == item.fspath
assert item2.path == item.path assert item2.path == item.path
def test_find_byid_without_instance_parents(self, pytester: Pytester) -> None: def test_find_byid_without_instance_parents(self, pytester: Pytester) -> None:

View File

@ -0,0 +1,27 @@
import pytest
from _pytest.legacypath import Testdir
def test_testdir_testtmproot(testdir: Testdir) -> None:
"""Check test_tmproot is a py.path attribute for backward compatibility."""
assert testdir.test_tmproot.check(dir=1)
def test_testdir_makefile_dot_prefixes_extension_silently(
testdir: Testdir,
) -> None:
"""For backwards compat #8192"""
p1 = testdir.makefile("foo.bar", "")
assert ".foo.bar" in str(p1)
def test_testdir_makefile_ext_none_raises_type_error(testdir: Testdir) -> None:
"""For backwards compat #8192"""
with pytest.raises(TypeError):
testdir.makefile(None, "")
def test_testdir_makefile_ext_empty_string_makes_file(testdir: Testdir) -> None:
"""For backwards compat #8192"""
p1 = testdir.makefile("", "")
assert "test_testdir_makefile" in str(p1)

View File

@ -17,7 +17,6 @@ from _pytest.pytester import LineMatcher
from _pytest.pytester import Pytester from _pytest.pytester import Pytester
from _pytest.pytester import SysModulesSnapshot from _pytest.pytester import SysModulesSnapshot
from _pytest.pytester import SysPathsSnapshot from _pytest.pytester import SysPathsSnapshot
from _pytest.pytester import Testdir
def test_make_hook_recorder(pytester: Pytester) -> None: def test_make_hook_recorder(pytester: Pytester) -> None:
@ -814,19 +813,6 @@ def test_makefile_joins_absolute_path(pytester: Pytester) -> None:
assert str(p1) == str(pytester.path / "absfile.py") assert str(p1) == str(pytester.path / "absfile.py")
def test_testtmproot(testdir) -> None:
"""Check test_tmproot is a py.path attribute for backward compatibility."""
assert testdir.test_tmproot.check(dir=1)
def test_testdir_makefile_dot_prefixes_extension_silently(
testdir: Testdir,
) -> None:
"""For backwards compat #8192"""
p1 = testdir.makefile("foo.bar", "")
assert ".foo.bar" in str(p1)
def test_pytester_makefile_dot_prefixes_extension_with_warning( def test_pytester_makefile_dot_prefixes_extension_with_warning(
pytester: Pytester, pytester: Pytester,
) -> None: ) -> None:
@ -837,18 +823,6 @@ def test_pytester_makefile_dot_prefixes_extension_with_warning(
pytester.makefile("foo.bar", "") pytester.makefile("foo.bar", "")
def test_testdir_makefile_ext_none_raises_type_error(testdir) -> None:
"""For backwards compat #8192"""
with pytest.raises(TypeError):
testdir.makefile(None, "")
def test_testdir_makefile_ext_empty_string_makes_file(testdir) -> None:
"""For backwards compat #8192"""
p1 = testdir.makefile("", "")
assert "test_testdir_makefile" in str(p1)
@pytest.mark.filterwarnings("default") @pytest.mark.filterwarnings("default")
def test_pytester_assert_outcomes_warnings(pytester: Pytester) -> None: def test_pytester_assert_outcomes_warnings(pytester: Pytester) -> None:
pytester.makepyfile( pytester.makepyfile(