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