Handle EPIPE/BrokenPipeError in pytest's CLI
Running `pytest | head -1` and similar causes an annoying error to be
printed to stderr:
Exception ignored in: <_io.TextIOWrapper name='<stdout>' mode='w' encoding='utf-8'>
BrokenPipeError: [Errno 32] Broken pipe
(or possibly even a propagating exception in older/other Python versions).
The standard UNIX behavior is to handle the EPIPE silently. To
recommended method to do this in Python is described here:
https://docs.python.org/3/library/signal.html#note-on-sigpipe
It is not appropriate to apply this recommendation to `pytest.main()`,
which is used programmatically for in-process runs. Hence, change
pytest's entrypoint to a new `pytest.console_main()` function, to be
used exclusively by pytest's CLI, and add the SIGPIPE code there.
Fixes #4375.
This commit is contained in:
@@ -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())
|
||||
|
||||
Reference in New Issue
Block a user