1# -*- test-case-name: twisted.conch.test.test_helper -*- 2# Copyright (c) Twisted Matrix Laboratories. 3# See LICENSE for details. 4 5from twisted.conch.insults import helper 6from twisted.conch.insults.insults import G0, G1, G2, G3 7from twisted.conch.insults.insults import modes, privateModes 8from twisted.conch.insults.insults import ( 9 NORMAL, BOLD, UNDERLINE, BLINK, REVERSE_VIDEO) 10 11from twisted.trial import unittest 12 13WIDTH = 80 14HEIGHT = 24 15 16class BufferTestCase(unittest.TestCase): 17 def setUp(self): 18 self.term = helper.TerminalBuffer() 19 self.term.connectionMade() 20 21 def testInitialState(self): 22 self.assertEqual(self.term.width, WIDTH) 23 self.assertEqual(self.term.height, HEIGHT) 24 self.assertEqual(str(self.term), 25 '\n' * (HEIGHT - 1)) 26 self.assertEqual(self.term.reportCursorPosition(), (0, 0)) 27 28 29 def test_initialPrivateModes(self): 30 """ 31 Verify that only DEC Auto Wrap Mode (DECAWM) and DEC Text Cursor Enable 32 Mode (DECTCEM) are initially in the Set Mode (SM) state. 33 """ 34 self.assertEqual( 35 {privateModes.AUTO_WRAP: True, 36 privateModes.CURSOR_MODE: True}, 37 self.term.privateModes) 38 39 40 def test_carriageReturn(self): 41 """ 42 C{"\r"} moves the cursor to the first column in the current row. 43 """ 44 self.term.cursorForward(5) 45 self.term.cursorDown(3) 46 self.assertEqual(self.term.reportCursorPosition(), (5, 3)) 47 self.term.insertAtCursor("\r") 48 self.assertEqual(self.term.reportCursorPosition(), (0, 3)) 49 50 51 def test_linefeed(self): 52 """ 53 C{"\n"} moves the cursor to the next row without changing the column. 54 """ 55 self.term.cursorForward(5) 56 self.assertEqual(self.term.reportCursorPosition(), (5, 0)) 57 self.term.insertAtCursor("\n") 58 self.assertEqual(self.term.reportCursorPosition(), (5, 1)) 59 60 61 def test_newline(self): 62 """ 63 C{write} transforms C{"\n"} into C{"\r\n"}. 64 """ 65 self.term.cursorForward(5) 66 self.term.cursorDown(3) 67 self.assertEqual(self.term.reportCursorPosition(), (5, 3)) 68 self.term.write("\n") 69 self.assertEqual(self.term.reportCursorPosition(), (0, 4)) 70 71 72 def test_setPrivateModes(self): 73 """ 74 Verify that L{helper.TerminalBuffer.setPrivateModes} changes the Set 75 Mode (SM) state to "set" for the private modes it is passed. 76 """ 77 expected = self.term.privateModes.copy() 78 self.term.setPrivateModes([privateModes.SCROLL, privateModes.SCREEN]) 79 expected[privateModes.SCROLL] = True 80 expected[privateModes.SCREEN] = True 81 self.assertEqual(expected, self.term.privateModes) 82 83 84 def test_resetPrivateModes(self): 85 """ 86 Verify that L{helper.TerminalBuffer.resetPrivateModes} changes the Set 87 Mode (SM) state to "reset" for the private modes it is passed. 88 """ 89 expected = self.term.privateModes.copy() 90 self.term.resetPrivateModes([privateModes.AUTO_WRAP, privateModes.CURSOR_MODE]) 91 del expected[privateModes.AUTO_WRAP] 92 del expected[privateModes.CURSOR_MODE] 93 self.assertEqual(expected, self.term.privateModes) 94 95 96 def testCursorDown(self): 97 self.term.cursorDown(3) 98 self.assertEqual(self.term.reportCursorPosition(), (0, 3)) 99 self.term.cursorDown() 100 self.assertEqual(self.term.reportCursorPosition(), (0, 4)) 101 self.term.cursorDown(HEIGHT) 102 self.assertEqual(self.term.reportCursorPosition(), (0, HEIGHT - 1)) 103 104 def testCursorUp(self): 105 self.term.cursorUp(5) 106 self.assertEqual(self.term.reportCursorPosition(), (0, 0)) 107 108 self.term.cursorDown(20) 109 self.term.cursorUp(1) 110 self.assertEqual(self.term.reportCursorPosition(), (0, 19)) 111 112 self.term.cursorUp(19) 113 self.assertEqual(self.term.reportCursorPosition(), (0, 0)) 114 115 def testCursorForward(self): 116 self.term.cursorForward(2) 117 self.assertEqual(self.term.reportCursorPosition(), (2, 0)) 118 self.term.cursorForward(2) 119 self.assertEqual(self.term.reportCursorPosition(), (4, 0)) 120 self.term.cursorForward(WIDTH) 121 self.assertEqual(self.term.reportCursorPosition(), (WIDTH, 0)) 122 123 def testCursorBackward(self): 124 self.term.cursorForward(10) 125 self.term.cursorBackward(2) 126 self.assertEqual(self.term.reportCursorPosition(), (8, 0)) 127 self.term.cursorBackward(7) 128 self.assertEqual(self.term.reportCursorPosition(), (1, 0)) 129 self.term.cursorBackward(1) 130 self.assertEqual(self.term.reportCursorPosition(), (0, 0)) 131 self.term.cursorBackward(1) 132 self.assertEqual(self.term.reportCursorPosition(), (0, 0)) 133 134 def testCursorPositioning(self): 135 self.term.cursorPosition(3, 9) 136 self.assertEqual(self.term.reportCursorPosition(), (3, 9)) 137 138 def testSimpleWriting(self): 139 s = "Hello, world." 140 self.term.write(s) 141 self.assertEqual( 142 str(self.term), 143 s + '\n' + 144 '\n' * (HEIGHT - 2)) 145 146 def testOvertype(self): 147 s = "hello, world." 148 self.term.write(s) 149 self.term.cursorBackward(len(s)) 150 self.term.resetModes([modes.IRM]) 151 self.term.write("H") 152 self.assertEqual( 153 str(self.term), 154 ("H" + s[1:]) + '\n' + 155 '\n' * (HEIGHT - 2)) 156 157 def testInsert(self): 158 s = "ello, world." 159 self.term.write(s) 160 self.term.cursorBackward(len(s)) 161 self.term.setModes([modes.IRM]) 162 self.term.write("H") 163 self.assertEqual( 164 str(self.term), 165 ("H" + s) + '\n' + 166 '\n' * (HEIGHT - 2)) 167 168 def testWritingInTheMiddle(self): 169 s = "Hello, world." 170 self.term.cursorDown(5) 171 self.term.cursorForward(5) 172 self.term.write(s) 173 self.assertEqual( 174 str(self.term), 175 '\n' * 5 + 176 (self.term.fill * 5) + s + '\n' + 177 '\n' * (HEIGHT - 7)) 178 179 def testWritingWrappedAtEndOfLine(self): 180 s = "Hello, world." 181 self.term.cursorForward(WIDTH - 5) 182 self.term.write(s) 183 self.assertEqual( 184 str(self.term), 185 s[:5].rjust(WIDTH) + '\n' + 186 s[5:] + '\n' + 187 '\n' * (HEIGHT - 3)) 188 189 def testIndex(self): 190 self.term.index() 191 self.assertEqual(self.term.reportCursorPosition(), (0, 1)) 192 self.term.cursorDown(HEIGHT) 193 self.assertEqual(self.term.reportCursorPosition(), (0, HEIGHT - 1)) 194 self.term.index() 195 self.assertEqual(self.term.reportCursorPosition(), (0, HEIGHT - 1)) 196 197 def testReverseIndex(self): 198 self.term.reverseIndex() 199 self.assertEqual(self.term.reportCursorPosition(), (0, 0)) 200 self.term.cursorDown(2) 201 self.assertEqual(self.term.reportCursorPosition(), (0, 2)) 202 self.term.reverseIndex() 203 self.assertEqual(self.term.reportCursorPosition(), (0, 1)) 204 205 def test_nextLine(self): 206 """ 207 C{nextLine} positions the cursor at the beginning of the row below the 208 current row. 209 """ 210 self.term.nextLine() 211 self.assertEqual(self.term.reportCursorPosition(), (0, 1)) 212 self.term.cursorForward(5) 213 self.assertEqual(self.term.reportCursorPosition(), (5, 1)) 214 self.term.nextLine() 215 self.assertEqual(self.term.reportCursorPosition(), (0, 2)) 216 217 def testSaveCursor(self): 218 self.term.cursorDown(5) 219 self.term.cursorForward(7) 220 self.assertEqual(self.term.reportCursorPosition(), (7, 5)) 221 self.term.saveCursor() 222 self.term.cursorDown(7) 223 self.term.cursorBackward(3) 224 self.assertEqual(self.term.reportCursorPosition(), (4, 12)) 225 self.term.restoreCursor() 226 self.assertEqual(self.term.reportCursorPosition(), (7, 5)) 227 228 def testSingleShifts(self): 229 self.term.singleShift2() 230 self.term.write('Hi') 231 232 ch = self.term.getCharacter(0, 0) 233 self.assertEqual(ch[0], 'H') 234 self.assertEqual(ch[1].charset, G2) 235 236 ch = self.term.getCharacter(1, 0) 237 self.assertEqual(ch[0], 'i') 238 self.assertEqual(ch[1].charset, G0) 239 240 self.term.singleShift3() 241 self.term.write('!!') 242 243 ch = self.term.getCharacter(2, 0) 244 self.assertEqual(ch[0], '!') 245 self.assertEqual(ch[1].charset, G3) 246 247 ch = self.term.getCharacter(3, 0) 248 self.assertEqual(ch[0], '!') 249 self.assertEqual(ch[1].charset, G0) 250 251 def testShifting(self): 252 s1 = "Hello" 253 s2 = "World" 254 s3 = "Bye!" 255 self.term.write("Hello\n") 256 self.term.shiftOut() 257 self.term.write("World\n") 258 self.term.shiftIn() 259 self.term.write("Bye!\n") 260 261 g = G0 262 h = 0 263 for s in (s1, s2, s3): 264 for i in range(len(s)): 265 ch = self.term.getCharacter(i, h) 266 self.assertEqual(ch[0], s[i]) 267 self.assertEqual(ch[1].charset, g) 268 g = g == G0 and G1 or G0 269 h += 1 270 271 def testGraphicRendition(self): 272 self.term.selectGraphicRendition(BOLD, UNDERLINE, BLINK, REVERSE_VIDEO) 273 self.term.write('W') 274 self.term.selectGraphicRendition(NORMAL) 275 self.term.write('X') 276 self.term.selectGraphicRendition(BLINK) 277 self.term.write('Y') 278 self.term.selectGraphicRendition(BOLD) 279 self.term.write('Z') 280 281 ch = self.term.getCharacter(0, 0) 282 self.assertEqual(ch[0], 'W') 283 self.assertTrue(ch[1].bold) 284 self.assertTrue(ch[1].underline) 285 self.assertTrue(ch[1].blink) 286 self.assertTrue(ch[1].reverseVideo) 287 288 ch = self.term.getCharacter(1, 0) 289 self.assertEqual(ch[0], 'X') 290 self.assertFalse(ch[1].bold) 291 self.assertFalse(ch[1].underline) 292 self.assertFalse(ch[1].blink) 293 self.assertFalse(ch[1].reverseVideo) 294 295 ch = self.term.getCharacter(2, 0) 296 self.assertEqual(ch[0], 'Y') 297 self.assertTrue(ch[1].blink) 298 self.assertFalse(ch[1].bold) 299 self.assertFalse(ch[1].underline) 300 self.assertFalse(ch[1].reverseVideo) 301 302 ch = self.term.getCharacter(3, 0) 303 self.assertEqual(ch[0], 'Z') 304 self.assertTrue(ch[1].blink) 305 self.assertTrue(ch[1].bold) 306 self.assertFalse(ch[1].underline) 307 self.assertFalse(ch[1].reverseVideo) 308 309 def testColorAttributes(self): 310 s1 = "Merry xmas" 311 s2 = "Just kidding" 312 self.term.selectGraphicRendition(helper.FOREGROUND + helper.RED, 313 helper.BACKGROUND + helper.GREEN) 314 self.term.write(s1 + "\n") 315 self.term.selectGraphicRendition(NORMAL) 316 self.term.write(s2 + "\n") 317 318 for i in range(len(s1)): 319 ch = self.term.getCharacter(i, 0) 320 self.assertEqual(ch[0], s1[i]) 321 self.assertEqual(ch[1].charset, G0) 322 self.assertEqual(ch[1].bold, False) 323 self.assertEqual(ch[1].underline, False) 324 self.assertEqual(ch[1].blink, False) 325 self.assertEqual(ch[1].reverseVideo, False) 326 self.assertEqual(ch[1].foreground, helper.RED) 327 self.assertEqual(ch[1].background, helper.GREEN) 328 329 for i in range(len(s2)): 330 ch = self.term.getCharacter(i, 1) 331 self.assertEqual(ch[0], s2[i]) 332 self.assertEqual(ch[1].charset, G0) 333 self.assertEqual(ch[1].bold, False) 334 self.assertEqual(ch[1].underline, False) 335 self.assertEqual(ch[1].blink, False) 336 self.assertEqual(ch[1].reverseVideo, False) 337 self.assertEqual(ch[1].foreground, helper.WHITE) 338 self.assertEqual(ch[1].background, helper.BLACK) 339 340 def testEraseLine(self): 341 s1 = 'line 1' 342 s2 = 'line 2' 343 s3 = 'line 3' 344 self.term.write('\n'.join((s1, s2, s3)) + '\n') 345 self.term.cursorPosition(1, 1) 346 self.term.eraseLine() 347 348 self.assertEqual( 349 str(self.term), 350 s1 + '\n' + 351 '\n' + 352 s3 + '\n' + 353 '\n' * (HEIGHT - 4)) 354 355 def testEraseToLineEnd(self): 356 s = 'Hello, world.' 357 self.term.write(s) 358 self.term.cursorBackward(5) 359 self.term.eraseToLineEnd() 360 self.assertEqual( 361 str(self.term), 362 s[:-5] + '\n' + 363 '\n' * (HEIGHT - 2)) 364 365 def testEraseToLineBeginning(self): 366 s = 'Hello, world.' 367 self.term.write(s) 368 self.term.cursorBackward(5) 369 self.term.eraseToLineBeginning() 370 self.assertEqual( 371 str(self.term), 372 s[-4:].rjust(len(s)) + '\n' + 373 '\n' * (HEIGHT - 2)) 374 375 def testEraseDisplay(self): 376 self.term.write('Hello world\n') 377 self.term.write('Goodbye world\n') 378 self.term.eraseDisplay() 379 380 self.assertEqual( 381 str(self.term), 382 '\n' * (HEIGHT - 1)) 383 384 def testEraseToDisplayEnd(self): 385 s1 = "Hello world" 386 s2 = "Goodbye world" 387 self.term.write('\n'.join((s1, s2, ''))) 388 self.term.cursorPosition(5, 1) 389 self.term.eraseToDisplayEnd() 390 391 self.assertEqual( 392 str(self.term), 393 s1 + '\n' + 394 s2[:5] + '\n' + 395 '\n' * (HEIGHT - 3)) 396 397 def testEraseToDisplayBeginning(self): 398 s1 = "Hello world" 399 s2 = "Goodbye world" 400 self.term.write('\n'.join((s1, s2))) 401 self.term.cursorPosition(5, 1) 402 self.term.eraseToDisplayBeginning() 403 404 self.assertEqual( 405 str(self.term), 406 '\n' + 407 s2[6:].rjust(len(s2)) + '\n' + 408 '\n' * (HEIGHT - 3)) 409 410 def testLineInsertion(self): 411 s1 = "Hello world" 412 s2 = "Goodbye world" 413 self.term.write('\n'.join((s1, s2))) 414 self.term.cursorPosition(7, 1) 415 self.term.insertLine() 416 417 self.assertEqual( 418 str(self.term), 419 s1 + '\n' + 420 '\n' + 421 s2 + '\n' + 422 '\n' * (HEIGHT - 4)) 423 424 def testLineDeletion(self): 425 s1 = "Hello world" 426 s2 = "Middle words" 427 s3 = "Goodbye world" 428 self.term.write('\n'.join((s1, s2, s3))) 429 self.term.cursorPosition(9, 1) 430 self.term.deleteLine() 431 432 self.assertEqual( 433 str(self.term), 434 s1 + '\n' + 435 s3 + '\n' + 436 '\n' * (HEIGHT - 3)) 437 438class FakeDelayedCall: 439 called = False 440 cancelled = False 441 def __init__(self, fs, timeout, f, a, kw): 442 self.fs = fs 443 self.timeout = timeout 444 self.f = f 445 self.a = a 446 self.kw = kw 447 448 def active(self): 449 return not (self.cancelled or self.called) 450 451 def cancel(self): 452 self.cancelled = True 453# self.fs.calls.remove(self) 454 455 def call(self): 456 self.called = True 457 self.f(*self.a, **self.kw) 458 459class FakeScheduler: 460 def __init__(self): 461 self.calls = [] 462 463 def callLater(self, timeout, f, *a, **kw): 464 self.calls.append(FakeDelayedCall(self, timeout, f, a, kw)) 465 return self.calls[-1] 466 467class ExpectTestCase(unittest.TestCase): 468 def setUp(self): 469 self.term = helper.ExpectableBuffer() 470 self.term.connectionMade() 471 self.fs = FakeScheduler() 472 473 def testSimpleString(self): 474 result = [] 475 d = self.term.expect("hello world", timeout=1, scheduler=self.fs) 476 d.addCallback(result.append) 477 478 self.term.write("greeting puny earthlings\n") 479 self.assertFalse(result) 480 self.term.write("hello world\n") 481 self.assertTrue(result) 482 self.assertEqual(result[0].group(), "hello world") 483 self.assertEqual(len(self.fs.calls), 1) 484 self.assertFalse(self.fs.calls[0].active()) 485 486 def testBrokenUpString(self): 487 result = [] 488 d = self.term.expect("hello world") 489 d.addCallback(result.append) 490 491 self.assertFalse(result) 492 self.term.write("hello ") 493 self.assertFalse(result) 494 self.term.write("worl") 495 self.assertFalse(result) 496 self.term.write("d") 497 self.assertTrue(result) 498 self.assertEqual(result[0].group(), "hello world") 499 500 501 def testMultiple(self): 502 result = [] 503 d1 = self.term.expect("hello ") 504 d1.addCallback(result.append) 505 d2 = self.term.expect("world") 506 d2.addCallback(result.append) 507 508 self.assertFalse(result) 509 self.term.write("hello") 510 self.assertFalse(result) 511 self.term.write(" ") 512 self.assertEqual(len(result), 1) 513 self.term.write("world") 514 self.assertEqual(len(result), 2) 515 self.assertEqual(result[0].group(), "hello ") 516 self.assertEqual(result[1].group(), "world") 517 518 def testSynchronous(self): 519 self.term.write("hello world") 520 521 result = [] 522 d = self.term.expect("hello world") 523 d.addCallback(result.append) 524 self.assertTrue(result) 525 self.assertEqual(result[0].group(), "hello world") 526 527 def testMultipleSynchronous(self): 528 self.term.write("goodbye world") 529 530 result = [] 531 d1 = self.term.expect("bye") 532 d1.addCallback(result.append) 533 d2 = self.term.expect("world") 534 d2.addCallback(result.append) 535 536 self.assertEqual(len(result), 2) 537 self.assertEqual(result[0].group(), "bye") 538 self.assertEqual(result[1].group(), "world") 539 540 def _cbTestTimeoutFailure(self, res): 541 self.assertTrue(hasattr(res, 'type')) 542 self.assertEqual(res.type, helper.ExpectationTimeout) 543 544 def testTimeoutFailure(self): 545 d = self.term.expect("hello world", timeout=1, scheduler=self.fs) 546 d.addBoth(self._cbTestTimeoutFailure) 547 self.fs.calls[0].call() 548 549 def testOverlappingTimeout(self): 550 self.term.write("not zoomtastic") 551 552 result = [] 553 d1 = self.term.expect("hello world", timeout=1, scheduler=self.fs) 554 d1.addBoth(self._cbTestTimeoutFailure) 555 d2 = self.term.expect("zoom") 556 d2.addCallback(result.append) 557 558 self.fs.calls[0].call() 559 560 self.assertEqual(len(result), 1) 561 self.assertEqual(result[0].group(), "zoom") 562 563 564 565class CharacterAttributeTests(unittest.TestCase): 566 """ 567 Tests for L{twisted.conch.insults.helper.CharacterAttribute}. 568 """ 569 def test_equality(self): 570 """ 571 L{CharacterAttribute}s must have matching character attribute values 572 (bold, blink, underline, etc) with the same values to be considered 573 equal. 574 """ 575 self.assertEqual( 576 helper.CharacterAttribute(), 577 helper.CharacterAttribute()) 578 579 self.assertEqual( 580 helper.CharacterAttribute(), 581 helper.CharacterAttribute(charset=G0)) 582 583 self.assertEqual( 584 helper.CharacterAttribute( 585 bold=True, underline=True, blink=False, reverseVideo=True, 586 foreground=helper.BLUE), 587 helper.CharacterAttribute( 588 bold=True, underline=True, blink=False, reverseVideo=True, 589 foreground=helper.BLUE)) 590 591 self.assertNotEqual( 592 helper.CharacterAttribute(), 593 helper.CharacterAttribute(charset=G1)) 594 595 self.assertNotEqual( 596 helper.CharacterAttribute(bold=True), 597 helper.CharacterAttribute(bold=False)) 598 599 600 def test_wantOneDeprecated(self): 601 """ 602 L{twisted.conch.insults.helper.CharacterAttribute.wantOne} emits 603 a deprecation warning when invoked. 604 """ 605 # Trigger the deprecation warning. 606 helper._FormattingState().wantOne(bold=True) 607 608 warningsShown = self.flushWarnings([self.test_wantOneDeprecated]) 609 self.assertEqual(len(warningsShown), 1) 610 self.assertEqual(warningsShown[0]['category'], DeprecationWarning) 611 self.assertEqual( 612 warningsShown[0]['message'], 613 'twisted.conch.insults.helper.wantOne was deprecated in ' 614 'Twisted 13.1.0') 615