Skip to content

Commit

Permalink
[3.8] [3.9] bpo-42789: Don't skip curses tests on non-tty. (GH-24009) (
Browse files Browse the repository at this point in the history
…GH-24076) (GH-24078)

If __stdout__ is not attached to terminal, try to use __stderr__
if it is attached to terminal, or open the terminal device, or
use regular file as terminal, but some functions will be untested
in the latter case.
(cherry picked from commit 607501a)
(cherry picked from commit 0303008)
  • Loading branch information
serhiy-storchaka committed Jan 3, 2021
1 parent 2e8b1c9 commit 645174a
Showing 1 changed file with 63 additions and 39 deletions.
102 changes: 63 additions & 39 deletions Lib/test/test_curses.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,37 +47,57 @@ class TestCurses(unittest.TestCase):

@classmethod
def setUpClass(cls):
if not sys.__stdout__.isatty():
# Temporary skip tests on non-tty
raise unittest.SkipTest('sys.__stdout__ is not a tty')
cls.tmp = tempfile.TemporaryFile()
fd = cls.tmp.fileno()
else:
cls.tmp = None
fd = sys.__stdout__.fileno()
# testing setupterm() inside initscr/endwin
# causes terminal breakage
curses.setupterm(fd=fd)

@classmethod
def tearDownClass(cls):
if cls.tmp:
cls.tmp.close()
del cls.tmp
stdout_fd = sys.__stdout__.fileno()
curses.setupterm(fd=stdout_fd)

def setUp(self):
self.isatty = True
self.output = sys.__stdout__
stdout_fd = sys.__stdout__.fileno()
if not sys.__stdout__.isatty():
# initstr() unconditionally uses C stdout.
# If it is redirected to file or pipe, try to attach it
# to terminal.
# First, save a copy of the file descriptor of stdout, so it
# can be restored after finishing the test.
dup_fd = os.dup(stdout_fd)
self.addCleanup(os.close, dup_fd)
self.addCleanup(os.dup2, dup_fd, stdout_fd)

if sys.__stderr__.isatty():
# If stderr is connected to terminal, use it.
tmp = sys.__stderr__
self.output = sys.__stderr__
else:
try:
# Try to open the terminal device.
tmp = open('/dev/tty', 'wb', buffering=0)
except OSError:
# As a fallback, use regular file to write control codes.
# Some functions (like savetty) will not work, but at
# least the garbage control sequences will not be mixed
# with the testing report.
tmp = tempfile.TemporaryFile(mode='wb', buffering=0)
self.isatty = False
self.addCleanup(tmp.close)
self.output = None
os.dup2(tmp.fileno(), stdout_fd)

self.save_signals = SaveSignals()
self.save_signals.save()
if verbose:
self.addCleanup(self.save_signals.restore)
if verbose and self.output is not None:
# just to make the test output a little more readable
print()
sys.stderr.flush()
sys.stdout.flush()
print(file=self.output, flush=True)
self.stdscr = curses.initscr()
curses.savetty()

def tearDown(self):
curses.resetty()
curses.endwin()
self.save_signals.restore()
if self.isatty:
curses.savetty()
self.addCleanup(curses.endwin)
self.addCleanup(curses.resetty)

def test_window_funcs(self):
"Test the methods of windows"
Expand All @@ -95,7 +115,7 @@ def test_window_funcs(self):
for meth in [stdscr.clear, stdscr.clrtobot,
stdscr.clrtoeol, stdscr.cursyncup, stdscr.delch,
stdscr.deleteln, stdscr.erase, stdscr.getbegyx,
stdscr.getbkgd, stdscr.getkey, stdscr.getmaxyx,
stdscr.getbkgd, stdscr.getmaxyx,
stdscr.getparyx, stdscr.getyx, stdscr.inch,
stdscr.insertln, stdscr.instr, stdscr.is_wintouched,
win.noutrefresh, stdscr.redrawwin, stdscr.refresh,
Expand Down Expand Up @@ -206,6 +226,11 @@ def test_window_funcs(self):
if hasattr(stdscr, 'enclose'):
stdscr.enclose(10, 10)

with tempfile.TemporaryFile() as f:
self.stdscr.putwin(f)
f.seek(0)
curses.getwin(f)

self.assertRaises(ValueError, stdscr.getstr, -400)
self.assertRaises(ValueError, stdscr.getstr, 2, 3, -400)
self.assertRaises(ValueError, stdscr.instr, -2)
Expand All @@ -224,16 +249,19 @@ def test_embedded_null_chars(self):
def test_module_funcs(self):
"Test module-level functions"
for func in [curses.baudrate, curses.beep, curses.can_change_color,
curses.cbreak, curses.def_prog_mode, curses.doupdate,
curses.flash, curses.flushinp,
curses.doupdate, curses.flash, curses.flushinp,
curses.has_colors, curses.has_ic, curses.has_il,
curses.isendwin, curses.killchar, curses.longname,
curses.nocbreak, curses.noecho, curses.nonl,
curses.noqiflush, curses.noraw,
curses.reset_prog_mode, curses.termattrs,
curses.termname, curses.erasechar]:
curses.noecho, curses.nonl, curses.noqiflush,
curses.termattrs, curses.termname, curses.erasechar]:
with self.subTest(func=func.__qualname__):
func()
if self.isatty:
for func in [curses.cbreak, curses.def_prog_mode,
curses.nocbreak, curses.noraw,
curses.reset_prog_mode]:
with self.subTest(func=func.__qualname__):
func()
if hasattr(curses, 'filter'):
curses.filter()
if hasattr(curses, 'getsyx'):
Expand All @@ -245,13 +273,9 @@ def test_module_funcs(self):
curses.delay_output(1)
curses.echo() ; curses.echo(1)

with tempfile.TemporaryFile() as f:
self.stdscr.putwin(f)
f.seek(0)
curses.getwin(f)

curses.halfdelay(1)
curses.intrflush(1)
if self.isatty:
curses.intrflush(1)
curses.meta(1)
curses.napms(100)
curses.newpad(50,50)
Expand All @@ -260,7 +284,8 @@ def test_module_funcs(self):
curses.nl() ; curses.nl(1)
curses.putp(b'abc')
curses.qiflush()
curses.raw() ; curses.raw(1)
if self.isatty:
curses.raw() ; curses.raw(1)
if hasattr(curses, 'setsyx'):
curses.setsyx(5,5)
curses.tigetflag('hc')
Expand All @@ -282,7 +307,7 @@ def test_colors_funcs(self):
curses.init_pair(2, 1,1)
curses.color_content(1)
curses.color_pair(2)
curses.pair_content(curses.COLOR_PAIRS - 1)
curses.pair_content(min(curses.COLOR_PAIRS - 1, 0x7fff))
curses.pair_number(0)

if hasattr(curses, 'use_default_colors'):
Expand Down Expand Up @@ -354,7 +379,6 @@ def test_resize_term(self):

@requires_curses_func('resizeterm')
def test_resizeterm(self):
stdscr = self.stdscr
lines, cols = curses.LINES, curses.COLS
new_lines = lines - 1
new_cols = cols + 1
Expand Down

0 comments on commit 645174a

Please sign in to comment.