# Copyright (c) Twisted Matrix Laboratories. # See LICENSE for details. """ Tests for L{twisted.internet.abstract}, a collection of APIs for implementing reactors. """ from __future__ import annotations from typing import Union from hypothesis import example, given, strategies as st from twisted.internet.abstract import FileDescriptor, isIPv6Address from twisted.trial.unittest import SynchronousTestCase from .test_tcp import _FakeFDSetReactor class IPv6AddressTests(SynchronousTestCase): """ Tests for L{isIPv6Address}, a function for determining if a particular string is an IPv6 address literal. """ def test_empty(self) -> None: """ The empty string is not an IPv6 address literal. """ self.assertFalse(isIPv6Address("")) def test_colon(self) -> None: """ A single C{":"} is not an IPv6 address literal. """ self.assertFalse(isIPv6Address(":")) def test_loopback(self) -> None: """ C{"::1"} is the IPv6 loopback address literal. """ self.assertTrue(isIPv6Address("::1")) def test_scopeID(self) -> None: """ An otherwise valid IPv6 address literal may also include a C{"%"} followed by an arbitrary scope identifier. """ self.assertTrue(isIPv6Address("fe80::1%eth0")) self.assertTrue(isIPv6Address("fe80::2%1")) self.assertTrue(isIPv6Address("fe80::3%en2")) def test_invalidWithScopeID(self) -> None: """ An otherwise invalid IPv6 address literal is still invalid with a trailing scope identifier. """ self.assertFalse(isIPv6Address("%eth0")) self.assertFalse(isIPv6Address(":%eth0")) self.assertFalse(isIPv6Address("hello%eth0")) def test_unicodeAndBytes(self) -> None: """ L{isIPv6Address} evaluates ASCII-encoded bytes as well as text. """ # the type annotation only supports str, but bytes is supported at # runtime self.assertTrue(isIPv6Address(b"fe80::2%1")) # type: ignore[arg-type] self.assertTrue(isIPv6Address("fe80::2%1")) self.assertFalse(isIPv6Address("\u4321")) self.assertFalse(isIPv6Address("hello%eth0")) self.assertFalse(isIPv6Address(b"hello%eth0")) # type: ignore[arg-type] class TrackingFileDescriptor(FileDescriptor): """ Write a limited amount, and track what gets written. """ # Annoying implementation details we need to make it work: connected = True _writeDisconnected = False def __init__( self, operations: list[Union[int, bytes]], written: list[bytes], send_limit: int ): self.operations = operations self.written = written self.SEND_LIMIT = send_limit FileDescriptor.__init__(self, _FakeFDSetReactor()) def writeSomeData(self, data: bytes) -> int: toWrite = self.operations.pop(0) assert isinstance(toWrite, int) toWrite = min(toWrite, len(data)) self.written.append(data[:toWrite]) return toWrite class WriteBufferingTests(SynchronousTestCase): """ Tests for the complex logic in the L{FileDescriptor} class. """ @given( operations=st.lists( st.one_of( st.binary(min_size=1, max_size=10), st.integers(min_value=0, max_value=10), ), min_size=3, max_size=30, ) ) # This catches a bug that was introduced by a performance refactoring: @example(operations=[b"abcdef", 0, b"g"]) def test_writeBuffering(self, operations: list[Union[bytes, int]]) -> None: """ A sequence of C{write()} and C{doWrite()} will eventually write all the data correctly and in order. @param operations: A list of C{bytes} (indicating a C{write()}) or C{int} (indicating C{doWrite()} with the integer being how much C{writeSomeData()} writeSomeData will successfully write). """ expected = b"".join(op for op in operations if isinstance(op, bytes)) written: list[bytes] = [] # Send at most 5 bytes per call to writeSomeData(); default is much # higher, of course, but made it smaller so we can have faster # tests. SEND_LIMIT = 5 fd = TrackingFileDescriptor(operations, written, SEND_LIMIT) # Make sure we flush whatever is left at the end: operations += [SEND_LIMIT * 2] * (1 + len(expected) // SEND_LIMIT) while operations: if isinstance(operations[0], bytes): fd.write(operations.pop(0)) # type: ignore[arg-type] else: fd.doWrite() result = b"".join(written) self.assertEqual(expected, result)