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]
|
[options.entry_points]
|
||||||
console_scripts =
|
console_scripts =
|
||||||
pytest=pytest:main
|
pytest=pytest:console_main
|
||||||
py.test=pytest:main
|
py.test=pytest:console_main
|
||||||
|
|
||||||
[build_sphinx]
|
[build_sphinx]
|
||||||
source-dir = doc/en/
|
source-dir = doc/en/
|
||||||
|
|
|
@ -137,6 +137,24 @@ def main(args=None, plugins=None) -> Union[int, ExitCode]:
|
||||||
return ExitCode.USAGE_ERROR
|
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
|
class cmdline: # compatibility namespace
|
||||||
main = staticmethod(main)
|
main = staticmethod(main)
|
||||||
|
|
||||||
|
|
|
@ -6,6 +6,7 @@ from . import collect
|
||||||
from _pytest import __version__
|
from _pytest import __version__
|
||||||
from _pytest.assertion import register_assert_rewrite
|
from _pytest.assertion import register_assert_rewrite
|
||||||
from _pytest.config import cmdline
|
from _pytest.config import cmdline
|
||||||
|
from _pytest.config import console_main
|
||||||
from _pytest.config import ExitCode
|
from _pytest.config import ExitCode
|
||||||
from _pytest.config import hookimpl
|
from _pytest.config import hookimpl
|
||||||
from _pytest.config import hookspec
|
from _pytest.config import hookspec
|
||||||
|
@ -57,6 +58,7 @@ __all__ = [
|
||||||
"cmdline",
|
"cmdline",
|
||||||
"collect",
|
"collect",
|
||||||
"Collector",
|
"Collector",
|
||||||
|
"console_main",
|
||||||
"deprecated_call",
|
"deprecated_call",
|
||||||
"exit",
|
"exit",
|
||||||
"ExitCode",
|
"ExitCode",
|
||||||
|
|
|
@ -4,4 +4,4 @@ pytest entry point
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
if __name__ == "__main__":
|
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.compat import importlib_metadata
|
||||||
from _pytest.config import ExitCode
|
from _pytest.config import ExitCode
|
||||||
from _pytest.monkeypatch import MonkeyPatch
|
from _pytest.monkeypatch import MonkeyPatch
|
||||||
|
from _pytest.pytester import Testdir
|
||||||
|
|
||||||
|
|
||||||
def prepend_pythonpath(*dirs):
|
def prepend_pythonpath(*dirs):
|
||||||
|
@ -1343,3 +1344,21 @@ def test_tee_stdio_captures_and_live_prints(testdir):
|
||||||
fullXml = f.read()
|
fullXml = f.read()
|
||||||
assert "@this is stdout@\n" in fullXml
|
assert "@this is stdout@\n" in fullXml
|
||||||
assert "@this is stderr@\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