Merge pull request #7185 from bluetech/sigpipe
Handle SIGPIPE/BrokenPipeError in pytest's CLI
This commit is contained in:
commit
857b5c9ccd
|
@ -0,0 +1,3 @@
|
|||
The ``pytest`` command now supresses the ``BrokenPipeError`` error message that
|
||||
is printed to stderr when the output of ``pytest`` is piped and and the pipe is
|
||||
closed by the piped-to program (common examples are ``less`` and ``head``).
|
|
@ -44,8 +44,8 @@ python_requires = >=3.5
|
|||
|
||||
[options.entry_points]
|
||||
console_scripts =
|
||||
pytest=pytest:main
|
||||
py.test=pytest:main
|
||||
pytest=pytest:console_main
|
||||
py.test=pytest:console_main
|
||||
|
||||
[build_sphinx]
|
||||
source-dir = doc/en/
|
||||
|
|
|
@ -137,6 +137,24 @@ def main(args=None, plugins=None) -> Union[int, ExitCode]:
|
|||
return ExitCode.USAGE_ERROR
|
||||
|
||||
|
||||
def console_main() -> int:
|
||||
"""pytest's CLI entry point.
|
||||
|
||||
This function is not meant for programmable use; use `main()` instead.
|
||||
"""
|
||||
# https://docs.python.org/3/library/signal.html#note-on-sigpipe
|
||||
try:
|
||||
code = main()
|
||||
sys.stdout.flush()
|
||||
return code
|
||||
except BrokenPipeError:
|
||||
# Python flushes standard streams on exit; redirect remaining output
|
||||
# to devnull to avoid another BrokenPipeError at shutdown
|
||||
devnull = os.open(os.devnull, os.O_WRONLY)
|
||||
os.dup2(devnull, sys.stdout.fileno())
|
||||
return 1 # Python exits with error code 1 on EPIPE
|
||||
|
||||
|
||||
class cmdline: # compatibility namespace
|
||||
main = staticmethod(main)
|
||||
|
||||
|
|
|
@ -6,6 +6,7 @@ from . import collect
|
|||
from _pytest import __version__
|
||||
from _pytest.assertion import register_assert_rewrite
|
||||
from _pytest.config import cmdline
|
||||
from _pytest.config import console_main
|
||||
from _pytest.config import ExitCode
|
||||
from _pytest.config import hookimpl
|
||||
from _pytest.config import hookspec
|
||||
|
@ -57,6 +58,7 @@ __all__ = [
|
|||
"cmdline",
|
||||
"collect",
|
||||
"Collector",
|
||||
"console_main",
|
||||
"deprecated_call",
|
||||
"exit",
|
||||
"ExitCode",
|
||||
|
|
|
@ -4,4 +4,4 @@ pytest entry point
|
|||
import pytest
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(pytest.main())
|
||||
raise SystemExit(pytest.console_main())
|
||||
|
|
|
@ -11,6 +11,7 @@ import pytest
|
|||
from _pytest.compat import importlib_metadata
|
||||
from _pytest.config import ExitCode
|
||||
from _pytest.monkeypatch import MonkeyPatch
|
||||
from _pytest.pytester import Testdir
|
||||
|
||||
|
||||
def prepend_pythonpath(*dirs):
|
||||
|
@ -1343,3 +1344,21 @@ def test_tee_stdio_captures_and_live_prints(testdir):
|
|||
fullXml = f.read()
|
||||
assert "@this is stdout@\n" in fullXml
|
||||
assert "@this is stderr@\n" in fullXml
|
||||
|
||||
|
||||
@pytest.mark.skipif(
|
||||
sys.platform == "win32",
|
||||
reason="Windows raises `OSError: [Errno 22] Invalid argument` instead",
|
||||
)
|
||||
def test_no_brokenpipeerror_message(testdir: Testdir) -> None:
|
||||
"""Ensure that the broken pipe error message is supressed.
|
||||
|
||||
In some Python versions, it reaches sys.unraisablehook, in others
|
||||
a BrokenPipeError exception is propagated, but either way it prints
|
||||
to stderr on shutdown, so checking nothing is printed is enough.
|
||||
"""
|
||||
popen = testdir.popen((*testdir._getpytestargs(), "--help"))
|
||||
popen.stdout.close()
|
||||
ret = popen.wait()
|
||||
assert popen.stderr.read() == b""
|
||||
assert ret == 1
|
||||
|
|
Loading…
Reference in New Issue