• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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