diff options
author | Oleg Iarygin <oleg@arhadthedev.net> | 2023-05-06 22:53:48 (GMT) |
---|---|---|
committer | GitHub <noreply@github.com> | 2023-05-06 22:53:48 (GMT) |
commit | 42f54d1f9244784fec99e0610aa05a5051e594bb (patch) | |
tree | 9261147628e5c1543fc8d3784e34ad4dd5edaa58 | |
parent | 92d8bfffbf377e91d8b92666525cb8700bb1d5e8 (diff) | |
download | cpython-42f54d1f9244784fec99e0610aa05a5051e594bb.zip cpython-42f54d1f9244784fec99e0610aa05a5051e594bb.tar.gz cpython-42f54d1f9244784fec99e0610aa05a5051e594bb.tar.bz2 |
gh-101640: Make argparse _print_message catch any write error (#101802)
* In particular, don't exit when trying to print to stderr = None.
* Add tests
Co-authored-by: Terry Jan Reedy <tjreedy@udel.edu>
-rw-r--r-- | Lib/argparse.py | 8 | ||||
-rw-r--r-- | Lib/test/test_argparse.py | 31 | ||||
-rw-r--r-- | Misc/NEWS.d/next/Library/2023-02-09-22-24-34.gh-issue-101640.oFuEpB.rst | 1 |
3 files changed, 37 insertions, 3 deletions
diff --git a/Lib/argparse.py b/Lib/argparse.py index a819d26..68089a5 100644 --- a/Lib/argparse.py +++ b/Lib/argparse.py @@ -2605,9 +2605,11 @@ class ArgumentParser(_AttributeHolder, _ActionsContainer): def _print_message(self, message, file=None): if message: - if file is None: - file = _sys.stderr - file.write(message) + file = file or _sys.stderr + try: + file.write(message) + except (AttributeError, OSError): + pass # =============== # Exiting methods diff --git a/Lib/test/test_argparse.py b/Lib/test/test_argparse.py index 861da23..0659d24 100644 --- a/Lib/test/test_argparse.py +++ b/Lib/test/test_argparse.py @@ -1,5 +1,7 @@ # Author: Steven J. Bethard <steven.bethard@gmail.com>. +import contextlib +import functools import inspect import io import operator @@ -35,6 +37,35 @@ class StdIOBuffer(io.TextIOWrapper): return self.buffer.raw.getvalue().decode('utf-8') +class StdStreamTest(unittest.TestCase): + + def test_skip_invalid_stderr(self): + parser = argparse.ArgumentParser() + with ( + contextlib.redirect_stderr(None), + mock.patch('argparse._sys.exit') + ): + parser.exit(status=0, message='foo') + + def test_skip_invalid_stdout(self): + parser = argparse.ArgumentParser() + for func in ( + parser.print_usage, + parser.print_help, + functools.partial(parser.parse_args, ['-h']) + ): + with ( + self.subTest(func=func), + contextlib.redirect_stdout(None), + # argparse uses stderr as a fallback + StdIOBuffer() as mocked_stderr, + contextlib.redirect_stderr(mocked_stderr), + mock.patch('argparse._sys.exit'), + ): + func() + self.assertRegex(mocked_stderr.getvalue(), r'usage:') + + class TestCase(unittest.TestCase): def setUp(self): diff --git a/Misc/NEWS.d/next/Library/2023-02-09-22-24-34.gh-issue-101640.oFuEpB.rst b/Misc/NEWS.d/next/Library/2023-02-09-22-24-34.gh-issue-101640.oFuEpB.rst new file mode 100644 index 0000000..917cf0f --- /dev/null +++ b/Misc/NEWS.d/next/Library/2023-02-09-22-24-34.gh-issue-101640.oFuEpB.rst @@ -0,0 +1 @@ +:class:`argparse.ArgumentParser` now catches errors when writing messages, such as when :data:`sys.stderr` is ``None``. Patch by Oleg Iarygin. |