2

I am working on setting up a configurable SSH stub shell, similar to MockSSH, and I want to do it test-driven.

I am running into issues testing when I use conch.recvline.HistoricRecvLine instead of the base twisted.internet.protocol.Protocol

# test.py
from twisted.trial import unittest
from twisted.test.proto_helpers import StringTransportenter code here

class ShellTest(unittest.TestCase):
    def setUp(self):
        shell_factory = StubShell.ShellFactory()
        self.shell = shell_factory.buildProtocol(('127.0.0.1', 0))
        self.transport = StringTransport()
        self.shell.makeConnection(self.transport)

    def test_echo(self):
        self.shell.lineReceived('irrelevant')
        self.assertEqual(self.transport.value(), 'something')

# shell.py
from twisted.internet import protocol

class ShellProtocol(HistoricRecvLine):

    def connectionMade(self):
        HistoricRecvLine.connectionMade(self)

    def lineReceived(self, line):
        self.terminal.write('line received')

class ShellFactory(protocol.Factory):
    protocol = ShellProtocol

This Works just as expected. However, when I make the change:

class ShellProtocol(HistoricRecvLine):

I get the error:

exceptions.AttributeError: StringTransport instance has no attribute 'LEFT_ARROW'

NEXT STEP: Thanks to Glyph's help, i've gotten a bit further, still trying to get a bare-minimum test set up to ensure that I am setting up HistoricRecvLine protocol correctly. I stole some code from test_recvline.py, which still seems a bit magical to me, especially setting sp.factory = self(unittest.Testcase). I have been trying to hack it down to the minimum to get this test to pass.

class ShellTestConch(unittest.TestCase):

def setUp(self):
    self.sp = insults.ServerProtocol()
    self.transport = StringTransport()
    self.my_shell = StubShell.ShellProtocol()
    self.sp.protocolFactory = lambda: self.my_shell
    self.sp.factory = self
    self.sp.makeConnection(self.transport)

which gets me closer to expected output, however now I see that the Terminal is mangling the output a big, which should be expected.

twisted.trial.unittest.FailTest: '\x1bc>>> \x1b[4hline received' != 'line received'

For TDD purposes, I'm not sure if I should just accept that '\x1bc>>>...' version of the output (at least until I break it by setting a custom prompt) or attempt to overwrite the shell prompt to get clean output.

Lundy
  • 663
  • 5
  • 19
  • Edit: added "NEXT STEP" after Glyph's Answer got me a bit further. – Lundy Aug 08 '15 at 22:44
  • I am really trying to do deliberate step-by-step TDD to develop this SSH terminal emulator. ie. create a single test, minimum code to get it to pass, then the next test. Right now i'm stuck at the hardest part, step 1, bare-minimum to get my first FT (login successfully) to pass... but that requires several classes which all seem hopelessly interdependent. any advice on how to get started would be greatly appreciated. – Lundy Aug 09 '15 at 18:02

1 Answers1

1

Excellent question; thanks for asking (and thanks for using Twisted, and doing TDD :-)).

HistoricRecvLine is a TerminalProtocol, which provides ITerminalProtocol. ITerminalProtocol.makeConnection must be called with a provider of an ITerminalTransport. On the other hand, your unit test is calling your ShellProtocol.makeConnection with an instance of StringTransport, which provides only IConsumer, IPushProducer, and ITransport.

Since ShellProtocol.makeConnection expects an ITerminalTransport, it expects that it can call all of its methods. Unfortunately, LEFT_ARROW is not formally documented on this interface, which is an oversight, but the providers of this interface within Twisted also provide that attribute.

What you want to do is to wrap a ServerProtocol around your ITerminalProtocol, which in the process will serve as your ITerminalTransport. You can see a simple example of this composition in twisted.conch.stdio.

Glyph
  • 31,152
  • 11
  • 87
  • 129
  • Thanks for the Help. I have gotten a bit further by wrapping the StringTransport() mock in insults.ServerProtocol, and i've updated my question with the details. My question now is, should I test for the mangled string that the terminal sends to the transport layer "twisted.trial.unittest.FailTest: '\x1bc>>> \x1b[4hline received' != 'something'"? Or is there a better standard way to test this? – Lundy Aug 08 '15 at 22:51
  • 1
    It depends on your application, but you may want to make your own `ITerminalTransport` mock since it has relatively high-level methods you can assert about. – Glyph Aug 09 '15 at 23:18
  • 1
    Really, the answer is that Twisted ought to have provided you with a nice high-level way to do this, and we didn't, and for that I'm sorry. Feel free to file feature requests against Twisted Conch in the area of testing. I can't promise we'll have the ability to get to them, but we do at least consider testing incredibly important and would like it to be easier. – Glyph Aug 10 '15 at 01:37
  • I think i've hit on a workable solution, though i'm not sure its ideal, or exactly what you were suggesting, it does seem to do the trick. this is my [unit-test](https://github.com/Locky1138/StubShell/blob/dc748fdd65efc3c09440edb839462cf766d48a72/tests/test_stub_shell.py) I'll see about putting in that feature request, and I'd like to contribute a good Mock for it, if i can put it together. – Lundy Aug 15 '15 at 15:13
  • That seems pretty reasonable. I will be very happy to have that contribution to Twisted - I look forward to it :). – Glyph Aug 17 '15 at 23:06