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