diff --git a/tests/discovery/test_multiaddr.py b/tests/discovery/test_multiaddr.py index 9b1520d6..95eac491 100644 --- a/tests/discovery/test_multiaddr.py +++ b/tests/discovery/test_multiaddr.py @@ -19,7 +19,10 @@ from tractor.discovery._multiaddr import ( _tpt_proto_to_maddr, _maddr_to_tpt_proto, ) -from tractor.discovery._addr import wrap_address +from tractor.discovery._addr import ( + wrap_address, + _address_types, +) def test_tpt_proto_to_maddr_mapping(): @@ -30,7 +33,12 @@ def test_tpt_proto_to_maddr_mapping(): ''' assert _tpt_proto_to_maddr['tcp'] == 'tcp' assert _tpt_proto_to_maddr['uds'] == 'unix' - assert len(_tpt_proto_to_maddr) == 2 + assert _tpt_proto_to_maddr['tipc'] == 'tipc' + + # NOTE, drive the expected set off the registration table + # (per the "drive-the-set-from-the-`Literal`" pattern) so + # adding a backend can't fail this for the wrong reason. + assert set(_tpt_proto_to_maddr) == set(_address_types) def test_mk_maddr_tcp_ipv4(): @@ -153,9 +161,12 @@ def test_maddr_to_tpt_proto_mapping(): ''' assert _maddr_to_tpt_proto == { - 'tcp': 'tcp', - 'unix': 'uds', + maddr_proto: proto_key + for proto_key, maddr_proto in _tpt_proto_to_maddr.items() } + assert _maddr_to_tpt_proto['tcp'] == 'tcp' + assert _maddr_to_tpt_proto['unix'] == 'uds' + assert _maddr_to_tpt_proto['tipc'] == 'tipc' def test_parse_maddr_tcp_ipv4(): diff --git a/tests/ipc/test_tipc.py b/tests/ipc/test_tipc.py index fad4758d..3dcfa445 100644 --- a/tests/ipc/test_tipc.py +++ b/tests/ipc/test_tipc.py @@ -19,6 +19,12 @@ import pytest import trio from tractor.msg.types import Aid +from tractor.discovery import _addr +from tractor.discovery._addr import wrap_address +from tractor.discovery._multiaddr import ( + mk_maddr, + parse_maddr, +) from tractor.ipc import _tipc from tractor.ipc._tipc import ( AF_TIPC, @@ -206,6 +212,48 @@ def test_get_random_honors_bindspace(): assert addr.bindspace == TIPC_NODE_SCOPE == addr._scope +def test_wrap_address_dispatches_on_the_proto_key(): + ''' + The proto-keyed unwrapped form must round-trip through the + *global* `wrap_address()` — and NOT get stolen by `tcp`s + `(str(), int())` case nor `uds`s `(_, str())` one. + + ''' + addr: TIPCAddress = TIPCAddress.get_random() + assert wrap_address(addr.unwrap()) == addr + # ..and via the `list` form `msgpack` decodes to + assert wrap_address(list(addr.unwrap())) == addr + + assert _addr._address_types['tipc'] is TIPCAddress + assert _addr.get_address_cls('tipc') is TIPCAddress + + # the host-singleton registrar default is import-time cheap + # (no kernel module, no I/O) and mirrors the `1616` idiom + assert _addr._default_lo_addrs['tipc'] == ( + 'tipc', TRACTOR_STYPE, 1616, TIPC_CLUSTER_SCOPE, + ) + + +def test_maddr_roundtrip(): + ''' + Interim `str`-only `/tipc/` maddr grammar (there's no + registered `/tipc` multiaddr proto yet, gh #483), which + `parse_maddr()` special-cases before `Multiaddr()` ever sees + the string. + + ''' + addr: TIPCAddress = TIPCAddress.get_random() + maddr: str = mk_maddr(addr) + + assert isinstance(maddr, str) + assert maddr == ( + f'/tipc/{addr._stype}/{addr._instance}/{addr._scope}' + ) + assert parse_maddr(maddr) == addr + # ..and through the generic entrypoint + assert wrap_address(maddr) == addr + + def test_eafnosupport_is_actionable_connerr( monkeypatch: pytest.MonkeyPatch, ): diff --git a/tractor/discovery/_multiaddr.py b/tractor/discovery/_multiaddr.py index 74076fd4..cc9eb9db 100644 --- a/tractor/discovery/_multiaddr.py +++ b/tractor/discovery/_multiaddr.py @@ -38,8 +38,23 @@ if TYPE_CHECKING: _tpt_proto_to_maddr: dict[str, str] = { 'tcp': 'tcp', 'uds': 'unix', + 'tipc': 'tipc', } +# XXX, there is NO `/tipc` in the multiaddr protocol table yet +# (upstream track: gh #483 + multiformats/py-multiaddr#107), and +# `Multiaddr()` rejects an unregistered proto name outright. +# +# So until that lands `tipc` maddrs stay **`str`**-only — which +# `MsgTransport.maddr`s `Multiaddr|str` return type already +# allows and `MsgpackUDSStream.maddr` already exercises — and +# `parse_maddr()` special-cases the prefix BEFORE handing +# anything to `Multiaddr()`. +# +# This is also why gh #443's "always return `Multiaddr`" item +# stays blocked. +_tipc_maddr_prefix: str = '/tipc/' + # reverse mapping: multiaddr protocol name -> tractor proto_key _maddr_to_tpt_proto: dict[str, str] = { v: k for k, v in _tpt_proto_to_maddr.items() @@ -49,7 +64,7 @@ _maddr_to_tpt_proto: dict[str, str] = { def mk_maddr( addr: 'Address', -) -> Multiaddr: +) -> Multiaddr|str: ''' Construct a `Multiaddr` from a tractor `Address` instance, dispatching on the `.proto_key` to build the correct @@ -75,6 +90,18 @@ def mk_maddr( f'/{net_proto}/{host}/{maddr_proto}/{port}' ) + # NOTE, interim `str`-only grammar (see the + # `_tipc_maddr_prefix` note above), + # + # /tipc/// + # + # mirroring how `uds` maps onto the spec-legal `/unix`. + case 'tipc': + _, stype, instance, scope = addr.unwrap() + return ( + f'/{maddr_proto}/{stype}/{instance}/{scope}' + ) + case 'uds': filedir, filename = addr.unwrap() filepath = Path(filedir) / filename @@ -100,6 +127,17 @@ def parse_maddr( # lazy imports to avoid circular deps from tractor.ipc._tcp import TCPAddress from tractor.ipc._uds import UDSAddress + from tractor.ipc._tipc import TIPCAddress + + # XXX MUST come before `Multiaddr()` which rejects the + # not-yet-registered `/tipc` proto name outright. + if maddr_str.startswith(_tipc_maddr_prefix): + _, _, stype, instance, scope = maddr_str.split('/') + return TIPCAddress( + _stype=int(stype), + _instance=int(instance), + _scope=int(scope), + ) maddr = Multiaddr(maddr_str) proto_names: list[str] = [