From cd96f2d3cb5c17235ee02ed622787498591c9754 Mon Sep 17 00:00:00 2001 From: Daniel Hahler Date: Fri, 5 Apr 2019 13:22:36 +0200 Subject: [PATCH 1/4] bpo-14841: shutil.get_terminal_size: use stdin/stderr also stdout might be a pipe, e.g. `less`, and then stdin or stderr should be used to query the terminal for its size. This patch prefers stdin, since that is most likely connected to a terminal. --- Lib/shutil.py | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/Lib/shutil.py b/Lib/shutil.py index 7dd470dfaba41a..e2f6fd72fcfe46 100644 --- a/Lib/shutil.py +++ b/Lib/shutil.py @@ -1242,8 +1242,8 @@ def get_terminal_size(fallback=(80, 24)): the value is a positive integer, it is used. When COLUMNS or LINES is not defined, which is the common case, - the terminal connected to sys.__stdout__ is queried - by invoking os.get_terminal_size. + the terminal connected to sys.__stdin__, sys.__stderr__, or sys.__stdout__ + is queried by invoking os.get_terminal_size. If the terminal size cannot be successfully queried, either because the system doesn't support querying, or because we are not @@ -1266,11 +1266,17 @@ def get_terminal_size(fallback=(80, 24)): # only query if necessary if columns <= 0 or lines <= 0: - try: - size = os.get_terminal_size(sys.__stdout__.fileno()) - except (AttributeError, ValueError, OSError): - # stdout is None, closed, detached, or not a terminal, or - # os.get_terminal_size() is unsupported + for check in [sys.__stdin__, sys.__stderr__, sys.__stdout__]: + fd = check.fileno() + try: + size = os.get_terminal_size(fd) + except (AttributeError, ValueError, OSError): + # fd is None, closed, detached, or not a terminal, or + # os.get_terminal_size() is unsupported + continue + else: + break + else: size = os.terminal_size(fallback) if columns <= 0: columns = size.columns From c614adca57f74a53d211b7b525760b864030696c Mon Sep 17 00:00:00 2001 From: Daniel Hahler Date: Fri, 5 Apr 2019 14:02:23 +0200 Subject: [PATCH 2/4] tests --- Lib/test/test_shutil.py | 27 +++++++++++++++++++++------ 1 file changed, 21 insertions(+), 6 deletions(-) diff --git a/Lib/test/test_shutil.py b/Lib/test/test_shutil.py index 678a190bcf5ee0..993a7dc253c3a3 100644 --- a/Lib/test/test_shutil.py +++ b/Lib/test/test_shutil.py @@ -2320,24 +2320,39 @@ def test_stty_match(self): del env['LINES'] del env['COLUMNS'] actual = shutil.get_terminal_size() + self.assertEqual(expected, actual) - self.assertEqual(expected, actual) + # Falls back to stdout. + with support.swap_attr(sys, '__stdin__', None), \ + support.swap_attr(sys, '__stderr__', None): + actual = shutil.get_terminal_size() + self.assertEqual(expected, actual) + + # Falls back to stderr. + with support.swap_attr(sys, '__stdin__', None), \ + support.swap_attr(sys, '__stdout__', None): + actual = shutil.get_terminal_size() + self.assertEqual(expected, actual) def test_fallback(self): with support.EnvironmentVarGuard() as env: del env['LINES'] del env['COLUMNS'] - # sys.__stdout__ has no fileno() - with support.swap_attr(sys, '__stdout__', None): + # stdin/stderr/stdout have no fileno(). + with support.swap_attr(sys, '__stdin__', None), \ + support.swap_attr(sys, '__stderr__', None), \ + support.swap_attr(sys, '__stdout__', None): size = shutil.get_terminal_size(fallback=(10, 20)) self.assertEqual(size.columns, 10) self.assertEqual(size.lines, 20) - # sys.__stdout__ is not a terminal on Unix - # or fileno() not in (0, 1, 2) on Windows + # stdin/stderr/stdout are not a terminal on Unix, + # or fileno() not in (0, 1, 2) on Windows. with open(os.devnull, 'w') as f, \ - support.swap_attr(sys, '__stdout__', f): + support.swap_attr(sys, '__stdin__', f), \ + support.swap_attr(sys, '__stderr__', f), \ + support.swap_attr(sys, '__stdout__', f): size = shutil.get_terminal_size(fallback=(30, 40)) self.assertEqual(size.columns, 30) self.assertEqual(size.lines, 40) From 364a1926bda84d2eba597b0805d06585a470e5e4 Mon Sep 17 00:00:00 2001 From: Daniel Hahler Date: Fri, 5 Apr 2019 14:04:13 +0200 Subject: [PATCH 3/4] fixup! bpo-14841: shutil.get_terminal_size: use stdin/stderr also --- Lib/shutil.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Lib/shutil.py b/Lib/shutil.py index e2f6fd72fcfe46..4db78953562c8a 100644 --- a/Lib/shutil.py +++ b/Lib/shutil.py @@ -1267,9 +1267,8 @@ def get_terminal_size(fallback=(80, 24)): # only query if necessary if columns <= 0 or lines <= 0: for check in [sys.__stdin__, sys.__stderr__, sys.__stdout__]: - fd = check.fileno() try: - size = os.get_terminal_size(fd) + size = os.get_terminal_size(check.fileno()) except (AttributeError, ValueError, OSError): # fd is None, closed, detached, or not a terminal, or # os.get_terminal_size() is unsupported From 5baa77841ce849e955ff8b12f44add54f9adcc93 Mon Sep 17 00:00:00 2001 From: Daniel Hahler Date: Fri, 5 Apr 2019 14:16:35 +0200 Subject: [PATCH 4/4] check for os.get_terminal_size explicitly --- Lib/shutil.py | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/Lib/shutil.py b/Lib/shutil.py index 4db78953562c8a..48d1f941d5d81e 100644 --- a/Lib/shutil.py +++ b/Lib/shutil.py @@ -1266,17 +1266,21 @@ def get_terminal_size(fallback=(80, 24)): # only query if necessary if columns <= 0 or lines <= 0: - for check in [sys.__stdin__, sys.__stderr__, sys.__stdout__]: - try: - size = os.get_terminal_size(check.fileno()) - except (AttributeError, ValueError, OSError): - # fd is None, closed, detached, or not a terminal, or - # os.get_terminal_size() is unsupported - continue - else: - break - else: + try: + os_get_terminal_size = os.get_terminal_size + except AttributeError: size = os.terminal_size(fallback) + else: + for check in [sys.__stdin__, sys.__stderr__, sys.__stdout__]: + try: + size = os_get_terminal_size(check.fileno()) + except (AttributeError, ValueError, OSError): + # fd is None, closed, detached, or not a terminal. + continue + else: + break + else: + size = os.terminal_size(fallback) if columns <= 0: columns = size.columns if lines <= 0: