1# 2# Test script for the curses module 3# 4# This script doesn't actually display anything very coherent. but it 5# does call (nearly) every method and function. 6# 7# Functions not tested: {def,reset}_{shell,prog}_mode, getch(), getstr(), 8# init_color() 9# Only called, not tested: getmouse(), ungetmouse() 10# 11 12import os 13import string 14import sys 15import tempfile 16import unittest 17 18from test.support import requires, import_module, verbose, SaveSignals 19 20# Optionally test curses module. This currently requires that the 21# 'curses' resource be given on the regrtest command line using the -u 22# option. If not available, nothing after this line will be executed. 23import inspect 24requires('curses') 25 26# If either of these don't exist, skip the tests. 27curses = import_module('curses') 28import_module('curses.ascii') 29import_module('curses.textpad') 30try: 31 import curses.panel 32except ImportError: 33 pass 34 35def requires_curses_func(name): 36 return unittest.skipUnless(hasattr(curses, name), 37 'requires curses.%s' % name) 38 39term = os.environ.get('TERM') 40 41# If newterm was supported we could use it instead of initscr and not exit 42@unittest.skipIf(not term or term == 'unknown', 43 "$TERM=%r, calling initscr() may cause exit" % term) 44@unittest.skipIf(sys.platform == "cygwin", 45 "cygwin's curses mostly just hangs") 46class TestCurses(unittest.TestCase): 47 48 @classmethod 49 def 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() 58 # testing setupterm() inside initscr/endwin 59 # 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 67 68 def setUp(self): 69 self.save_signals = SaveSignals() 70 self.save_signals.save() 71 if verbose: 72 # just to make the test output a little more readable 73 print() 74 self.stdscr = curses.initscr() 75 curses.savetty() 76 77 def tearDown(self): 78 curses.resetty() 79 curses.endwin() 80 self.save_signals.restore() 81 82 def test_window_funcs(self): 83 "Test the methods of windows" 84 stdscr = self.stdscr 85 win = curses.newwin(10,10) 86 win = curses.newwin(5,5, 5,5) 87 win2 = curses.newwin(15,15, 5,5) 88 89 for meth in [stdscr.addch, stdscr.addstr]: 90 for args in [('a',), ('a', curses.A_BOLD), 91 (4,4, 'a'), (5,5, 'a', curses.A_BOLD)]: 92 with self.subTest(meth=meth.__qualname__, args=args): 93 meth(*args) 94 95 for meth in [stdscr.clear, stdscr.clrtobot, 96 stdscr.clrtoeol, stdscr.cursyncup, stdscr.delch, 97 stdscr.deleteln, stdscr.erase, stdscr.getbegyx, 98 stdscr.getbkgd, stdscr.getkey, stdscr.getmaxyx, 99 stdscr.getparyx, stdscr.getyx, stdscr.inch, 100 stdscr.insertln, stdscr.instr, stdscr.is_wintouched, 101 win.noutrefresh, stdscr.redrawwin, stdscr.refresh, 102 stdscr.standout, stdscr.standend, stdscr.syncdown, 103 stdscr.syncup, stdscr.touchwin, stdscr.untouchwin]: 104 with self.subTest(meth=meth.__qualname__): 105 meth() 106 107 stdscr.addnstr('1234', 3) 108 stdscr.addnstr('1234', 3, curses.A_BOLD) 109 stdscr.addnstr(4,4, '1234', 3) 110 stdscr.addnstr(5,5, '1234', 3, curses.A_BOLD) 111 112 stdscr.attron(curses.A_BOLD) 113 stdscr.attroff(curses.A_BOLD) 114 stdscr.attrset(curses.A_BOLD) 115 stdscr.bkgd(' ') 116 stdscr.bkgd(' ', curses.A_REVERSE) 117 stdscr.bkgdset(' ') 118 stdscr.bkgdset(' ', curses.A_REVERSE) 119 120 win.border(65, 66, 67, 68, 121 69, 70, 71, 72) 122 win.border('|', '!', '-', '_', 123 '+', '\\', '#', '/') 124 with self.assertRaises(TypeError, 125 msg="Expected win.border() to raise TypeError"): 126 win.border(65, 66, 67, 68, 127 69, [], 71, 72) 128 129 win.box(65, 67) 130 win.box('!', '_') 131 win.box(b':', b'~') 132 self.assertRaises(TypeError, win.box, 65, 66, 67) 133 self.assertRaises(TypeError, win.box, 65) 134 win.box() 135 136 stdscr.clearok(1) 137 138 win4 = stdscr.derwin(2,2) 139 win4 = stdscr.derwin(1,1, 5,5) 140 win4.mvderwin(9,9) 141 142 stdscr.echochar('a') 143 stdscr.echochar('a', curses.A_BOLD) 144 stdscr.hline('-', 5) 145 stdscr.hline('-', 5, curses.A_BOLD) 146 stdscr.hline(1,1,'-', 5) 147 stdscr.hline(1,1,'-', 5, curses.A_BOLD) 148 149 stdscr.idcok(1) 150 stdscr.idlok(1) 151 if hasattr(stdscr, 'immedok'): 152 stdscr.immedok(1) 153 stdscr.immedok(0) 154 stdscr.insch('c') 155 stdscr.insdelln(1) 156 stdscr.insnstr('abc', 3) 157 stdscr.insnstr('abc', 3, curses.A_BOLD) 158 stdscr.insnstr(5, 5, 'abc', 3) 159 stdscr.insnstr(5, 5, 'abc', 3, curses.A_BOLD) 160 161 stdscr.insstr('def') 162 stdscr.insstr('def', curses.A_BOLD) 163 stdscr.insstr(5, 5, 'def') 164 stdscr.insstr(5, 5, 'def', curses.A_BOLD) 165 stdscr.is_linetouched(0) 166 stdscr.keypad(1) 167 stdscr.leaveok(1) 168 stdscr.move(3,3) 169 win.mvwin(2,2) 170 stdscr.nodelay(1) 171 stdscr.notimeout(1) 172 win2.overlay(win) 173 win2.overwrite(win) 174 win2.overlay(win, 1, 2, 2, 1, 3, 3) 175 win2.overwrite(win, 1, 2, 2, 1, 3, 3) 176 stdscr.redrawln(1,2) 177 178 stdscr.scrollok(1) 179 stdscr.scroll() 180 stdscr.scroll(2) 181 stdscr.scroll(-3) 182 183 stdscr.move(12, 2) 184 stdscr.setscrreg(10,15) 185 win3 = stdscr.subwin(10,10) 186 win3 = stdscr.subwin(10,10, 5,5) 187 if hasattr(stdscr, 'syncok') and not sys.platform.startswith("sunos"): 188 stdscr.syncok(1) 189 stdscr.timeout(5) 190 stdscr.touchline(5,5) 191 stdscr.touchline(5,5,0) 192 stdscr.vline('a', 3) 193 stdscr.vline('a', 3, curses.A_STANDOUT) 194 if hasattr(stdscr, 'chgat'): 195 stdscr.chgat(5, 2, 3, curses.A_BLINK) 196 stdscr.chgat(3, curses.A_BOLD) 197 stdscr.chgat(5, 8, curses.A_UNDERLINE) 198 stdscr.chgat(curses.A_BLINK) 199 stdscr.refresh() 200 201 stdscr.vline(1,1, 'a', 3) 202 stdscr.vline(1,1, 'a', 3, curses.A_STANDOUT) 203 204 if hasattr(stdscr, 'resize'): 205 stdscr.resize(25, 80) 206 if hasattr(stdscr, 'enclose'): 207 stdscr.enclose(10, 10) 208 209 self.assertRaises(ValueError, stdscr.getstr, -400) 210 self.assertRaises(ValueError, stdscr.getstr, 2, 3, -400) 211 self.assertRaises(ValueError, stdscr.instr, -2) 212 self.assertRaises(ValueError, stdscr.instr, 2, 3, -2) 213 214 def test_embedded_null_chars(self): 215 # reject embedded null bytes and characters 216 stdscr = self.stdscr 217 for arg in ['a', b'a']: 218 with self.subTest(arg=arg): 219 self.assertRaises(ValueError, stdscr.addstr, 'a\0') 220 self.assertRaises(ValueError, stdscr.addnstr, 'a\0', 1) 221 self.assertRaises(ValueError, stdscr.insstr, 'a\0') 222 self.assertRaises(ValueError, stdscr.insnstr, 'a\0', 1) 223 224 def test_module_funcs(self): 225 "Test module-level functions" 226 for func in [curses.baudrate, curses.beep, curses.can_change_color, 227 curses.cbreak, curses.def_prog_mode, curses.doupdate, 228 curses.flash, curses.flushinp, 229 curses.has_colors, curses.has_ic, curses.has_il, 230 curses.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]: 235 with self.subTest(func=func.__qualname__): 236 func() 237 if hasattr(curses, 'filter'): 238 curses.filter() 239 if hasattr(curses, 'getsyx'): 240 curses.getsyx() 241 242 # Functions that actually need arguments 243 if curses.tigetstr("cnorm"): 244 curses.curs_set(1) 245 curses.delay_output(1) 246 curses.echo() ; curses.echo(1) 247 248 with tempfile.TemporaryFile() as f: 249 self.stdscr.putwin(f) 250 f.seek(0) 251 curses.getwin(f) 252 253 curses.halfdelay(1) 254 curses.intrflush(1) 255 curses.meta(1) 256 curses.napms(100) 257 curses.newpad(50,50) 258 win = curses.newwin(5,5) 259 win = curses.newwin(5,5, 1,1) 260 curses.nl() ; curses.nl(1) 261 curses.putp(b'abc') 262 curses.qiflush() 263 curses.raw() ; curses.raw(1) 264 if hasattr(curses, 'setsyx'): 265 curses.setsyx(5,5) 266 curses.tigetflag('hc') 267 curses.tigetnum('co') 268 curses.tigetstr('cr') 269 curses.tparm(b'cr') 270 if hasattr(curses, 'typeahead'): 271 curses.typeahead(sys.__stdin__.fileno()) 272 curses.unctrl('a') 273 curses.ungetch('a') 274 if hasattr(curses, 'use_env'): 275 curses.use_env(1) 276 277 # Functions only available on a few platforms 278 def test_colors_funcs(self): 279 if not curses.has_colors(): 280 self.skipTest('requires colors support') 281 curses.start_color() 282 curses.init_pair(2, 1,1) 283 curses.color_content(1) 284 curses.color_pair(2) 285 curses.pair_content(curses.COLOR_PAIRS - 1) 286 curses.pair_number(0) 287 288 if hasattr(curses, 'use_default_colors'): 289 curses.use_default_colors() 290 291 @requires_curses_func('keyname') 292 def test_keyname(self): 293 curses.keyname(13) 294 295 @requires_curses_func('has_key') 296 def test_has_key(self): 297 curses.has_key(13) 298 299 @requires_curses_func('getmouse') 300 def test_getmouse(self): 301 (availmask, oldmask) = curses.mousemask(curses.BUTTON1_PRESSED) 302 if availmask == 0: 303 self.skipTest('mouse stuff not available') 304 curses.mouseinterval(10) 305 # just verify these don't cause errors 306 curses.ungetmouse(0, 0, 0, 0, curses.BUTTON1_PRESSED) 307 m = curses.getmouse() 308 309 @requires_curses_func('panel') 310 def test_userptr_without_set(self): 311 w = curses.newwin(10, 10) 312 p = curses.panel.new_panel(w) 313 # try to access userptr() before calling set_userptr() -- segfaults 314 with self.assertRaises(curses.panel.error, 315 msg='userptr should fail since not set'): 316 p.userptr() 317 318 @requires_curses_func('panel') 319 def test_userptr_memory_leak(self): 320 w = curses.newwin(10, 10) 321 p = curses.panel.new_panel(w) 322 obj = object() 323 nrefs = sys.getrefcount(obj) 324 for i in range(100): 325 p.set_userptr(obj) 326 327 p.set_userptr(None) 328 self.assertEqual(sys.getrefcount(obj), nrefs, 329 "set_userptr leaked references") 330 331 @requires_curses_func('panel') 332 def test_userptr_segfault(self): 333 w = curses.newwin(10, 10) 334 panel = curses.panel.new_panel(w) 335 class A: 336 def __del__(self): 337 panel.set_userptr(None) 338 panel.set_userptr(A()) 339 panel.set_userptr(None) 340 341 @requires_curses_func('panel') 342 def test_new_curses_panel(self): 343 w = curses.newwin(10, 10) 344 panel = curses.panel.new_panel(w) 345 self.assertRaises(TypeError, type(panel)) 346 347 @requires_curses_func('is_term_resized') 348 def test_is_term_resized(self): 349 curses.is_term_resized(*self.stdscr.getmaxyx()) 350 351 @requires_curses_func('resize_term') 352 def test_resize_term(self): 353 curses.resize_term(*self.stdscr.getmaxyx()) 354 355 @requires_curses_func('resizeterm') 356 def test_resizeterm(self): 357 stdscr = self.stdscr 358 lines, cols = curses.LINES, curses.COLS 359 new_lines = lines - 1 360 new_cols = cols + 1 361 curses.resizeterm(new_lines, new_cols) 362 363 self.assertEqual(curses.LINES, new_lines) 364 self.assertEqual(curses.COLS, new_cols) 365 366 def test_issue6243(self): 367 curses.ungetch(1025) 368 self.stdscr.getkey() 369 370 @requires_curses_func('unget_wch') 371 @unittest.skipIf(getattr(curses, 'ncurses_version', (99,)) < (5, 8), 372 "unget_wch is broken in ncurses 5.7 and earlier") 373 def test_unget_wch(self): 374 stdscr = self.stdscr 375 encoding = stdscr.encoding 376 for ch in ('a', '\xe9', '\u20ac', '\U0010FFFF'): 377 try: 378 ch.encode(encoding) 379 except UnicodeEncodeError: 380 continue 381 try: 382 curses.unget_wch(ch) 383 except Exception as err: 384 self.fail("unget_wch(%a) failed with encoding %s: %s" 385 % (ch, stdscr.encoding, err)) 386 read = stdscr.get_wch() 387 self.assertEqual(read, ch) 388 389 code = ord(ch) 390 curses.unget_wch(code) 391 read = stdscr.get_wch() 392 self.assertEqual(read, ch) 393 394 def test_issue10570(self): 395 b = curses.tparm(curses.tigetstr("cup"), 5, 3) 396 self.assertIs(type(b), bytes) 397 398 def test_encoding(self): 399 stdscr = self.stdscr 400 import codecs 401 encoding = stdscr.encoding 402 codecs.lookup(encoding) 403 with self.assertRaises(TypeError): 404 stdscr.encoding = 10 405 stdscr.encoding = encoding 406 with self.assertRaises(TypeError): 407 del stdscr.encoding 408 409 def test_issue21088(self): 410 stdscr = self.stdscr 411 # 412 # http://bugs.python.org/issue21088 413 # 414 # the bug: 415 # when converting curses.window.addch to Argument Clinic 416 # the first two parameters were switched. 417 418 # if someday we can represent the signature of addch 419 # we will need to rewrite this test. 420 try: 421 signature = inspect.signature(stdscr.addch) 422 self.assertFalse(signature) 423 except ValueError: 424 # not generating a signature is fine. 425 pass 426 427 # So. No signature for addch. 428 # But Argument Clinic gave us a human-readable equivalent 429 # as the first line of the docstring. So we parse that, 430 # and ensure that the parameters appear in the correct order. 431 # Since this is parsing output from Argument Clinic, we can 432 # be reasonably certain the generated parsing code will be 433 # correct too. 434 human_readable_signature = stdscr.addch.__doc__.split("\n")[0] 435 self.assertIn("[y, x,]", human_readable_signature) 436 437 def test_issue13051(self): 438 stdscr = self.stdscr 439 if not hasattr(stdscr, 'resize'): 440 raise unittest.SkipTest('requires curses.window.resize') 441 box = curses.textpad.Textbox(stdscr, insert_mode=True) 442 lines, cols = stdscr.getmaxyx() 443 stdscr.resize(lines-2, cols-2) 444 # this may cause infinite recursion, leading to a RuntimeError 445 box._insert_printable_char('a') 446 447 448class MiscTests(unittest.TestCase): 449 450 @requires_curses_func('update_lines_cols') 451 def test_update_lines_cols(self): 452 # this doesn't actually test that LINES and COLS are updated, 453 # because we can't automate changing them. See Issue #4254 for 454 # a manual test script. We can only test that the function 455 # can be called. 456 curses.update_lines_cols() 457 458 @requires_curses_func('ncurses_version') 459 def test_ncurses_version(self): 460 v = curses.ncurses_version 461 self.assertIsInstance(v[:], tuple) 462 self.assertEqual(len(v), 3) 463 self.assertIsInstance(v[0], int) 464 self.assertIsInstance(v[1], int) 465 self.assertIsInstance(v[2], int) 466 self.assertIsInstance(v.major, int) 467 self.assertIsInstance(v.minor, int) 468 self.assertIsInstance(v.patch, int) 469 self.assertEqual(v[0], v.major) 470 self.assertEqual(v[1], v.minor) 471 self.assertEqual(v[2], v.patch) 472 self.assertGreaterEqual(v.major, 0) 473 self.assertGreaterEqual(v.minor, 0) 474 self.assertGreaterEqual(v.patch, 0) 475 476class TestAscii(unittest.TestCase): 477 478 def test_controlnames(self): 479 for name in curses.ascii.controlnames: 480 self.assertTrue(hasattr(curses.ascii, name), name) 481 482 def test_ctypes(self): 483 def check(func, expected): 484 with self.subTest(ch=c, func=func): 485 self.assertEqual(func(i), expected) 486 self.assertEqual(func(c), expected) 487 488 for i in range(256): 489 c = chr(i) 490 b = bytes([i]) 491 check(curses.ascii.isalnum, b.isalnum()) 492 check(curses.ascii.isalpha, b.isalpha()) 493 check(curses.ascii.isdigit, b.isdigit()) 494 check(curses.ascii.islower, b.islower()) 495 check(curses.ascii.isspace, b.isspace()) 496 check(curses.ascii.isupper, b.isupper()) 497 498 check(curses.ascii.isascii, i < 128) 499 check(curses.ascii.ismeta, i >= 128) 500 check(curses.ascii.isctrl, i < 32) 501 check(curses.ascii.iscntrl, i < 32 or i == 127) 502 check(curses.ascii.isblank, c in ' \t') 503 check(curses.ascii.isgraph, 32 < i <= 126) 504 check(curses.ascii.isprint, 32 <= i <= 126) 505 check(curses.ascii.ispunct, c in string.punctuation) 506 check(curses.ascii.isxdigit, c in string.hexdigits) 507 508 for i in (-2, -1, 256, sys.maxunicode, sys.maxunicode+1): 509 self.assertFalse(curses.ascii.isalnum(i)) 510 self.assertFalse(curses.ascii.isalpha(i)) 511 self.assertFalse(curses.ascii.isdigit(i)) 512 self.assertFalse(curses.ascii.islower(i)) 513 self.assertFalse(curses.ascii.isspace(i)) 514 self.assertFalse(curses.ascii.isupper(i)) 515 516 self.assertFalse(curses.ascii.isascii(i)) 517 self.assertFalse(curses.ascii.isctrl(i)) 518 self.assertFalse(curses.ascii.iscntrl(i)) 519 self.assertFalse(curses.ascii.isblank(i)) 520 self.assertFalse(curses.ascii.isgraph(i)) 521 self.assertFalse(curses.ascii.isprint(i)) 522 self.assertFalse(curses.ascii.ispunct(i)) 523 self.assertFalse(curses.ascii.isxdigit(i)) 524 525 self.assertFalse(curses.ascii.ismeta(-1)) 526 527 def test_ascii(self): 528 ascii = curses.ascii.ascii 529 self.assertEqual(ascii('\xc1'), 'A') 530 self.assertEqual(ascii('A'), 'A') 531 self.assertEqual(ascii(ord('\xc1')), ord('A')) 532 533 def test_ctrl(self): 534 ctrl = curses.ascii.ctrl 535 self.assertEqual(ctrl('J'), '\n') 536 self.assertEqual(ctrl('\n'), '\n') 537 self.assertEqual(ctrl('@'), '\0') 538 self.assertEqual(ctrl(ord('J')), ord('\n')) 539 540 def test_alt(self): 541 alt = curses.ascii.alt 542 self.assertEqual(alt('\n'), '\x8a') 543 self.assertEqual(alt('A'), '\xc1') 544 self.assertEqual(alt(ord('A')), 0xc1) 545 546 def test_unctrl(self): 547 unctrl = curses.ascii.unctrl 548 self.assertEqual(unctrl('a'), 'a') 549 self.assertEqual(unctrl('A'), 'A') 550 self.assertEqual(unctrl(';'), ';') 551 self.assertEqual(unctrl(' '), ' ') 552 self.assertEqual(unctrl('\x7f'), '^?') 553 self.assertEqual(unctrl('\n'), '^J') 554 self.assertEqual(unctrl('\0'), '^@') 555 self.assertEqual(unctrl(ord('A')), 'A') 556 self.assertEqual(unctrl(ord('\n')), '^J') 557 # Meta-bit characters 558 self.assertEqual(unctrl('\x8a'), '!^J') 559 self.assertEqual(unctrl('\xc1'), '!A') 560 self.assertEqual(unctrl(ord('\x8a')), '!^J') 561 self.assertEqual(unctrl(ord('\xc1')), '!A') 562 563 564if __name__ == '__main__': 565 unittest.main() 566