Merge pull request #7185 from bluetech/sigpipe

Handle SIGPIPE/BrokenPipeError in pytest's CLI
This commit is contained in:
Ran Benita 2020-05-08 13:32:34 +03:00 committed by GitHub
commit 857b5c9ccd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 45 additions and 3 deletions

View File

@ -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``).

View File

@ -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/

View File

@ -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)

View File

@ -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",

View File

@ -4,4 +4,4 @@ pytest entry point
import pytest
if __name__ == "__main__":
raise SystemExit(pytest.main())
raise SystemExit(pytest.console_main())

View File

@ -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