1import functools
2import inspect
3import os
4import string
5import sys
6import tempfile
7import unittest
8
9from test.support import (requires, verbose, SaveSignals, cpython_only,
10                          check_disallow_instantiation)
11from test.support.import_helper import import_module
12
13# Optionally test curses module.  This currently requires that the
14# 'curses' resource be given on the regrtest command line using the -u
15# option.  If not available, nothing after this line will be executed.
16requires('curses')
17
18# If either of these don't exist, skip the tests.
19curses = import_module('curses')
20import_module('curses.ascii')
21import_module('curses.textpad')
22try:
23    import curses.panel
24except ImportError:
25    pass
26
27def requires_curses_func(name):
28    return unittest.skipUnless(hasattr(curses, name),
29                               'requires curses.%s' % name)
30
31def requires_curses_window_meth(name):
32    def deco(test):
33        @functools.wraps(test)
34        def wrapped(self, *args, **kwargs):
35            if not hasattr(self.stdscr, name):
36                raise unittest.SkipTest('requires curses.window.%s' % name)
37            test(self, *args, **kwargs)
38        return wrapped
39    return deco
40
41
42def requires_colors(test):
43    @functools.wraps(test)
44    def wrapped(self, *args, **kwargs):
45        if not curses.has_colors():
46            self.skipTest('requires colors support')
47        curses.start_color()
48        test(self, *args, **kwargs)
49    return wrapped
50
51term = os.environ.get('TERM')
52SHORT_MAX = 0x7fff
53
54# If newterm was supported we could use it instead of initscr and not exit
55@unittest.skipIf(not term or term == 'unknown',
56                 "$TERM=%r, calling initscr() may cause exit" % term)
57@unittest.skipIf(sys.platform == "cygwin",
58                 "cygwin's curses mostly just hangs")
59class TestCurses(unittest.TestCase):
60
61    @classmethod
62    def setUpClass(cls):
63        if verbose:
64            print(f'TERM={term}', file=sys.stderr, flush=True)
65        # testing setupterm() inside initscr/endwin
66        # causes terminal breakage
67        stdout_fd = sys.__stdout__.fileno()
68        curses.setupterm(fd=stdout_fd)
69
70    def setUp(self):
71        self.isatty = True
72        self.output = sys.__stdout__
73        stdout_fd = sys.__stdout__.fileno()
74        if not sys.__stdout__.isatty():
75            # initstr() unconditionally uses C stdout.
76            # If it is redirected to file or pipe, try to attach it
77            # to terminal.
78            # First, save a copy of the file descriptor of stdout, so it
79            # can be restored after finishing the test.
80            dup_fd = os.dup(stdout_fd)
81            self.addCleanup(os.close, dup_fd)
82            self.addCleanup(os.dup2, dup_fd, stdout_fd)
83
84            if sys.__stderr__.isatty():
85                # If stderr is connected to terminal, use it.
86                tmp = sys.__stderr__
87                self.output = sys.__stderr__
88            else:
89                try:
90                    # Try to open the terminal device.
91                    tmp = open('/dev/tty', 'wb', buffering=0)
92                except OSError:
93                    # As a fallback, use regular file to write control codes.
94                    # Some functions (like savetty) will not work, but at
95                    # least the garbage control sequences will not be mixed
96                    # with the testing report.
97                    tmp = tempfile.TemporaryFile(mode='wb', buffering=0)
98                    self.isatty = False
99                self.addCleanup(tmp.close)
100                self.output = None
101            os.dup2(tmp.fileno(), stdout_fd)
102
103        self.save_signals = SaveSignals()
104        self.save_signals.save()
105        self.addCleanup(self.save_signals.restore)
106        if verbose and self.output is not None:
107            # just to make the test output a little more readable
108            sys.stderr.flush()
109            sys.stdout.flush()
110            print(file=self.output, flush=True)
111        self.stdscr = curses.initscr()
112        if self.isatty:
113            curses.savetty()
114            self.addCleanup(curses.endwin)
115            self.addCleanup(curses.resetty)
116        self.stdscr.erase()
117
118    @requires_curses_func('filter')
119    def test_filter(self):
120        # TODO: Should be called before initscr() or newterm() are called.
121        # TODO: nofilter()
122        curses.filter()
123
124    @requires_curses_func('use_env')
125    def test_use_env(self):
126        # TODO: Should be called before initscr() or newterm() are called.
127        # TODO: use_tioctl()
128        curses.use_env(False)
129        curses.use_env(True)
130
131    def test_create_windows(self):
132        win = curses.newwin(5, 10)
133        self.assertEqual(win.getbegyx(), (0, 0))
134        self.assertEqual(win.getparyx(), (-1, -1))
135        self.assertEqual(win.getmaxyx(), (5, 10))
136
137        win = curses.newwin(10, 15, 2, 5)
138        self.assertEqual(win.getbegyx(), (2, 5))
139        self.assertEqual(win.getparyx(), (-1, -1))
140        self.assertEqual(win.getmaxyx(), (10, 15))
141
142        win2 = win.subwin(3, 7)
143        self.assertEqual(win2.getbegyx(), (3, 7))
144        self.assertEqual(win2.getparyx(), (1, 2))
145        self.assertEqual(win2.getmaxyx(), (9, 13))
146
147        win2 = win.subwin(5, 10, 3, 7)
148        self.assertEqual(win2.getbegyx(), (3, 7))
149        self.assertEqual(win2.getparyx(), (1, 2))
150        self.assertEqual(win2.getmaxyx(), (5, 10))
151
152        win3 = win.derwin(2, 3)
153        self.assertEqual(win3.getbegyx(), (4, 8))
154        self.assertEqual(win3.getparyx(), (2, 3))
155        self.assertEqual(win3.getmaxyx(), (8, 12))
156
157        win3 = win.derwin(6, 11, 2, 3)
158        self.assertEqual(win3.getbegyx(), (4, 8))
159        self.assertEqual(win3.getparyx(), (2, 3))
160        self.assertEqual(win3.getmaxyx(), (6, 11))
161
162        win.mvwin(0, 1)
163        self.assertEqual(win.getbegyx(), (0, 1))
164        self.assertEqual(win.getparyx(), (-1, -1))
165        self.assertEqual(win.getmaxyx(), (10, 15))
166        self.assertEqual(win2.getbegyx(), (3, 7))
167        self.assertEqual(win2.getparyx(), (1, 2))
168        self.assertEqual(win2.getmaxyx(), (5, 10))
169        self.assertEqual(win3.getbegyx(), (4, 8))
170        self.assertEqual(win3.getparyx(), (2, 3))
171        self.assertEqual(win3.getmaxyx(), (6, 11))
172
173        win2.mvderwin(2, 1)
174        self.assertEqual(win2.getbegyx(), (3, 7))
175        self.assertEqual(win2.getparyx(), (2, 1))
176        self.assertEqual(win2.getmaxyx(), (5, 10))
177
178        win3.mvderwin(2, 1)
179        self.assertEqual(win3.getbegyx(), (4, 8))
180        self.assertEqual(win3.getparyx(), (2, 1))
181        self.assertEqual(win3.getmaxyx(), (6, 11))
182
183    def test_move_cursor(self):
184        stdscr = self.stdscr
185        win = stdscr.subwin(10, 15, 2, 5)
186        stdscr.move(1, 2)
187        win.move(2, 4)
188        self.assertEqual(stdscr.getyx(), (1, 2))
189        self.assertEqual(win.getyx(), (2, 4))
190
191        win.cursyncup()
192        self.assertEqual(stdscr.getyx(), (4, 9))
193
194    def test_refresh_control(self):
195        stdscr = self.stdscr
196        # touchwin()/untouchwin()/is_wintouched()
197        stdscr.refresh()
198        self.assertIs(stdscr.is_wintouched(), False)
199        stdscr.touchwin()
200        self.assertIs(stdscr.is_wintouched(), True)
201        stdscr.refresh()
202        self.assertIs(stdscr.is_wintouched(), False)
203        stdscr.touchwin()
204        self.assertIs(stdscr.is_wintouched(), True)
205        stdscr.untouchwin()
206        self.assertIs(stdscr.is_wintouched(), False)
207
208        # touchline()/untouchline()/is_linetouched()
209        stdscr.touchline(5, 2)
210        self.assertIs(stdscr.is_linetouched(5), True)
211        self.assertIs(stdscr.is_linetouched(6), True)
212        self.assertIs(stdscr.is_wintouched(), True)
213        stdscr.touchline(5, 1, False)
214        self.assertIs(stdscr.is_linetouched(5), False)
215
216        # syncup()
217        win = stdscr.subwin(10, 15, 2, 5)
218        win2 = win.subwin(5, 10, 3, 7)
219        win2.touchwin()
220        stdscr.untouchwin()
221        win2.syncup()
222        self.assertIs(win.is_wintouched(), True)
223        self.assertIs(stdscr.is_wintouched(), True)
224
225        # syncdown()
226        stdscr.touchwin()
227        win.untouchwin()
228        win2.untouchwin()
229        win2.syncdown()
230        self.assertIs(win2.is_wintouched(), True)
231
232        # syncok()
233        if hasattr(stdscr, 'syncok') and not sys.platform.startswith("sunos"):
234            win.untouchwin()
235            stdscr.untouchwin()
236            for syncok in [False, True]:
237                win2.syncok(syncok)
238                win2.addch('a')
239                self.assertIs(win.is_wintouched(), syncok)
240                self.assertIs(stdscr.is_wintouched(), syncok)
241
242    def test_output_character(self):
243        stdscr = self.stdscr
244        encoding = stdscr.encoding
245        # addch()
246        stdscr.refresh()
247        stdscr.move(0, 0)
248        stdscr.addch('A')
249        stdscr.addch(b'A')
250        stdscr.addch(65)
251        c = '\u20ac'
252        try:
253            stdscr.addch(c)
254        except UnicodeEncodeError:
255            self.assertRaises(UnicodeEncodeError, c.encode, encoding)
256        except OverflowError:
257            encoded = c.encode(encoding)
258            self.assertNotEqual(len(encoded), 1, repr(encoded))
259        stdscr.addch('A', curses.A_BOLD)
260        stdscr.addch(1, 2, 'A')
261        stdscr.addch(2, 3, 'A', curses.A_BOLD)
262        self.assertIs(stdscr.is_wintouched(), True)
263
264        # echochar()
265        stdscr.refresh()
266        stdscr.move(0, 0)
267        stdscr.echochar('A')
268        stdscr.echochar(b'A')
269        stdscr.echochar(65)
270        with self.assertRaises((UnicodeEncodeError, OverflowError)):
271            stdscr.echochar('\u20ac')
272        stdscr.echochar('A', curses.A_BOLD)
273        self.assertIs(stdscr.is_wintouched(), False)
274
275    def test_output_string(self):
276        stdscr = self.stdscr
277        encoding = stdscr.encoding
278        # addstr()/insstr()
279        for func in [stdscr.addstr, stdscr.insstr]:
280            with self.subTest(func.__qualname__):
281                stdscr.move(0, 0)
282                func('abcd')
283                func(b'abcd')
284                s = 'àßçđ'
285                try:
286                    func(s)
287                except UnicodeEncodeError:
288                    self.assertRaises(UnicodeEncodeError, s.encode, encoding)
289                func('abcd', curses.A_BOLD)
290                func(1, 2, 'abcd')
291                func(2, 3, 'abcd', curses.A_BOLD)
292
293        # addnstr()/insnstr()
294        for func in [stdscr.addnstr, stdscr.insnstr]:
295            with self.subTest(func.__qualname__):
296                stdscr.move(0, 0)
297                func('1234', 3)
298                func(b'1234', 3)
299                s = '\u0661\u0662\u0663\u0664'
300                try:
301                    func(s, 3)
302                except UnicodeEncodeError:
303                    self.assertRaises(UnicodeEncodeError, s.encode, encoding)
304                func('1234', 5)
305                func('1234', 3, curses.A_BOLD)
306                func(1, 2, '1234', 3)
307                func(2, 3, '1234', 3, curses.A_BOLD)
308
309    def test_output_string_embedded_null_chars(self):
310        # reject embedded null bytes and characters
311        stdscr = self.stdscr
312        for arg in ['a\0', b'a\0']:
313            with self.subTest(arg=arg):
314                self.assertRaises(ValueError, stdscr.addstr, arg)
315                self.assertRaises(ValueError, stdscr.addnstr, arg, 1)
316                self.assertRaises(ValueError, stdscr.insstr, arg)
317                self.assertRaises(ValueError, stdscr.insnstr, arg, 1)
318
319    def test_read_from_window(self):
320        stdscr = self.stdscr
321        stdscr.addstr(0, 1, 'ABCD', curses.A_BOLD)
322        # inch()
323        stdscr.move(0, 1)
324        self.assertEqual(stdscr.inch(), 65 | curses.A_BOLD)
325        self.assertEqual(stdscr.inch(0, 3), 67 | curses.A_BOLD)
326        stdscr.move(0, 0)
327        # instr()
328        self.assertEqual(stdscr.instr()[:6], b' ABCD ')
329        self.assertEqual(stdscr.instr(3)[:6], b' AB')
330        self.assertEqual(stdscr.instr(0, 2)[:4], b'BCD ')
331        self.assertEqual(stdscr.instr(0, 2, 4), b'BCD ')
332        self.assertRaises(ValueError, stdscr.instr, -2)
333        self.assertRaises(ValueError, stdscr.instr, 0, 2, -2)
334
335    def test_getch(self):
336        win = curses.newwin(5, 12, 5, 2)
337
338        # TODO: Test with real input by writing to master fd.
339        for c in 'spam\n'[::-1]:
340            curses.ungetch(c)
341        self.assertEqual(win.getch(3, 1), b's'[0])
342        self.assertEqual(win.getyx(), (3, 1))
343        self.assertEqual(win.getch(3, 4), b'p'[0])
344        self.assertEqual(win.getyx(), (3, 4))
345        self.assertEqual(win.getch(), b'a'[0])
346        self.assertEqual(win.getyx(), (3, 4))
347        self.assertEqual(win.getch(), b'm'[0])
348        self.assertEqual(win.getch(), b'\n'[0])
349
350    def test_getstr(self):
351        win = curses.newwin(5, 12, 5, 2)
352        curses.echo()
353        self.addCleanup(curses.noecho)
354
355        self.assertRaises(ValueError, win.getstr, -400)
356        self.assertRaises(ValueError, win.getstr, 2, 3, -400)
357
358        # TODO: Test with real input by writing to master fd.
359        for c in 'Lorem\nipsum\ndolor\nsit\namet\n'[::-1]:
360            curses.ungetch(c)
361        self.assertEqual(win.getstr(3, 1, 2), b'Lo')
362        self.assertEqual(win.instr(3, 0), b' Lo         ')
363        self.assertEqual(win.getstr(3, 5, 10), b'ipsum')
364        self.assertEqual(win.instr(3, 0), b' Lo  ipsum  ')
365        self.assertEqual(win.getstr(1, 5), b'dolor')
366        self.assertEqual(win.instr(1, 0), b'     dolor  ')
367        self.assertEqual(win.getstr(2), b'si')
368        self.assertEqual(win.instr(1, 0), b'si   dolor  ')
369        self.assertEqual(win.getstr(), b'amet')
370        self.assertEqual(win.instr(1, 0), b'amet dolor  ')
371
372    def test_clear(self):
373        win = curses.newwin(5, 15, 5, 2)
374        lorem_ipsum(win)
375
376        win.move(0, 8)
377        win.clrtoeol()
378        self.assertEqual(win.instr(0, 0).rstrip(), b'Lorem ip')
379        self.assertEqual(win.instr(1, 0).rstrip(), b'dolor sit amet,')
380
381        win.move(0, 3)
382        win.clrtobot()
383        self.assertEqual(win.instr(0, 0).rstrip(), b'Lor')
384        self.assertEqual(win.instr(1, 0).rstrip(), b'')
385
386        for func in [win.erase, win.clear]:
387            lorem_ipsum(win)
388            func()
389            self.assertEqual(win.instr(0, 0).rstrip(), b'')
390            self.assertEqual(win.instr(1, 0).rstrip(), b'')
391
392    def test_insert_delete(self):
393        win = curses.newwin(5, 15, 5, 2)
394        lorem_ipsum(win)
395
396        win.move(0, 2)
397        win.delch()
398        self.assertEqual(win.instr(0, 0), b'Loem ipsum     ')
399        win.delch(0, 7)
400        self.assertEqual(win.instr(0, 0), b'Loem ipum      ')
401
402        win.move(1, 5)
403        win.deleteln()
404        self.assertEqual(win.instr(0, 0), b'Loem ipum      ')
405        self.assertEqual(win.instr(1, 0), b'consectetur    ')
406        self.assertEqual(win.instr(2, 0), b'adipiscing elit')
407        self.assertEqual(win.instr(3, 0), b'sed do eiusmod ')
408        self.assertEqual(win.instr(4, 0), b'               ')
409
410        win.move(1, 5)
411        win.insertln()
412        self.assertEqual(win.instr(0, 0), b'Loem ipum      ')
413        self.assertEqual(win.instr(1, 0), b'               ')
414        self.assertEqual(win.instr(2, 0), b'consectetur    ')
415
416        win.clear()
417        lorem_ipsum(win)
418        win.move(1, 5)
419        win.insdelln(2)
420        self.assertEqual(win.instr(0, 0), b'Lorem ipsum    ')
421        self.assertEqual(win.instr(1, 0), b'               ')
422        self.assertEqual(win.instr(2, 0), b'               ')
423        self.assertEqual(win.instr(3, 0), b'dolor sit amet,')
424
425        win.clear()
426        lorem_ipsum(win)
427        win.move(1, 5)
428        win.insdelln(-2)
429        self.assertEqual(win.instr(0, 0), b'Lorem ipsum    ')
430        self.assertEqual(win.instr(1, 0), b'adipiscing elit')
431        self.assertEqual(win.instr(2, 0), b'sed do eiusmod ')
432        self.assertEqual(win.instr(3, 0), b'               ')
433
434    def test_scroll(self):
435        win = curses.newwin(5, 15, 5, 2)
436        lorem_ipsum(win)
437        win.scrollok(True)
438        win.scroll()
439        self.assertEqual(win.instr(0, 0), b'dolor sit amet,')
440        win.scroll(2)
441        self.assertEqual(win.instr(0, 0), b'adipiscing elit')
442        win.scroll(-3)
443        self.assertEqual(win.instr(0, 0), b'               ')
444        self.assertEqual(win.instr(2, 0), b'               ')
445        self.assertEqual(win.instr(3, 0), b'adipiscing elit')
446        win.scrollok(False)
447
448    def test_attributes(self):
449        # TODO: attr_get(), attr_set(), ...
450        win = curses.newwin(5, 15, 5, 2)
451        win.attron(curses.A_BOLD)
452        win.attroff(curses.A_BOLD)
453        win.attrset(curses.A_BOLD)
454
455        win.standout()
456        win.standend()
457
458    @requires_curses_window_meth('chgat')
459    def test_chgat(self):
460        win = curses.newwin(5, 15, 5, 2)
461        win.addstr(2, 0, 'Lorem ipsum')
462        win.addstr(3, 0, 'dolor sit amet')
463
464        win.move(2, 8)
465        win.chgat(curses.A_BLINK)
466        self.assertEqual(win.inch(2, 7), b'p'[0])
467        self.assertEqual(win.inch(2, 8), b's'[0] | curses.A_BLINK)
468        self.assertEqual(win.inch(2, 14), b' '[0] | curses.A_BLINK)
469
470        win.move(2, 1)
471        win.chgat(3, curses.A_BOLD)
472        self.assertEqual(win.inch(2, 0), b'L'[0])
473        self.assertEqual(win.inch(2, 1), b'o'[0] | curses.A_BOLD)
474        self.assertEqual(win.inch(2, 3), b'e'[0] | curses.A_BOLD)
475        self.assertEqual(win.inch(2, 4), b'm'[0])
476
477        win.chgat(3, 2, curses.A_UNDERLINE)
478        self.assertEqual(win.inch(3, 1), b'o'[0])
479        self.assertEqual(win.inch(3, 2), b'l'[0] | curses.A_UNDERLINE)
480        self.assertEqual(win.inch(3, 14), b' '[0] | curses.A_UNDERLINE)
481
482        win.chgat(3, 4, 7, curses.A_BLINK)
483        self.assertEqual(win.inch(3, 3), b'o'[0] | curses.A_UNDERLINE)
484        self.assertEqual(win.inch(3, 4), b'r'[0] | curses.A_BLINK)
485        self.assertEqual(win.inch(3, 10), b'a'[0] | curses.A_BLINK)
486        self.assertEqual(win.inch(3, 11), b'm'[0] | curses.A_UNDERLINE)
487        self.assertEqual(win.inch(3, 14), b' '[0] | curses.A_UNDERLINE)
488
489    def test_background(self):
490        win = curses.newwin(5, 15, 5, 2)
491        win.addstr(0, 0, 'Lorem ipsum')
492
493        self.assertIn(win.getbkgd(), (0, 32))
494
495        # bkgdset()
496        win.bkgdset('_')
497        self.assertEqual(win.getbkgd(), b'_'[0])
498        win.bkgdset(b'#')
499        self.assertEqual(win.getbkgd(), b'#'[0])
500        win.bkgdset(65)
501        self.assertEqual(win.getbkgd(), 65)
502        win.bkgdset(0)
503        self.assertEqual(win.getbkgd(), 32)
504
505        win.bkgdset('#', curses.A_REVERSE)
506        self.assertEqual(win.getbkgd(), b'#'[0] | curses.A_REVERSE)
507        self.assertEqual(win.inch(0, 0), b'L'[0])
508        self.assertEqual(win.inch(0, 5), b' '[0])
509        win.bkgdset(0)
510
511        # bkgd()
512        win.bkgd('_')
513        self.assertEqual(win.getbkgd(), b'_'[0])
514        self.assertEqual(win.inch(0, 0), b'L'[0])
515        self.assertEqual(win.inch(0, 5), b'_'[0])
516
517        win.bkgd('#', curses.A_REVERSE)
518        self.assertEqual(win.getbkgd(), b'#'[0] | curses.A_REVERSE)
519        self.assertEqual(win.inch(0, 0), b'L'[0] | curses.A_REVERSE)
520        self.assertEqual(win.inch(0, 5), b'#'[0] | curses.A_REVERSE)
521
522    def test_overlay(self):
523        srcwin = curses.newwin(5, 18, 3, 4)
524        lorem_ipsum(srcwin)
525        dstwin = curses.newwin(7, 17, 5, 7)
526        for i in range(6):
527            dstwin.addstr(i, 0, '_'*17)
528
529        srcwin.overlay(dstwin)
530        self.assertEqual(dstwin.instr(0, 0), b'sectetur_________')
531        self.assertEqual(dstwin.instr(1, 0), b'piscing_elit,____')
532        self.assertEqual(dstwin.instr(2, 0), b'_do_eiusmod______')
533        self.assertEqual(dstwin.instr(3, 0), b'_________________')
534
535        srcwin.overwrite(dstwin)
536        self.assertEqual(dstwin.instr(0, 0), b'sectetur       __')
537        self.assertEqual(dstwin.instr(1, 0), b'piscing elit,  __')
538        self.assertEqual(dstwin.instr(2, 0), b' do eiusmod    __')
539        self.assertEqual(dstwin.instr(3, 0), b'_________________')
540
541        srcwin.overlay(dstwin, 1, 4, 3, 2, 4, 11)
542        self.assertEqual(dstwin.instr(3, 0), b'__r_sit_amet_____')
543        self.assertEqual(dstwin.instr(4, 0), b'__ectetur________')
544        self.assertEqual(dstwin.instr(5, 0), b'_________________')
545
546        srcwin.overwrite(dstwin, 1, 4, 3, 2, 4, 11)
547        self.assertEqual(dstwin.instr(3, 0), b'__r sit amet_____')
548        self.assertEqual(dstwin.instr(4, 0), b'__ectetur   _____')
549        self.assertEqual(dstwin.instr(5, 0), b'_________________')
550
551    def test_refresh(self):
552        win = curses.newwin(5, 15, 2, 5)
553        win.noutrefresh()
554        win.redrawln(1, 2)
555        win.redrawwin()
556        win.refresh()
557        curses.doupdate()
558
559    @requires_curses_window_meth('resize')
560    def test_resize(self):
561        win = curses.newwin(5, 15, 2, 5)
562        win.resize(4, 20)
563        self.assertEqual(win.getmaxyx(), (4, 20))
564        win.resize(5, 15)
565        self.assertEqual(win.getmaxyx(), (5, 15))
566
567    @requires_curses_window_meth('enclose')
568    def test_enclose(self):
569        win = curses.newwin(5, 15, 2, 5)
570        self.assertIs(win.enclose(2, 5), True)
571        self.assertIs(win.enclose(1, 5), False)
572        self.assertIs(win.enclose(2, 4), False)
573        self.assertIs(win.enclose(6, 19), True)
574        self.assertIs(win.enclose(7, 19), False)
575        self.assertIs(win.enclose(6, 20), False)
576
577    def test_putwin(self):
578        win = curses.newwin(5, 12, 1, 2)
579        win.addstr(2, 1, 'Lorem ipsum')
580        with tempfile.TemporaryFile() as f:
581            win.putwin(f)
582            del win
583            f.seek(0)
584            win = curses.getwin(f)
585            self.assertEqual(win.getbegyx(), (1, 2))
586            self.assertEqual(win.getmaxyx(), (5, 12))
587            self.assertEqual(win.instr(2, 0), b' Lorem ipsum')
588
589    def test_borders_and_lines(self):
590        win = curses.newwin(5, 10, 5, 2)
591        win.border('|', '!', '-', '_',
592                   '+', '\\', '#', '/')
593        self.assertEqual(win.instr(0, 0), b'+--------\\')
594        self.assertEqual(win.instr(1, 0), b'|        !')
595        self.assertEqual(win.instr(4, 0), b'#________/')
596        win.border(b'|', b'!', b'-', b'_',
597                   b'+', b'\\', b'#', b'/')
598        win.border(65, 66, 67, 68,
599                   69, 70, 71, 72)
600        self.assertRaises(TypeError, win.border,
601                          65, 66, 67, 68, 69, [], 71, 72)
602        self.assertRaises(TypeError, win.border,
603                          65, 66, 67, 68, 69, 70, 71, 72, 73)
604        self.assertRaises(TypeError, win.border,
605                          65, 66, 67, 68, 69, 70, 71, 72, 73)
606        win.border(65, 66, 67, 68, 69, 70, 71)
607        win.border(65, 66, 67, 68, 69, 70)
608        win.border(65, 66, 67, 68, 69)
609        win.border(65, 66, 67, 68)
610        win.border(65, 66, 67)
611        win.border(65, 66)
612        win.border(65)
613        win.border()
614
615        win.box(':', '~')
616        self.assertEqual(win.instr(0, 1, 8), b'~~~~~~~~')
617        self.assertEqual(win.instr(1, 0),   b':        :')
618        self.assertEqual(win.instr(4, 1, 8), b'~~~~~~~~')
619        win.box(b':', b'~')
620        win.box(65, 67)
621        self.assertRaises(TypeError, win.box, 65, 66, 67)
622        self.assertRaises(TypeError, win.box, 65)
623        win.box()
624
625        win.move(1, 2)
626        win.hline('-', 5)
627        self.assertEqual(win.instr(1, 1, 7), b' ----- ')
628        win.hline(b'-', 5)
629        win.hline(45, 5)
630        win.hline('-', 5, curses.A_BOLD)
631        win.hline(1, 1, '-', 5)
632        win.hline(1, 1, '-', 5, curses.A_BOLD)
633
634        win.move(1, 2)
635        win.vline('a', 3)
636        win.vline(b'a', 3)
637        win.vline(97, 3)
638        win.vline('a', 3, curses.A_STANDOUT)
639        win.vline(1, 1, 'a', 3)
640        win.vline(1, 1, ';', 2, curses.A_STANDOUT)
641        self.assertEqual(win.inch(1, 1), b';'[0] | curses.A_STANDOUT)
642        self.assertEqual(win.inch(2, 1), b';'[0] | curses.A_STANDOUT)
643        self.assertEqual(win.inch(3, 1), b'a'[0])
644
645    def test_unctrl(self):
646        # TODO: wunctrl()
647        self.assertEqual(curses.unctrl(b'A'), b'A')
648        self.assertEqual(curses.unctrl('A'), b'A')
649        self.assertEqual(curses.unctrl(65), b'A')
650        self.assertEqual(curses.unctrl(b'\n'), b'^J')
651        self.assertEqual(curses.unctrl('\n'), b'^J')
652        self.assertEqual(curses.unctrl(10), b'^J')
653        self.assertRaises(TypeError, curses.unctrl, b'')
654        self.assertRaises(TypeError, curses.unctrl, b'AB')
655        self.assertRaises(TypeError, curses.unctrl, '')
656        self.assertRaises(TypeError, curses.unctrl, 'AB')
657        self.assertRaises(OverflowError, curses.unctrl, 2**64)
658
659    def test_endwin(self):
660        if not self.isatty:
661            self.skipTest('requires terminal')
662        self.assertIs(curses.isendwin(), False)
663        curses.endwin()
664        self.assertIs(curses.isendwin(), True)
665        curses.doupdate()
666        self.assertIs(curses.isendwin(), False)
667
668    def test_terminfo(self):
669        self.assertIsInstance(curses.tigetflag('hc'), int)
670        self.assertEqual(curses.tigetflag('cols'), -1)
671        self.assertEqual(curses.tigetflag('cr'), -1)
672
673        self.assertIsInstance(curses.tigetnum('cols'), int)
674        self.assertEqual(curses.tigetnum('hc'), -2)
675        self.assertEqual(curses.tigetnum('cr'), -2)
676
677        self.assertIsInstance(curses.tigetstr('cr'), (bytes, type(None)))
678        self.assertIsNone(curses.tigetstr('hc'))
679        self.assertIsNone(curses.tigetstr('cols'))
680
681        cud = curses.tigetstr('cud')
682        if cud is not None:
683            # See issue10570.
684            self.assertIsInstance(cud, bytes)
685            curses.tparm(cud, 2)
686            cud_2 = curses.tparm(cud, 2)
687            self.assertIsInstance(cud_2, bytes)
688            curses.putp(cud_2)
689
690        curses.putp(b'abc\n')
691
692    def test_misc_module_funcs(self):
693        curses.delay_output(1)
694        curses.flushinp()
695
696        curses.doupdate()
697        self.assertIs(curses.isendwin(), False)
698
699        curses.napms(100)
700
701        curses.newpad(50, 50)
702
703    def test_env_queries(self):
704        # TODO: term_attrs(), erasewchar(), killwchar()
705        self.assertIsInstance(curses.termname(), bytes)
706        self.assertIsInstance(curses.longname(), bytes)
707        self.assertIsInstance(curses.baudrate(), int)
708        self.assertIsInstance(curses.has_ic(), bool)
709        self.assertIsInstance(curses.has_il(), bool)
710        self.assertIsInstance(curses.termattrs(), int)
711
712        c = curses.killchar()
713        self.assertIsInstance(c, bytes)
714        self.assertEqual(len(c), 1)
715        c = curses.erasechar()
716        self.assertIsInstance(c, bytes)
717        self.assertEqual(len(c), 1)
718
719    def test_output_options(self):
720        stdscr = self.stdscr
721
722        stdscr.clearok(True)
723        stdscr.clearok(False)
724
725        stdscr.idcok(True)
726        stdscr.idcok(False)
727
728        stdscr.idlok(False)
729        stdscr.idlok(True)
730
731        if hasattr(stdscr, 'immedok'):
732            stdscr.immedok(True)
733            stdscr.immedok(False)
734
735        stdscr.leaveok(True)
736        stdscr.leaveok(False)
737
738        stdscr.scrollok(True)
739        stdscr.scrollok(False)
740
741        stdscr.setscrreg(5, 10)
742
743        curses.nonl()
744        curses.nl(True)
745        curses.nl(False)
746        curses.nl()
747
748
749    def test_input_options(self):
750        stdscr = self.stdscr
751
752        if self.isatty:
753            curses.nocbreak()
754            curses.cbreak()
755            curses.cbreak(False)
756            curses.cbreak(True)
757
758            curses.intrflush(True)
759            curses.intrflush(False)
760
761            curses.raw()
762            curses.raw(False)
763            curses.raw(True)
764            curses.noraw()
765
766        curses.noecho()
767        curses.echo()
768        curses.echo(False)
769        curses.echo(True)
770
771        curses.halfdelay(255)
772        curses.halfdelay(1)
773
774        stdscr.keypad(True)
775        stdscr.keypad(False)
776
777        curses.meta(True)
778        curses.meta(False)
779
780        stdscr.nodelay(True)
781        stdscr.nodelay(False)
782
783        curses.noqiflush()
784        curses.qiflush(True)
785        curses.qiflush(False)
786        curses.qiflush()
787
788        stdscr.notimeout(True)
789        stdscr.notimeout(False)
790
791        stdscr.timeout(-1)
792        stdscr.timeout(0)
793        stdscr.timeout(5)
794
795    @requires_curses_func('typeahead')
796    def test_typeahead(self):
797        curses.typeahead(sys.__stdin__.fileno())
798        curses.typeahead(-1)
799
800    def test_prog_mode(self):
801        if not self.isatty:
802            self.skipTest('requires terminal')
803        curses.def_prog_mode()
804        curses.reset_prog_mode()
805
806    def test_beep(self):
807        if (curses.tigetstr("bel") is not None
808            or curses.tigetstr("flash") is not None):
809            curses.beep()
810        else:
811            try:
812                curses.beep()
813            except curses.error:
814                self.skipTest('beep() failed')
815
816    def test_flash(self):
817        if (curses.tigetstr("bel") is not None
818            or curses.tigetstr("flash") is not None):
819            curses.flash()
820        else:
821            try:
822                curses.flash()
823            except curses.error:
824                self.skipTest('flash() failed')
825
826    def test_curs_set(self):
827        for vis, cap in [(0, 'civis'), (2, 'cvvis'), (1, 'cnorm')]:
828            if curses.tigetstr(cap) is not None:
829                curses.curs_set(vis)
830            else:
831                try:
832                    curses.curs_set(vis)
833                except curses.error:
834                    pass
835
836    @requires_curses_func('get_escdelay')
837    def test_escdelay(self):
838        escdelay = curses.get_escdelay()
839        self.assertIsInstance(escdelay, int)
840        curses.set_escdelay(25)
841        self.assertEqual(curses.get_escdelay(), 25)
842        curses.set_escdelay(escdelay)
843
844    @requires_curses_func('get_tabsize')
845    def test_tabsize(self):
846        tabsize = curses.get_tabsize()
847        self.assertIsInstance(tabsize, int)
848        curses.set_tabsize(4)
849        self.assertEqual(curses.get_tabsize(), 4)
850        curses.set_tabsize(tabsize)
851
852    @requires_curses_func('getsyx')
853    def test_getsyx(self):
854        y, x = curses.getsyx()
855        self.assertIsInstance(y, int)
856        self.assertIsInstance(x, int)
857        curses.setsyx(4, 5)
858        self.assertEqual(curses.getsyx(), (4, 5))
859
860    def bad_colors(self):
861        return (-1, curses.COLORS, -2**31 - 1, 2**31, -2**63 - 1, 2**63, 2**64)
862
863    def bad_colors2(self):
864        return (curses.COLORS, 2**31, 2**63, 2**64)
865
866    def bad_pairs(self):
867        return (-1, -2**31 - 1, 2**31, -2**63 - 1, 2**63, 2**64)
868
869    def test_has_colors(self):
870        self.assertIsInstance(curses.has_colors(), bool)
871        self.assertIsInstance(curses.can_change_color(), bool)
872
873    def test_start_color(self):
874        if not curses.has_colors():
875            self.skipTest('requires colors support')
876        curses.start_color()
877        if verbose:
878            print(f'COLORS = {curses.COLORS}', file=sys.stderr)
879            print(f'COLOR_PAIRS = {curses.COLOR_PAIRS}', file=sys.stderr)
880
881    @requires_colors
882    def test_color_content(self):
883        self.assertEqual(curses.color_content(curses.COLOR_BLACK), (0, 0, 0))
884        curses.color_content(0)
885        maxcolor = curses.COLORS - 1
886        curses.color_content(maxcolor)
887
888        for color in self.bad_colors():
889            self.assertRaises(ValueError, curses.color_content, color)
890
891    @requires_colors
892    def test_init_color(self):
893        if not curses.can_change_color():
894            self.skipTest('cannot change color')
895
896        old = curses.color_content(0)
897        try:
898            curses.init_color(0, *old)
899        except curses.error:
900            self.skipTest('cannot change color (init_color() failed)')
901        self.addCleanup(curses.init_color, 0, *old)
902        curses.init_color(0, 0, 0, 0)
903        self.assertEqual(curses.color_content(0), (0, 0, 0))
904        curses.init_color(0, 1000, 1000, 1000)
905        self.assertEqual(curses.color_content(0), (1000, 1000, 1000))
906
907        maxcolor = curses.COLORS - 1
908        old = curses.color_content(maxcolor)
909        curses.init_color(maxcolor, *old)
910        self.addCleanup(curses.init_color, maxcolor, *old)
911        curses.init_color(maxcolor, 0, 500, 1000)
912        self.assertEqual(curses.color_content(maxcolor), (0, 500, 1000))
913
914        for color in self.bad_colors():
915            self.assertRaises(ValueError, curses.init_color, color, 0, 0, 0)
916        for comp in (-1, 1001):
917            self.assertRaises(ValueError, curses.init_color, 0, comp, 0, 0)
918            self.assertRaises(ValueError, curses.init_color, 0, 0, comp, 0)
919            self.assertRaises(ValueError, curses.init_color, 0, 0, 0, comp)
920
921    def get_pair_limit(self):
922        pair_limit = curses.COLOR_PAIRS
923        if hasattr(curses, 'ncurses_version'):
924            if curses.has_extended_color_support():
925                pair_limit += 2*curses.COLORS + 1
926            if (not curses.has_extended_color_support()
927                    or (6, 1) <= curses.ncurses_version < (6, 2)):
928                pair_limit = min(pair_limit, SHORT_MAX)
929            # If use_default_colors() is called, the upper limit of the extended
930            # range may be restricted, so we need to check if the limit is still
931            # correct
932            try:
933                curses.init_pair(pair_limit - 1, 0, 0)
934            except ValueError:
935                pair_limit = curses.COLOR_PAIRS
936        return pair_limit
937
938    @requires_colors
939    def test_pair_content(self):
940        if not hasattr(curses, 'use_default_colors'):
941            self.assertEqual(curses.pair_content(0),
942                             (curses.COLOR_WHITE, curses.COLOR_BLACK))
943        curses.pair_content(0)
944        maxpair = self.get_pair_limit() - 1
945        if maxpair > 0:
946            curses.pair_content(maxpair)
947
948        for pair in self.bad_pairs():
949            self.assertRaises(ValueError, curses.pair_content, pair)
950
951    @requires_colors
952    def test_init_pair(self):
953        old = curses.pair_content(1)
954        curses.init_pair(1, *old)
955        self.addCleanup(curses.init_pair, 1, *old)
956
957        curses.init_pair(1, 0, 0)
958        self.assertEqual(curses.pair_content(1), (0, 0))
959        maxcolor = curses.COLORS - 1
960        curses.init_pair(1, maxcolor, 0)
961        self.assertEqual(curses.pair_content(1), (maxcolor, 0))
962        curses.init_pair(1, 0, maxcolor)
963        self.assertEqual(curses.pair_content(1), (0, maxcolor))
964        maxpair = self.get_pair_limit() - 1
965        if maxpair > 1:
966            curses.init_pair(maxpair, 0, 0)
967            self.assertEqual(curses.pair_content(maxpair), (0, 0))
968
969        for pair in self.bad_pairs():
970            self.assertRaises(ValueError, curses.init_pair, pair, 0, 0)
971        for color in self.bad_colors2():
972            self.assertRaises(ValueError, curses.init_pair, 1, color, 0)
973            self.assertRaises(ValueError, curses.init_pair, 1, 0, color)
974
975    @requires_colors
976    def test_color_attrs(self):
977        for pair in 0, 1, 255:
978            attr = curses.color_pair(pair)
979            self.assertEqual(curses.pair_number(attr), pair, attr)
980            self.assertEqual(curses.pair_number(attr | curses.A_BOLD), pair)
981        self.assertEqual(curses.color_pair(0), 0)
982        self.assertEqual(curses.pair_number(0), 0)
983
984    @requires_curses_func('use_default_colors')
985    @requires_colors
986    def test_use_default_colors(self):
987        old = curses.pair_content(0)
988        try:
989            curses.use_default_colors()
990        except curses.error:
991            self.skipTest('cannot change color (use_default_colors() failed)')
992        self.assertEqual(curses.pair_content(0), (-1, -1))
993        self.assertIn(old, [(curses.COLOR_WHITE, curses.COLOR_BLACK), (-1, -1), (0, 0)])
994
995    def test_keyname(self):
996        # TODO: key_name()
997        self.assertEqual(curses.keyname(65), b'A')
998        self.assertEqual(curses.keyname(13), b'^M')
999        self.assertEqual(curses.keyname(127), b'^?')
1000        self.assertEqual(curses.keyname(0), b'^@')
1001        self.assertRaises(ValueError, curses.keyname, -1)
1002        self.assertIsInstance(curses.keyname(256), bytes)
1003
1004    @requires_curses_func('has_key')
1005    def test_has_key(self):
1006        curses.has_key(13)
1007
1008    @requires_curses_func('getmouse')
1009    def test_getmouse(self):
1010        (availmask, oldmask) = curses.mousemask(curses.BUTTON1_PRESSED)
1011        if availmask == 0:
1012            self.skipTest('mouse stuff not available')
1013        curses.mouseinterval(10)
1014        # just verify these don't cause errors
1015        curses.ungetmouse(0, 0, 0, 0, curses.BUTTON1_PRESSED)
1016        m = curses.getmouse()
1017
1018    @requires_curses_func('panel')
1019    def test_userptr_without_set(self):
1020        w = curses.newwin(10, 10)
1021        p = curses.panel.new_panel(w)
1022        # try to access userptr() before calling set_userptr() -- segfaults
1023        with self.assertRaises(curses.panel.error,
1024                               msg='userptr should fail since not set'):
1025            p.userptr()
1026
1027    @requires_curses_func('panel')
1028    def test_userptr_memory_leak(self):
1029        w = curses.newwin(10, 10)
1030        p = curses.panel.new_panel(w)
1031        obj = object()
1032        nrefs = sys.getrefcount(obj)
1033        for i in range(100):
1034            p.set_userptr(obj)
1035
1036        p.set_userptr(None)
1037        self.assertEqual(sys.getrefcount(obj), nrefs,
1038                         "set_userptr leaked references")
1039
1040    @requires_curses_func('panel')
1041    def test_userptr_segfault(self):
1042        w = curses.newwin(10, 10)
1043        panel = curses.panel.new_panel(w)
1044        class A:
1045            def __del__(self):
1046                panel.set_userptr(None)
1047        panel.set_userptr(A())
1048        panel.set_userptr(None)
1049
1050    @cpython_only
1051    @requires_curses_func('panel')
1052    def test_disallow_instantiation(self):
1053        # Ensure that the type disallows instantiation (bpo-43916)
1054        w = curses.newwin(10, 10)
1055        panel = curses.panel.new_panel(w)
1056        check_disallow_instantiation(self, type(panel))
1057
1058    @requires_curses_func('is_term_resized')
1059    def test_is_term_resized(self):
1060        lines, cols = curses.LINES, curses.COLS
1061        self.assertIs(curses.is_term_resized(lines, cols), False)
1062        self.assertIs(curses.is_term_resized(lines-1, cols-1), True)
1063
1064    @requires_curses_func('resize_term')
1065    def test_resize_term(self):
1066        curses.update_lines_cols()
1067        lines, cols = curses.LINES, curses.COLS
1068        new_lines = lines - 1
1069        new_cols = cols + 1
1070        curses.resize_term(new_lines, new_cols)
1071        self.assertEqual(curses.LINES, new_lines)
1072        self.assertEqual(curses.COLS, new_cols)
1073
1074        curses.resize_term(lines, cols)
1075        self.assertEqual(curses.LINES, lines)
1076        self.assertEqual(curses.COLS, cols)
1077
1078    @requires_curses_func('resizeterm')
1079    def test_resizeterm(self):
1080        curses.update_lines_cols()
1081        lines, cols = curses.LINES, curses.COLS
1082        new_lines = lines - 1
1083        new_cols = cols + 1
1084        curses.resizeterm(new_lines, new_cols)
1085        self.assertEqual(curses.LINES, new_lines)
1086        self.assertEqual(curses.COLS, new_cols)
1087
1088        curses.resizeterm(lines, cols)
1089        self.assertEqual(curses.LINES, lines)
1090        self.assertEqual(curses.COLS, cols)
1091
1092    def test_ungetch(self):
1093        curses.ungetch(b'A')
1094        self.assertEqual(self.stdscr.getkey(), 'A')
1095        curses.ungetch('B')
1096        self.assertEqual(self.stdscr.getkey(), 'B')
1097        curses.ungetch(67)
1098        self.assertEqual(self.stdscr.getkey(), 'C')
1099
1100    def test_issue6243(self):
1101        curses.ungetch(1025)
1102        self.stdscr.getkey()
1103
1104    @requires_curses_func('unget_wch')
1105    @unittest.skipIf(getattr(curses, 'ncurses_version', (99,)) < (5, 8),
1106                     "unget_wch is broken in ncurses 5.7 and earlier")
1107    def test_unget_wch(self):
1108        stdscr = self.stdscr
1109        encoding = stdscr.encoding
1110        for ch in ('a', '\xe9', '\u20ac', '\U0010FFFF'):
1111            try:
1112                ch.encode(encoding)
1113            except UnicodeEncodeError:
1114                continue
1115            try:
1116                curses.unget_wch(ch)
1117            except Exception as err:
1118                self.fail("unget_wch(%a) failed with encoding %s: %s"
1119                          % (ch, stdscr.encoding, err))
1120            read = stdscr.get_wch()
1121            self.assertEqual(read, ch)
1122
1123            code = ord(ch)
1124            curses.unget_wch(code)
1125            read = stdscr.get_wch()
1126            self.assertEqual(read, ch)
1127
1128    def test_encoding(self):
1129        stdscr = self.stdscr
1130        import codecs
1131        encoding = stdscr.encoding
1132        codecs.lookup(encoding)
1133        with self.assertRaises(TypeError):
1134            stdscr.encoding = 10
1135        stdscr.encoding = encoding
1136        with self.assertRaises(TypeError):
1137            del stdscr.encoding
1138
1139    def test_issue21088(self):
1140        stdscr = self.stdscr
1141        #
1142        # http://bugs.python.org/issue21088
1143        #
1144        # the bug:
1145        # when converting curses.window.addch to Argument Clinic
1146        # the first two parameters were switched.
1147
1148        # if someday we can represent the signature of addch
1149        # we will need to rewrite this test.
1150        try:
1151            signature = inspect.signature(stdscr.addch)
1152            self.assertFalse(signature)
1153        except ValueError:
1154            # not generating a signature is fine.
1155            pass
1156
1157        # So.  No signature for addch.
1158        # But Argument Clinic gave us a human-readable equivalent
1159        # as the first line of the docstring.  So we parse that,
1160        # and ensure that the parameters appear in the correct order.
1161        # Since this is parsing output from Argument Clinic, we can
1162        # be reasonably certain the generated parsing code will be
1163        # correct too.
1164        human_readable_signature = stdscr.addch.__doc__.split("\n")[0]
1165        self.assertIn("[y, x,]", human_readable_signature)
1166
1167    @requires_curses_window_meth('resize')
1168    def test_issue13051(self):
1169        win = curses.newwin(5, 15, 2, 5)
1170        box = curses.textpad.Textbox(win, insert_mode=True)
1171        lines, cols = win.getmaxyx()
1172        win.resize(lines-2, cols-2)
1173        # this may cause infinite recursion, leading to a RuntimeError
1174        box._insert_printable_char('a')
1175
1176
1177class MiscTests(unittest.TestCase):
1178
1179    @requires_curses_func('update_lines_cols')
1180    def test_update_lines_cols(self):
1181        curses.update_lines_cols()
1182        lines, cols = curses.LINES, curses.COLS
1183        curses.LINES = curses.COLS = 0
1184        curses.update_lines_cols()
1185        self.assertEqual(curses.LINES, lines)
1186        self.assertEqual(curses.COLS, cols)
1187
1188    @requires_curses_func('ncurses_version')
1189    def test_ncurses_version(self):
1190        v = curses.ncurses_version
1191        if verbose:
1192            print(f'ncurses_version = {curses.ncurses_version}', flush=True)
1193        self.assertIsInstance(v[:], tuple)
1194        self.assertEqual(len(v), 3)
1195        self.assertIsInstance(v[0], int)
1196        self.assertIsInstance(v[1], int)
1197        self.assertIsInstance(v[2], int)
1198        self.assertIsInstance(v.major, int)
1199        self.assertIsInstance(v.minor, int)
1200        self.assertIsInstance(v.patch, int)
1201        self.assertEqual(v[0], v.major)
1202        self.assertEqual(v[1], v.minor)
1203        self.assertEqual(v[2], v.patch)
1204        self.assertGreaterEqual(v.major, 0)
1205        self.assertGreaterEqual(v.minor, 0)
1206        self.assertGreaterEqual(v.patch, 0)
1207
1208    def test_has_extended_color_support(self):
1209        r = curses.has_extended_color_support()
1210        self.assertIsInstance(r, bool)
1211
1212
1213class TestAscii(unittest.TestCase):
1214
1215    def test_controlnames(self):
1216        for name in curses.ascii.controlnames:
1217            self.assertTrue(hasattr(curses.ascii, name), name)
1218
1219    def test_ctypes(self):
1220        def check(func, expected):
1221            with self.subTest(ch=c, func=func):
1222                self.assertEqual(func(i), expected)
1223                self.assertEqual(func(c), expected)
1224
1225        for i in range(256):
1226            c = chr(i)
1227            b = bytes([i])
1228            check(curses.ascii.isalnum, b.isalnum())
1229            check(curses.ascii.isalpha, b.isalpha())
1230            check(curses.ascii.isdigit, b.isdigit())
1231            check(curses.ascii.islower, b.islower())
1232            check(curses.ascii.isspace, b.isspace())
1233            check(curses.ascii.isupper, b.isupper())
1234
1235            check(curses.ascii.isascii, i < 128)
1236            check(curses.ascii.ismeta, i >= 128)
1237            check(curses.ascii.isctrl, i < 32)
1238            check(curses.ascii.iscntrl, i < 32 or i == 127)
1239            check(curses.ascii.isblank, c in ' \t')
1240            check(curses.ascii.isgraph, 32 < i <= 126)
1241            check(curses.ascii.isprint, 32 <= i <= 126)
1242            check(curses.ascii.ispunct, c in string.punctuation)
1243            check(curses.ascii.isxdigit, c in string.hexdigits)
1244
1245        for i in (-2, -1, 256, sys.maxunicode, sys.maxunicode+1):
1246            self.assertFalse(curses.ascii.isalnum(i))
1247            self.assertFalse(curses.ascii.isalpha(i))
1248            self.assertFalse(curses.ascii.isdigit(i))
1249            self.assertFalse(curses.ascii.islower(i))
1250            self.assertFalse(curses.ascii.isspace(i))
1251            self.assertFalse(curses.ascii.isupper(i))
1252
1253            self.assertFalse(curses.ascii.isascii(i))
1254            self.assertFalse(curses.ascii.isctrl(i))
1255            self.assertFalse(curses.ascii.iscntrl(i))
1256            self.assertFalse(curses.ascii.isblank(i))
1257            self.assertFalse(curses.ascii.isgraph(i))
1258            self.assertFalse(curses.ascii.isprint(i))
1259            self.assertFalse(curses.ascii.ispunct(i))
1260            self.assertFalse(curses.ascii.isxdigit(i))
1261
1262        self.assertFalse(curses.ascii.ismeta(-1))
1263
1264    def test_ascii(self):
1265        ascii = curses.ascii.ascii
1266        self.assertEqual(ascii('\xc1'), 'A')
1267        self.assertEqual(ascii('A'), 'A')
1268        self.assertEqual(ascii(ord('\xc1')), ord('A'))
1269
1270    def test_ctrl(self):
1271        ctrl = curses.ascii.ctrl
1272        self.assertEqual(ctrl('J'), '\n')
1273        self.assertEqual(ctrl('\n'), '\n')
1274        self.assertEqual(ctrl('@'), '\0')
1275        self.assertEqual(ctrl(ord('J')), ord('\n'))
1276
1277    def test_alt(self):
1278        alt = curses.ascii.alt
1279        self.assertEqual(alt('\n'), '\x8a')
1280        self.assertEqual(alt('A'), '\xc1')
1281        self.assertEqual(alt(ord('A')), 0xc1)
1282
1283    def test_unctrl(self):
1284        unctrl = curses.ascii.unctrl
1285        self.assertEqual(unctrl('a'), 'a')
1286        self.assertEqual(unctrl('A'), 'A')
1287        self.assertEqual(unctrl(';'), ';')
1288        self.assertEqual(unctrl(' '), ' ')
1289        self.assertEqual(unctrl('\x7f'), '^?')
1290        self.assertEqual(unctrl('\n'), '^J')
1291        self.assertEqual(unctrl('\0'), '^@')
1292        self.assertEqual(unctrl(ord('A')), 'A')
1293        self.assertEqual(unctrl(ord('\n')), '^J')
1294        # Meta-bit characters
1295        self.assertEqual(unctrl('\x8a'), '!^J')
1296        self.assertEqual(unctrl('\xc1'), '!A')
1297        self.assertEqual(unctrl(ord('\x8a')), '!^J')
1298        self.assertEqual(unctrl(ord('\xc1')), '!A')
1299
1300
1301def lorem_ipsum(win):
1302    text = [
1303        'Lorem ipsum',
1304        'dolor sit amet,',
1305        'consectetur',
1306        'adipiscing elit,',
1307        'sed do eiusmod',
1308        'tempor incididunt',
1309        'ut labore et',
1310        'dolore magna',
1311        'aliqua.',
1312    ]
1313    maxy, maxx = win.getmaxyx()
1314    for y, line in enumerate(text[:maxy]):
1315        win.addstr(y, 0, line[:maxx - (y == maxy - 1)])
1316
1317if __name__ == '__main__':
1318    unittest.main()
1319