| |
| """Add backward compatibility support for the legacy py path type.""" |
|
|
| from __future__ import annotations |
|
|
| import dataclasses |
| from pathlib import Path |
| import shlex |
| import subprocess |
| from typing import Final |
| from typing import final |
| from typing import TYPE_CHECKING |
|
|
| from iniconfig import SectionWrapper |
|
|
| from _pytest.cacheprovider import Cache |
| from _pytest.compat import LEGACY_PATH |
| from _pytest.compat import legacy_path |
| from _pytest.config import Config |
| from _pytest.config import hookimpl |
| from _pytest.config import PytestPluginManager |
| from _pytest.deprecated import check_ispytest |
| from _pytest.fixtures import fixture |
| from _pytest.fixtures import FixtureRequest |
| from _pytest.main import Session |
| from _pytest.monkeypatch import MonkeyPatch |
| from _pytest.nodes import Collector |
| from _pytest.nodes import Item |
| from _pytest.nodes import Node |
| from _pytest.pytester import HookRecorder |
| from _pytest.pytester import Pytester |
| from _pytest.pytester import RunResult |
| from _pytest.terminal import TerminalReporter |
| from _pytest.tmpdir import TempPathFactory |
|
|
|
|
| if TYPE_CHECKING: |
| 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 = 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: |
| return self._pytester._finalize() |
|
|
| def makefile(self, ext, *args, **kwargs) -> LEGACY_PATH: |
| """See :meth:`Pytester.makefile`.""" |
| if ext and not ext.startswith("."): |
| |
| |
| |
| |
| |
| |
| 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) -> Item | Collector | None: |
| """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[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) -> Item | Collector | None: |
| """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) |
|
|
|
|
| class LegacyTestdirPlugin: |
| @staticmethod |
| @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) |
|
|
|
|
| @final |
| @dataclasses.dataclass |
| class TempdirFactory: |
| """Backward compatibility wrapper that implements ``py.path.local`` |
| for :class:`TempPathFactory`. |
| |
| .. note:: |
| These days, it is preferred to use ``tmp_path_factory``. |
| |
| :ref:`About the tmpdir and tmpdir_factory fixtures<tmpdir and tmpdir_factory>`. |
| |
| """ |
|
|
| _tmppath_factory: TempPathFactory |
|
|
| def __init__( |
| self, tmppath_factory: TempPathFactory, *, _ispytest: bool = False |
| ) -> None: |
| check_ispytest(_ispytest) |
| self._tmppath_factory = tmppath_factory |
|
|
| def mktemp(self, basename: str, numbered: bool = True) -> LEGACY_PATH: |
| """Same as :meth:`TempPathFactory.mktemp`, but returns a ``py.path.local`` object.""" |
| return legacy_path(self._tmppath_factory.mktemp(basename, numbered).resolve()) |
|
|
| def getbasetemp(self) -> LEGACY_PATH: |
| """Same as :meth:`TempPathFactory.getbasetemp`, but returns a ``py.path.local`` object.""" |
| return legacy_path(self._tmppath_factory.getbasetemp().resolve()) |
|
|
|
|
| class LegacyTmpdirPlugin: |
| @staticmethod |
| @fixture(scope="session") |
| def tmpdir_factory(request: FixtureRequest) -> TempdirFactory: |
| """Return a :class:`pytest.TempdirFactory` instance for the test session.""" |
| |
| return request.config._tmpdirhandler |
|
|
| @staticmethod |
| @fixture |
| def tmpdir(tmp_path: Path) -> LEGACY_PATH: |
| """Return a temporary directory (as `legacy_path`_ object) |
| which is unique to each test function invocation. |
| The temporary directory is created as a subdirectory |
| of the base temporary directory, with configurable retention, |
| as discussed in :ref:`temporary directory location and retention`. |
| |
| .. note:: |
| These days, it is preferred to use ``tmp_path``. |
| |
| :ref:`About the tmpdir and tmpdir_factory fixtures<tmpdir and tmpdir_factory>`. |
| |
| .. _legacy_path: https://py.readthedocs.io/en/latest/path.html |
| """ |
| return legacy_path(tmp_path) |
|
|
|
|
| def Cache_makedir(self: Cache, name: str) -> LEGACY_PATH: |
| """Return a directory path object with the given name. |
| |
| Same as :func:`mkdir`, but returns a legacy py path instance. |
| """ |
| return legacy_path(self.mkdir(name)) |
|
|
|
|
| def FixtureRequest_fspath(self: FixtureRequest) -> LEGACY_PATH: |
| """(deprecated) The file system path of the test module which collected this test.""" |
| return legacy_path(self.path) |
|
|
|
|
| def TerminalReporter_startdir(self: TerminalReporter) -> LEGACY_PATH: |
| """The directory from which pytest was invoked. |
| |
| Prefer to use ``startpath`` which is a :class:`pathlib.Path`. |
| |
| :type: LEGACY_PATH |
| """ |
| return legacy_path(self.startpath) |
|
|
|
|
| def Config_invocation_dir(self: Config) -> LEGACY_PATH: |
| """The directory from which pytest was invoked. |
| |
| Prefer to use :attr:`invocation_params.dir <InvocationParams.dir>`, |
| which is a :class:`pathlib.Path`. |
| |
| :type: LEGACY_PATH |
| """ |
| return legacy_path(str(self.invocation_params.dir)) |
|
|
|
|
| def Config_rootdir(self: Config) -> LEGACY_PATH: |
| """The path to the :ref:`rootdir <rootdir>`. |
| |
| Prefer to use :attr:`rootpath`, which is a :class:`pathlib.Path`. |
| |
| :type: LEGACY_PATH |
| """ |
| return legacy_path(str(self.rootpath)) |
|
|
|
|
| def Config_inifile(self: Config) -> LEGACY_PATH | None: |
| """The path to the :ref:`configfile <configfiles>`. |
| |
| Prefer to use :attr:`inipath`, which is a :class:`pathlib.Path`. |
| |
| :type: Optional[LEGACY_PATH] |
| """ |
| return legacy_path(str(self.inipath)) if self.inipath else None |
|
|
|
|
| def Session_startdir(self: Session) -> LEGACY_PATH: |
| """The path from which pytest was invoked. |
| |
| Prefer to use ``startpath`` which is a :class:`pathlib.Path`. |
| |
| :type: LEGACY_PATH |
| """ |
| return legacy_path(self.startpath) |
|
|
|
|
| def Config__getini_unknown_type(self, name: str, type: str, value: str | list[str]): |
| if type == "pathlist": |
| |
| assert self.inipath is not None |
| dp = self.inipath.parent |
| input_values = shlex.split(value) if isinstance(value, str) else value |
| return [legacy_path(str(dp / x)) for x in input_values] |
| else: |
| raise ValueError(f"unknown configuration type: {type}", value) |
|
|
|
|
| def Node_fspath(self: Node) -> LEGACY_PATH: |
| """(deprecated) returns a legacy_path copy of self.path""" |
| return legacy_path(self.path) |
|
|
|
|
| def Node_fspath_set(self: Node, value: LEGACY_PATH) -> None: |
| self.path = Path(value) |
|
|
|
|
| @hookimpl(tryfirst=True) |
| def pytest_load_initial_conftests(early_config: Config) -> None: |
| """Monkeypatch legacy path attributes in several classes, as early as possible.""" |
| mp = MonkeyPatch() |
| early_config.add_cleanup(mp.undo) |
|
|
| |
| mp.setattr(Cache, "makedir", Cache_makedir, raising=False) |
|
|
| |
| mp.setattr(FixtureRequest, "fspath", property(FixtureRequest_fspath), raising=False) |
|
|
| |
| mp.setattr( |
| TerminalReporter, "startdir", property(TerminalReporter_startdir), raising=False |
| ) |
|
|
| |
| mp.setattr(Config, "invocation_dir", property(Config_invocation_dir), raising=False) |
| mp.setattr(Config, "rootdir", property(Config_rootdir), raising=False) |
| mp.setattr(Config, "inifile", property(Config_inifile), raising=False) |
|
|
| |
| mp.setattr(Session, "startdir", property(Session_startdir), raising=False) |
|
|
| |
| mp.setattr(Config, "_getini_unknown_type", Config__getini_unknown_type) |
|
|
| |
| mp.setattr(Node, "fspath", property(Node_fspath, Node_fspath_set), raising=False) |
|
|
|
|
| @hookimpl |
| def pytest_configure(config: Config) -> None: |
| """Installs the LegacyTmpdirPlugin if the ``tmpdir`` plugin is also installed.""" |
| if config.pluginmanager.has_plugin("tmpdir"): |
| mp = MonkeyPatch() |
| config.add_cleanup(mp.undo) |
| |
| |
| |
| |
| |
| try: |
| tmp_path_factory = config._tmp_path_factory |
| except AttributeError: |
| |
| pass |
| else: |
| _tmpdirhandler = TempdirFactory(tmp_path_factory, _ispytest=True) |
| mp.setattr(config, "_tmpdirhandler", _tmpdirhandler, raising=False) |
|
|
| config.pluginmanager.register(LegacyTmpdirPlugin, "legacypath-tmpdir") |
|
|
|
|
| @hookimpl |
| def pytest_plugin_registered(plugin: object, manager: PytestPluginManager) -> None: |
| |
| |
| is_pytester = plugin is manager.get_plugin("pytester") |
| if is_pytester and not manager.is_registered(LegacyTestdirPlugin): |
| manager.register(LegacyTestdirPlugin, "legacypath-pytester") |
|
|