Skip to content

Commit 1df28a4

Browse files
committed
Move testdir to legacypath plugin
1 parent c2ece58 commit 1df28a4

File tree

7 files changed

+284
-260
lines changed

7 files changed

+284
-260
lines changed

doc/en/conf.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -446,3 +446,7 @@ def patched_is_final(self, decorators: List[ast.expr]) -> bool:
446446
)
447447

448448
sphinx.pycode.parser.VariableCommentPicker.is_final = patched_is_final
449+
450+
# legacypath.py monkey-patches pytest.Testdir in. Import the file so
451+
# that autodoc can discover references to it.
452+
import _pytest.legacypath # noqa: F401

src/_pytest/legacypath.py

Lines changed: 253 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,254 @@
11
"""Add backward compatibility support for the legacy py path type."""
2+
import subprocess
3+
from typing import List
4+
from typing import Optional
5+
from typing import TYPE_CHECKING
6+
from typing import Union
7+
8+
from iniconfig import SectionWrapper
9+
10+
import pytest
11+
from _pytest.compat import final
12+
from _pytest.compat import LEGACY_PATH
13+
from _pytest.compat import legacy_path
14+
from _pytest.deprecated import check_ispytest
15+
16+
if TYPE_CHECKING:
17+
from typing_extensions import Final
18+
19+
import pexpect
20+
21+
22+
@final
23+
class Testdir:
24+
"""
25+
Similar to :class:`Pytester`, but this class works with legacy legacy_path objects instead.
26+
27+
All methods just forward to an internal :class:`Pytester` instance, converting results
28+
to `legacy_path` objects as necessary.
29+
"""
30+
31+
__test__ = False
32+
33+
CLOSE_STDIN: "Final" = pytest.Pytester.CLOSE_STDIN
34+
TimeoutExpired: "Final" = pytest.Pytester.TimeoutExpired
35+
36+
def __init__(self, pytester: pytest.Pytester, *, _ispytest: bool = False) -> None:
37+
check_ispytest(_ispytest)
38+
self._pytester = pytester
39+
40+
@property
41+
def tmpdir(self) -> LEGACY_PATH:
42+
"""Temporary directory where tests are executed."""
43+
return legacy_path(self._pytester.path)
44+
45+
@property
46+
def test_tmproot(self) -> LEGACY_PATH:
47+
return legacy_path(self._pytester._test_tmproot)
48+
49+
@property
50+
def request(self):
51+
return self._pytester._request
52+
53+
@property
54+
def plugins(self):
55+
return self._pytester.plugins
56+
57+
@plugins.setter
58+
def plugins(self, plugins):
59+
self._pytester.plugins = plugins
60+
61+
@property
62+
def monkeypatch(self) -> pytest.MonkeyPatch:
63+
return self._pytester._monkeypatch
64+
65+
def make_hook_recorder(self, pluginmanager) -> pytest.HookRecorder:
66+
"""See :meth:`Pytester.make_hook_recorder`."""
67+
return self._pytester.make_hook_recorder(pluginmanager)
68+
69+
def chdir(self) -> None:
70+
"""See :meth:`Pytester.chdir`."""
71+
return self._pytester.chdir()
72+
73+
def finalize(self) -> None:
74+
"""See :meth:`Pytester._finalize`."""
75+
return self._pytester._finalize()
76+
77+
def makefile(self, ext, *args, **kwargs) -> LEGACY_PATH:
78+
"""See :meth:`Pytester.makefile`."""
79+
if ext and not ext.startswith("."):
80+
# pytester.makefile is going to throw a ValueError in a way that
81+
# testdir.makefile did not, because
82+
# pathlib.Path is stricter suffixes than py.path
83+
# This ext arguments is likely user error, but since testdir has
84+
# allowed this, we will prepend "." as a workaround to avoid breaking
85+
# testdir usage that worked before
86+
ext = "." + ext
87+
return legacy_path(self._pytester.makefile(ext, *args, **kwargs))
88+
89+
def makeconftest(self, source) -> LEGACY_PATH:
90+
"""See :meth:`Pytester.makeconftest`."""
91+
return legacy_path(self._pytester.makeconftest(source))
92+
93+
def makeini(self, source) -> LEGACY_PATH:
94+
"""See :meth:`Pytester.makeini`."""
95+
return legacy_path(self._pytester.makeini(source))
96+
97+
def getinicfg(self, source: str) -> SectionWrapper:
98+
"""See :meth:`Pytester.getinicfg`."""
99+
return self._pytester.getinicfg(source)
100+
101+
def makepyprojecttoml(self, source) -> LEGACY_PATH:
102+
"""See :meth:`Pytester.makepyprojecttoml`."""
103+
return legacy_path(self._pytester.makepyprojecttoml(source))
104+
105+
def makepyfile(self, *args, **kwargs) -> LEGACY_PATH:
106+
"""See :meth:`Pytester.makepyfile`."""
107+
return legacy_path(self._pytester.makepyfile(*args, **kwargs))
108+
109+
def maketxtfile(self, *args, **kwargs) -> LEGACY_PATH:
110+
"""See :meth:`Pytester.maketxtfile`."""
111+
return legacy_path(self._pytester.maketxtfile(*args, **kwargs))
112+
113+
def syspathinsert(self, path=None) -> None:
114+
"""See :meth:`Pytester.syspathinsert`."""
115+
return self._pytester.syspathinsert(path)
116+
117+
def mkdir(self, name) -> LEGACY_PATH:
118+
"""See :meth:`Pytester.mkdir`."""
119+
return legacy_path(self._pytester.mkdir(name))
120+
121+
def mkpydir(self, name) -> LEGACY_PATH:
122+
"""See :meth:`Pytester.mkpydir`."""
123+
return legacy_path(self._pytester.mkpydir(name))
124+
125+
def copy_example(self, name=None) -> LEGACY_PATH:
126+
"""See :meth:`Pytester.copy_example`."""
127+
return legacy_path(self._pytester.copy_example(name))
128+
129+
def getnode(
130+
self, config: pytest.Config, arg
131+
) -> Optional[Union[pytest.Item, pytest.Collector]]:
132+
"""See :meth:`Pytester.getnode`."""
133+
return self._pytester.getnode(config, arg)
134+
135+
def getpathnode(self, path):
136+
"""See :meth:`Pytester.getpathnode`."""
137+
return self._pytester.getpathnode(path)
138+
139+
def genitems(
140+
self, colitems: List[Union[pytest.Item, pytest.Collector]]
141+
) -> List[pytest.Item]:
142+
"""See :meth:`Pytester.genitems`."""
143+
return self._pytester.genitems(colitems)
144+
145+
def runitem(self, source):
146+
"""See :meth:`Pytester.runitem`."""
147+
return self._pytester.runitem(source)
148+
149+
def inline_runsource(self, source, *cmdlineargs):
150+
"""See :meth:`Pytester.inline_runsource`."""
151+
return self._pytester.inline_runsource(source, *cmdlineargs)
152+
153+
def inline_genitems(self, *args):
154+
"""See :meth:`Pytester.inline_genitems`."""
155+
return self._pytester.inline_genitems(*args)
156+
157+
def inline_run(self, *args, plugins=(), no_reraise_ctrlc: bool = False):
158+
"""See :meth:`Pytester.inline_run`."""
159+
return self._pytester.inline_run(
160+
*args, plugins=plugins, no_reraise_ctrlc=no_reraise_ctrlc
161+
)
162+
163+
def runpytest_inprocess(self, *args, **kwargs) -> pytest.RunResult:
164+
"""See :meth:`Pytester.runpytest_inprocess`."""
165+
return self._pytester.runpytest_inprocess(*args, **kwargs)
166+
167+
def runpytest(self, *args, **kwargs) -> pytest.RunResult:
168+
"""See :meth:`Pytester.runpytest`."""
169+
return self._pytester.runpytest(*args, **kwargs)
170+
171+
def parseconfig(self, *args) -> pytest.Config:
172+
"""See :meth:`Pytester.parseconfig`."""
173+
return self._pytester.parseconfig(*args)
174+
175+
def parseconfigure(self, *args) -> pytest.Config:
176+
"""See :meth:`Pytester.parseconfigure`."""
177+
return self._pytester.parseconfigure(*args)
178+
179+
def getitem(self, source, funcname="test_func"):
180+
"""See :meth:`Pytester.getitem`."""
181+
return self._pytester.getitem(source, funcname)
182+
183+
def getitems(self, source):
184+
"""See :meth:`Pytester.getitems`."""
185+
return self._pytester.getitems(source)
186+
187+
def getmodulecol(self, source, configargs=(), withinit=False):
188+
"""See :meth:`Pytester.getmodulecol`."""
189+
return self._pytester.getmodulecol(
190+
source, configargs=configargs, withinit=withinit
191+
)
192+
193+
def collect_by_name(
194+
self, modcol: pytest.Collector, name: str
195+
) -> Optional[Union[pytest.Item, pytest.Collector]]:
196+
"""See :meth:`Pytester.collect_by_name`."""
197+
return self._pytester.collect_by_name(modcol, name)
198+
199+
def popen(
200+
self,
201+
cmdargs,
202+
stdout=subprocess.PIPE,
203+
stderr=subprocess.PIPE,
204+
stdin=CLOSE_STDIN,
205+
**kw,
206+
):
207+
"""See :meth:`Pytester.popen`."""
208+
return self._pytester.popen(cmdargs, stdout, stderr, stdin, **kw)
209+
210+
def run(self, *cmdargs, timeout=None, stdin=CLOSE_STDIN) -> pytest.RunResult:
211+
"""See :meth:`Pytester.run`."""
212+
return self._pytester.run(*cmdargs, timeout=timeout, stdin=stdin)
213+
214+
def runpython(self, script) -> pytest.RunResult:
215+
"""See :meth:`Pytester.runpython`."""
216+
return self._pytester.runpython(script)
217+
218+
def runpython_c(self, command):
219+
"""See :meth:`Pytester.runpython_c`."""
220+
return self._pytester.runpython_c(command)
221+
222+
def runpytest_subprocess(self, *args, timeout=None) -> pytest.RunResult:
223+
"""See :meth:`Pytester.runpytest_subprocess`."""
224+
return self._pytester.runpytest_subprocess(*args, timeout=timeout)
225+
226+
def spawn_pytest(
227+
self, string: str, expect_timeout: float = 10.0
228+
) -> "pexpect.spawn":
229+
"""See :meth:`Pytester.spawn_pytest`."""
230+
return self._pytester.spawn_pytest(string, expect_timeout=expect_timeout)
231+
232+
def spawn(self, cmd: str, expect_timeout: float = 10.0) -> "pexpect.spawn":
233+
"""See :meth:`Pytester.spawn`."""
234+
return self._pytester.spawn(cmd, expect_timeout=expect_timeout)
235+
236+
def __repr__(self) -> str:
237+
return f"<Testdir {self.tmpdir!r}>"
238+
239+
def __str__(self) -> str:
240+
return str(self.tmpdir)
241+
242+
243+
pytest.Testdir = Testdir # type: ignore[attr-defined]
244+
245+
246+
@pytest.fixture
247+
def testdir(pytester: pytest.Pytester) -> Testdir:
248+
"""
249+
Identical to :fixture:`pytester`, and provides an instance whose methods return
250+
legacy ``LEGACY_PATH`` objects instead when applicable.
251+
252+
New code should avoid using :fixture:`testdir` in favor of :fixture:`pytester`.
253+
"""
254+
return Testdir(pytester, _ispytest=True)

0 commit comments

Comments
 (0)