123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479 |
- """Add backward compatibility support for the legacy py path type."""
- import dataclasses
- import shlex
- import subprocess
- from pathlib import Path
- from typing import List
- from typing import Optional
- from typing import TYPE_CHECKING
- from typing import Union
- from iniconfig import SectionWrapper
- from _pytest.cacheprovider import Cache
- from _pytest.compat import final
- 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:
- 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" = 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)
- 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 :class:`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 :class:`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 :class:`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."""
- # Set dynamically by pytest_configure().
- return request.config._tmpdirhandler # type: ignore
- @staticmethod
- @fixture
- def tmpdir(tmp_path: Path) -> LEGACY_PATH:
- """Return a temporary directory path object which is unique to each test
- function invocation, created as a sub directory of the base temporary
- directory.
- By default, a new base temporary directory is created each test session,
- and old bases are removed after 3 sessions, to aid in debugging. If
- ``--basetemp`` is used then it is cleared each session. See :ref:`base
- temporary directory`.
- The returned object is a `legacy_path`_ object.
- .. 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) -> Optional[LEGACY_PATH]:
- """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_stardir(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: Union[str, List[str]]
- ):
- if type == "pathlist":
- # TODO: This assert is probably not valid in all cases.
- 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)
- # Add Cache.makedir().
- mp.setattr(Cache, "makedir", Cache_makedir, raising=False)
- # Add FixtureRequest.fspath property.
- mp.setattr(FixtureRequest, "fspath", property(FixtureRequest_fspath), raising=False)
- # Add TerminalReporter.startdir property.
- mp.setattr(
- TerminalReporter, "startdir", property(TerminalReporter_startdir), raising=False
- )
- # Add Config.{invocation_dir,rootdir,inifile} properties.
- 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)
- # Add Session.startdir property.
- mp.setattr(Session, "startdir", property(Session_stardir), raising=False)
- # Add pathlist configuration type.
- mp.setattr(Config, "_getini_unknown_type", Config__getini_unknown_type)
- # Add Node.fspath property.
- 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)
- # Create TmpdirFactory and attach it to the config object.
- #
- # This is to comply with existing plugins which expect the handler to be
- # available at pytest_configure time, but ideally should be moved entirely
- # to the tmpdir_factory session fixture.
- try:
- tmp_path_factory = config._tmp_path_factory # type: ignore[attr-defined]
- except AttributeError:
- # tmpdir plugin is blocked.
- 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:
- # pytester is not loaded by default and is commonly loaded from a conftest,
- # so checking for it in `pytest_configure` is not enough.
- is_pytester = plugin is manager.get_plugin("pytester")
- if is_pytester and not manager.is_registered(LegacyTestdirPlugin):
- manager.register(LegacyTestdirPlugin, "legacypath-pytester")
|