[3.8] [3.9] bpo-42789: Don't skip curses tests on non-tty. (GH-24009)… · python/cpython@645174a
@@ -47,37 +47,57 @@ class TestCurses(unittest.TestCase):
47474848@classmethod
4949def setUpClass(cls):
50-if not sys.__stdout__.isatty():
51-# Temporary skip tests on non-tty
52-raise unittest.SkipTest('sys.__stdout__ is not a tty')
53-cls.tmp = tempfile.TemporaryFile()
54-fd = cls.tmp.fileno()
55-else:
56-cls.tmp = None
57-fd = sys.__stdout__.fileno()
5850# testing setupterm() inside initscr/endwin
5951# causes terminal breakage
60-curses.setupterm(fd=fd)
61-62-@classmethod
63-def tearDownClass(cls):
64-if cls.tmp:
65-cls.tmp.close()
66-del cls.tmp
52+stdout_fd = sys.__stdout__.fileno()
53+curses.setupterm(fd=stdout_fd)
67546855def setUp(self):
56+self.isatty = True
57+self.output = sys.__stdout__
58+stdout_fd = sys.__stdout__.fileno()
59+if not sys.__stdout__.isatty():
60+# initstr() unconditionally uses C stdout.
61+# If it is redirected to file or pipe, try to attach it
62+# to terminal.
63+# First, save a copy of the file descriptor of stdout, so it
64+# can be restored after finishing the test.
65+dup_fd = os.dup(stdout_fd)
66+self.addCleanup(os.close, dup_fd)
67+self.addCleanup(os.dup2, dup_fd, stdout_fd)
68+69+if sys.__stderr__.isatty():
70+# If stderr is connected to terminal, use it.
71+tmp = sys.__stderr__
72+self.output = sys.__stderr__
73+else:
74+try:
75+# Try to open the terminal device.
76+tmp = open('/dev/tty', 'wb', buffering=0)
77+except OSError:
78+# As a fallback, use regular file to write control codes.
79+# Some functions (like savetty) will not work, but at
80+# least the garbage control sequences will not be mixed
81+# with the testing report.
82+tmp = tempfile.TemporaryFile(mode='wb', buffering=0)
83+self.isatty = False
84+self.addCleanup(tmp.close)
85+self.output = None
86+os.dup2(tmp.fileno(), stdout_fd)
87+6988self.save_signals = SaveSignals()
7089self.save_signals.save()
71-if verbose:
90+self.addCleanup(self.save_signals.restore)
91+if verbose and self.output is not None:
7292# just to make the test output a little more readable
73-print()
93+sys.stderr.flush()
94+sys.stdout.flush()
95+print(file=self.output, flush=True)
7496self.stdscr = curses.initscr()
75-curses.savetty()
76-77-def tearDown(self):
78-curses.resetty()
79-curses.endwin()
80-self.save_signals.restore()
97+if self.isatty:
98+curses.savetty()
99+self.addCleanup(curses.endwin)
100+self.addCleanup(curses.resetty)
8110182102def test_window_funcs(self):
83103"Test the methods of windows"
@@ -95,7 +115,7 @@ def test_window_funcs(self):
95115for meth in [stdscr.clear, stdscr.clrtobot,
96116stdscr.clrtoeol, stdscr.cursyncup, stdscr.delch,
97117stdscr.deleteln, stdscr.erase, stdscr.getbegyx,
98-stdscr.getbkgd, stdscr.getkey, stdscr.getmaxyx,
118+stdscr.getbkgd, stdscr.getmaxyx,
99119stdscr.getparyx, stdscr.getyx, stdscr.inch,
100120stdscr.insertln, stdscr.instr, stdscr.is_wintouched,
101121win.noutrefresh, stdscr.redrawwin, stdscr.refresh,
@@ -206,6 +226,11 @@ def test_window_funcs(self):
206226if hasattr(stdscr, 'enclose'):
207227stdscr.enclose(10, 10)
208228229+with tempfile.TemporaryFile() as f:
230+self.stdscr.putwin(f)
231+f.seek(0)
232+curses.getwin(f)
233+209234self.assertRaises(ValueError, stdscr.getstr, -400)
210235self.assertRaises(ValueError, stdscr.getstr, 2, 3, -400)
211236self.assertRaises(ValueError, stdscr.instr, -2)
@@ -224,16 +249,19 @@ def test_embedded_null_chars(self):
224249def test_module_funcs(self):
225250"Test module-level functions"
226251for func in [curses.baudrate, curses.beep, curses.can_change_color,
227-curses.cbreak, curses.def_prog_mode, curses.doupdate,
228-curses.flash, curses.flushinp,
252+curses.doupdate, curses.flash, curses.flushinp,
229253curses.has_colors, curses.has_ic, curses.has_il,
230254curses.isendwin, curses.killchar, curses.longname,
231-curses.nocbreak, curses.noecho, curses.nonl,
232-curses.noqiflush, curses.noraw,
233-curses.reset_prog_mode, curses.termattrs,
234-curses.termname, curses.erasechar]:
255+curses.noecho, curses.nonl, curses.noqiflush,
256+curses.termattrs, curses.termname, curses.erasechar]:
235257with self.subTest(func=func.__qualname__):
236258func()
259+if self.isatty:
260+for func in [curses.cbreak, curses.def_prog_mode,
261+curses.nocbreak, curses.noraw,
262+curses.reset_prog_mode]:
263+with self.subTest(func=func.__qualname__):
264+func()
237265if hasattr(curses, 'filter'):
238266curses.filter()
239267if hasattr(curses, 'getsyx'):
@@ -245,13 +273,9 @@ def test_module_funcs(self):
245273curses.delay_output(1)
246274curses.echo() ; curses.echo(1)
247275248-with tempfile.TemporaryFile() as f:
249-self.stdscr.putwin(f)
250-f.seek(0)
251-curses.getwin(f)
252-253276curses.halfdelay(1)
254-curses.intrflush(1)
277+if self.isatty:
278+curses.intrflush(1)
255279curses.meta(1)
256280curses.napms(100)
257281curses.newpad(50,50)
@@ -260,7 +284,8 @@ def test_module_funcs(self):
260284curses.nl() ; curses.nl(1)
261285curses.putp(b'abc')
262286curses.qiflush()
263-curses.raw() ; curses.raw(1)
287+if self.isatty:
288+curses.raw() ; curses.raw(1)
264289if hasattr(curses, 'setsyx'):
265290curses.setsyx(5,5)
266291curses.tigetflag('hc')
@@ -282,7 +307,7 @@ def test_colors_funcs(self):
282307curses.init_pair(2, 1,1)
283308curses.color_content(1)
284309curses.color_pair(2)
285-curses.pair_content(curses.COLOR_PAIRS - 1)
310+curses.pair_content(min(curses.COLOR_PAIRS - 1, 0x7fff))
286311curses.pair_number(0)
287312288313if hasattr(curses, 'use_default_colors'):
@@ -354,7 +379,6 @@ def test_resize_term(self):
354379355380@requires_curses_func('resizeterm')
356381def test_resizeterm(self):
357-stdscr = self.stdscr
358382lines, cols = curses.LINES, curses.COLS
359383new_lines = lines - 1
360384new_cols = cols + 1