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