%PDF- %PDF-
Direktori : /lib/python3/dist-packages/twisted/protocols/test/ |
Current File : //lib/python3/dist-packages/twisted/protocols/test/test_basic.py |
# Copyright (c) Twisted Matrix Laboratories. # See LICENSE for details. """ Test cases for L{twisted.protocols.basic}. """ import struct import sys from io import BytesIO from typing import List, Optional, Type from zope.interface.verify import verifyObject from twisted.internet import protocol, task from twisted.internet.interfaces import IProducer from twisted.internet.protocol import connectionDone from twisted.protocols import basic from twisted.python.compat import iterbytes from twisted.python.failure import Failure from twisted.test import proto_helpers from twisted.trial import unittest class FlippingLineTester(basic.LineReceiver): """ A line receiver that flips between line and raw data modes after one byte. """ delimiter = b"\n" def __init__(self): self.lines = [] def lineReceived(self, line): """ Set the mode to raw. """ self.lines.append(line) self.setRawMode() def rawDataReceived(self, data): """ Set the mode back to line. """ self.setLineMode(data[1:]) class LineTester(basic.LineReceiver): """ A line receiver that parses data received and make actions on some tokens. @type delimiter: C{bytes} @ivar delimiter: character used between received lines. @type MAX_LENGTH: C{int} @ivar MAX_LENGTH: size of a line when C{lineLengthExceeded} will be called. @type clock: L{twisted.internet.task.Clock} @ivar clock: clock simulating reactor callLater. Pass it to constructor if you want to use the pause/rawpause functionalities. """ delimiter = b"\n" MAX_LENGTH = 64 def __init__(self, clock=None): """ If given, use a clock to make callLater calls. """ self.clock = clock def connectionMade(self): """ Create/clean data received on connection. """ self.received = [] def lineReceived(self, line): """ Receive line and make some action for some tokens: pause, rawpause, stop, len, produce, unproduce. """ self.received.append(line) if line == b"": self.setRawMode() elif line == b"pause": self.pauseProducing() self.clock.callLater(0, self.resumeProducing) elif line == b"rawpause": self.pauseProducing() self.setRawMode() self.received.append(b"") self.clock.callLater(0, self.resumeProducing) elif line == b"stop": self.stopProducing() elif line[:4] == b"len ": self.length = int(line[4:]) elif line.startswith(b"produce"): self.transport.registerProducer(self, False) elif line.startswith(b"unproduce"): self.transport.unregisterProducer() def rawDataReceived(self, data): """ Read raw data, until the quantity specified by a previous 'len' line is reached. """ data, rest = data[: self.length], data[self.length :] self.length = self.length - len(data) self.received[-1] = self.received[-1] + data if self.length == 0: self.setLineMode(rest) def lineLengthExceeded(self, line): """ Adjust line mode when long lines received. """ if len(line) > self.MAX_LENGTH + 1: self.setLineMode(line[self.MAX_LENGTH + 1 :]) class LineOnlyTester(basic.LineOnlyReceiver): """ A buffering line only receiver. """ delimiter = b"\n" MAX_LENGTH = 64 def connectionMade(self): """ Create/clean data received on connection. """ self.received = [] def lineReceived(self, line): """ Save received data. """ self.received.append(line) class LineReceiverTests(unittest.SynchronousTestCase): """ Test L{twisted.protocols.basic.LineReceiver}, using the C{LineTester} wrapper. """ buffer = b"""\ len 10 0123456789len 5 1234 len 20 foo 123 0123456789 012345678len 0 foo 5 1234567890123456789012345678901234567890123456789012345678901234567890 len 1 a""" output = [ b"len 10", b"0123456789", b"len 5", b"1234\n", b"len 20", b"foo 123", b"0123456789\n012345678", b"len 0", b"foo 5", b"", b"67890", b"len 1", b"a", ] def test_buffer(self): """ Test buffering for different packet size, checking received matches expected data. """ for packet_size in range(1, 10): t = proto_helpers.StringIOWithoutClosing() a = LineTester() a.makeConnection(protocol.FileWrapper(t)) for i in range(len(self.buffer) // packet_size + 1): s = self.buffer[i * packet_size : (i + 1) * packet_size] a.dataReceived(s) self.assertEqual(self.output, a.received) pauseBuf = b"twiddle1\ntwiddle2\npause\ntwiddle3\n" pauseOutput1 = [b"twiddle1", b"twiddle2", b"pause"] pauseOutput2 = pauseOutput1 + [b"twiddle3"] def test_pausing(self): """ Test pause inside data receiving. It uses fake clock to see if pausing/resuming work. """ for packet_size in range(1, 10): t = proto_helpers.StringIOWithoutClosing() clock = task.Clock() a = LineTester(clock) a.makeConnection(protocol.FileWrapper(t)) for i in range(len(self.pauseBuf) // packet_size + 1): s = self.pauseBuf[i * packet_size : (i + 1) * packet_size] a.dataReceived(s) self.assertEqual(self.pauseOutput1, a.received) clock.advance(0) self.assertEqual(self.pauseOutput2, a.received) rawpauseBuf = b"twiddle1\ntwiddle2\nlen 5\nrawpause\n12345twiddle3\n" rawpauseOutput1 = [b"twiddle1", b"twiddle2", b"len 5", b"rawpause", b""] rawpauseOutput2 = [ b"twiddle1", b"twiddle2", b"len 5", b"rawpause", b"12345", b"twiddle3", ] def test_rawPausing(self): """ Test pause inside raw date receiving. """ for packet_size in range(1, 10): t = proto_helpers.StringIOWithoutClosing() clock = task.Clock() a = LineTester(clock) a.makeConnection(protocol.FileWrapper(t)) for i in range(len(self.rawpauseBuf) // packet_size + 1): s = self.rawpauseBuf[i * packet_size : (i + 1) * packet_size] a.dataReceived(s) self.assertEqual(self.rawpauseOutput1, a.received) clock.advance(0) self.assertEqual(self.rawpauseOutput2, a.received) stop_buf = b"twiddle1\ntwiddle2\nstop\nmore\nstuff\n" stop_output = [b"twiddle1", b"twiddle2", b"stop"] def test_stopProducing(self): """ Test stop inside producing. """ for packet_size in range(1, 10): t = proto_helpers.StringIOWithoutClosing() a = LineTester() a.makeConnection(protocol.FileWrapper(t)) for i in range(len(self.stop_buf) // packet_size + 1): s = self.stop_buf[i * packet_size : (i + 1) * packet_size] a.dataReceived(s) self.assertEqual(self.stop_output, a.received) def test_lineReceiverAsProducer(self): """ Test produce/unproduce in receiving. """ a = LineTester() t = proto_helpers.StringIOWithoutClosing() a.makeConnection(protocol.FileWrapper(t)) a.dataReceived(b"produce\nhello world\nunproduce\ngoodbye\n") self.assertEqual( a.received, [b"produce", b"hello world", b"unproduce", b"goodbye"] ) def test_clearLineBuffer(self): """ L{LineReceiver.clearLineBuffer} removes all buffered data and returns it as a C{bytes} and can be called from beneath C{dataReceived}. """ class ClearingReceiver(basic.LineReceiver): def lineReceived(self, line): self.line = line self.rest = self.clearLineBuffer() protocol = ClearingReceiver() protocol.dataReceived(b"foo\r\nbar\r\nbaz") self.assertEqual(protocol.line, b"foo") self.assertEqual(protocol.rest, b"bar\r\nbaz") # Deliver another line to make sure the previously buffered data is # really gone. protocol.dataReceived(b"quux\r\n") self.assertEqual(protocol.line, b"quux") self.assertEqual(protocol.rest, b"") def test_stackRecursion(self): """ Test switching modes many times on the same data. """ proto = FlippingLineTester() transport = proto_helpers.StringIOWithoutClosing() proto.makeConnection(protocol.FileWrapper(transport)) limit = sys.getrecursionlimit() proto.dataReceived(b"x\nx" * limit) self.assertEqual(b"x" * limit, b"".join(proto.lines)) def test_maximumLineLength(self): """ C{LineReceiver} disconnects the transport if it receives a line longer than its C{MAX_LENGTH}. """ proto = basic.LineReceiver() transport = proto_helpers.StringTransport() proto.makeConnection(transport) proto.dataReceived(b"x" * (proto.MAX_LENGTH + 1) + b"\r\nr") self.assertTrue(transport.disconnecting) def test_maximumLineLengthPartialDelimiter(self): """ C{LineReceiver} doesn't disconnect the transport when it receives a finished line as long as its C{MAX_LENGTH}, when the second-to-last packet ended with a pattern that could have been -- and turns out to have been -- the start of a delimiter, and that packet causes the total input to exceed C{MAX_LENGTH} + len(delimiter). """ proto = LineTester() proto.MAX_LENGTH = 4 t = proto_helpers.StringTransport() proto.makeConnection(t) line = b"x" * (proto.MAX_LENGTH - 1) proto.dataReceived(line) proto.dataReceived(proto.delimiter[:-1]) proto.dataReceived(proto.delimiter[-1:] + line) self.assertFalse(t.disconnecting) self.assertEqual(len(proto.received), 1) self.assertEqual(line, proto.received[0]) def test_notQuiteMaximumLineLengthUnfinished(self): """ C{LineReceiver} doesn't disconnect the transport it if receives a non-finished line whose length, counting the delimiter, is longer than its C{MAX_LENGTH} but shorter than its C{MAX_LENGTH} + len(delimiter). (When the first part that exceeds the max is the beginning of the delimiter.) """ proto = basic.LineReceiver() # '\r\n' is the default, but we set it just to be explicit in # this test. proto.delimiter = b"\r\n" transport = proto_helpers.StringTransport() proto.makeConnection(transport) proto.dataReceived( (b"x" * proto.MAX_LENGTH) + proto.delimiter[: len(proto.delimiter) - 1] ) self.assertFalse(transport.disconnecting) def test_rawDataError(self): """ C{LineReceiver.dataReceived} forwards errors returned by C{rawDataReceived}. """ proto = basic.LineReceiver() proto.rawDataReceived = lambda data: RuntimeError("oops") transport = proto_helpers.StringTransport() proto.makeConnection(transport) proto.setRawMode() why = proto.dataReceived(b"data") self.assertIsInstance(why, RuntimeError) def test_rawDataReceivedNotImplemented(self): """ When L{LineReceiver.rawDataReceived} is not overridden in a subclass, calling it raises C{NotImplementedError}. """ proto = basic.LineReceiver() self.assertRaises(NotImplementedError, proto.rawDataReceived, "foo") def test_lineReceivedNotImplemented(self): """ When L{LineReceiver.lineReceived} is not overridden in a subclass, calling it raises C{NotImplementedError}. """ proto = basic.LineReceiver() self.assertRaises(NotImplementedError, proto.lineReceived, "foo") class ExcessivelyLargeLineCatcher(basic.LineReceiver): """ Helper for L{LineReceiverLineLengthExceededTests}. @ivar longLines: A L{list} of L{bytes} giving the values C{lineLengthExceeded} has been called with. """ def connectionMade(self): self.longLines = [] def lineReceived(self, line): """ Disregard any received lines. """ def lineLengthExceeded(self, data): """ Record any data that exceeds the line length limits. """ self.longLines.append(data) class LineReceiverLineLengthExceededTests(unittest.SynchronousTestCase): """ Tests for L{twisted.protocols.basic.LineReceiver.lineLengthExceeded}. """ def setUp(self): self.proto = ExcessivelyLargeLineCatcher() self.proto.MAX_LENGTH = 6 self.transport = proto_helpers.StringTransport() self.proto.makeConnection(self.transport) def test_longUnendedLine(self): """ If more bytes than C{LineReceiver.MAX_LENGTH} arrive containing no line delimiter, all of the bytes are passed as a single string to L{LineReceiver.lineLengthExceeded}. """ excessive = b"x" * (self.proto.MAX_LENGTH * 2 + 2) self.proto.dataReceived(excessive) self.assertEqual([excessive], self.proto.longLines) def test_longLineAfterShortLine(self): """ If L{LineReceiver.dataReceived} is called with bytes representing a short line followed by bytes that exceed the length limit without a line delimiter, L{LineReceiver.lineLengthExceeded} is called with all of the bytes following the short line's delimiter. """ excessive = b"x" * (self.proto.MAX_LENGTH * 2 + 2) self.proto.dataReceived(b"x" + self.proto.delimiter + excessive) self.assertEqual([excessive], self.proto.longLines) def test_longLineWithDelimiter(self): """ If L{LineReceiver.dataReceived} is called with more than C{LineReceiver.MAX_LENGTH} bytes containing a line delimiter somewhere not in the first C{MAX_LENGTH} bytes, the entire byte string is passed to L{LineReceiver.lineLengthExceeded}. """ excessive = self.proto.delimiter.join( [b"x" * (self.proto.MAX_LENGTH * 2 + 2)] * 2 ) self.proto.dataReceived(excessive) self.assertEqual([excessive], self.proto.longLines) def test_multipleLongLines(self): """ If L{LineReceiver.dataReceived} is called with more than C{LineReceiver.MAX_LENGTH} bytes containing multiple line delimiters somewhere not in the first C{MAX_LENGTH} bytes, the entire byte string is passed to L{LineReceiver.lineLengthExceeded}. """ excessive = (b"x" * (self.proto.MAX_LENGTH * 2 + 2) + self.proto.delimiter) * 2 self.proto.dataReceived(excessive) self.assertEqual([excessive], self.proto.longLines) def test_maximumLineLength(self): """ C{LineReceiver} disconnects the transport if it receives a line longer than its C{MAX_LENGTH}. """ proto = basic.LineReceiver() transport = proto_helpers.StringTransport() proto.makeConnection(transport) proto.dataReceived(b"x" * (proto.MAX_LENGTH + 1) + b"\r\nr") self.assertTrue(transport.disconnecting) def test_maximumLineLengthRemaining(self): """ C{LineReceiver} disconnects the transport it if receives a non-finished line longer than its C{MAX_LENGTH}. """ proto = basic.LineReceiver() transport = proto_helpers.StringTransport() proto.makeConnection(transport) proto.dataReceived(b"x" * (proto.MAX_LENGTH + len(proto.delimiter))) self.assertTrue(transport.disconnecting) class LineOnlyReceiverTests(unittest.SynchronousTestCase): """ Tests for L{twisted.protocols.basic.LineOnlyReceiver}. """ buffer = b"""foo bleakness desolation plastic forks """ def test_buffer(self): """ Test buffering over line protocol: data received should match buffer. """ t = proto_helpers.StringTransport() a = LineOnlyTester() a.makeConnection(t) for c in iterbytes(self.buffer): a.dataReceived(c) self.assertEqual(a.received, self.buffer.split(b"\n")[:-1]) def test_greaterThanMaximumLineLength(self): """ C{LineOnlyReceiver} disconnects the transport if it receives a line longer than its C{MAX_LENGTH} + len(delimiter). """ proto = LineOnlyTester() transport = proto_helpers.StringTransport() proto.makeConnection(transport) proto.dataReceived( b"x" * (proto.MAX_LENGTH + len(proto.delimiter) + 1) + b"\r\nr" ) self.assertTrue(transport.disconnecting) def test_lineReceivedNotImplemented(self): """ When L{LineOnlyReceiver.lineReceived} is not overridden in a subclass, calling it raises C{NotImplementedError}. """ proto = basic.LineOnlyReceiver() self.assertRaises(NotImplementedError, proto.lineReceived, "foo") class TestMixin: def connectionMade(self): self.received = [] def stringReceived(self, s): self.received.append(s) MAX_LENGTH = 50 closed = 0 def connectionLost(self, reason: Failure = connectionDone) -> None: self.closed = 1 class TestNetstring(TestMixin, basic.NetstringReceiver): def stringReceived(self, s): self.received.append(s) self.transport.write(s) class LPTestCaseMixin: illegalStrings: Optional[List[bytes]] = [] protocol: "Optional[Type[protocol.Protocol]]" = None def getProtocol(self): """ Return a new instance of C{self.protocol} connected to a new instance of L{proto_helpers.StringTransport}. """ t = proto_helpers.StringTransport() a = self.protocol() a.makeConnection(t) return a def test_illegal(self): """ Assert that illegal strings cause the transport to be closed. """ for s in self.illegalStrings: r = self.getProtocol() for c in iterbytes(s): r.dataReceived(c) self.assertTrue(r.transport.disconnecting) class NetstringReceiverTests(unittest.SynchronousTestCase, LPTestCaseMixin): """ Tests for L{twisted.protocols.basic.NetstringReceiver}. """ strings = [b"hello", b"world", b"how", b"are", b"you123", b":today", b"a" * 515] illegalStrings = [ b"9999999999999999999999", b"abc", b"4:abcde", b"51:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaab,", ] protocol = TestNetstring def setUp(self): self.transport = proto_helpers.StringTransport() self.netstringReceiver = TestNetstring() self.netstringReceiver.makeConnection(self.transport) def test_buffer(self): """ Strings can be received in chunks of different lengths. """ for packet_size in range(1, 10): t = proto_helpers.StringTransport() a = TestNetstring() a.MAX_LENGTH = 699 a.makeConnection(t) for s in self.strings: a.sendString(s) out = t.value() for i in range(len(out) // packet_size + 1): s = out[i * packet_size : (i + 1) * packet_size] if s: a.dataReceived(s) self.assertEqual(a.received, self.strings) def test_receiveEmptyNetstring(self): """ Empty netstrings (with length '0') can be received. """ self.netstringReceiver.dataReceived(b"0:,") self.assertEqual(self.netstringReceiver.received, [b""]) def test_receiveOneCharacter(self): """ One-character netstrings can be received. """ self.netstringReceiver.dataReceived(b"1:a,") self.assertEqual(self.netstringReceiver.received, [b"a"]) def test_receiveTwoCharacters(self): """ Two-character netstrings can be received. """ self.netstringReceiver.dataReceived(b"2:ab,") self.assertEqual(self.netstringReceiver.received, [b"ab"]) def test_receiveNestedNetstring(self): """ Netstrings with embedded netstrings. This test makes sure that the parser does not become confused about the ',' and ':' characters appearing inside the data portion of the netstring. """ self.netstringReceiver.dataReceived(b"4:1:a,,") self.assertEqual(self.netstringReceiver.received, [b"1:a,"]) def test_moreDataThanSpecified(self): """ Netstrings containing more data than expected are refused. """ self.netstringReceiver.dataReceived(b"2:aaa,") self.assertTrue(self.transport.disconnecting) def test_moreDataThanSpecifiedBorderCase(self): """ Netstrings that should be empty according to their length specification are refused if they contain data. """ self.netstringReceiver.dataReceived(b"0:a,") self.assertTrue(self.transport.disconnecting) def test_missingNumber(self): """ Netstrings without leading digits that specify the length are refused. """ self.netstringReceiver.dataReceived(b":aaa,") self.assertTrue(self.transport.disconnecting) def test_missingColon(self): """ Netstrings without a colon between length specification and data are refused. """ self.netstringReceiver.dataReceived(b"3aaa,") self.assertTrue(self.transport.disconnecting) def test_missingNumberAndColon(self): """ Netstrings that have no leading digits nor a colon are refused. """ self.netstringReceiver.dataReceived(b"aaa,") self.assertTrue(self.transport.disconnecting) def test_onlyData(self): """ Netstrings consisting only of data are refused. """ self.netstringReceiver.dataReceived(b"aaa") self.assertTrue(self.transport.disconnecting) def test_receiveNetstringPortions_1(self): """ Netstrings can be received in two portions. """ self.netstringReceiver.dataReceived(b"4:aa") self.netstringReceiver.dataReceived(b"aa,") self.assertEqual(self.netstringReceiver.received, [b"aaaa"]) self.assertTrue(self.netstringReceiver._payloadComplete()) def test_receiveNetstringPortions_2(self): """ Netstrings can be received in more than two portions, even if the length specification is split across two portions. """ for part in [b"1", b"0:01234", b"56789", b","]: self.netstringReceiver.dataReceived(part) self.assertEqual(self.netstringReceiver.received, [b"0123456789"]) def test_receiveNetstringPortions_3(self): """ Netstrings can be received one character at a time. """ for part in [b"2", b":", b"a", b"b", b","]: self.netstringReceiver.dataReceived(part) self.assertEqual(self.netstringReceiver.received, [b"ab"]) def test_receiveTwoNetstrings(self): """ A stream of two netstrings can be received in two portions, where the first portion contains the complete first netstring and the length specification of the second netstring. """ self.netstringReceiver.dataReceived(b"1:a,1") self.assertTrue(self.netstringReceiver._payloadComplete()) self.assertEqual(self.netstringReceiver.received, [b"a"]) self.netstringReceiver.dataReceived(b":b,") self.assertEqual(self.netstringReceiver.received, [b"a", b"b"]) def test_maxReceiveLimit(self): """ Netstrings with a length specification exceeding the specified C{MAX_LENGTH} are refused. """ tooLong = self.netstringReceiver.MAX_LENGTH + 1 self.netstringReceiver.dataReceived( b"".join((bytes(tooLong), b":", b"a" * tooLong)) ) self.assertTrue(self.transport.disconnecting) def test_consumeLength(self): """ C{_consumeLength} returns the expected length of the netstring, including the trailing comma. """ self.netstringReceiver._remainingData = b"12:" self.netstringReceiver._consumeLength() self.assertEqual(self.netstringReceiver._expectedPayloadSize, 13) def test_consumeLengthBorderCase1(self): """ C{_consumeLength} works as expected if the length specification contains the value of C{MAX_LENGTH} (border case). """ self.netstringReceiver._remainingData = b"12:" self.netstringReceiver.MAX_LENGTH = 12 self.netstringReceiver._consumeLength() self.assertEqual(self.netstringReceiver._expectedPayloadSize, 13) def test_consumeLengthBorderCase2(self): """ C{_consumeLength} raises a L{basic.NetstringParseError} if the length specification exceeds the value of C{MAX_LENGTH} by 1 (border case). """ self.netstringReceiver._remainingData = b"12:" self.netstringReceiver.MAX_LENGTH = 11 self.assertRaises( basic.NetstringParseError, self.netstringReceiver._consumeLength ) def test_consumeLengthBorderCase3(self): """ C{_consumeLength} raises a L{basic.NetstringParseError} if the length specification exceeds the value of C{MAX_LENGTH} by more than 1. """ self.netstringReceiver._remainingData = b"1000:" self.netstringReceiver.MAX_LENGTH = 11 self.assertRaises( basic.NetstringParseError, self.netstringReceiver._consumeLength ) def test_stringReceivedNotImplemented(self): """ When L{NetstringReceiver.stringReceived} is not overridden in a subclass, calling it raises C{NotImplementedError}. """ proto = basic.NetstringReceiver() self.assertRaises(NotImplementedError, proto.stringReceived, "foo") class IntNTestCaseMixin(LPTestCaseMixin): """ TestCase mixin for int-prefixed protocols. """ protocol: "Optional[Type[protocol.Protocol]]" = None strings: Optional[List[bytes]] = None illegalStrings: Optional[List[bytes]] = None partialStrings: Optional[List[bytes]] = None def test_receive(self): """ Test receiving data find the same data send. """ r = self.getProtocol() for s in self.strings: for c in iterbytes(struct.pack(r.structFormat, len(s)) + s): r.dataReceived(c) self.assertEqual(r.received, self.strings) def test_partial(self): """ Send partial data, nothing should be definitely received. """ for s in self.partialStrings: r = self.getProtocol() for c in iterbytes(s): r.dataReceived(c) self.assertEqual(r.received, []) def test_send(self): """ Test sending data over protocol. """ r = self.getProtocol() r.sendString(b"b" * 16) self.assertEqual( r.transport.value(), struct.pack(r.structFormat, 16) + b"b" * 16 ) def test_lengthLimitExceeded(self): """ When a length prefix is received which is greater than the protocol's C{MAX_LENGTH} attribute, the C{lengthLimitExceeded} method is called with the received length prefix. """ length = [] r = self.getProtocol() r.lengthLimitExceeded = length.append r.MAX_LENGTH = 10 r.dataReceived(struct.pack(r.structFormat, 11)) self.assertEqual(length, [11]) def test_longStringNotDelivered(self): """ If a length prefix for a string longer than C{MAX_LENGTH} is delivered to C{dataReceived} at the same time as the entire string, the string is not passed to C{stringReceived}. """ r = self.getProtocol() r.MAX_LENGTH = 10 r.dataReceived(struct.pack(r.structFormat, 11) + b"x" * 11) self.assertEqual(r.received, []) def test_stringReceivedNotImplemented(self): """ When L{IntNStringReceiver.stringReceived} is not overridden in a subclass, calling it raises C{NotImplementedError}. """ proto = basic.IntNStringReceiver() self.assertRaises(NotImplementedError, proto.stringReceived, "foo") class RecvdAttributeMixin: """ Mixin defining tests for string receiving protocols with a C{recvd} attribute which should be settable by application code, to be combined with L{IntNTestCaseMixin} on a L{TestCase} subclass """ def makeMessage(self, protocol, data): """ Return C{data} prefixed with message length in C{protocol.structFormat} form. """ return struct.pack(protocol.structFormat, len(data)) + data def test_recvdContainsRemainingData(self): """ In stringReceived, recvd contains the remaining data that was passed to dataReceived that was not part of the current message. """ result = [] r = self.getProtocol() def stringReceived(receivedString): result.append(r.recvd) r.stringReceived = stringReceived completeMessage = struct.pack(r.structFormat, 5) + (b"a" * 5) incompleteMessage = struct.pack(r.structFormat, 5) + (b"b" * 4) # Receive a complete message, followed by an incomplete one r.dataReceived(completeMessage + incompleteMessage) self.assertEqual(result, [incompleteMessage]) def test_recvdChanged(self): """ In stringReceived, if recvd is changed, messages should be parsed from it rather than the input to dataReceived. """ r = self.getProtocol() result = [] payloadC = b"c" * 5 messageC = self.makeMessage(r, payloadC) def stringReceived(receivedString): if not result: r.recvd = messageC result.append(receivedString) r.stringReceived = stringReceived payloadA = b"a" * 5 payloadB = b"b" * 5 messageA = self.makeMessage(r, payloadA) messageB = self.makeMessage(r, payloadB) r.dataReceived(messageA + messageB) self.assertEqual(result, [payloadA, payloadC]) def test_switching(self): """ Data already parsed by L{IntNStringReceiver.dataReceived} is not reparsed if C{stringReceived} consumes some of the L{IntNStringReceiver.recvd} buffer. """ proto = self.getProtocol() mix = [] SWITCH = b"\x00\x00\x00\x00" for s in self.strings: mix.append(self.makeMessage(proto, s)) mix.append(SWITCH) result = [] def stringReceived(receivedString): result.append(receivedString) proto.recvd = proto.recvd[len(SWITCH) :] proto.stringReceived = stringReceived proto.dataReceived(b"".join(mix)) # Just another byte, to trigger processing of anything that might have # been left in the buffer (should be nothing). proto.dataReceived(b"\x01") self.assertEqual(result, self.strings) # And verify that another way self.assertEqual(proto.recvd, b"\x01") def test_recvdInLengthLimitExceeded(self): """ The L{IntNStringReceiver.recvd} buffer contains all data not yet processed by L{IntNStringReceiver.dataReceived} if the C{lengthLimitExceeded} event occurs. """ proto = self.getProtocol() DATA = b"too long" proto.MAX_LENGTH = len(DATA) - 1 message = self.makeMessage(proto, DATA) result = [] def lengthLimitExceeded(length): result.append(length) result.append(proto.recvd) proto.lengthLimitExceeded = lengthLimitExceeded proto.dataReceived(message) self.assertEqual(result[0], len(DATA)) self.assertEqual(result[1], message) class TestInt32(TestMixin, basic.Int32StringReceiver): """ A L{basic.Int32StringReceiver} storing received strings in an array. @ivar received: array holding received strings. """ class Int32Tests(unittest.SynchronousTestCase, IntNTestCaseMixin, RecvdAttributeMixin): """ Test case for int32-prefixed protocol """ protocol = TestInt32 strings = [b"a", b"b" * 16] illegalStrings = [b"\x10\x00\x00\x00aaaaaa"] partialStrings = [b"\x00\x00\x00", b"hello there", b""] def test_data(self): """ Test specific behavior of the 32-bits length. """ r = self.getProtocol() r.sendString(b"foo") self.assertEqual(r.transport.value(), b"\x00\x00\x00\x03foo") r.dataReceived(b"\x00\x00\x00\x04ubar") self.assertEqual(r.received, [b"ubar"]) class TestInt16(TestMixin, basic.Int16StringReceiver): """ A L{basic.Int16StringReceiver} storing received strings in an array. @ivar received: array holding received strings. """ class Int16Tests(unittest.SynchronousTestCase, IntNTestCaseMixin, RecvdAttributeMixin): """ Test case for int16-prefixed protocol """ protocol = TestInt16 strings = [b"a", b"b" * 16] illegalStrings = [b"\x10\x00aaaaaa"] partialStrings = [b"\x00", b"hello there", b""] def test_data(self): """ Test specific behavior of the 16-bits length. """ r = self.getProtocol() r.sendString(b"foo") self.assertEqual(r.transport.value(), b"\x00\x03foo") r.dataReceived(b"\x00\x04ubar") self.assertEqual(r.received, [b"ubar"]) def test_tooLongSend(self): """ Send too much data: that should cause an error. """ r = self.getProtocol() tooSend = b"b" * (2 ** (r.prefixLength * 8) + 1) self.assertRaises(AssertionError, r.sendString, tooSend) class TestInt8(TestMixin, basic.Int8StringReceiver): """ A L{basic.Int8StringReceiver} storing received strings in an array. @ivar received: array holding received strings. """ class Int8Tests(unittest.SynchronousTestCase, IntNTestCaseMixin, RecvdAttributeMixin): """ Test case for int8-prefixed protocol """ protocol = TestInt8 strings = [b"a", b"b" * 16] illegalStrings = [b"\x00\x00aaaaaa"] partialStrings = [b"\x08", b"dzadz", b""] def test_data(self): """ Test specific behavior of the 8-bits length. """ r = self.getProtocol() r.sendString(b"foo") self.assertEqual(r.transport.value(), b"\x03foo") r.dataReceived(b"\x04ubar") self.assertEqual(r.received, [b"ubar"]) def test_tooLongSend(self): """ Send too much data: that should cause an error. """ r = self.getProtocol() tooSend = b"b" * (2 ** (r.prefixLength * 8) + 1) self.assertRaises(AssertionError, r.sendString, tooSend) class OnlyProducerTransport: """ Transport which isn't really a transport, just looks like one to someone not looking very hard. """ paused = False disconnecting = False def __init__(self): self.data = [] def pauseProducing(self): self.paused = True def resumeProducing(self): self.paused = False def write(self, bytes): self.data.append(bytes) class ConsumingProtocol(basic.LineReceiver): """ Protocol that really, really doesn't want any more bytes. """ def lineReceived(self, line): self.transport.write(line) self.pauseProducing() class ProducerTests(unittest.SynchronousTestCase): """ Tests for L{basic._PausableMixin} and L{basic.LineReceiver.paused}. """ def test_pauseResume(self): """ When L{basic.LineReceiver} is paused, it doesn't deliver lines to L{basic.LineReceiver.lineReceived} and delivers them immediately upon being resumed. L{ConsumingProtocol} is a L{LineReceiver} that pauses itself after every line, and writes that line to its transport. """ p = ConsumingProtocol() t = OnlyProducerTransport() p.makeConnection(t) # Deliver a partial line. # This doesn't trigger a pause and doesn't deliver a line. p.dataReceived(b"hello, ") self.assertEqual(t.data, []) self.assertFalse(t.paused) self.assertFalse(p.paused) # Deliver the rest of the line. # This triggers the pause, and the line is echoed. p.dataReceived(b"world\r\n") self.assertEqual(t.data, [b"hello, world"]) self.assertTrue(t.paused) self.assertTrue(p.paused) # Unpausing doesn't deliver more data, and the protocol is unpaused. p.resumeProducing() self.assertEqual(t.data, [b"hello, world"]) self.assertFalse(t.paused) self.assertFalse(p.paused) # Deliver two lines at once. # The protocol is paused after receiving and echoing the first line. p.dataReceived(b"hello\r\nworld\r\n") self.assertEqual(t.data, [b"hello, world", b"hello"]) self.assertTrue(t.paused) self.assertTrue(p.paused) # Unpausing delivers the waiting line, and causes the protocol to # pause again. p.resumeProducing() self.assertEqual(t.data, [b"hello, world", b"hello", b"world"]) self.assertTrue(t.paused) self.assertTrue(p.paused) # Deliver a line while paused. # This doesn't have a visible effect. p.dataReceived(b"goodbye\r\n") self.assertEqual(t.data, [b"hello, world", b"hello", b"world"]) self.assertTrue(t.paused) self.assertTrue(p.paused) # Unpausing delivers the waiting line, and causes the protocol to # pause again. p.resumeProducing() self.assertEqual(t.data, [b"hello, world", b"hello", b"world", b"goodbye"]) self.assertTrue(t.paused) self.assertTrue(p.paused) # Unpausing doesn't deliver more data, and the protocol is unpaused. p.resumeProducing() self.assertEqual(t.data, [b"hello, world", b"hello", b"world", b"goodbye"]) self.assertFalse(t.paused) self.assertFalse(p.paused) class FileSenderTests(unittest.TestCase): """ Tests for L{basic.FileSender}. """ def test_interface(self): """ L{basic.FileSender} implements the L{IPullProducer} interface. """ sender = basic.FileSender() self.assertTrue(verifyObject(IProducer, sender)) def test_producerRegistered(self): """ When L{basic.FileSender.beginFileTransfer} is called, it registers itself with provided consumer, as a non-streaming producer. """ source = BytesIO(b"Test content") consumer = proto_helpers.StringTransport() sender = basic.FileSender() sender.beginFileTransfer(source, consumer) self.assertEqual(consumer.producer, sender) self.assertFalse(consumer.streaming) def test_transfer(self): """ L{basic.FileSender} sends the content of the given file using a C{IConsumer} interface via C{beginFileTransfer}. It returns a L{Deferred} which fires with the last byte sent. """ source = BytesIO(b"Test content") consumer = proto_helpers.StringTransport() sender = basic.FileSender() d = sender.beginFileTransfer(source, consumer) sender.resumeProducing() # resumeProducing only finishes after trying to read at eof sender.resumeProducing() self.assertIsNone(consumer.producer) self.assertEqual(b"t", self.successResultOf(d)) self.assertEqual(b"Test content", consumer.value()) def test_transferMultipleChunks(self): """ L{basic.FileSender} reads at most C{CHUNK_SIZE} every time it resumes producing. """ source = BytesIO(b"Test content") consumer = proto_helpers.StringTransport() sender = basic.FileSender() sender.CHUNK_SIZE = 4 d = sender.beginFileTransfer(source, consumer) # Ideally we would assertNoResult(d) here, but <http://tm.tl/6291> sender.resumeProducing() self.assertEqual(b"Test", consumer.value()) sender.resumeProducing() self.assertEqual(b"Test con", consumer.value()) sender.resumeProducing() self.assertEqual(b"Test content", consumer.value()) # resumeProducing only finishes after trying to read at eof sender.resumeProducing() self.assertEqual(b"t", self.successResultOf(d)) self.assertEqual(b"Test content", consumer.value()) def test_transferWithTransform(self): """ L{basic.FileSender.beginFileTransfer} takes a C{transform} argument which allows to manipulate the data on the fly. """ def transform(chunk): return chunk.swapcase() source = BytesIO(b"Test content") consumer = proto_helpers.StringTransport() sender = basic.FileSender() d = sender.beginFileTransfer(source, consumer, transform) sender.resumeProducing() # resumeProducing only finishes after trying to read at eof sender.resumeProducing() self.assertEqual(b"T", self.successResultOf(d)) self.assertEqual(b"tEST CONTENT", consumer.value()) def test_abortedTransfer(self): """ The C{Deferred} returned by L{basic.FileSender.beginFileTransfer} fails with an C{Exception} if C{stopProducing} when the transfer is not complete. """ source = BytesIO(b"Test content") consumer = proto_helpers.StringTransport() sender = basic.FileSender() d = sender.beginFileTransfer(source, consumer) # Abort the transfer right away sender.stopProducing() failure = self.failureResultOf(d) failure.trap(Exception) self.assertEqual("Consumer asked us to stop producing", str(failure.value))