Ticket #3277: lineReceiver-bug-3277-20100414.patch

File lineReceiver-bug-3277-20100414.patch, 10.0 KB (added by spiv, 12 years ago)

Updated patch for review

  • 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                why = self.lineLengthExceeded(line)
     148                if why:
     149                    return why
    147150            else:
    148151                self.lineReceived(line)
    149152        if len(self._buffer) > self.MAX_LENGTH:
     
    163166        """Called when the maximum line length has been reached.
    164167        Override if it needs to be dealt with in some special way.
    165168        """
     169        self.transport.loseConnection()
    166170        return error.ConnectionLost('Line length exceeded')
    167171
    168172
     
    227231            except ValueError:
    228232                if len(self.__buffer) > self.MAX_LENGTH:
    229233                    line, self.__buffer = self.__buffer, ''
    230                     return self.lineLengthExceeded(line)
     234                    why = self.lineLengthExceeded(line)
     235                    if why or self.transport and self.transport.disconnecting:
     236                        return why
    231237                break
    232238            else:
    233239                linelength = len(line)
    234240                if linelength > self.MAX_LENGTH:
    235                     exceeded = line + self.__buffer
    236                     self.__buffer = ''
    237                     return self.lineLengthExceeded(exceeded)
    238                 why = self.lineReceived(line)
     241                    why = self.lineLengthExceeded(line)
     242                else:
     243                    why = self.lineReceived(line)
    239244                if why or self.transport and self.transport.disconnecting:
    240245                    return why
    241246        else:
     
    291296        be more than one line, or may be only the initial portion of the
    292297        line.
    293298        """
    294         return self.transport.loseConnection()
     299        self.transport.loseConnection()
     300        return error.ConnectionLost('Line length exceeded')
    295301
    296302
    297303class StringTooLongError(AssertionError):
  • twisted/test/test_protocols.py

    === modified file 'twisted/test/test_protocols.py'
     
    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.
     
    297278        self.assertEqual(protocol.rest, '')
    298279
    299280
     281class LineOnlyReceiverTestsMixin:
     282    """
     283    Tests to apply to both LineReceiver and LineOnlyReceiver.
     284    """
    300285
    301 class LineOnlyReceiverTestCase(unittest.TestCase):
    302     """
    303     Test line only receiveer.
    304     """
    305286    buffer = """foo
    306287    bleakness
    307288    desolation
    308289    plastic forks
    309290    """
    310291
     292    def makeLineReceiver(self, disconnectOnLongLine=True):
     293        """
     294        Construct a line receiver for testing with.
     295
     296        It will be a simple subclass of whatever self.lineReceiverClass is.
     297        The subclass will log calls to lineReceived and lineLengthExceeded to a
     298        '.calls' attribute.
     299        """
     300        transport = protocol.FileWrapper(proto_helpers.StringIOWithoutClosing())
     301        baseClass = self.klass
     302        class LoggingLineReceiver(baseClass):
     303            """
     304            A line receiver subclass that records calls made to it, but
     305            otherwise behaves like its base class.
     306            """
     307            delimiter = '\n'
     308            MAX_LENGTH = 64
     309
     310            def connectionMade(self):
     311                self.calls = []
     312                return baseClass.connectionMade(self)
     313
     314            def lineReceived(self, line):
     315                self.calls.append(('lineReceived', line))
     316
     317            def lineLengthExceeded(self, line):
     318                self.calls.append(('lineLengthExceeded', line))
     319                if disconnectOnLongLine:
     320                    return baseClass.lineLengthExceeded(self, line)
     321           
     322        lineReceiver = LoggingLineReceiver()
     323        lineReceiver.makeConnection(transport)
     324        return lineReceiver
     325
    311326    def test_buffer(self):
    312327        """
    313328        Test buffering over line protocol: data received should match buffer.
    314329        """
    315         t = proto_helpers.StringTransport()
    316         a = LineOnlyTester()
    317         a.makeConnection(t)
     330        lineReceiver = self.makeLineReceiver()
    318331        for c in self.buffer:
    319             a.dataReceived(c)
    320         self.assertEquals(a.received, self.buffer.split('\n')[:-1])
     332            lineReceiver.dataReceived(c)
     333        expectedLines = self.buffer.split('\n')[:-1]
     334        expectedCalls = [('lineReceived', line) for line in expectedLines]
     335        self.assertEquals(expectedCalls, lineReceiver.calls)
    321336
    322337    def test_lineTooLong(self):
    323338        """
    324         Test sending a line too long: it should close the connection.
    325         """
    326         t = proto_helpers.StringTransport()
    327         a = LineOnlyTester()
    328         a.makeConnection(t)
    329         res = a.dataReceived('x'*200)
    330         self.assertIsInstance(res, error.ConnectionLost)
     339        When a line greater than MAX_LENGTH is received, lineLengthExceeded is
     340        called.  The default implementation lineLengthExceeded closes the
     341        connection, and returns a ConnectionLost error.
     342        """
     343        lineReceiver = self.makeLineReceiver()
     344        res = lineReceiver.dataReceived('x'*200)
     345        self.failUnlessEqual(
     346            [('lineLengthExceeded', 'x'*200)], lineReceiver.calls)
     347        self.assertTrue(lineReceiver.transport.closed)
     348        self.assertIsInstance(res, error.ConnectionLost)
     349        self.assertEqual(('Line length exceeded',), res.args)
     350
     351    def test_secondLineTooLong(self):
     352        """
     353        When two lines are received together and the second line is too long
     354        (but not the first), lineLengthExceeded is only called with the
     355        too-long line.
     356        """
     357        lineReceiver = self.makeLineReceiver()
     358        res = lineReceiver.dataReceived('short line\n' + 'x' * 200 + '\n')
     359        self.failUnlessEqual(
     360            [('lineReceived', 'short line'),
     361             ('lineLengthExceeded', 'x'*200)],
     362            lineReceiver.calls)
     363        self.assertTrue(lineReceiver.transport.closed)
     364        self.assertIsInstance(res, error.ConnectionLost)
     365        self.assertEqual(('Line length exceeded',), res.args)
     366
     367    def test_shortLineAfterLong(self):
     368        lineReceiver = self.makeLineReceiver()
     369        res = lineReceiver.dataReceived('x' * 200 + '\nshort line\n')
     370        self.failUnlessEqual(
     371            [('lineLengthExceeded', 'x'*200)], lineReceiver.calls)
     372        self.assertTrue(lineReceiver.transport.closed)
     373        self.assertIsInstance(res, error.ConnectionLost)
     374        self.assertEqual(('Line length exceeded',), res.args)
     375
     376    def test_lineLengthExceededWithoutLoseConnection(self):
     377        lineReceiver = self.makeLineReceiver(disconnectOnLongLine=False)
     378        longLine = 'x' * 200 + '\n'
     379        res = lineReceiver.dataReceived(longLine + longLine + 'short line\n')
     380        self.failUnlessEqual(
     381            [('lineLengthExceeded', 'x'*200),
     382             ('lineLengthExceeded', 'x'*200),
     383             ('lineReceived', 'short line')],
     384            lineReceiver.calls)
     385        self.assertFalse(lineReceiver.transport.closed)
     386
     387    def test_longLineWithDelimiter(self):
     388        """
     389        When MAX_LENGTH is exceeded *and* a delimiter has been received,
     390        lineLengthExceeded is called with the right bytes.
     391
     392        See http://twistedmatrix.com/trac/ticket/3277
     393        """
     394        # Set up a line receiver with a short MAX_LENGTH that logs
     395        # lineLengthExceeded events.
     396        lineReceiver = self.makeLineReceiver()
     397        lineReceiver.MAX_LENGTH = 10
     398        # Call dataReceived with two lines, the first longer than MAX_LENGTH.
     399        longLine = ('x' * 11) + '\n'
     400        nextLine = 'next line\n'
     401        lineReceiver.dataReceived(longLine + nextLine)
     402        # We expect lineLengthExceeded to be called with just the first long
     403        # line.  lineReceived is not called.
     404        expectedCalls = [('lineLengthExceeded', longLine[:-1])]
     405        self.assertEqual(expectedCalls, lineReceiver.calls)
     406
     407    def test_lineReceiverAsProducer(self):
     408        """
     409        Test produce/unproduce in receiving.
     410        """
     411        lineReceiver = self.makeLineReceiver()
     412        if isinstance(lineReceiver, basic.LineOnlyReceiver):
     413            raise unittest.SkipTest(
     414                'LineOnlyReceiver does not implement IPushProducer')
     415        lineReceiver.transport.registerProducer(lineReceiver, False)
     416        lineReceiver.dataReceived('hello world\n')
     417        lineReceiver.transport.unregisterProducer()
     418        lineReceiver.dataReceived('goodbye\n')
     419        self.assertEquals(
     420            [('lineReceived', 'hello world'), ('lineReceived', 'goodbye')],
     421            lineReceiver.calls)
     422
     423
     424class LineOnlyReceiverTestCase(unittest.TestCase, LineOnlyReceiverTestsMixin):
     425    """
     426    LineOnlyReceiverTestsMixin applied to LineOnlyReceiver.
     427    """
     428
     429    klass = basic.LineOnlyReceiver
     430
     431
     432class LineReceiverLineOnlyTestCase(unittest.TestCase,
     433    LineOnlyReceiverTestsMixin):
     434    """
     435    LineOnlyReceiverTestsMixin applied to LineReceiver.
     436    """
     437
     438    klass = basic.LineReceiver
    331439
    332440
    333441