Ticket #3277: lr.patch

File lr.patch, 8.1 KB (added by spiv, 12 years ago)

Patch for inconsistencies between LineReceiver and LineOnlyReceiver (depends on testscenarios)

  • twisted/protocols/basic.py

    === modified file 'twisted/protocols/basic.py'
     
    133133
    134134    def dataReceived(self, data):
    135135        """Translates bytes into lines, and calls lineReceived."""
    136         lines  = (self._buffer+data).split(self.delimiter)
     136        dataSoFar = self._buffer + data
     137        lines = dataSoFar.split(self.delimiter)
    137138        self._buffer = lines.pop(-1)
    138139        for line in lines:
    139140            if self.transport.disconnecting:
     
    143144                # the one that told it to close.
    144145                return
    145146            if len(line) > self.MAX_LENGTH:
    146                 return self.lineLengthExceeded(line)
     147                return self.lineLengthExceeded(dataSoFar)
    147148            else:
    148149                self.lineReceived(line)
    149150        if len(self._buffer) > self.MAX_LENGTH:
     
    163164        """Called when the maximum line length has been reached.
    164165        Override if it needs to be dealt with in some special way.
    165166        """
     167        self.transport.loseConnection()
    166168        return error.ConnectionLost('Line length exceeded')
    167169
    168170
     
    232234            else:
    233235                linelength = len(line)
    234236                if linelength > self.MAX_LENGTH:
    235                     exceeded = line + self.__buffer
     237                    exceeded = line + self.delimiter + self.__buffer
    236238                    self.__buffer = ''
    237239                    return self.lineLengthExceeded(exceeded)
    238240                why = self.lineReceived(line)
     
    291293        be more than one line, or may be only the initial portion of the
    292294        line.
    293295        """
    294         return self.transport.loseConnection()
     296        self.transport.loseConnection()
     297        return error.ConnectionLost('Line length exceeded')
    295298
    296299
    297300class StringTooLongError(AssertionError):
  • twisted/test/test_protocols.py

    === modified file 'twisted/test/test_protocols.py'
     
    77
    88import struct
    99
    10 from twisted.trial import unittest
     10from twisted.trial import unittest, runner
    1111from twisted.protocols import basic, wire, portforward
    1212from twisted.internet import reactor, protocol, defer, task, error
    1313from twisted.test import proto_helpers
     
    8585            self.setLineMode(line[self.MAX_LENGTH + 1:])
    8686
    8787
    88 class LineOnlyTester(basic.LineOnlyReceiver):
    89     """
    90     A buffering line only receiver.
    91     """
    92     delimiter = '\n'
    93     MAX_LENGTH = 64
    94 
    95     def connectionMade(self):
    96         """
    97         Create/clean data received on connection.
    98         """
    99         self.received = []
    100 
    101     def lineReceived(self, line):
    102         """
    103         Save received data.
    104         """
    105         self.received.append(line)
    106 
    10788class WireTestCase(unittest.TestCase):
    10889    """
    10990    Test wire protocols.
     
    300281
    301282class LineOnlyReceiverTestCase(unittest.TestCase):
    302283    """
    303     Test line only receiveer.
     284    Test line only receiver interface.
     285
     286    This test case is applied to both LineReceiver and LineOnlyReceiver by the
     287    test_suite function.
    304288    """
    305289    buffer = """foo
    306290    bleakness
     
    308292    plastic forks
    309293    """
    310294
     295    scenarios = [
     296        ('LineReceiver', {'klass': basic.LineReceiver}),
     297        ('LineOnlyReceiver', {'klass': basic.LineOnlyReceiver})]
     298
     299    def makeLineReceiver(self):
     300        """
     301        Construct a line receiver for testing with.
     302
     303        It will be a simple subclass of whatever self.lineReceiverClass is.
     304        The subclass will log calls to lineReceived and lineLengthExceeded to a
     305        '.calls' attribute.
     306        """
     307        transport = protocol.FileWrapper(proto_helpers.StringIOWithoutClosing())
     308        baseClass = self.klass
     309        class LoggingLineReceiver(baseClass):
     310            """
     311            A line receiver subclass that records calls made to it, but
     312            otherwise behaves like its base class.
     313            """
     314            delimiter = '\n'
     315            MAX_LENGTH = 64
     316
     317            def connectionMade(self):
     318                self.calls = []
     319                return baseClass.connectionMade(self)
     320
     321            def lineReceived(self, line):
     322                self.calls.append(('lineReceived', line))
     323
     324            def lineLengthExceeded(self, line):
     325                self.calls.append(('lineLengthExceeded', line))
     326                return baseClass.lineLengthExceeded(self, line)
     327           
     328        lineReceiver = LoggingLineReceiver()
     329        lineReceiver.makeConnection(transport)
     330        return lineReceiver
     331
    311332    def test_buffer(self):
    312333        """
    313334        Test buffering over line protocol: data received should match buffer.
    314335        """
    315         t = proto_helpers.StringTransport()
    316         a = LineOnlyTester()
    317         a.makeConnection(t)
     336        lineReceiver = self.makeLineReceiver()
    318337        for c in self.buffer:
    319             a.dataReceived(c)
    320         self.assertEquals(a.received, self.buffer.split('\n')[:-1])
     338            lineReceiver.dataReceived(c)
     339        expectedLines = self.buffer.split('\n')[:-1]
     340        expectedCalls = [('lineReceived', line) for line in expectedLines]
     341        self.assertEquals(expectedCalls, lineReceiver.calls)
    321342
    322343    def test_lineTooLong(self):
    323344        """
    324         Test sending a line too long: it should close the connection.
     345        When a line greater than MAX_LENGTH is received, lineLengthExceeded is
     346        called.  The default implementation lineLengthExceeded closes the
     347        connection, and returns a ConnectionLost error.
    325348        """
    326         t = proto_helpers.StringTransport()
    327         a = LineOnlyTester()
    328         a.makeConnection(t)
    329         res = a.dataReceived('x'*200)
     349        lineReceiver = self.makeLineReceiver()
     350        res = lineReceiver.dataReceived('x'*200)
     351        self.failUnlessEqual(
     352            [('lineLengthExceeded', 'x'*200)], lineReceiver.calls)
     353        self.assertTrue(lineReceiver.transport.closed)
    330354        self.assertIsInstance(res, error.ConnectionLost)
     355        self.assertEqual(('Line length exceeded',), res.args)
     356
     357    def test_longLineWithDelimiter(self):
     358        """
     359        When MAX_LENGTH is exceeded *and* a delimiter has been received,
     360        lineLengthExceeded is called with the right bytes.
     361
     362        See http://twistedmatrix.com/trac/ticket/3277
     363        """
     364        # Set up a line receiver with a short MAX_LENGTH that logs
     365        # lineLengthExceeded events.
     366        lineReceiver = self.makeLineReceiver()
     367        lineReceiver.MAX_LENGTH = 10
     368        # Call dataReceived with two lines, the first longer than MAX_LENGTH.
     369        longLine = ('x' * 11) + '\n'
     370        nextLine = 'next line\n'
     371        lineReceiver.dataReceived(longLine + nextLine)
     372        # We expect lineLengthExceeded to be called with exactly what we just
     373        # passed dataReceived.  lineReceived is not called.
     374        expectedCalls = [('lineLengthExceeded', longLine + nextLine)]
     375        self.assertEqual(expectedCalls, lineReceiver.calls)
     376
     377    def test_lineReceiverAsProducer(self):
     378        """
     379        Test produce/unproduce in receiving.
     380        """
     381        lineReceiver = self.makeLineReceiver()
     382        if isinstance(lineReceiver, basic.LineOnlyReceiver):
     383            raise unittest.SkipTest(
     384                'LineOnlyReceiver does not implement IPushProducer')
     385        lineReceiver.transport.registerProducer(lineReceiver, False)
     386        lineReceiver.dataReceived('hello world\n')
     387        lineReceiver.transport.unregisterProducer()
     388        lineReceiver.dataReceived('goodbye\n')
     389        self.assertEquals(
     390            [('lineReceived', 'hello world'), ('lineReceived', 'goodbye')],
     391            lineReceiver.calls)
    331392
    332393
    333394
     
    778839        """
    779840        s = proto_helpers.StringTransport()
    780841        self.assertRaises(TypeError, s.write, u'foo')
     842
     843
     844def test_suite():
     845    import twisted.test.test_protocols as this_module
     846    from testscenarios import generate_scenarios
     847    loader = runner.TestLoader()
     848    result = unittest.TestSuite()
     849    tests = []
     850    for testClass in loader.findTestClasses(this_module):
     851        tests.append(loader.loadClass(testClass))
     852    result.addTests(generate_scenarios(tests))
     853    return result