test_insults.py 33 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973
  1. # -*- test-case-name: twisted.conch.test.test_insults -*-
  2. # Copyright (c) Twisted Matrix Laboratories.
  3. # See LICENSE for details.
  4. from twisted.python.reflect import namedAny
  5. from twisted.trial import unittest
  6. from twisted.test.proto_helpers import StringTransport
  7. from twisted.conch.insults.insults import ServerProtocol, ClientProtocol
  8. from twisted.conch.insults.insults import (CS_UK, CS_US, CS_DRAWING,
  9. CS_ALTERNATE,
  10. CS_ALTERNATE_SPECIAL,
  11. BLINK, UNDERLINE)
  12. from twisted.conch.insults.insults import G0, G1
  13. from twisted.conch.insults.insults import modes, privateModes
  14. from twisted.python.compat import intToBytes, iterbytes
  15. from twisted.python.constants import ValueConstant, Values
  16. import textwrap
  17. def _getattr(mock, name):
  18. return super(Mock, mock).__getattribute__(name)
  19. def occurrences(mock):
  20. return _getattr(mock, 'occurrences')
  21. def methods(mock):
  22. return _getattr(mock, 'methods')
  23. def _append(mock, obj):
  24. occurrences(mock).append(obj)
  25. default = object()
  26. def _ecmaCodeTableCoordinate(column, row):
  27. """
  28. Return the byte in 7- or 8-bit code table identified by C{column}
  29. and C{row}.
  30. "An 8-bit code table consists of 256 positions arranged in 16
  31. columns and 16 rows. The columns and rows are numbered 00 to 15."
  32. "A 7-bit code table consists of 128 positions arranged in 8
  33. columns and 16 rows. The columns are numbered 00 to 07 and the
  34. rows 00 to 15 (see figure 1)."
  35. p.5 of "Standard ECMA-35: Character Code Structure and Extension
  36. Techniques", 6th Edition (December 1994).
  37. """
  38. # 8 and 15 both happen to take up 4 bits, so the first number
  39. # should be shifted by 4 for both the 7- and 8-bit tables.
  40. return bytes(bytearray([(column << 4) | row]))
  41. def _makeControlFunctionSymbols(name, colOffset, names, doc):
  42. # the value for each name is the concatenation of the bit values
  43. # of its x, y locations, with an offset of 4 added to its x value.
  44. # so CUP is (0 + 4, 8) = (4, 8) = 4||8 = 1001000 = 72 = b"H"
  45. # this is how it's defined in the standard!
  46. attrs = {name: ValueConstant(_ecmaCodeTableCoordinate(i + colOffset, j))
  47. for j, row in enumerate(names)
  48. for i, name in enumerate(row)
  49. if name}
  50. attrs["__doc__"] = doc
  51. return type(name, (Values,), attrs)
  52. CSFinalByte = _makeControlFunctionSymbols(
  53. "CSFinalByte",
  54. colOffset=4,
  55. names=[
  56. # 4, 5, 6
  57. ['ICH', 'DCH', 'HPA'],
  58. ['CUU', 'SSE', 'HPR'],
  59. ['CUD', 'CPR', 'REP'],
  60. ['CUF', 'SU', 'DA'],
  61. ['CUB', 'SD', 'VPA'],
  62. ['CNL', 'NP', 'VPR'],
  63. ['CPL', 'PP', 'HVP'],
  64. ['CHA', 'CTC', 'TBC'],
  65. ['CUP', 'ECH', 'SM'],
  66. ['CHT', 'CVT', 'MC'],
  67. ['ED', 'CBT', 'HPB'],
  68. ['EL', 'SRS', 'VPB'],
  69. ['IL', 'PTX', 'RM'],
  70. ['DL', 'SDS', 'SGR'],
  71. ['EF', 'SIMD', 'DSR'],
  72. ['EA', None, 'DAQ'],
  73. ],
  74. doc=textwrap.dedent("""
  75. Symbolic constants for all control sequence final bytes
  76. that do not imply intermediate bytes. This happens to cover
  77. movement control sequences.
  78. See page 11 of "Standard ECMA 48: Control Functions for Coded
  79. Character Sets", 5th Edition (June 1991).
  80. Each L{ValueConstant} maps a control sequence name to L{bytes}
  81. """))
  82. C1SevenBit = _makeControlFunctionSymbols(
  83. "C1SevenBit",
  84. colOffset=4,
  85. names=[
  86. [None, "DCS"],
  87. [None, "PU1"],
  88. ["BPH", "PU2"],
  89. ["NBH", "STS"],
  90. [None, "CCH"],
  91. ["NEL", "MW"],
  92. ["SSA", "SPA"],
  93. ["ESA", "EPA"],
  94. ["HTS", "SOS"],
  95. ["HTJ", None],
  96. ["VTS", "SCI"],
  97. ["PLD", "CSI"],
  98. ["PLU", "ST"],
  99. ["RI", "OSC"],
  100. ["SS2", "PM"],
  101. ["SS3", "APC"],
  102. ],
  103. doc=textwrap.dedent("""
  104. Symbolic constants for all 7 bit versions of the C1 control functions
  105. See page 9 "Standard ECMA 48: Control Functions for Coded
  106. Character Sets", 5th Edition (June 1991).
  107. Each L{ValueConstant} maps a control sequence name to L{bytes}
  108. """))
  109. class Mock(object):
  110. callReturnValue = default
  111. def __init__(self, methods=None, callReturnValue=default):
  112. """
  113. @param methods: Mapping of names to return values
  114. @param callReturnValue: object __call__ should return
  115. """
  116. self.occurrences = []
  117. if methods is None:
  118. methods = {}
  119. self.methods = methods
  120. if callReturnValue is not default:
  121. self.callReturnValue = callReturnValue
  122. def __call__(self, *a, **kw):
  123. returnValue = _getattr(self, 'callReturnValue')
  124. if returnValue is default:
  125. returnValue = Mock()
  126. # _getattr(self, 'occurrences').append(('__call__', returnValue, a, kw))
  127. _append(self, ('__call__', returnValue, a, kw))
  128. return returnValue
  129. def __getattribute__(self, name):
  130. methods = _getattr(self, 'methods')
  131. if name in methods:
  132. attrValue = Mock(callReturnValue=methods[name])
  133. else:
  134. attrValue = Mock()
  135. # _getattr(self, 'occurrences').append((name, attrValue))
  136. _append(self, (name, attrValue))
  137. return attrValue
  138. class MockMixin:
  139. def assertCall(self, occurrence, methodName, expectedPositionalArgs=(),
  140. expectedKeywordArgs={}):
  141. attr, mock = occurrence
  142. self.assertEqual(attr, methodName)
  143. self.assertEqual(len(occurrences(mock)), 1)
  144. [(call, result, args, kw)] = occurrences(mock)
  145. self.assertEqual(call, "__call__")
  146. self.assertEqual(args, expectedPositionalArgs)
  147. self.assertEqual(kw, expectedKeywordArgs)
  148. return result
  149. _byteGroupingTestTemplate = """\
  150. def testByte%(groupName)s(self):
  151. transport = StringTransport()
  152. proto = Mock()
  153. parser = self.protocolFactory(lambda: proto)
  154. parser.factory = self
  155. parser.makeConnection(transport)
  156. bytes = self.TEST_BYTES
  157. while bytes:
  158. chunk = bytes[:%(bytesPer)d]
  159. bytes = bytes[%(bytesPer)d:]
  160. parser.dataReceived(chunk)
  161. self.verifyResults(transport, proto, parser)
  162. """
  163. class ByteGroupingsMixin(MockMixin):
  164. protocolFactory = None
  165. for word, n in [('Pairs', 2), ('Triples', 3), ('Quads', 4), ('Quints', 5), ('Sexes', 6)]:
  166. exec(_byteGroupingTestTemplate % {'groupName': word, 'bytesPer': n})
  167. del word, n
  168. def verifyResults(self, transport, proto, parser):
  169. result = self.assertCall(occurrences(proto).pop(0), "makeConnection", (parser,))
  170. self.assertEqual(occurrences(result), [])
  171. del _byteGroupingTestTemplate
  172. class ServerArrowKeysTests(ByteGroupingsMixin, unittest.TestCase):
  173. protocolFactory = ServerProtocol
  174. # All the arrow keys once
  175. TEST_BYTES = b'\x1b[A\x1b[B\x1b[C\x1b[D'
  176. def verifyResults(self, transport, proto, parser):
  177. ByteGroupingsMixin.verifyResults(self, transport, proto, parser)
  178. for arrow in (parser.UP_ARROW, parser.DOWN_ARROW,
  179. parser.RIGHT_ARROW, parser.LEFT_ARROW):
  180. result = self.assertCall(occurrences(proto).pop(0), "keystrokeReceived", (arrow, None))
  181. self.assertEqual(occurrences(result), [])
  182. self.assertFalse(occurrences(proto))
  183. class PrintableCharactersTests(ByteGroupingsMixin, unittest.TestCase):
  184. protocolFactory = ServerProtocol
  185. # Some letters and digits, first on their own, then capitalized,
  186. # then modified with alt
  187. TEST_BYTES = b'abc123ABC!@#\x1ba\x1bb\x1bc\x1b1\x1b2\x1b3'
  188. def verifyResults(self, transport, proto, parser):
  189. ByteGroupingsMixin.verifyResults(self, transport, proto, parser)
  190. for char in iterbytes(b'abc123ABC!@#'):
  191. result = self.assertCall(occurrences(proto).pop(0), "keystrokeReceived", (char, None))
  192. self.assertEqual(occurrences(result), [])
  193. for char in iterbytes(b'abc123'):
  194. result = self.assertCall(occurrences(proto).pop(0), "keystrokeReceived", (char, parser.ALT))
  195. self.assertEqual(occurrences(result), [])
  196. occs = occurrences(proto)
  197. self.assertFalse(occs, "%r should have been []" % (occs,))
  198. class ServerFunctionKeysTests(ByteGroupingsMixin, unittest.TestCase):
  199. """Test for parsing and dispatching function keys (F1 - F12)
  200. """
  201. protocolFactory = ServerProtocol
  202. byteList = []
  203. for byteCodes in (b'OP', b'OQ', b'OR', b'OS', # F1 - F4
  204. b'15~', b'17~', b'18~', b'19~', # F5 - F8
  205. b'20~', b'21~', b'23~', b'24~'): # F9 - F12
  206. byteList.append(b'\x1b[' + byteCodes)
  207. TEST_BYTES = b''.join(byteList)
  208. del byteList, byteCodes
  209. def verifyResults(self, transport, proto, parser):
  210. ByteGroupingsMixin.verifyResults(self, transport, proto, parser)
  211. for funcNum in range(1, 13):
  212. funcArg = getattr(parser, 'F%d' % (funcNum,))
  213. result = self.assertCall(occurrences(proto).pop(0), "keystrokeReceived", (funcArg, None))
  214. self.assertEqual(occurrences(result), [])
  215. self.assertFalse(occurrences(proto))
  216. class ClientCursorMovementTests(ByteGroupingsMixin, unittest.TestCase):
  217. protocolFactory = ClientProtocol
  218. d2 = b"\x1b[2B"
  219. r4 = b"\x1b[4C"
  220. u1 = b"\x1b[A"
  221. l2 = b"\x1b[2D"
  222. # Move the cursor down two, right four, up one, left two, up one, left two
  223. TEST_BYTES = d2 + r4 + u1 + l2 + u1 + l2
  224. del d2, r4, u1, l2
  225. def verifyResults(self, transport, proto, parser):
  226. ByteGroupingsMixin.verifyResults(self, transport, proto, parser)
  227. for (method, count) in [('Down', 2), ('Forward', 4), ('Up', 1),
  228. ('Backward', 2), ('Up', 1), ('Backward', 2)]:
  229. result = self.assertCall(occurrences(proto).pop(0), "cursor" + method, (count,))
  230. self.assertEqual(occurrences(result), [])
  231. self.assertFalse(occurrences(proto))
  232. class ClientControlSequencesTests(unittest.TestCase, MockMixin):
  233. def setUp(self):
  234. self.transport = StringTransport()
  235. self.proto = Mock()
  236. self.parser = ClientProtocol(lambda: self.proto)
  237. self.parser.factory = self
  238. self.parser.makeConnection(self.transport)
  239. result = self.assertCall(occurrences(self.proto).pop(0), "makeConnection", (self.parser,))
  240. self.assertFalse(occurrences(result))
  241. def testSimpleCardinals(self):
  242. self.parser.dataReceived(
  243. b''.join(
  244. [b''.join([b'\x1b[' + n + ch
  245. for n in (b'', intToBytes(2), intToBytes(20), intToBytes(200))]
  246. ) for ch in iterbytes(b'BACD')
  247. ]))
  248. occs = occurrences(self.proto)
  249. for meth in ("Down", "Up", "Forward", "Backward"):
  250. for count in (1, 2, 20, 200):
  251. result = self.assertCall(occs.pop(0), "cursor" + meth, (count,))
  252. self.assertFalse(occurrences(result))
  253. self.assertFalse(occs)
  254. def testScrollRegion(self):
  255. self.parser.dataReceived(b'\x1b[5;22r\x1b[r')
  256. occs = occurrences(self.proto)
  257. result = self.assertCall(occs.pop(0), "setScrollRegion", (5, 22))
  258. self.assertFalse(occurrences(result))
  259. result = self.assertCall(occs.pop(0), "setScrollRegion", (None, None))
  260. self.assertFalse(occurrences(result))
  261. self.assertFalse(occs)
  262. def testHeightAndWidth(self):
  263. self.parser.dataReceived(b"\x1b#3\x1b#4\x1b#5\x1b#6")
  264. occs = occurrences(self.proto)
  265. result = self.assertCall(occs.pop(0), "doubleHeightLine", (True,))
  266. self.assertFalse(occurrences(result))
  267. result = self.assertCall(occs.pop(0), "doubleHeightLine", (False,))
  268. self.assertFalse(occurrences(result))
  269. result = self.assertCall(occs.pop(0), "singleWidthLine")
  270. self.assertFalse(occurrences(result))
  271. result = self.assertCall(occs.pop(0), "doubleWidthLine")
  272. self.assertFalse(occurrences(result))
  273. self.assertFalse(occs)
  274. def testCharacterSet(self):
  275. self.parser.dataReceived(
  276. b''.join(
  277. [b''.join([b'\x1b' + g + n for n in iterbytes(b'AB012')])
  278. for g in iterbytes(b'()')
  279. ]))
  280. occs = occurrences(self.proto)
  281. for which in (G0, G1):
  282. for charset in (CS_UK, CS_US, CS_DRAWING, CS_ALTERNATE, CS_ALTERNATE_SPECIAL):
  283. result = self.assertCall(occs.pop(0), "selectCharacterSet", (charset, which))
  284. self.assertFalse(occurrences(result))
  285. self.assertFalse(occs)
  286. def testShifting(self):
  287. self.parser.dataReceived(b"\x15\x14")
  288. occs = occurrences(self.proto)
  289. result = self.assertCall(occs.pop(0), "shiftIn")
  290. self.assertFalse(occurrences(result))
  291. result = self.assertCall(occs.pop(0), "shiftOut")
  292. self.assertFalse(occurrences(result))
  293. self.assertFalse(occs)
  294. def testSingleShifts(self):
  295. self.parser.dataReceived(b"\x1bN\x1bO")
  296. occs = occurrences(self.proto)
  297. result = self.assertCall(occs.pop(0), "singleShift2")
  298. self.assertFalse(occurrences(result))
  299. result = self.assertCall(occs.pop(0), "singleShift3")
  300. self.assertFalse(occurrences(result))
  301. self.assertFalse(occs)
  302. def testKeypadMode(self):
  303. self.parser.dataReceived(b"\x1b=\x1b>")
  304. occs = occurrences(self.proto)
  305. result = self.assertCall(occs.pop(0), "applicationKeypadMode")
  306. self.assertFalse(occurrences(result))
  307. result = self.assertCall(occs.pop(0), "numericKeypadMode")
  308. self.assertFalse(occurrences(result))
  309. self.assertFalse(occs)
  310. def testCursor(self):
  311. self.parser.dataReceived(b"\x1b7\x1b8")
  312. occs = occurrences(self.proto)
  313. result = self.assertCall(occs.pop(0), "saveCursor")
  314. self.assertFalse(occurrences(result))
  315. result = self.assertCall(occs.pop(0), "restoreCursor")
  316. self.assertFalse(occurrences(result))
  317. self.assertFalse(occs)
  318. def testReset(self):
  319. self.parser.dataReceived(b"\x1bc")
  320. occs = occurrences(self.proto)
  321. result = self.assertCall(occs.pop(0), "reset")
  322. self.assertFalse(occurrences(result))
  323. self.assertFalse(occs)
  324. def testIndex(self):
  325. self.parser.dataReceived(b"\x1bD\x1bM\x1bE")
  326. occs = occurrences(self.proto)
  327. result = self.assertCall(occs.pop(0), "index")
  328. self.assertFalse(occurrences(result))
  329. result = self.assertCall(occs.pop(0), "reverseIndex")
  330. self.assertFalse(occurrences(result))
  331. result = self.assertCall(occs.pop(0), "nextLine")
  332. self.assertFalse(occurrences(result))
  333. self.assertFalse(occs)
  334. def testModes(self):
  335. self.parser.dataReceived(
  336. b"\x1b[" + b';'.join(map(intToBytes, [modes.KAM, modes.IRM, modes.LNM])) + b"h")
  337. self.parser.dataReceived(
  338. b"\x1b[" + b';'.join(map(intToBytes, [modes.KAM, modes.IRM, modes.LNM])) + b"l")
  339. occs = occurrences(self.proto)
  340. result = self.assertCall(occs.pop(0), "setModes", ([modes.KAM, modes.IRM, modes.LNM],))
  341. self.assertFalse(occurrences(result))
  342. result = self.assertCall(occs.pop(0), "resetModes", ([modes.KAM, modes.IRM, modes.LNM],))
  343. self.assertFalse(occurrences(result))
  344. self.assertFalse(occs)
  345. def testErasure(self):
  346. self.parser.dataReceived(
  347. b"\x1b[K\x1b[1K\x1b[2K\x1b[J\x1b[1J\x1b[2J\x1b[3P")
  348. occs = occurrences(self.proto)
  349. for meth in ("eraseToLineEnd", "eraseToLineBeginning", "eraseLine",
  350. "eraseToDisplayEnd", "eraseToDisplayBeginning",
  351. "eraseDisplay"):
  352. result = self.assertCall(occs.pop(0), meth)
  353. self.assertFalse(occurrences(result))
  354. result = self.assertCall(occs.pop(0), "deleteCharacter", (3,))
  355. self.assertFalse(occurrences(result))
  356. self.assertFalse(occs)
  357. def testLineDeletion(self):
  358. self.parser.dataReceived(b"\x1b[M\x1b[3M")
  359. occs = occurrences(self.proto)
  360. for arg in (1, 3):
  361. result = self.assertCall(occs.pop(0), "deleteLine", (arg,))
  362. self.assertFalse(occurrences(result))
  363. self.assertFalse(occs)
  364. def testLineInsertion(self):
  365. self.parser.dataReceived(b"\x1b[L\x1b[3L")
  366. occs = occurrences(self.proto)
  367. for arg in (1, 3):
  368. result = self.assertCall(occs.pop(0), "insertLine", (arg,))
  369. self.assertFalse(occurrences(result))
  370. self.assertFalse(occs)
  371. def testCursorPosition(self):
  372. methods(self.proto)['reportCursorPosition'] = (6, 7)
  373. self.parser.dataReceived(b"\x1b[6n")
  374. self.assertEqual(self.transport.value(), b"\x1b[7;8R")
  375. occs = occurrences(self.proto)
  376. result = self.assertCall(occs.pop(0), "reportCursorPosition")
  377. # This isn't really an interesting assert, since it only tests that
  378. # our mock setup is working right, but I'll include it anyway.
  379. self.assertEqual(result, (6, 7))
  380. def test_applicationDataBytes(self):
  381. """
  382. Contiguous non-control bytes are passed to a single call to the
  383. C{write} method of the terminal to which the L{ClientProtocol} is
  384. connected.
  385. """
  386. occs = occurrences(self.proto)
  387. self.parser.dataReceived(b'a')
  388. self.assertCall(occs.pop(0), "write", (b"a",))
  389. self.parser.dataReceived(b'bc')
  390. self.assertCall(occs.pop(0), "write", (b"bc",))
  391. def _applicationDataTest(self, data, calls):
  392. occs = occurrences(self.proto)
  393. self.parser.dataReceived(data)
  394. while calls:
  395. self.assertCall(occs.pop(0), *calls.pop(0))
  396. self.assertFalse(occs, "No other calls should happen: %r" % (occs,))
  397. def test_shiftInAfterApplicationData(self):
  398. """
  399. Application data bytes followed by a shift-in command are passed to a
  400. call to C{write} before the terminal's C{shiftIn} method is called.
  401. """
  402. self._applicationDataTest(
  403. b'ab\x15', [
  404. ("write", (b"ab",)),
  405. ("shiftIn",)])
  406. def test_shiftOutAfterApplicationData(self):
  407. """
  408. Application data bytes followed by a shift-out command are passed to a
  409. call to C{write} before the terminal's C{shiftOut} method is called.
  410. """
  411. self._applicationDataTest(
  412. b'ab\x14', [
  413. ("write", (b"ab",)),
  414. ("shiftOut",)])
  415. def test_cursorBackwardAfterApplicationData(self):
  416. """
  417. Application data bytes followed by a cursor-backward command are passed
  418. to a call to C{write} before the terminal's C{cursorBackward} method is
  419. called.
  420. """
  421. self._applicationDataTest(
  422. b'ab\x08', [
  423. ("write", (b"ab",)),
  424. ("cursorBackward",)])
  425. def test_escapeAfterApplicationData(self):
  426. """
  427. Application data bytes followed by an escape character are passed to a
  428. call to C{write} before the terminal's handler method for the escape is
  429. called.
  430. """
  431. # Test a short escape
  432. self._applicationDataTest(
  433. b'ab\x1bD', [
  434. ("write", (b"ab",)),
  435. ("index",)])
  436. # And a long escape
  437. self._applicationDataTest(
  438. b'ab\x1b[4h', [
  439. ("write", (b"ab",)),
  440. ("setModes", ([4],))])
  441. # There's some other cases too, but they're all handled by the same
  442. # codepaths as above.
  443. class ServerProtocolOutputTests(unittest.TestCase):
  444. """
  445. Tests for the bytes L{ServerProtocol} writes to its transport when its
  446. methods are called.
  447. """
  448. # From ECMA 48: CSI is represented by bit combinations 01/11
  449. # (representing ESC) and 05/11 in a 7-bit code or by bit
  450. # combination 09/11 in an 8-bit code
  451. ESC = _ecmaCodeTableCoordinate(1, 11)
  452. CSI = ESC + _ecmaCodeTableCoordinate(5, 11)
  453. def setUp(self):
  454. self.protocol = ServerProtocol()
  455. self.transport = StringTransport()
  456. self.protocol.makeConnection(self.transport)
  457. def test_cursorUp(self):
  458. """
  459. L{ServerProtocol.cursorUp} writes the control sequence
  460. ending with L{CSFinalByte.CUU} to its transport.
  461. """
  462. self.protocol.cursorUp(1)
  463. self.assertEqual(self.transport.value(),
  464. self.CSI + b'1' + CSFinalByte.CUU.value)
  465. def test_cursorDown(self):
  466. """
  467. L{ServerProtocol.cursorDown} writes the control sequence
  468. ending with L{CSFinalByte.CUD} to its transport.
  469. """
  470. self.protocol.cursorDown(1)
  471. self.assertEqual(self.transport.value(),
  472. self.CSI + b'1' + CSFinalByte.CUD.value)
  473. def test_cursorForward(self):
  474. """
  475. L{ServerProtocol.cursorForward} writes the control sequence
  476. ending with L{CSFinalByte.CUF} to its transport.
  477. """
  478. self.protocol.cursorForward(1)
  479. self.assertEqual(self.transport.value(),
  480. self.CSI + b'1' + CSFinalByte.CUF.value)
  481. def test_cursorBackward(self):
  482. """
  483. L{ServerProtocol.cursorBackward} writes the control sequence
  484. ending with L{CSFinalByte.CUB} to its transport.
  485. """
  486. self.protocol.cursorBackward(1)
  487. self.assertEqual(self.transport.value(),
  488. self.CSI + b'1' + CSFinalByte.CUB.value)
  489. def test_cursorPosition(self):
  490. """
  491. L{ServerProtocol.cursorPosition} writes a control sequence
  492. ending with L{CSFinalByte.CUP} and containing the expected
  493. coordinates to its transport.
  494. """
  495. self.protocol.cursorPosition(0, 0)
  496. self.assertEqual(self.transport.value(),
  497. self.CSI + b'1;1' + CSFinalByte.CUP.value)
  498. def test_cursorHome(self):
  499. """
  500. L{ServerProtocol.cursorHome} writes a control sequence ending
  501. with L{CSFinalByte.CUP} and no parameters, so that the client
  502. defaults to (1, 1).
  503. """
  504. self.protocol.cursorHome()
  505. self.assertEqual(self.transport.value(),
  506. self.CSI + CSFinalByte.CUP.value)
  507. def test_index(self):
  508. """
  509. L{ServerProtocol.index} writes the control sequence ending in
  510. the 8-bit code table coordinates 4, 4.
  511. Note that ECMA48 5th Edition removes C{IND}.
  512. """
  513. self.protocol.index()
  514. self.assertEqual(self.transport.value(),
  515. self.ESC + _ecmaCodeTableCoordinate(4, 4))
  516. def test_reverseIndex(self):
  517. """
  518. L{ServerProtocol.reverseIndex} writes the control sequence
  519. ending in the L{C1SevenBit.RI}.
  520. """
  521. self.protocol.reverseIndex()
  522. self.assertEqual(self.transport.value(),
  523. self.ESC + C1SevenBit.RI.value)
  524. def test_nextLine(self):
  525. """
  526. L{ServerProtocol.nextLine} writes C{"\r\n"} to its transport.
  527. """
  528. # Why doesn't it write ESC E? Because ESC E is poorly supported. For
  529. # example, gnome-terminal (many different versions) fails to scroll if
  530. # it receives ESC E and the cursor is already on the last row.
  531. self.protocol.nextLine()
  532. self.assertEqual(self.transport.value(), b"\r\n")
  533. def test_setModes(self):
  534. """
  535. L{ServerProtocol.setModes} writes a control sequence
  536. containing the requested modes and ending in the
  537. L{CSFinalByte.SM}.
  538. """
  539. modesToSet = [modes.KAM, modes.IRM, modes.LNM]
  540. self.protocol.setModes(modesToSet)
  541. self.assertEqual(self.transport.value(),
  542. self.CSI +
  543. b';'.join(map(intToBytes, modesToSet)) +
  544. CSFinalByte.SM.value)
  545. def test_setPrivateModes(self):
  546. """
  547. L{ServerProtocol.setPrivatesModes} writes a control sequence
  548. containing the requested private modes and ending in the
  549. L{CSFinalByte.SM}.
  550. """
  551. privateModesToSet = [privateModes.ERROR,
  552. privateModes.COLUMN,
  553. privateModes.ORIGIN]
  554. self.protocol.setModes(privateModesToSet)
  555. self.assertEqual(self.transport.value(),
  556. self.CSI +
  557. b';'.join(map(intToBytes, privateModesToSet)) +
  558. CSFinalByte.SM.value)
  559. def test_resetModes(self):
  560. """
  561. L{ServerProtocol.resetModes} writes the control sequence
  562. ending in the L{CSFinalByte.RM}.
  563. """
  564. modesToSet = [modes.KAM, modes.IRM, modes.LNM]
  565. self.protocol.resetModes(modesToSet)
  566. self.assertEqual(self.transport.value(),
  567. self.CSI +
  568. b';'.join(map(intToBytes, modesToSet)) +
  569. CSFinalByte.RM.value)
  570. def test_singleShift2(self):
  571. """
  572. L{ServerProtocol.singleShift2} writes an escape sequence
  573. followed by L{C1SevenBit.SS2}
  574. """
  575. self.protocol.singleShift2()
  576. self.assertEqual(self.transport.value(),
  577. self.ESC + C1SevenBit.SS2.value)
  578. def test_singleShift3(self):
  579. """
  580. L{ServerProtocol.singleShift3} writes an escape sequence
  581. followed by L{C1SevenBit.SS3}
  582. """
  583. self.protocol.singleShift3()
  584. self.assertEqual(self.transport.value(),
  585. self.ESC + C1SevenBit.SS3.value)
  586. def test_selectGraphicRendition(self):
  587. """
  588. L{ServerProtocol.selectGraphicRendition} writes a control
  589. sequence containing the requested attributes and ending with
  590. L{CSFinalByte.SGR}
  591. """
  592. self.protocol.selectGraphicRendition(str(BLINK), str(UNDERLINE))
  593. self.assertEqual(self.transport.value(),
  594. self.CSI +
  595. intToBytes(BLINK) + b';' + intToBytes(UNDERLINE) +
  596. CSFinalByte.SGR.value)
  597. def test_horizontalTabulationSet(self):
  598. """
  599. L{ServerProtocol.horizontalTabulationSet} writes the escape
  600. sequence ending in L{C1SevenBit.HTS}
  601. """
  602. self.protocol.horizontalTabulationSet()
  603. self.assertEqual(self.transport.value(),
  604. self.ESC +
  605. C1SevenBit.HTS.value)
  606. def test_eraseToLineEnd(self):
  607. """
  608. L{ServerProtocol.eraseToLineEnd} writes the control sequence
  609. sequence ending in L{CSFinalByte.EL} and no parameters,
  610. forcing the client to default to 0 (from the active present
  611. position's current location to the end of the line.)
  612. """
  613. self.protocol.eraseToLineEnd()
  614. self.assertEqual(self.transport.value(),
  615. self.CSI + CSFinalByte.EL.value)
  616. def test_eraseToLineBeginning(self):
  617. """
  618. L{ServerProtocol.eraseToLineBeginning} writes the control
  619. sequence sequence ending in L{CSFinalByte.EL} and a parameter
  620. of 1 (from the beginning of the line up to and include the
  621. active present position's current location.)
  622. """
  623. self.protocol.eraseToLineBeginning()
  624. self.assertEqual(self.transport.value(),
  625. self.CSI + b'1' + CSFinalByte.EL.value)
  626. def test_eraseLine(self):
  627. """
  628. L{ServerProtocol.eraseLine} writes the control
  629. sequence sequence ending in L{CSFinalByte.EL} and a parameter
  630. of 2 (the entire line.)
  631. """
  632. self.protocol.eraseLine()
  633. self.assertEqual(self.transport.value(),
  634. self.CSI + b'2' + CSFinalByte.EL.value)
  635. def test_eraseToDisplayEnd(self):
  636. """
  637. L{ServerProtocol.eraseToDisplayEnd} writes the control
  638. sequence sequence ending in L{CSFinalByte.ED} and no parameters,
  639. forcing the client to default to 0 (from the active present
  640. position's current location to the end of the page.)
  641. """
  642. self.protocol.eraseToDisplayEnd()
  643. self.assertEqual(self.transport.value(),
  644. self.CSI + CSFinalByte.ED.value)
  645. def test_eraseToDisplayBeginning(self):
  646. """
  647. L{ServerProtocol.eraseToDisplayBeginning} writes the control
  648. sequence sequence ending in L{CSFinalByte.ED} a parameter of 1
  649. (from the beginning of the page up to and include the active
  650. present position's current location.)
  651. """
  652. self.protocol.eraseToDisplayBeginning()
  653. self.assertEqual(self.transport.value(),
  654. self.CSI + b'1' + CSFinalByte.ED.value)
  655. def test_eraseToDisplay(self):
  656. """
  657. L{ServerProtocol.eraseDisplay} writes the control sequence
  658. sequence ending in L{CSFinalByte.ED} a parameter of 2 (the
  659. entire page)
  660. """
  661. self.protocol.eraseDisplay()
  662. self.assertEqual(self.transport.value(),
  663. self.CSI + b'2' + CSFinalByte.ED.value)
  664. def test_deleteCharacter(self):
  665. """
  666. L{ServerProtocol.deleteCharacter} writes the control sequence
  667. containing the number of characters to delete and ending in
  668. L{CSFinalByte.DCH}
  669. """
  670. self.protocol.deleteCharacter(4)
  671. self.assertEqual(self.transport.value(),
  672. self.CSI + b'4' + CSFinalByte.DCH.value)
  673. def test_insertLine(self):
  674. """
  675. L{ServerProtocol.insertLine} writes the control sequence
  676. containing the number of lines to insert and ending in
  677. L{CSFinalByte.IL}
  678. """
  679. self.protocol.insertLine(5)
  680. self.assertEqual(self.transport.value(),
  681. self.CSI + b'5' + CSFinalByte.IL.value)
  682. def test_deleteLine(self):
  683. """
  684. L{ServerProtocol.deleteLine} writes the control sequence
  685. containing the number of lines to delete and ending in
  686. L{CSFinalByte.DL}
  687. """
  688. self.protocol.deleteLine(6)
  689. self.assertEqual(self.transport.value(),
  690. self.CSI + b'6' + CSFinalByte.DL.value)
  691. def test_setScrollRegionNoArgs(self):
  692. """
  693. With no arguments, L{ServerProtocol.setScrollRegion} writes a
  694. control sequence with no parameters, but a parameter
  695. separator, and ending in C{b'r'}.
  696. """
  697. self.protocol.setScrollRegion()
  698. self.assertEqual(self.transport.value(), self.CSI + b';' + b'r')
  699. def test_setScrollRegionJustFirst(self):
  700. """
  701. With just a value for its C{first} argument,
  702. L{ServerProtocol.setScrollRegion} writes a control sequence with
  703. that parameter, a parameter separator, and finally a C{b'r'}.
  704. """
  705. self.protocol.setScrollRegion(first=1)
  706. self.assertEqual(self.transport.value(), self.CSI + b'1;' + b'r')
  707. def test_setScrollRegionJustLast(self):
  708. """
  709. With just a value for its C{last} argument,
  710. L{ServerProtocol.setScrollRegion} writes a control sequence with
  711. a parameter separator, that parameter, and finally a C{b'r'}.
  712. """
  713. self.protocol.setScrollRegion(last=1)
  714. self.assertEqual(self.transport.value(), self.CSI + b';1' + b'r')
  715. def test_setScrollRegionFirstAndLast(self):
  716. """
  717. When given both C{first} and C{last}
  718. L{ServerProtocol.setScrollRegion} writes a control sequence with
  719. the first parameter, a parameter separator, the last
  720. parameter, and finally a C{b'r'}.
  721. """
  722. self.protocol.setScrollRegion(first=1, last=2)
  723. self.assertEqual(self.transport.value(), self.CSI + b'1;2' + b'r')
  724. def test_reportCursorPosition(self):
  725. """
  726. L{ServerProtocol.reportCursorPosition} writes a control
  727. sequence ending in L{CSFinalByte.DSR} with a parameter of 6
  728. (the Device Status Report returns the current active
  729. position.)
  730. """
  731. self.protocol.reportCursorPosition()
  732. self.assertEqual(self.transport.value(),
  733. self.CSI + b'6' + CSFinalByte.DSR.value)
  734. class DeprecationsTests(unittest.TestCase):
  735. """
  736. Tests to ensure deprecation of L{insults.colors} and L{insults.client}
  737. """
  738. def ensureDeprecated(self, message):
  739. """
  740. Ensures that the correct deprecation warning was issued.
  741. """
  742. warnings = self.flushWarnings()
  743. self.assertIs(warnings[0]['category'], DeprecationWarning)
  744. self.assertEqual(warnings[0]['message'], message)
  745. self.assertEqual(len(warnings), 1)
  746. def test_colors(self):
  747. """
  748. The L{insults.colors} module is deprecated
  749. """
  750. namedAny('twisted.conch.insults.colors')
  751. self.ensureDeprecated("twisted.conch.insults.colors was deprecated "
  752. "in Twisted 10.1.0: Please use "
  753. "twisted.conch.insults.helper instead.")
  754. def test_client(self):
  755. """
  756. The L{insults.client} module is deprecated
  757. """
  758. namedAny('twisted.conch.insults.client')
  759. self.ensureDeprecated("twisted.conch.insults.client was deprecated "
  760. "in Twisted 10.1.0: Please use "
  761. "twisted.conch.insults.insults instead.")