twisted conch trial TDD, "StringTransport instance has no attribute..."

152 Views Asked by At

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.

1

There are 1 best solutions below

5
Glyph On BEST ANSWER

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.