diff --git a/src/neo4j/_async/io/__init__.py b/src/neo4j/_async/io/__init__.py index 3571ad94..b69f8377 100644 --- a/src/neo4j/_async/io/__init__.py +++ b/src/neo4j/_async/io/__init__.py @@ -31,6 +31,11 @@ ] +from . import ( # noqa - imports needed to register protocol handlers + _bolt3, + _bolt4, + _bolt5, +) from ._bolt import AsyncBolt from ._common import ( check_supported_server_product, diff --git a/src/neo4j/_async/io/_bolt.py b/src/neo4j/_async/io/_bolt.py index a997393d..3c527146 100644 --- a/src/neo4j/_async/io/_bolt.py +++ b/src/neo4j/_async/io/_bolt.py @@ -60,6 +60,8 @@ if t.TYPE_CHECKING: + import typing_extensions as te + from ..._api import TelemetryAPI @@ -272,83 +274,31 @@ def assert_notification_filtering_support(self): f"{self.server_info.agent!r}" ) - # [bolt-version-bump] search tag when changing bolt version support - @classmethod - def protocol_handlers(cls): - """ - Return a dictionary of available Bolt protocol handlers. - - The handlers are keyed by version tuple. If an explicit protocol - version is provided, the dictionary will contain either zero or one - items, depending on whether that version is supported. If no protocol - version is provided, all available versions will be returned. - - :param protocol_version: tuple identifying a specific protocol - version (e.g. (3, 5)) or None - :returns: dictionary of version tuple to handler class for all - relevant and supported protocol versions - :raise TypeError: if protocol version is not passed in a tuple - """ - # Carry out Bolt subclass imports locally to avoid circular dependency - # issues. - from ._bolt3 import AsyncBolt3 - from ._bolt4 import ( - AsyncBolt4x2, - AsyncBolt4x3, - AsyncBolt4x4, - ) - from ._bolt5 import ( - AsyncBolt5x0, - AsyncBolt5x1, - AsyncBolt5x2, - AsyncBolt5x3, - AsyncBolt5x4, - AsyncBolt5x5, - AsyncBolt5x6, - ) - - return { - AsyncBolt3.PROTOCOL_VERSION: AsyncBolt3, - # 4.0-4.1 unsupported because no space left in the handshake - AsyncBolt4x2.PROTOCOL_VERSION: AsyncBolt4x2, - AsyncBolt4x3.PROTOCOL_VERSION: AsyncBolt4x3, - AsyncBolt4x4.PROTOCOL_VERSION: AsyncBolt4x4, - AsyncBolt5x0.PROTOCOL_VERSION: AsyncBolt5x0, - AsyncBolt5x1.PROTOCOL_VERSION: AsyncBolt5x1, - AsyncBolt5x2.PROTOCOL_VERSION: AsyncBolt5x2, - AsyncBolt5x3.PROTOCOL_VERSION: AsyncBolt5x3, - AsyncBolt5x4.PROTOCOL_VERSION: AsyncBolt5x4, - AsyncBolt5x5.PROTOCOL_VERSION: AsyncBolt5x5, - AsyncBolt5x6.PROTOCOL_VERSION: AsyncBolt5x6, - } - - @classmethod - def version_list(cls, versions, limit=4): - """ - Return a list of supported protocol versions in order of preference. + protocol_handlers: t.ClassVar[dict[Version, type[AsyncBolt]]] = {} - The number of protocol versions (or ranges) returned is limited to 4. - """ - # In fact, 4.3 is the fist version to support ranges. However, the - # range support got backported to 4.2. But even if the server is too - # old to have the backport, negotiating BOLT 4.1 is no problem as it's - # equivalent to 4.2 - first_with_range_support = Version(4, 2) - result = [] - for version in versions: - if ( - result - and version >= first_with_range_support - and result[-1][0] == version[0] - and result[-1][1][1] == version[1] + 1 - ): - # can use range to encompass this version - result[-1][1][1] = version[1] - continue - result.append(Version(version[0], [version[1], version[1]])) - if len(result) >= limit: - break - return result + def __init_subclass__(cls: type[te.Self], **kwargs: t.Any) -> None: + protocol_version = cls.PROTOCOL_VERSION + if protocol_version is None: + raise ValueError( + "AsyncBolt subclasses must define PROTOCOL_VERSION" + ) + if not ( + isinstance(protocol_version, Version) + and len(protocol_version) == 2 + and all(isinstance(i, int) for i in protocol_version) + ): + raise TypeError( + "PROTOCOL_VERSION must be a 2-tuple of integers, not " + f"{protocol_version!r}" + ) + if protocol_version in AsyncBolt.protocol_handlers: + cls_conflict = AsyncBolt.protocol_handlers[protocol_version] + raise TypeError( + f"Multiple classes for the same protocol version " + f"{protocol_version}: {cls}, {cls_conflict}" + ) + cls.protocol_handlers[protocol_version] = cls + super().__init_subclass__(**kwargs) @classmethod def get_handshake(cls): @@ -358,15 +308,9 @@ def get_handshake(cls): The length is 16 bytes as specified in the Bolt version negotiation. :returns: bytes """ - supported_versions = sorted( - cls.protocol_handlers().keys(), reverse=True - ) - offered_versions = cls.version_list(supported_versions, limit=3) - versions_bytes = ( - Version(0xFF, 1).to_bytes(), # handshake v2 - *(v.to_bytes() for v in offered_versions), + return ( + b"\x00\x00\x01\xff\x00\x06\x06\x05\x00\x04\x04\x04\x00\x00\x00\x03" ) - return b"".join(versions_bytes).ljust(16, b"\x00") @classmethod async def ping(cls, address, *, deadline=None, pool_config=None): @@ -442,64 +386,16 @@ async def open( ) pool_config.protocol_version = protocol_version - - # Carry out Bolt subclass imports locally to avoid circular dependency - # issues. - - # avoid new lines after imports for better readability and conciseness - # fmt: off - if protocol_version == (5, 6): - from ._bolt5 import AsyncBolt5x6 - bolt_cls = AsyncBolt5x6 - elif protocol_version == (5, 5): - from ._bolt5 import AsyncBolt5x5 - bolt_cls = AsyncBolt5x5 - elif protocol_version == (5, 4): - from ._bolt5 import AsyncBolt5x4 - bolt_cls = AsyncBolt5x4 - elif protocol_version == (5, 3): - from ._bolt5 import AsyncBolt5x3 - bolt_cls = AsyncBolt5x3 - elif protocol_version == (5, 2): - from ._bolt5 import AsyncBolt5x2 - bolt_cls = AsyncBolt5x2 - elif protocol_version == (5, 1): - from ._bolt5 import AsyncBolt5x1 - bolt_cls = AsyncBolt5x1 - elif protocol_version == (5, 0): - from ._bolt5 import AsyncBolt5x0 - bolt_cls = AsyncBolt5x0 - elif protocol_version == (4, 4): - from ._bolt4 import AsyncBolt4x4 - bolt_cls = AsyncBolt4x4 - elif protocol_version == (4, 3): - from ._bolt4 import AsyncBolt4x3 - bolt_cls = AsyncBolt4x3 - elif protocol_version == (4, 2): - from ._bolt4 import AsyncBolt4x2 - bolt_cls = AsyncBolt4x2 - # Implementations for exist, but there was no space left in the - # handshake to offer this version to the server. Hence, the server - # should never request us to speak these bolt versions. - # elif protocol_version == (4, 1): - # from ._bolt4 import AsyncBolt4x1 - # bolt_cls = AsyncBolt4x1 - # elif protocol_version == (4, 0): - # from ._bolt4 import AsyncBolt4x0 - # bolt_cls = AsyncBolt4x0 - elif protocol_version == (3, 0): - from ._bolt3 import AsyncBolt3 - bolt_cls = AsyncBolt3 - # fmt: on - else: + protocol_handlers = AsyncBolt.protocol_handlers + bolt_cls = protocol_handlers.get(protocol_version) + if bolt_cls is None: log.debug("[#%04X] C: ", s.getsockname()[1]) await AsyncBoltSocket.close_socket(s) - supported_versions = cls.protocol_handlers().keys() raise BoltHandshakeError( "The neo4j server does not support communication with this " "driver. This driver has support for Bolt protocols " - f"{tuple(map(str, supported_versions))}.", + f"{tuple(map(str, AsyncBolt.protocol_handlers.keys()))}.", address=address, request_data=handshake, response_data=data, diff --git a/src/neo4j/_sync/io/__init__.py b/src/neo4j/_sync/io/__init__.py index a1833c74..4f9009d8 100644 --- a/src/neo4j/_sync/io/__init__.py +++ b/src/neo4j/_sync/io/__init__.py @@ -31,6 +31,11 @@ ] +from . import ( # noqa - imports needed to register protocol handlers + _bolt3, + _bolt4, + _bolt5, +) from ._bolt import Bolt from ._common import ( check_supported_server_product, diff --git a/src/neo4j/_sync/io/_bolt.py b/src/neo4j/_sync/io/_bolt.py index e2b232d2..46ac83a5 100644 --- a/src/neo4j/_sync/io/_bolt.py +++ b/src/neo4j/_sync/io/_bolt.py @@ -60,6 +60,8 @@ if t.TYPE_CHECKING: + import typing_extensions as te + from ..._api import TelemetryAPI @@ -272,83 +274,31 @@ def assert_notification_filtering_support(self): f"{self.server_info.agent!r}" ) - # [bolt-version-bump] search tag when changing bolt version support - @classmethod - def protocol_handlers(cls): - """ - Return a dictionary of available Bolt protocol handlers. - - The handlers are keyed by version tuple. If an explicit protocol - version is provided, the dictionary will contain either zero or one - items, depending on whether that version is supported. If no protocol - version is provided, all available versions will be returned. - - :param protocol_version: tuple identifying a specific protocol - version (e.g. (3, 5)) or None - :returns: dictionary of version tuple to handler class for all - relevant and supported protocol versions - :raise TypeError: if protocol version is not passed in a tuple - """ - # Carry out Bolt subclass imports locally to avoid circular dependency - # issues. - from ._bolt3 import Bolt3 - from ._bolt4 import ( - Bolt4x2, - Bolt4x3, - Bolt4x4, - ) - from ._bolt5 import ( - Bolt5x0, - Bolt5x1, - Bolt5x2, - Bolt5x3, - Bolt5x4, - Bolt5x5, - Bolt5x6, - ) - - return { - Bolt3.PROTOCOL_VERSION: Bolt3, - # 4.0-4.1 unsupported because no space left in the handshake - Bolt4x2.PROTOCOL_VERSION: Bolt4x2, - Bolt4x3.PROTOCOL_VERSION: Bolt4x3, - Bolt4x4.PROTOCOL_VERSION: Bolt4x4, - Bolt5x0.PROTOCOL_VERSION: Bolt5x0, - Bolt5x1.PROTOCOL_VERSION: Bolt5x1, - Bolt5x2.PROTOCOL_VERSION: Bolt5x2, - Bolt5x3.PROTOCOL_VERSION: Bolt5x3, - Bolt5x4.PROTOCOL_VERSION: Bolt5x4, - Bolt5x5.PROTOCOL_VERSION: Bolt5x5, - Bolt5x6.PROTOCOL_VERSION: Bolt5x6, - } - - @classmethod - def version_list(cls, versions, limit=4): - """ - Return a list of supported protocol versions in order of preference. + protocol_handlers: t.ClassVar[dict[Version, type[Bolt]]] = {} - The number of protocol versions (or ranges) returned is limited to 4. - """ - # In fact, 4.3 is the fist version to support ranges. However, the - # range support got backported to 4.2. But even if the server is too - # old to have the backport, negotiating BOLT 4.1 is no problem as it's - # equivalent to 4.2 - first_with_range_support = Version(4, 2) - result = [] - for version in versions: - if ( - result - and version >= first_with_range_support - and result[-1][0] == version[0] - and result[-1][1][1] == version[1] + 1 - ): - # can use range to encompass this version - result[-1][1][1] = version[1] - continue - result.append(Version(version[0], [version[1], version[1]])) - if len(result) >= limit: - break - return result + def __init_subclass__(cls: type[te.Self], **kwargs: t.Any) -> None: + protocol_version = cls.PROTOCOL_VERSION + if protocol_version is None: + raise ValueError( + "Bolt subclasses must define PROTOCOL_VERSION" + ) + if not ( + isinstance(protocol_version, Version) + and len(protocol_version) == 2 + and all(isinstance(i, int) for i in protocol_version) + ): + raise TypeError( + "PROTOCOL_VERSION must be a 2-tuple of integers, not " + f"{protocol_version!r}" + ) + if protocol_version in Bolt.protocol_handlers: + cls_conflict = Bolt.protocol_handlers[protocol_version] + raise TypeError( + f"Multiple classes for the same protocol version " + f"{protocol_version}: {cls}, {cls_conflict}" + ) + cls.protocol_handlers[protocol_version] = cls + super().__init_subclass__(**kwargs) @classmethod def get_handshake(cls): @@ -358,15 +308,9 @@ def get_handshake(cls): The length is 16 bytes as specified in the Bolt version negotiation. :returns: bytes """ - supported_versions = sorted( - cls.protocol_handlers().keys(), reverse=True - ) - offered_versions = cls.version_list(supported_versions, limit=3) - versions_bytes = ( - Version(0xFF, 1).to_bytes(), # handshake v2 - *(v.to_bytes() for v in offered_versions), + return ( + b"\x00\x00\x01\xff\x00\x06\x06\x05\x00\x04\x04\x04\x00\x00\x00\x03" ) - return b"".join(versions_bytes).ljust(16, b"\x00") @classmethod def ping(cls, address, *, deadline=None, pool_config=None): @@ -442,64 +386,16 @@ def open( ) pool_config.protocol_version = protocol_version - - # Carry out Bolt subclass imports locally to avoid circular dependency - # issues. - - # avoid new lines after imports for better readability and conciseness - # fmt: off - if protocol_version == (5, 6): - from ._bolt5 import Bolt5x6 - bolt_cls = Bolt5x6 - elif protocol_version == (5, 5): - from ._bolt5 import Bolt5x5 - bolt_cls = Bolt5x5 - elif protocol_version == (5, 4): - from ._bolt5 import Bolt5x4 - bolt_cls = Bolt5x4 - elif protocol_version == (5, 3): - from ._bolt5 import Bolt5x3 - bolt_cls = Bolt5x3 - elif protocol_version == (5, 2): - from ._bolt5 import Bolt5x2 - bolt_cls = Bolt5x2 - elif protocol_version == (5, 1): - from ._bolt5 import Bolt5x1 - bolt_cls = Bolt5x1 - elif protocol_version == (5, 0): - from ._bolt5 import Bolt5x0 - bolt_cls = Bolt5x0 - elif protocol_version == (4, 4): - from ._bolt4 import Bolt4x4 - bolt_cls = Bolt4x4 - elif protocol_version == (4, 3): - from ._bolt4 import Bolt4x3 - bolt_cls = Bolt4x3 - elif protocol_version == (4, 2): - from ._bolt4 import Bolt4x2 - bolt_cls = Bolt4x2 - # Implementations for exist, but there was no space left in the - # handshake to offer this version to the server. Hence, the server - # should never request us to speak these bolt versions. - # elif protocol_version == (4, 1): - # from ._bolt4 import AsyncBolt4x1 - # bolt_cls = AsyncBolt4x1 - # elif protocol_version == (4, 0): - # from ._bolt4 import AsyncBolt4x0 - # bolt_cls = AsyncBolt4x0 - elif protocol_version == (3, 0): - from ._bolt3 import Bolt3 - bolt_cls = Bolt3 - # fmt: on - else: + protocol_handlers = Bolt.protocol_handlers + bolt_cls = protocol_handlers.get(protocol_version) + if bolt_cls is None: log.debug("[#%04X] C: ", s.getsockname()[1]) BoltSocket.close_socket(s) - supported_versions = cls.protocol_handlers().keys() raise BoltHandshakeError( "The neo4j server does not support communication with this " "driver. This driver has support for Bolt protocols " - f"{tuple(map(str, supported_versions))}.", + f"{tuple(map(str, Bolt.protocol_handlers.keys()))}.", address=address, request_data=handshake, response_data=data, diff --git a/src/neo4j/api.py b/src/neo4j/api.py index cbf0c418..1a4199c2 100644 --- a/src/neo4j/api.py +++ b/src/neo4j/api.py @@ -409,7 +409,13 @@ def update(self, metadata: dict) -> None: # TODO: 6.0 - this class should not be public. # As far the user is concerned, protocol versions should simply be a # tuple[int, int]. -class Version(tuple): +if t.TYPE_CHECKING: + _version_base = t.Tuple[int, int] +else: + _version_base = tuple + + +class Version(_version_base): def __new__(cls, *v): return super().__new__(cls, v) diff --git a/testkit/_common.py b/testkit/_common.py index 7e3e6a34..1c39b36d 100644 --- a/testkit/_common.py +++ b/testkit/_common.py @@ -47,6 +47,6 @@ def get_python_version(): def run_python(args, env=None, warning_as_error=True): cmd = [TEST_BACKEND_VERSION, "-u"] if warning_as_error: - cmd += ["-W", "error"] + cmd += ["-W", "error", "-X", "tracemalloc=10"] cmd += list(args) run(cmd, env=env) diff --git a/testkitbackend/test_config.json b/testkitbackend/test_config.json index e4d8b14b..779ad2ce 100644 --- a/testkitbackend/test_config.json +++ b/testkitbackend/test_config.json @@ -47,7 +47,6 @@ "Feature:Auth:Kerberos": true, "Feature:Auth:Managed": true, "Feature:Bolt:3.0": true, - "Feature:Bolt:4.1": true, "Feature:Bolt:4.2": true, "Feature:Bolt:4.3": true, "Feature:Bolt:4.4": true, diff --git a/tests/unit/async_/io/test_class_bolt.py b/tests/unit/async_/io/test_class_bolt.py index 5091a24b..0a6b7fb5 100644 --- a/tests/unit/async_/io/test_class_bolt.py +++ b/tests/unit/async_/io/test_class_bolt.py @@ -29,20 +29,17 @@ ) -# python -m pytest tests/unit/io/test_class_bolt.py -s -v - - # [bolt-version-bump] search tag when changing bolt version support def test_class_method_protocol_handlers(): # fmt: off expected_handlers = { (3, 0), - (4, 2), (4, 3), (4, 4), + (4, 0), (4, 1), (4, 2), (4, 3), (4, 4), (5, 0), (5, 1), (5, 2), (5, 3), (5, 4), (5, 5), (5, 6), } # fmt: on - protocol_handlers = AsyncBolt.protocol_handlers() + protocol_handlers = AsyncBolt.protocol_handlers assert len(protocol_handlers) == len(expected_handlers) assert protocol_handlers.keys() == expected_handlers @@ -56,11 +53,13 @@ def test_class_method_protocol_handlers(): ((1, 0), False), ((2, 0), False), ((3, 0), True), - ((4, 0), False), - ((4, 1), False), + ((3, 1), False), + ((4, 0), True), + ((4, 1), True), ((4, 2), True), ((4, 3), True), ((4, 4), True), + ((4, 5), False), ((5, 0), True), ((5, 1), True), ((5, 2), True), @@ -75,7 +74,7 @@ def test_class_method_protocol_handlers(): def test_class_method_protocol_handlers_with_protocol_version( test_input, expected ): - protocol_handlers = AsyncBolt.protocol_handlers() + protocol_handlers = AsyncBolt.protocol_handlers assert (test_input in protocol_handlers) == expected @@ -84,7 +83,7 @@ def test_class_method_get_handshake(): handshake = AsyncBolt.get_handshake() assert ( handshake - == b"\x00\x00\x01\xff\x00\x06\x06\x05\x00\x02\x04\x04\x00\x00\x00\x03" + == b"\x00\x00\x01\xff\x00\x06\x06\x05\x00\x04\x04\x04\x00\x00\x00\x03" ) @@ -111,6 +110,10 @@ async def test_cancel_hello_in_open(mocker, none_auth): bolt_mock.socket = socket_mock bolt_mock.hello.side_effect = asyncio.CancelledError() bolt_mock.local_port = 1234 + mocker.patch.dict( + AsyncBolt.protocol_handlers, + {(5, 0): bolt_cls_mock}, + ) with pytest.raises(asyncio.CancelledError): await AsyncBolt.open(address, auth_manager=none_auth) @@ -123,6 +126,7 @@ async def test_cancel_hello_in_open(mocker, none_auth): ("bolt_version", "bolt_cls_path"), ( ((3, 0), "neo4j._async.io._bolt3.AsyncBolt3"), + ((4, 0), "neo4j._async.io._bolt4.AsyncBolt4x0"), ((4, 1), "neo4j._async.io._bolt4.AsyncBolt4x1"), ((4, 2), "neo4j._async.io._bolt4.AsyncBolt4x2"), ((4, 3), "neo4j._async.io._bolt4.AsyncBolt4x3"), @@ -157,6 +161,10 @@ async def test_version_negotiation( bolt_cls_mock.return_value.local_port = 1234 bolt_mock = bolt_cls_mock.return_value bolt_mock.socket = socket_mock + mocker.patch.dict( + AsyncBolt.protocol_handlers, + {bolt_version: bolt_cls_mock}, + ) connection = await AsyncBolt.open(address, auth_manager=none_auth) @@ -170,8 +178,6 @@ async def test_version_negotiation( ( (0, 0), (2, 0), - (4, 0), - (4, 1), (3, 1), (5, 7), (6, 0), @@ -180,7 +186,7 @@ async def test_version_negotiation( @mark_async_test async def test_failing_version_negotiation(mocker, bolt_version, none_auth): supported_protocols = ( - "('3.0', '4.2', '4.3', '4.4', " + "('3.0', '4.0', '4.1', '4.2', '4.3', '4.4', " "'5.0', '5.1', '5.2', '5.3', '5.4', '5.5', '5.6')" ) @@ -214,12 +220,6 @@ async def test_cancel_manager_in_open(mocker): ) socket_cls_mock.connect.return_value = (socket_mock, (5, 0), None, None) socket_mock.getpeername.return_value = address - bolt_cls_mock = mocker.patch( - "neo4j._async.io._bolt5.AsyncBolt5x0", autospec=True - ) - bolt_mock = bolt_cls_mock.return_value - bolt_mock.socket = socket_mock - bolt_mock.local_port = 1234 auth_manager = mocker.AsyncMock( spec=neo4j.auth_management.AsyncAuthManager @@ -242,12 +242,6 @@ async def test_fail_manager_in_open(mocker): ) socket_cls_mock.connect.return_value = (socket_mock, (5, 0), None, None) socket_mock.getpeername.return_value = address - bolt_cls_mock = mocker.patch( - "neo4j._async.io._bolt5.AsyncBolt5x0", autospec=True - ) - bolt_mock = bolt_cls_mock.return_value - bolt_mock.socket = socket_mock - bolt_mock.local_port = 1234 auth_manager = mocker.AsyncMock( spec=neo4j.auth_management.AsyncAuthManager diff --git a/tests/unit/common/test_exceptions.py b/tests/unit/common/test_exceptions.py index 8cc192cd..64cbb6ff 100644 --- a/tests/unit/common/test_exceptions.py +++ b/tests/unit/common/test_exceptions.py @@ -73,7 +73,7 @@ def test_bolt_handshake_error(): b"\x00\x00\x00\x04\x00\x00\x00\x03\x00\x00\x00\x00\x00\x00\x00\x00" ) response = b"\x00\x00\x00\x00" - supported_versions = Bolt.protocol_handlers().keys() + supported_versions = Bolt.protocol_handlers.keys() with pytest.raises(BoltHandshakeError) as e: error = BoltHandshakeError( diff --git a/tests/unit/sync/io/test_class_bolt.py b/tests/unit/sync/io/test_class_bolt.py index 73b91030..31186a78 100644 --- a/tests/unit/sync/io/test_class_bolt.py +++ b/tests/unit/sync/io/test_class_bolt.py @@ -29,20 +29,17 @@ ) -# python -m pytest tests/unit/io/test_class_bolt.py -s -v - - # [bolt-version-bump] search tag when changing bolt version support def test_class_method_protocol_handlers(): # fmt: off expected_handlers = { (3, 0), - (4, 2), (4, 3), (4, 4), + (4, 0), (4, 1), (4, 2), (4, 3), (4, 4), (5, 0), (5, 1), (5, 2), (5, 3), (5, 4), (5, 5), (5, 6), } # fmt: on - protocol_handlers = Bolt.protocol_handlers() + protocol_handlers = Bolt.protocol_handlers assert len(protocol_handlers) == len(expected_handlers) assert protocol_handlers.keys() == expected_handlers @@ -56,11 +53,13 @@ def test_class_method_protocol_handlers(): ((1, 0), False), ((2, 0), False), ((3, 0), True), - ((4, 0), False), - ((4, 1), False), + ((3, 1), False), + ((4, 0), True), + ((4, 1), True), ((4, 2), True), ((4, 3), True), ((4, 4), True), + ((4, 5), False), ((5, 0), True), ((5, 1), True), ((5, 2), True), @@ -75,7 +74,7 @@ def test_class_method_protocol_handlers(): def test_class_method_protocol_handlers_with_protocol_version( test_input, expected ): - protocol_handlers = Bolt.protocol_handlers() + protocol_handlers = Bolt.protocol_handlers assert (test_input in protocol_handlers) == expected @@ -84,7 +83,7 @@ def test_class_method_get_handshake(): handshake = Bolt.get_handshake() assert ( handshake - == b"\x00\x00\x01\xff\x00\x06\x06\x05\x00\x02\x04\x04\x00\x00\x00\x03" + == b"\x00\x00\x01\xff\x00\x06\x06\x05\x00\x04\x04\x04\x00\x00\x00\x03" ) @@ -111,6 +110,10 @@ def test_cancel_hello_in_open(mocker, none_auth): bolt_mock.socket = socket_mock bolt_mock.hello.side_effect = asyncio.CancelledError() bolt_mock.local_port = 1234 + mocker.patch.dict( + Bolt.protocol_handlers, + {(5, 0): bolt_cls_mock}, + ) with pytest.raises(asyncio.CancelledError): Bolt.open(address, auth_manager=none_auth) @@ -123,6 +126,7 @@ def test_cancel_hello_in_open(mocker, none_auth): ("bolt_version", "bolt_cls_path"), ( ((3, 0), "neo4j._sync.io._bolt3.Bolt3"), + ((4, 0), "neo4j._sync.io._bolt4.Bolt4x0"), ((4, 1), "neo4j._sync.io._bolt4.Bolt4x1"), ((4, 2), "neo4j._sync.io._bolt4.Bolt4x2"), ((4, 3), "neo4j._sync.io._bolt4.Bolt4x3"), @@ -157,6 +161,10 @@ def test_version_negotiation( bolt_cls_mock.return_value.local_port = 1234 bolt_mock = bolt_cls_mock.return_value bolt_mock.socket = socket_mock + mocker.patch.dict( + Bolt.protocol_handlers, + {bolt_version: bolt_cls_mock}, + ) connection = Bolt.open(address, auth_manager=none_auth) @@ -170,8 +178,6 @@ def test_version_negotiation( ( (0, 0), (2, 0), - (4, 0), - (4, 1), (3, 1), (5, 7), (6, 0), @@ -180,7 +186,7 @@ def test_version_negotiation( @mark_sync_test def test_failing_version_negotiation(mocker, bolt_version, none_auth): supported_protocols = ( - "('3.0', '4.2', '4.3', '4.4', " + "('3.0', '4.0', '4.1', '4.2', '4.3', '4.4', " "'5.0', '5.1', '5.2', '5.3', '5.4', '5.5', '5.6')" ) @@ -214,12 +220,6 @@ def test_cancel_manager_in_open(mocker): ) socket_cls_mock.connect.return_value = (socket_mock, (5, 0), None, None) socket_mock.getpeername.return_value = address - bolt_cls_mock = mocker.patch( - "neo4j._sync.io._bolt5.Bolt5x0", autospec=True - ) - bolt_mock = bolt_cls_mock.return_value - bolt_mock.socket = socket_mock - bolt_mock.local_port = 1234 auth_manager = mocker.MagicMock( spec=neo4j.auth_management.AuthManager @@ -242,12 +242,6 @@ def test_fail_manager_in_open(mocker): ) socket_cls_mock.connect.return_value = (socket_mock, (5, 0), None, None) socket_mock.getpeername.return_value = address - bolt_cls_mock = mocker.patch( - "neo4j._sync.io._bolt5.Bolt5x0", autospec=True - ) - bolt_mock = bolt_cls_mock.return_value - bolt_mock.socket = socket_mock - bolt_mock.local_port = 1234 auth_manager = mocker.MagicMock( spec=neo4j.auth_management.AuthManager diff --git a/tox.ini b/tox.ini index d75274c2..99eaab19 100644 --- a/tox.ini +++ b/tox.ini @@ -4,13 +4,15 @@ envlist = py{37,38,39,310,311,312}-{unit,integration,performance} [testenv] passenv = TEST_NEO4J_* deps = -r requirements-dev.txt -setenv = COVERAGE_FILE={envdir}/.coverage +setenv = + COVERAGE_FILE={envdir}/.coverage + unit,performance,integration: PYTHONTRACEMALLOC = 10 usedevelop = true warnargs = - py{37,38,39,310,311,312}: -W error + py{37,38,39,310,311,312}: -W error -W ignore::pytest.PytestUnraisableExceptionWarning commands = coverage erase unit: coverage run -m pytest {[testenv]warnargs} -v {posargs} tests/unit integration: coverage run -m pytest {[testenv]warnargs} -v {posargs} tests/integration performance: python -m pytest --benchmark-autosave -v {posargs} tests/performance - unit,integration: coverage report +; unit,integration: coverage report