(root)/
Python-3.12.0/
Lib/
test/
test_curses.py
       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()