• 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    # XXX Remove the decorator when ncurses on OpenBSD be updated
372    @unittest.skipIf(sys.platform.startswith("openbsd"),
373                     "OpenBSD's curses (v.5.7) has bugs")
374    def test_unget_wch(self):
375        stdscr = self.stdscr
376        encoding = stdscr.encoding
377        for ch in ('a', '\xe9', '\u20ac', '\U0010FFFF'):
378            try:
379                ch.encode(encoding)
380            except UnicodeEncodeError:
381                continue
382            try:
383                curses.unget_wch(ch)
384            except Exception as err:
385                self.fail("unget_wch(%a) failed with encoding %s: %s"
386                          % (ch, stdscr.encoding, err))
387            read = stdscr.get_wch()
388            self.assertEqual(read, ch)
389
390            code = ord(ch)
391            curses.unget_wch(code)
392            read = stdscr.get_wch()
393            self.assertEqual(read, ch)
394
395    def test_issue10570(self):
396        b = curses.tparm(curses.tigetstr("cup"), 5, 3)
397        self.assertIs(type(b), bytes)
398
399    def test_encoding(self):
400        stdscr = self.stdscr
401        import codecs
402        encoding = stdscr.encoding
403        codecs.lookup(encoding)
404        with self.assertRaises(TypeError):
405            stdscr.encoding = 10
406        stdscr.encoding = encoding
407        with self.assertRaises(TypeError):
408            del stdscr.encoding
409
410    def test_issue21088(self):
411        stdscr = self.stdscr
412        #
413        # http://bugs.python.org/issue21088
414        #
415        # the bug:
416        # when converting curses.window.addch to Argument Clinic
417        # the first two parameters were switched.
418
419        # if someday we can represent the signature of addch
420        # we will need to rewrite this test.
421        try:
422            signature = inspect.signature(stdscr.addch)
423            self.assertFalse(signature)
424        except ValueError:
425            # not generating a signature is fine.
426            pass
427
428        # So.  No signature for addch.
429        # But Argument Clinic gave us a human-readable equivalent
430        # as the first line of the docstring.  So we parse that,
431        # and ensure that the parameters appear in the correct order.
432        # Since this is parsing output from Argument Clinic, we can
433        # be reasonably certain the generated parsing code will be
434        # correct too.
435        human_readable_signature = stdscr.addch.__doc__.split("\n")[0]
436        self.assertIn("[y, x,]", human_readable_signature)
437
438    def test_issue13051(self):
439        stdscr = self.stdscr
440        if not hasattr(stdscr, 'resize'):
441            raise unittest.SkipTest('requires curses.window.resize')
442        box = curses.textpad.Textbox(stdscr, insert_mode=True)
443        lines, cols = stdscr.getmaxyx()
444        stdscr.resize(lines-2, cols-2)
445        # this may cause infinite recursion, leading to a RuntimeError
446        box._insert_printable_char('a')
447
448
449class MiscTests(unittest.TestCase):
450
451    @requires_curses_func('update_lines_cols')
452    def test_update_lines_cols(self):
453        # this doesn't actually test that LINES and COLS are updated,
454        # because we can't automate changing them. See Issue #4254 for
455        # a manual test script. We can only test that the function
456        # can be called.
457        curses.update_lines_cols()
458
459
460class TestAscii(unittest.TestCase):
461
462    def test_controlnames(self):
463        for name in curses.ascii.controlnames:
464            self.assertTrue(hasattr(curses.ascii, name), name)
465
466    def test_ctypes(self):
467        def check(func, expected):
468            with self.subTest(ch=c, func=func):
469                self.assertEqual(func(i), expected)
470                self.assertEqual(func(c), expected)
471
472        for i in range(256):
473            c = chr(i)
474            b = bytes([i])
475            check(curses.ascii.isalnum, b.isalnum())
476            check(curses.ascii.isalpha, b.isalpha())
477            check(curses.ascii.isdigit, b.isdigit())
478            check(curses.ascii.islower, b.islower())
479            check(curses.ascii.isspace, b.isspace())
480            check(curses.ascii.isupper, b.isupper())
481
482            check(curses.ascii.isascii, i < 128)
483            check(curses.ascii.ismeta, i >= 128)
484            check(curses.ascii.isctrl, i < 32)
485            check(curses.ascii.iscntrl, i < 32 or i == 127)
486            check(curses.ascii.isblank, c in ' \t')
487            check(curses.ascii.isgraph, 32 < i <= 126)
488            check(curses.ascii.isprint, 32 <= i <= 126)
489            check(curses.ascii.ispunct, c in string.punctuation)
490            check(curses.ascii.isxdigit, c in string.hexdigits)
491
492        for i in (-2, -1, 256, sys.maxunicode, sys.maxunicode+1):
493            self.assertFalse(curses.ascii.isalnum(i))
494            self.assertFalse(curses.ascii.isalpha(i))
495            self.assertFalse(curses.ascii.isdigit(i))
496            self.assertFalse(curses.ascii.islower(i))
497            self.assertFalse(curses.ascii.isspace(i))
498            self.assertFalse(curses.ascii.isupper(i))
499
500            self.assertFalse(curses.ascii.isascii(i))
501            self.assertFalse(curses.ascii.isctrl(i))
502            self.assertFalse(curses.ascii.iscntrl(i))
503            self.assertFalse(curses.ascii.isblank(i))
504            self.assertFalse(curses.ascii.isgraph(i))
505            self.assertFalse(curses.ascii.isprint(i))
506            self.assertFalse(curses.ascii.ispunct(i))
507            self.assertFalse(curses.ascii.isxdigit(i))
508
509        self.assertFalse(curses.ascii.ismeta(-1))
510
511    def test_ascii(self):
512        ascii = curses.ascii.ascii
513        self.assertEqual(ascii('\xc1'), 'A')
514        self.assertEqual(ascii('A'), 'A')
515        self.assertEqual(ascii(ord('\xc1')), ord('A'))
516
517    def test_ctrl(self):
518        ctrl = curses.ascii.ctrl
519        self.assertEqual(ctrl('J'), '\n')
520        self.assertEqual(ctrl('\n'), '\n')
521        self.assertEqual(ctrl('@'), '\0')
522        self.assertEqual(ctrl(ord('J')), ord('\n'))
523
524    def test_alt(self):
525        alt = curses.ascii.alt
526        self.assertEqual(alt('\n'), '\x8a')
527        self.assertEqual(alt('A'), '\xc1')
528        self.assertEqual(alt(ord('A')), 0xc1)
529
530    def test_unctrl(self):
531        unctrl = curses.ascii.unctrl
532        self.assertEqual(unctrl('a'), 'a')
533        self.assertEqual(unctrl('A'), 'A')
534        self.assertEqual(unctrl(';'), ';')
535        self.assertEqual(unctrl(' '), ' ')
536        self.assertEqual(unctrl('\x7f'), '^?')
537        self.assertEqual(unctrl('\n'), '^J')
538        self.assertEqual(unctrl('\0'), '^@')
539        self.assertEqual(unctrl(ord('A')), 'A')
540        self.assertEqual(unctrl(ord('\n')), '^J')
541        # Meta-bit characters
542        self.assertEqual(unctrl('\x8a'), '!^J')
543        self.assertEqual(unctrl('\xc1'), '!A')
544        self.assertEqual(unctrl(ord('\x8a')), '!^J')
545        self.assertEqual(unctrl(ord('\xc1')), '!A')
546
547
548if __name__ == '__main__':
549    unittest.main()
550