1# Urwid terminal emulation widget unit tests 2# Copyright (C) 2010 aszlig 3# Copyright (C) 2011 Ian Ward 4# 5# This library is free software; you can redistribute it and/or 6# modify it under the terms of the GNU Lesser General Public 7# License as published by the Free Software Foundation; either 8# version 2.1 of the License, or (at your option) any later version. 9# 10# This library is distributed in the hope that it will be useful, 11# but WITHOUT ANY WARRANTY; without even the implied warranty of 12# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 13# Lesser General Public License for more details. 14# 15# You should have received a copy of the GNU Lesser General Public 16# License along with this library; if not, write to the Free Software 17# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 18# 19# Urwid web site: http://excess.org/urwid/ 20 21import errno 22import os 23import sys 24import unittest 25 26from itertools import dropwhile 27 28from urwid.listbox import ListBox 29from urwid.decoration import BoxAdapter 30from urwid import vterm 31from urwid import signals 32from urwid.compat import B 33 34class DummyCommand(object): 35 QUITSTRING = B('|||quit|||') 36 37 def __init__(self): 38 self.reader, self.writer = os.pipe() 39 40 def __call__(self): 41 # reset 42 stdout = getattr(sys.stdout, 'buffer', sys.stdout) 43 stdout.write(B('\x1bc')) 44 45 while True: 46 data = self.read(1024) 47 if self.QUITSTRING == data: 48 break 49 stdout.write(data) 50 stdout.flush() 51 52 def read(self, size): 53 while True: 54 try: 55 return os.read(self.reader, size) 56 except OSError as e: 57 if e.errno != errno.EINTR: 58 raise 59 60 def write(self, data): 61 os.write(self.writer, data) 62 63 def quit(self): 64 self.write(self.QUITSTRING) 65 66 67class TermTest(unittest.TestCase): 68 def setUp(self): 69 self.command = DummyCommand() 70 71 self.term = vterm.Terminal(self.command) 72 self.resize(80, 24) 73 74 def tearDown(self): 75 self.command.quit() 76 77 def connect_signal(self, signal): 78 self._sig_response = None 79 80 def _set_signal_response(widget, *args, **kwargs): 81 self._sig_response = (args, kwargs) 82 self._set_signal_response = _set_signal_response 83 84 signals.connect_signal(self.term, signal, self._set_signal_response) 85 86 def expect_signal(self, *args, **kwargs): 87 self.assertEqual(self._sig_response, (args, kwargs)) 88 89 def disconnect_signal(self, signal): 90 signals.disconnect_signal(self.term, signal, self._set_signal_response) 91 92 def caught_beep(self, obj): 93 self.beeped = True 94 95 def resize(self, width, height, soft=False): 96 self.termsize = (width, height) 97 if not soft: 98 self.term.render(self.termsize, focus=False) 99 100 def write(self, data): 101 data = B(data) 102 self.command.write(data.replace(B('\e'), B('\x1b'))) 103 104 def flush(self): 105 self.write(chr(0x7f)) 106 107 def read(self, raw=False, focus=False): 108 self.term.wait_and_feed() 109 rendered = self.term.render(self.termsize, focus=focus) 110 if raw: 111 is_empty = lambda c: c == (None, None, B(' ')) 112 content = list(rendered.content()) 113 lines = [list(dropwhile(is_empty, reversed(line))) 114 for line in content] 115 return [list(reversed(line)) for line in lines if len(line)] 116 else: 117 content = rendered.text 118 lines = [line.rstrip() for line in content] 119 return B('\n').join(lines).rstrip() 120 121 def expect(self, what, desc=None, raw=False, focus=False): 122 if not isinstance(what, list): 123 what = B(what) 124 got = self.read(raw=raw, focus=focus) 125 if desc is None: 126 desc = '' 127 else: 128 desc += '\n' 129 desc += 'Expected:\n%r\nGot:\n%r' % (what, got) 130 self.assertEqual(got, what, desc) 131 132 def test_simplestring(self): 133 self.write('hello world') 134 self.expect('hello world') 135 136 def test_linefeed(self): 137 self.write('hello\x0aworld') 138 self.expect('hello\nworld') 139 140 def test_linefeed2(self): 141 self.write('aa\b\b\eDbb') 142 self.expect('aa\nbb') 143 144 def test_carriage_return(self): 145 self.write('hello\x0dworld') 146 self.expect('world') 147 148 def test_insertlines(self): 149 self.write('\e[0;0flast\e[0;0f\e[10L\e[0;0ffirst\nsecond\n\e[11D') 150 self.expect('first\nsecond\n\n\n\n\n\n\n\n\nlast') 151 152 def test_deletelines(self): 153 self.write('1\n2\n3\n4\e[2;1f\e[2M') 154 self.expect('1\n4') 155 156 def test_nul(self): 157 self.write('a\0b') 158 self.expect('ab') 159 160 def test_movement(self): 161 self.write('\e[10;20H11\e[10;0f\e[20C\e[K') 162 self.expect('\n' * 9 + ' ' * 19 + '1') 163 self.write('\e[A\e[B\e[C\e[D\b\e[K') 164 self.expect('') 165 self.write('\e[50A2') 166 self.expect(' ' * 19 + '2') 167 self.write('\b\e[K\e[50B3') 168 self.expect('\n' * 23 + ' ' * 19 + '3') 169 self.write('\b\e[K' + '\eM' * 30 + '\e[100C4') 170 self.expect(' ' * 79 + '4') 171 self.write('\e[100D\e[K5') 172 self.expect('5') 173 174 def edgewall(self): 175 edgewall = '1-\e[1;%(x)df-2\e[%(y)d;1f3-\e[%(y)d;%(x)df-4\x0d' 176 self.write(edgewall % {'x': self.termsize[0] - 1, 177 'y': self.termsize[1] - 1}) 178 179 def test_horizontal_resize(self): 180 self.resize(80, 24) 181 self.edgewall() 182 self.expect('1-' + ' ' * 76 + '-2' + '\n' * 22 183 + '3-' + ' ' * 76 + '-4') 184 self.resize(78, 24, soft=True) 185 self.flush() 186 self.expect('1-' + '\n' * 22 + '3-') 187 self.resize(80, 24, soft=True) 188 self.flush() 189 self.expect('1-' + '\n' * 22 + '3-') 190 191 def test_vertical_resize(self): 192 self.resize(80, 24) 193 self.edgewall() 194 self.expect('1-' + ' ' * 76 + '-2' + '\n' * 22 195 + '3-' + ' ' * 76 + '-4') 196 for y in range(23, 1, -1): 197 self.resize(80, y, soft=True) 198 self.write('\e[%df\e[J3-\e[%d;%df-4' % (y, y, 79)) 199 desc = "try to rescale to 80x%d." % y 200 self.expect('\n' * (y - 2) + '3-' + ' ' * 76 + '-4', desc) 201 self.resize(80, 24, soft=True) 202 self.flush() 203 self.expect('1-' + ' ' * 76 + '-2' + '\n' * 22 204 + '3-' + ' ' * 76 + '-4') 205 206 def write_movements(self, arg): 207 fmt = 'XXX\n\e[faaa\e[Bccc\e[Addd\e[Bfff\e[Cbbb\e[A\e[Deee' 208 self.write(fmt.replace('\e[', '\e['+arg)) 209 210 def test_defargs(self): 211 self.write_movements('') 212 self.expect('aaa ddd eee\n ccc fff bbb') 213 214 def test_nullargs(self): 215 self.write_movements('0') 216 self.expect('aaa ddd eee\n ccc fff bbb') 217 218 def test_erase_line(self): 219 self.write('1234567890\e[5D\e[K\n1234567890\e[5D\e[1K\naaaaaaaaaaaaaaa\e[2Ka') 220 self.expect('12345\n 7890\n a') 221 222 def test_erase_display(self): 223 self.write('1234567890\e[5D\e[Ja') 224 self.expect('12345a') 225 self.write('98765\e[8D\e[1Jx') 226 self.expect(' x5a98765') 227 228 def test_scrolling_region_simple(self): 229 self.write('\e[10;20r\e[10f1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n11\n12\e[faa') 230 self.expect('aa' + '\n' * 9 + '2\n3\n4\n5\n6\n7\n8\n9\n10\n11\n12') 231 232 def test_scrolling_region_reverse(self): 233 self.write('\e[2J\e[1;2r\e[5Baaa\r\eM\eM\eMbbb\nXXX') 234 self.expect('\n\nbbb\nXXX\n\naaa') 235 236 def test_scrolling_region_move(self): 237 self.write('\e[10;20r\e[2J\e[10Bfoo\rbar\rblah\rmooh\r\e[10Aone\r\eM\eMtwo\r\eM\eMthree\r\eM\eMa') 238 self.expect('ahree\n\n\n\n\n\n\n\n\n\nmooh') 239 240 def test_scrolling_twice(self): 241 self.write('\e[?6h\e[10;20r\e[2;5rtest') 242 self.expect('\ntest') 243 244 def test_cursor_scrolling_region(self): 245 self.write('\e[?6h\e[10;20r\e[10f1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n11\n12\e[faa') 246 self.expect('\n' * 9 + 'aa\n3\n4\n5\n6\n7\n8\n9\n10\n11\n12') 247 248 def test_scrolling_region_simple_with_focus(self): 249 self.write('\e[10;20r\e[10f1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n11\n12\e[faa') 250 self.expect('aa' + '\n' * 9 + '2\n3\n4\n5\n6\n7\n8\n9\n10\n11\n12', focus=True) 251 252 def test_scrolling_region_reverse_with_focus(self): 253 self.write('\e[2J\e[1;2r\e[5Baaa\r\eM\eM\eMbbb\nXXX') 254 self.expect('\n\nbbb\nXXX\n\naaa', focus=True) 255 256 def test_scrolling_region_move_with_focus(self): 257 self.write('\e[10;20r\e[2J\e[10Bfoo\rbar\rblah\rmooh\r\e[10Aone\r\eM\eMtwo\r\eM\eMthree\r\eM\eMa') 258 self.expect('ahree\n\n\n\n\n\n\n\n\n\nmooh', focus=True) 259 260 def test_scrolling_twice_with_focus(self): 261 self.write('\e[?6h\e[10;20r\e[2;5rtest') 262 self.expect('\ntest', focus=True) 263 264 def test_cursor_scrolling_region_with_focus(self): 265 self.write('\e[?6h\e[10;20r\e[10f1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n11\n12\e[faa') 266 self.expect('\n' * 9 + 'aa\n3\n4\n5\n6\n7\n8\n9\n10\n11\n12', focus=True) 267 268 def test_relative_region_jump(self): 269 self.write('\e[21H---\e[10;20r\e[?6h\e[18Htest') 270 self.expect('\n' * 19 + 'test\n---') 271 272 def test_set_multiple_modes(self): 273 self.write('\e[?6;5htest') 274 self.expect('test') 275 self.assertTrue(self.term.term_modes.constrain_scrolling) 276 self.assertTrue(self.term.term_modes.reverse_video) 277 self.write('\e[?6;5l') 278 self.expect('test') 279 self.assertFalse(self.term.term_modes.constrain_scrolling) 280 self.assertFalse(self.term.term_modes.reverse_video) 281 282 def test_wrap_simple(self): 283 self.write('\e[?7h\e[1;%dHtt' % self.term.width) 284 self.expect(' ' * (self.term.width - 1) + 't\nt') 285 286 def test_wrap_backspace_tab(self): 287 self.write('\e[?7h\e[1;%dHt\b\b\t\ta' % self.term.width) 288 self.expect(' ' * (self.term.width - 1) + 'a') 289 290 def test_cursor_visibility(self): 291 self.write('\e[?25linvisible') 292 self.expect('invisible', focus=True) 293 self.assertEqual(self.term.term.cursor, None) 294 self.write('\rvisible\e[?25h\e[K') 295 self.expect('visible', focus=True) 296 self.assertNotEqual(self.term.term.cursor, None) 297 298 def test_get_utf8_len(self): 299 length = self.term.term.get_utf8_len(int("11110000", 2)) 300 self.assertEqual(length, 3) 301 length = self.term.term.get_utf8_len(int("11000000", 2)) 302 self.assertEqual(length, 1) 303 length = self.term.term.get_utf8_len(int("11111101", 2)) 304 self.assertEqual(length, 5) 305 306 def test_encoding_unicode(self): 307 vterm.util._target_encoding = 'utf-8' 308 self.write('\e%G\xe2\x80\x94') 309 self.expect('\xe2\x80\x94') 310 311 def test_encoding_unicode_ascii(self): 312 vterm.util._target_encoding = 'ascii' 313 self.write('\e%G\xe2\x80\x94') 314 self.expect('?') 315 316 def test_encoding_wrong_unicode(self): 317 vterm.util._target_encoding = 'utf-8' 318 self.write('\e%G\xc0\x99') 319 self.expect('') 320 321 def test_encoding_vt100_graphics(self): 322 vterm.util._target_encoding = 'ascii' 323 self.write('\e)0\e(0\x0fg\x0eg\e)Bn\e)0g\e)B\e(B\x0fn') 324 self.expect([[ 325 (None, '0', B('g')), (None, '0', B('g')), 326 (None, None, B('n')), (None, '0', B('g')), 327 (None, None, B('n')) 328 ]], raw=True) 329 330 def test_ibmpc_mapping(self): 331 vterm.util._target_encoding = 'ascii' 332 333 self.write('\e[11m\x18\e[10m\x18') 334 self.expect([[(None, 'U', B('\x18'))]], raw=True) 335 336 self.write('\ec\e)U\x0e\x18\x0f\e[3h\x18\e[3l\x18') 337 self.expect([[(None, None, B('\x18'))]], raw=True) 338 339 self.write('\ec\e[11m\xdb\x18\e[10m\xdb') 340 self.expect([[ 341 (None, 'U', B('\xdb')), (None, 'U', B('\x18')), 342 (None, None, B('\xdb')) 343 ]], raw=True) 344 345 def test_set_title(self): 346 self._the_title = None 347 348 def _change_title(widget, title): 349 self._the_title = title 350 351 self.connect_signal('title') 352 self.write('\e]666parsed right?\e\\te\e]0;test title\007st1') 353 self.expect('test1') 354 self.expect_signal(B('test title')) 355 self.write('\e]3;stupid title\e\\\e[0G\e[2Ktest2') 356 self.expect('test2') 357 self.expect_signal(B('stupid title')) 358 self.disconnect_signal('title') 359 360 def test_set_leds(self): 361 self.connect_signal('leds') 362 self.write('\e[0qtest1') 363 self.expect('test1') 364 self.expect_signal('clear') 365 self.write('\e[3q\e[H\e[Ktest2') 366 self.expect('test2') 367 self.expect_signal('caps_lock') 368 self.disconnect_signal('leds') 369 370 def test_in_listbox(self): 371 listbox = ListBox([BoxAdapter(self.term, 80)]) 372 rendered = listbox.render((80, 24)) 373