Coverage for /opt/homebrew/lib/python3.11/site-packages/_pytest/legacypath.py: 69%
216 statements
« prev ^ index » next coverage.py v7.2.3, created at 2023-05-04 13:14 +0700
« prev ^ index » next coverage.py v7.2.3, created at 2023-05-04 13:14 +0700
1"""Add backward compatibility support for the legacy py path type."""
2import shlex
3import subprocess
4from pathlib import Path
5from typing import List
6from typing import Optional
7from typing import TYPE_CHECKING
8from typing import Union
10import attr
11from iniconfig import SectionWrapper
13from _pytest.cacheprovider import Cache
14from _pytest.compat import final
15from _pytest.compat import LEGACY_PATH
16from _pytest.compat import legacy_path
17from _pytest.config import Config
18from _pytest.config import hookimpl
19from _pytest.config import PytestPluginManager
20from _pytest.deprecated import check_ispytest
21from _pytest.fixtures import fixture
22from _pytest.fixtures import FixtureRequest
23from _pytest.main import Session
24from _pytest.monkeypatch import MonkeyPatch
25from _pytest.nodes import Collector
26from _pytest.nodes import Item
27from _pytest.nodes import Node
28from _pytest.pytester import HookRecorder
29from _pytest.pytester import Pytester
30from _pytest.pytester import RunResult
31from _pytest.terminal import TerminalReporter
32from _pytest.tmpdir import TempPathFactory
34if TYPE_CHECKING:
35 from typing_extensions import Final
37 import pexpect
40@final
41class Testdir:
42 """
43 Similar to :class:`Pytester`, but this class works with legacy legacy_path objects instead.
45 All methods just forward to an internal :class:`Pytester` instance, converting results
46 to `legacy_path` objects as necessary.
47 """
49 __test__ = False
51 CLOSE_STDIN: "Final" = Pytester.CLOSE_STDIN
52 TimeoutExpired: "Final" = Pytester.TimeoutExpired
54 def __init__(self, pytester: Pytester, *, _ispytest: bool = False) -> None:
55 check_ispytest(_ispytest)
56 self._pytester = pytester
58 @property
59 def tmpdir(self) -> LEGACY_PATH:
60 """Temporary directory where tests are executed."""
61 return legacy_path(self._pytester.path)
63 @property
64 def test_tmproot(self) -> LEGACY_PATH:
65 return legacy_path(self._pytester._test_tmproot)
67 @property
68 def request(self):
69 return self._pytester._request
71 @property
72 def plugins(self):
73 return self._pytester.plugins
75 @plugins.setter
76 def plugins(self, plugins):
77 self._pytester.plugins = plugins
79 @property
80 def monkeypatch(self) -> MonkeyPatch:
81 return self._pytester._monkeypatch
83 def make_hook_recorder(self, pluginmanager) -> HookRecorder:
84 """See :meth:`Pytester.make_hook_recorder`."""
85 return self._pytester.make_hook_recorder(pluginmanager)
87 def chdir(self) -> None:
88 """See :meth:`Pytester.chdir`."""
89 return self._pytester.chdir()
91 def finalize(self) -> None:
92 """See :meth:`Pytester._finalize`."""
93 return self._pytester._finalize()
95 def makefile(self, ext, *args, **kwargs) -> LEGACY_PATH:
96 """See :meth:`Pytester.makefile`."""
97 if ext and not ext.startswith("."):
98 # pytester.makefile is going to throw a ValueError in a way that
99 # testdir.makefile did not, because
100 # pathlib.Path is stricter suffixes than py.path
101 # This ext arguments is likely user error, but since testdir has
102 # allowed this, we will prepend "." as a workaround to avoid breaking
103 # testdir usage that worked before
104 ext = "." + ext
105 return legacy_path(self._pytester.makefile(ext, *args, **kwargs))
107 def makeconftest(self, source) -> LEGACY_PATH:
108 """See :meth:`Pytester.makeconftest`."""
109 return legacy_path(self._pytester.makeconftest(source))
111 def makeini(self, source) -> LEGACY_PATH:
112 """See :meth:`Pytester.makeini`."""
113 return legacy_path(self._pytester.makeini(source))
115 def getinicfg(self, source: str) -> SectionWrapper:
116 """See :meth:`Pytester.getinicfg`."""
117 return self._pytester.getinicfg(source)
119 def makepyprojecttoml(self, source) -> LEGACY_PATH:
120 """See :meth:`Pytester.makepyprojecttoml`."""
121 return legacy_path(self._pytester.makepyprojecttoml(source))
123 def makepyfile(self, *args, **kwargs) -> LEGACY_PATH:
124 """See :meth:`Pytester.makepyfile`."""
125 return legacy_path(self._pytester.makepyfile(*args, **kwargs))
127 def maketxtfile(self, *args, **kwargs) -> LEGACY_PATH:
128 """See :meth:`Pytester.maketxtfile`."""
129 return legacy_path(self._pytester.maketxtfile(*args, **kwargs))
131 def syspathinsert(self, path=None) -> None:
132 """See :meth:`Pytester.syspathinsert`."""
133 return self._pytester.syspathinsert(path)
135 def mkdir(self, name) -> LEGACY_PATH:
136 """See :meth:`Pytester.mkdir`."""
137 return legacy_path(self._pytester.mkdir(name))
139 def mkpydir(self, name) -> LEGACY_PATH:
140 """See :meth:`Pytester.mkpydir`."""
141 return legacy_path(self._pytester.mkpydir(name))
143 def copy_example(self, name=None) -> LEGACY_PATH:
144 """See :meth:`Pytester.copy_example`."""
145 return legacy_path(self._pytester.copy_example(name))
147 def getnode(self, config: Config, arg) -> Optional[Union[Item, Collector]]:
148 """See :meth:`Pytester.getnode`."""
149 return self._pytester.getnode(config, arg)
151 def getpathnode(self, path):
152 """See :meth:`Pytester.getpathnode`."""
153 return self._pytester.getpathnode(path)
155 def genitems(self, colitems: List[Union[Item, Collector]]) -> List[Item]:
156 """See :meth:`Pytester.genitems`."""
157 return self._pytester.genitems(colitems)
159 def runitem(self, source):
160 """See :meth:`Pytester.runitem`."""
161 return self._pytester.runitem(source)
163 def inline_runsource(self, source, *cmdlineargs):
164 """See :meth:`Pytester.inline_runsource`."""
165 return self._pytester.inline_runsource(source, *cmdlineargs)
167 def inline_genitems(self, *args):
168 """See :meth:`Pytester.inline_genitems`."""
169 return self._pytester.inline_genitems(*args)
171 def inline_run(self, *args, plugins=(), no_reraise_ctrlc: bool = False):
172 """See :meth:`Pytester.inline_run`."""
173 return self._pytester.inline_run(
174 *args, plugins=plugins, no_reraise_ctrlc=no_reraise_ctrlc
175 )
177 def runpytest_inprocess(self, *args, **kwargs) -> RunResult:
178 """See :meth:`Pytester.runpytest_inprocess`."""
179 return self._pytester.runpytest_inprocess(*args, **kwargs)
181 def runpytest(self, *args, **kwargs) -> RunResult:
182 """See :meth:`Pytester.runpytest`."""
183 return self._pytester.runpytest(*args, **kwargs)
185 def parseconfig(self, *args) -> Config:
186 """See :meth:`Pytester.parseconfig`."""
187 return self._pytester.parseconfig(*args)
189 def parseconfigure(self, *args) -> Config:
190 """See :meth:`Pytester.parseconfigure`."""
191 return self._pytester.parseconfigure(*args)
193 def getitem(self, source, funcname="test_func"):
194 """See :meth:`Pytester.getitem`."""
195 return self._pytester.getitem(source, funcname)
197 def getitems(self, source):
198 """See :meth:`Pytester.getitems`."""
199 return self._pytester.getitems(source)
201 def getmodulecol(self, source, configargs=(), withinit=False):
202 """See :meth:`Pytester.getmodulecol`."""
203 return self._pytester.getmodulecol(
204 source, configargs=configargs, withinit=withinit
205 )
207 def collect_by_name(
208 self, modcol: Collector, name: str
209 ) -> Optional[Union[Item, Collector]]:
210 """See :meth:`Pytester.collect_by_name`."""
211 return self._pytester.collect_by_name(modcol, name)
213 def popen(
214 self,
215 cmdargs,
216 stdout=subprocess.PIPE,
217 stderr=subprocess.PIPE,
218 stdin=CLOSE_STDIN,
219 **kw,
220 ):
221 """See :meth:`Pytester.popen`."""
222 return self._pytester.popen(cmdargs, stdout, stderr, stdin, **kw)
224 def run(self, *cmdargs, timeout=None, stdin=CLOSE_STDIN) -> RunResult:
225 """See :meth:`Pytester.run`."""
226 return self._pytester.run(*cmdargs, timeout=timeout, stdin=stdin)
228 def runpython(self, script) -> RunResult:
229 """See :meth:`Pytester.runpython`."""
230 return self._pytester.runpython(script)
232 def runpython_c(self, command):
233 """See :meth:`Pytester.runpython_c`."""
234 return self._pytester.runpython_c(command)
236 def runpytest_subprocess(self, *args, timeout=None) -> RunResult:
237 """See :meth:`Pytester.runpytest_subprocess`."""
238 return self._pytester.runpytest_subprocess(*args, timeout=timeout)
240 def spawn_pytest(
241 self, string: str, expect_timeout: float = 10.0
242 ) -> "pexpect.spawn":
243 """See :meth:`Pytester.spawn_pytest`."""
244 return self._pytester.spawn_pytest(string, expect_timeout=expect_timeout)
246 def spawn(self, cmd: str, expect_timeout: float = 10.0) -> "pexpect.spawn":
247 """See :meth:`Pytester.spawn`."""
248 return self._pytester.spawn(cmd, expect_timeout=expect_timeout)
250 def __repr__(self) -> str:
251 return f"<Testdir {self.tmpdir!r}>"
253 def __str__(self) -> str:
254 return str(self.tmpdir)
257class LegacyTestdirPlugin:
258 @staticmethod
259 @fixture
260 def testdir(pytester: Pytester) -> Testdir:
261 """
262 Identical to :fixture:`pytester`, and provides an instance whose methods return
263 legacy ``LEGACY_PATH`` objects instead when applicable.
265 New code should avoid using :fixture:`testdir` in favor of :fixture:`pytester`.
266 """
267 return Testdir(pytester, _ispytest=True)
270@final
271@attr.s(init=False, auto_attribs=True)
272class TempdirFactory:
273 """Backward compatibility wrapper that implements :class:`py.path.local`
274 for :class:`TempPathFactory`.
276 .. note::
277 These days, it is preferred to use ``tmp_path_factory``.
279 :ref:`About the tmpdir and tmpdir_factory fixtures<tmpdir and tmpdir_factory>`.
281 """
283 _tmppath_factory: TempPathFactory
285 def __init__(
286 self, tmppath_factory: TempPathFactory, *, _ispytest: bool = False
287 ) -> None:
288 check_ispytest(_ispytest)
289 self._tmppath_factory = tmppath_factory
291 def mktemp(self, basename: str, numbered: bool = True) -> LEGACY_PATH:
292 """Same as :meth:`TempPathFactory.mktemp`, but returns a :class:`py.path.local` object."""
293 return legacy_path(self._tmppath_factory.mktemp(basename, numbered).resolve())
295 def getbasetemp(self) -> LEGACY_PATH:
296 """Same as :meth:`TempPathFactory.getbasetemp`, but returns a :class:`py.path.local` object."""
297 return legacy_path(self._tmppath_factory.getbasetemp().resolve())
300class LegacyTmpdirPlugin:
301 @staticmethod
302 @fixture(scope="session")
303 def tmpdir_factory(request: FixtureRequest) -> TempdirFactory:
304 """Return a :class:`pytest.TempdirFactory` instance for the test session."""
305 # Set dynamically by pytest_configure().
306 return request.config._tmpdirhandler # type: ignore
308 @staticmethod
309 @fixture
310 def tmpdir(tmp_path: Path) -> LEGACY_PATH:
311 """Return a temporary directory path object which is unique to each test
312 function invocation, created as a sub directory of the base temporary
313 directory.
315 By default, a new base temporary directory is created each test session,
316 and old bases are removed after 3 sessions, to aid in debugging. If
317 ``--basetemp`` is used then it is cleared each session. See :ref:`base
318 temporary directory`.
320 The returned object is a `legacy_path`_ object.
322 .. note::
323 These days, it is preferred to use ``tmp_path``.
325 :ref:`About the tmpdir and tmpdir_factory fixtures<tmpdir and tmpdir_factory>`.
327 .. _legacy_path: https://py.readthedocs.io/en/latest/path.html
328 """
329 return legacy_path(tmp_path)
332def Cache_makedir(self: Cache, name: str) -> LEGACY_PATH:
333 """Return a directory path object with the given name.
335 Same as :func:`mkdir`, but returns a legacy py path instance.
336 """
337 return legacy_path(self.mkdir(name))
340def FixtureRequest_fspath(self: FixtureRequest) -> LEGACY_PATH:
341 """(deprecated) The file system path of the test module which collected this test."""
342 return legacy_path(self.path)
345def TerminalReporter_startdir(self: TerminalReporter) -> LEGACY_PATH:
346 """The directory from which pytest was invoked.
348 Prefer to use ``startpath`` which is a :class:`pathlib.Path`.
350 :type: LEGACY_PATH
351 """
352 return legacy_path(self.startpath)
355def Config_invocation_dir(self: Config) -> LEGACY_PATH:
356 """The directory from which pytest was invoked.
358 Prefer to use :attr:`invocation_params.dir <InvocationParams.dir>`,
359 which is a :class:`pathlib.Path`.
361 :type: LEGACY_PATH
362 """
363 return legacy_path(str(self.invocation_params.dir))
366def Config_rootdir(self: Config) -> LEGACY_PATH:
367 """The path to the :ref:`rootdir <rootdir>`.
369 Prefer to use :attr:`rootpath`, which is a :class:`pathlib.Path`.
371 :type: LEGACY_PATH
372 """
373 return legacy_path(str(self.rootpath))
376def Config_inifile(self: Config) -> Optional[LEGACY_PATH]:
377 """The path to the :ref:`configfile <configfiles>`.
379 Prefer to use :attr:`inipath`, which is a :class:`pathlib.Path`.
381 :type: Optional[LEGACY_PATH]
382 """
383 return legacy_path(str(self.inipath)) if self.inipath else None
386def Session_stardir(self: Session) -> LEGACY_PATH:
387 """The path from which pytest was invoked.
389 Prefer to use ``startpath`` which is a :class:`pathlib.Path`.
391 :type: LEGACY_PATH
392 """
393 return legacy_path(self.startpath)
396def Config__getini_unknown_type(
397 self, name: str, type: str, value: Union[str, List[str]]
398):
399 if type == "pathlist":
400 # TODO: This assert is probably not valid in all cases.
401 assert self.inipath is not None
402 dp = self.inipath.parent
403 input_values = shlex.split(value) if isinstance(value, str) else value
404 return [legacy_path(str(dp / x)) for x in input_values]
405 else:
406 raise ValueError(f"unknown configuration type: {type}", value)
409def Node_fspath(self: Node) -> LEGACY_PATH:
410 """(deprecated) returns a legacy_path copy of self.path"""
411 return legacy_path(self.path)
414def Node_fspath_set(self: Node, value: LEGACY_PATH) -> None:
415 self.path = Path(value)
418@hookimpl(tryfirst=True)
419def pytest_load_initial_conftests(early_config: Config) -> None:
420 """Monkeypatch legacy path attributes in several classes, as early as possible."""
421 mp = MonkeyPatch()
422 early_config.add_cleanup(mp.undo)
424 # Add Cache.makedir().
425 mp.setattr(Cache, "makedir", Cache_makedir, raising=False)
427 # Add FixtureRequest.fspath property.
428 mp.setattr(FixtureRequest, "fspath", property(FixtureRequest_fspath), raising=False)
430 # Add TerminalReporter.startdir property.
431 mp.setattr(
432 TerminalReporter, "startdir", property(TerminalReporter_startdir), raising=False
433 )
435 # Add Config.{invocation_dir,rootdir,inifile} properties.
436 mp.setattr(Config, "invocation_dir", property(Config_invocation_dir), raising=False)
437 mp.setattr(Config, "rootdir", property(Config_rootdir), raising=False)
438 mp.setattr(Config, "inifile", property(Config_inifile), raising=False)
440 # Add Session.startdir property.
441 mp.setattr(Session, "startdir", property(Session_stardir), raising=False)
443 # Add pathlist configuration type.
444 mp.setattr(Config, "_getini_unknown_type", Config__getini_unknown_type)
446 # Add Node.fspath property.
447 mp.setattr(Node, "fspath", property(Node_fspath, Node_fspath_set), raising=False)
450@hookimpl
451def pytest_configure(config: Config) -> None:
452 """Installs the LegacyTmpdirPlugin if the ``tmpdir`` plugin is also installed."""
453 if config.pluginmanager.has_plugin("tmpdir"):
454 mp = MonkeyPatch()
455 config.add_cleanup(mp.undo)
456 # Create TmpdirFactory and attach it to the config object.
457 #
458 # This is to comply with existing plugins which expect the handler to be
459 # available at pytest_configure time, but ideally should be moved entirely
460 # to the tmpdir_factory session fixture.
461 try:
462 tmp_path_factory = config._tmp_path_factory # type: ignore[attr-defined]
463 except AttributeError:
464 # tmpdir plugin is blocked.
465 pass
466 else:
467 _tmpdirhandler = TempdirFactory(tmp_path_factory, _ispytest=True)
468 mp.setattr(config, "_tmpdirhandler", _tmpdirhandler, raising=False)
470 config.pluginmanager.register(LegacyTmpdirPlugin, "legacypath-tmpdir")
473@hookimpl
474def pytest_plugin_registered(plugin: object, manager: PytestPluginManager) -> None:
475 # pytester is not loaded by default and is commonly loaded from a conftest,
476 # so checking for it in `pytest_configure` is not enough.
477 is_pytester = plugin is manager.get_plugin("pytester")
478 if is_pytester and not manager.is_registered(LegacyTestdirPlugin):
479 manager.register(LegacyTestdirPlugin, "legacypath-pytester")