-
-
Notifications
You must be signed in to change notification settings - Fork 2.7k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Fix CLOSE_STDIN sentinel #6760
Fix CLOSE_STDIN sentinel #6760
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -11,8 +11,11 @@ | |
import traceback | ||
from fnmatch import fnmatch | ||
from io import StringIO | ||
from typing import AnyStr | ||
from typing import Callable | ||
from typing import Dict | ||
from typing import Generic | ||
from typing import IO | ||
from typing import Iterable | ||
from typing import List | ||
from typing import Optional | ||
|
@@ -25,8 +28,11 @@ | |
|
||
import pytest | ||
from _pytest._code import Source | ||
from _pytest.capture import CLOSE_STDIN | ||
from _pytest.capture import CloseStdinType | ||
from _pytest.capture import MultiCapture | ||
from _pytest.capture import SysCapture | ||
from _pytest.compat import overload | ||
from _pytest.compat import TYPE_CHECKING | ||
from _pytest.config import _PluggyPlugin | ||
from _pytest.config import ExitCode | ||
|
@@ -513,7 +519,7 @@ def restore(self) -> None: | |
sys.path[:], sys.meta_path[:] = self.__saved | ||
|
||
|
||
class Testdir: | ||
class Testdir(Generic[AnyStr]): | ||
"""Temporary test directory with tools to test/run pytest itself. | ||
|
||
This is based on the ``tmpdir`` fixture but provides a number of methods | ||
|
@@ -533,7 +539,8 @@ class Testdir: | |
|
||
__test__ = False | ||
|
||
CLOSE_STDIN = object | ||
CLOSE_STDIN = CLOSE_STDIN | ||
"""Sentinel to close stdin.""" | ||
|
||
class TimeoutExpired(Exception): | ||
pass | ||
|
@@ -1089,14 +1096,42 @@ def collect_by_name( | |
return colitem | ||
return None | ||
|
||
@overload | ||
def popen( | ||
self, | ||
cmdargs, | ||
stdout=subprocess.PIPE, | ||
stderr=subprocess.PIPE, | ||
stdin=CLOSE_STDIN, | ||
stdout: Optional[Union[int, IO]] = subprocess.PIPE, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is it specifically I see that typeshed punts on this: https://github.com/python/typeshed/blob/d8b081130d3e3a7fde1a49ac787d304f5c4c1966/stdlib/3/subprocess.pyi#L13-L27 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I've used There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'd leave it with the explicit There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It can be |
||
stderr: Optional[Union[int, IO]] = subprocess.PIPE, | ||
stdin: Optional[Union[CloseStdinType, bytes, int, IO]] = CLOSE_STDIN, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Does |
||
*, | ||
encoding: None = ..., | ||
**kw | ||
): | ||
) -> "subprocess.Popen[bytes]": | ||
... | ||
|
||
@overload | ||
def popen( # noqa: F811 | ||
self, | ||
cmdargs, | ||
stdout: Optional[Union[int, IO]] = subprocess.PIPE, | ||
stderr: Optional[Union[int, IO]] = subprocess.PIPE, | ||
stdin: Optional[Union[CloseStdinType, bytes, int, IO]] = CLOSE_STDIN, | ||
*, | ||
encoding: str, | ||
**kw | ||
) -> "subprocess.Popen[str]": | ||
... | ||
|
||
def popen( # noqa: F811 | ||
self, | ||
cmdargs, | ||
stdout: Optional[Union[int, IO]] = subprocess.PIPE, | ||
stderr: Optional[Union[int, IO]] = subprocess.PIPE, | ||
stdin: Optional[Union[CloseStdinType, bytes, int, IO]] = CLOSE_STDIN, | ||
*, | ||
encoding: Optional[str] = None, | ||
**kw | ||
) -> "Union[subprocess.Popen[bytes], subprocess.Popen[str]]": | ||
"""Invoke subprocess.Popen. | ||
|
||
This calls subprocess.Popen making sure the current working directory | ||
|
@@ -1111,36 +1146,45 @@ def popen( | |
) | ||
kw["env"] = env | ||
|
||
if stdin is Testdir.CLOSE_STDIN: | ||
if stdin is CLOSE_STDIN: | ||
kw["stdin"] = subprocess.PIPE | ||
elif isinstance(stdin, bytes): | ||
kw["stdin"] = subprocess.PIPE | ||
else: | ||
kw["stdin"] = stdin | ||
|
||
popen = subprocess.Popen(cmdargs, stdout=stdout, stderr=stderr, **kw) | ||
if stdin is Testdir.CLOSE_STDIN: | ||
popen = subprocess.Popen( | ||
cmdargs, stdout=stdout, stderr=stderr, encoding=encoding, **kw | ||
) | ||
if stdin is CLOSE_STDIN: | ||
assert popen.stdin | ||
popen.stdin.close() | ||
elif isinstance(stdin, bytes): | ||
assert popen.stdin | ||
popen.stdin.write(stdin) | ||
|
||
return popen | ||
|
||
def run(self, *cmdargs, timeout=None, stdin=CLOSE_STDIN) -> RunResult: | ||
def run( | ||
self, | ||
*cmdargs, | ||
timeout=None, | ||
stdin: Optional[Union[CloseStdinType, bytes, int, IO]] = CLOSE_STDIN | ||
) -> RunResult: | ||
"""Run a command with arguments. | ||
|
||
Run a process using subprocess.Popen saving the stdout and stderr. | ||
Run a process using :class:<python:subprocess.Popen> saving the stdout and stderr. | ||
|
||
:param args: the sequence of arguments to pass to `subprocess.Popen()` | ||
:kwarg timeout: the period in seconds after which to timeout and raise | ||
:py:class:`Testdir.TimeoutExpired` | ||
:kwarg stdin: optional standard input. Bytes are being send, closing | ||
the pipe, otherwise it is passed through to ``popen``. | ||
Defaults to ``CLOSE_STDIN``, which translates to using a pipe | ||
(``subprocess.PIPE``) that gets closed. | ||
|
||
Returns a :py:class:`RunResult`. | ||
Defaults to :attr:`CLOSE_STDIN`, which translates to using a pipe | ||
(:data:`python:subprocess.PIPE`) that gets closed. | ||
|
||
Returns a :py:class:`RunResult`. | ||
""" | ||
__tracebackhide__ = True | ||
|
||
|
@@ -1163,6 +1207,7 @@ def run(self, *cmdargs, timeout=None, stdin=CLOSE_STDIN) -> RunResult: | |
close_fds=(sys.platform != "win32"), | ||
) | ||
if isinstance(stdin, bytes): | ||
assert popen.stdin | ||
popen.stdin.close() | ||
|
||
def handle_timeout(): | ||
|
@@ -1411,7 +1456,7 @@ def _match_lines( | |
match_func: Callable[[str, str], bool], | ||
match_nickname: str, | ||
*, | ||
consecutive: bool = False | ||
consecutive: bool = False, | ||
) -> None: | ||
"""Underlying implementation of ``fnmatch_lines`` and ``re_match_lines``. | ||
|
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't see
AnyStr
mentioned elsewhere, why did you need this generic?