1 # -*- test-case-name: twisted.conch.test.test_manhole -*-
2 # Copyright (c) Twisted Matrix Laboratories.
3 # See LICENSE for details.
6 Tests for L{twisted.conch.manhole}.
11 from twisted.trial import unittest
12 from twisted.internet import error, defer
13 from twisted.test.proto_helpers import StringTransport
14 from twisted.conch.test.test_recvline import _TelnetMixin, _SSHMixin, _StdioMixin, stdio, ssh
15 from twisted.conch import manhole
16 from twisted.conch.insults import insults
19 def determineDefaultFunctionName():
21 Return the string used by Python as the name for code objects which are
22 compiled from interactive input or at the top-level of modules.
27 # The last frame is this function. The second to last frame is this
28 # function's caller, which is module-scope, which is what we want,
30 return traceback.extract_stack()[-2][2]
31 defaultFunctionName = determineDefaultFunctionName()
35 class ManholeInterpreterTests(unittest.TestCase):
37 Tests for L{manhole.ManholeInterpreter}.
39 def test_resetBuffer(self):
41 L{ManholeInterpreter.resetBuffer} should empty the input buffer.
43 interpreter = manhole.ManholeInterpreter(None)
44 interpreter.buffer.extend(["1", "2"])
45 interpreter.resetBuffer()
46 self.assertFalse(interpreter.buffer)
50 class ManholeProtocolTests(unittest.TestCase):
52 Tests for L{manhole.Manhole}.
54 def test_interruptResetsInterpreterBuffer(self):
56 L{manhole.Manhole.handle_INT} should cause the interpreter input buffer
59 transport = StringTransport()
60 terminal = insults.ServerProtocol(manhole.Manhole)
61 terminal.makeConnection(transport)
62 protocol = terminal.terminalProtocol
63 interpreter = protocol.interpreter
64 interpreter.buffer.extend(["1", "2"])
66 self.assertFalse(interpreter.buffer)
70 class WriterTestCase(unittest.TestCase):
71 def testInteger(self):
72 manhole.lastColorizedLine("1")
75 def testDoubleQuoteString(self):
76 manhole.lastColorizedLine('"1"')
79 def testSingleQuoteString(self):
80 manhole.lastColorizedLine("'1'")
83 def testTripleSingleQuotedString(self):
84 manhole.lastColorizedLine("'''1'''")
87 def testTripleDoubleQuotedString(self):
88 manhole.lastColorizedLine('"""1"""')
91 def testFunctionDefinition(self):
92 manhole.lastColorizedLine("def foo():")
95 def testClassDefinition(self):
96 manhole.lastColorizedLine("class foo:")
99 class ManholeLoopbackMixin:
100 serverProtocol = manhole.ColoredManhole
103 return defer.waitForDeferred(d)
105 def testSimpleExpression(self):
106 done = self.recvlineClient.expect("done")
118 return done.addCallback(finished)
120 def testTripleQuoteLineContinuation(self):
121 done = self.recvlineClient.expect("done")
134 return done.addCallback(finished)
136 def testFunctionDefinition(self):
137 done = self.recvlineClient.expect("done")
147 [">>> def foo(bar):",
154 return done.addCallback(finished)
156 def testClassDefinition(self):
157 done = self.recvlineClient.expect("done")
162 "\t\tprint 'Hello, world!'\n\n"
169 "... def bar(self):",
170 "... print 'Hello, world!'",
176 return done.addCallback(finished)
178 def testException(self):
179 done = self.recvlineClient.expect("done")
182 "raise Exception('foo bar baz')\n"
187 [">>> raise Exception('foo bar baz')",
188 "Traceback (most recent call last):",
189 ' File "<console>", line 1, in ' + defaultFunctionName,
190 "Exception: foo bar baz",
193 return done.addCallback(finished)
195 def testControlC(self):
196 done = self.recvlineClient.expect("done")
199 "cancelled line" + manhole.CTRL_C +
204 [">>> cancelled line",
208 return done.addCallback(finished)
211 def test_interruptDuringContinuation(self):
213 Sending ^C to Manhole while in a state where more input is required to
214 complete a statement should discard the entire ongoing statement and
215 reset the input prompt to the non-continuation prompt.
217 continuing = self.recvlineClient.expect("things")
219 self._testwrite("(\nthings")
221 def gotContinuation(ignored):
225 interrupted = self.recvlineClient.expect(">>> ")
226 self._testwrite(manhole.CTRL_C)
228 continuing.addCallback(gotContinuation)
230 def gotInterruption(ignored):
236 continuing.addCallback(gotInterruption)
240 def testControlBackslash(self):
241 self._testwrite("cancelled line")
242 partialLine = self.recvlineClient.expect("cancelled line")
244 def gotPartialLine(ign):
246 [">>> cancelled line"])
247 self._testwrite(manhole.CTRL_BACKSLASH)
249 d = self.recvlineClient.onDisconnection
250 return self.assertFailure(d, error.ConnectionDone)
252 def gotClearedLine(ign):
256 return partialLine.addCallback(gotPartialLine).addCallback(gotClearedLine)
258 def testControlD(self):
259 self._testwrite("1 + 1")
260 helloWorld = self.wfd(self.recvlineClient.expect(r"\+ 1"))
262 helloWorld.getResult()
263 self._assertBuffer([">>> 1 + 1"])
265 self._testwrite(manhole.CTRL_D + " + 1")
266 cleared = self.wfd(self.recvlineClient.expect(r"\+ 1"))
269 self._assertBuffer([">>> 1 + 1 + 1"])
271 self._testwrite("\n")
272 printed = self.wfd(self.recvlineClient.expect("3\n>>> "))
276 self._testwrite(manhole.CTRL_D)
277 d = self.recvlineClient.onDisconnection
278 disconnected = self.wfd(self.assertFailure(d, error.ConnectionDone))
280 disconnected.getResult()
281 testControlD = defer.deferredGenerator(testControlD)
284 def testControlL(self):
286 CTRL+L is generally used as a redraw-screen command in terminal
287 applications. Manhole doesn't currently respect this usage of it,
288 but it should at least do something reasonable in response to this
289 event (rather than, say, eating your face).
291 # Start off with a newline so that when we clear the display we can
292 # tell by looking for the missing first empty prompt line.
293 self._testwrite("\n1 + 1")
294 helloWorld = self.wfd(self.recvlineClient.expect(r"\+ 1"))
296 helloWorld.getResult()
297 self._assertBuffer([">>> ", ">>> 1 + 1"])
299 self._testwrite(manhole.CTRL_L + " + 1")
300 redrew = self.wfd(self.recvlineClient.expect(r"1 \+ 1 \+ 1"))
303 self._assertBuffer([">>> 1 + 1 + 1"])
304 testControlL = defer.deferredGenerator(testControlL)
307 def test_controlA(self):
309 CTRL-A can be used as HOME - returning cursor to beginning of
312 self._testwrite('rint "hello"' + '\x01' + 'p')
313 d = self.recvlineClient.expect('print "hello"')
315 self._assertBuffer(['>>> print "hello"'])
316 return d.addCallback(cb)
319 def test_controlE(self):
321 CTRL-E can be used as END - setting cursor to end of current
324 self._testwrite('rint "hello' + '\x01' + 'p' + '\x05' + '"')
325 d = self.recvlineClient.expect('print "hello"')
327 self._assertBuffer(['>>> print "hello"'])
328 return d.addCallback(cb)
331 def testDeferred(self):
333 "from twisted.internet import defer, reactor\n"
334 "d = defer.Deferred()\n"
337 deferred = self.wfd(self.recvlineClient.expect("<Deferred #0>"))
342 "c = reactor.callLater(0.1, d.callback, 'Hi!')\n")
343 delayed = self.wfd(self.recvlineClient.expect(">>> "))
347 called = self.wfd(self.recvlineClient.expect("Deferred #0 called back: 'Hi!'\n>>> "))
351 [">>> from twisted.internet import defer, reactor",
352 ">>> d = defer.Deferred()",
355 ">>> c = reactor.callLater(0.1, d.callback, 'Hi!')",
356 "Deferred #0 called back: 'Hi!'",
359 testDeferred = defer.deferredGenerator(testDeferred)
361 class ManholeLoopbackTelnet(_TelnetMixin, unittest.TestCase, ManholeLoopbackMixin):
364 class ManholeLoopbackSSH(_SSHMixin, unittest.TestCase, ManholeLoopbackMixin):
366 skip = "Crypto requirements missing, can't run manhole tests over ssh"
368 class ManholeLoopbackStdio(_StdioMixin, unittest.TestCase, ManholeLoopbackMixin):
370 skip = "Terminal requirements missing, can't run manhole tests over stdio"
372 serverProtocol = stdio.ConsoleManhole