diff --git a/Lib/shutil.py b/Lib/shutil.py index 7dd470dfaba41a..48d1f941d5d81e 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 @@ -1267,11 +1267,20 @@ 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 + 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: 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)