tractor/tests/discovery/test_address_serialization.py

160 lines
4.1 KiB
Python

'''
Canonical tagged-address decoding and legacy input compatibility.
'''
from pathlib import Path
from types import SimpleNamespace
import pytest
import trio
from tractor.discovery._addr import wrap_address
from tractor.discovery._registry import Registrar
from tractor.ipc._tcp import TCPAddress
from tractor.ipc._uds import UDSAddress
@pytest.mark.parametrize(
'value',
[
('tcp', '127.0.0.1', 1616),
['tcp', '127.0.0.1', 1616],
],
)
def test_decode_tagged_tcp_address(value):
'''
Shape-only decoding cannot distinguish future transport address
forms. Feed canonical tuple and msgpack-style list values through
the compatibility boundary and prove the explicit `tcp` tag
selects the TCP backend and emits the canonical tagged form.
'''
addr = wrap_address(value)
assert type(addr) is TCPAddress
assert addr.unwrap() == ('tcp', '127.0.0.1', 1616)
@pytest.mark.parametrize(
'tag',
['unix', 'uds'],
)
@pytest.mark.parametrize('container', [tuple, list])
def test_decode_tagged_unix_address(
tag: str,
container: type,
):
'''
Multiaddr calls the protocol `unix` while tractor's transport key
remains `uds`. Decode both spellings from tuple/list containers,
normalize them to one `UDSAddress`, and emit the canonical `unix`
spelling.
'''
value = container((tag, '/tmp/tractor/registry.sock'))
addr = wrap_address(value)
assert type(addr) is UDSAddress
assert addr.sockpath == Path('/tmp/tractor/registry.sock')
assert addr.unwrap() == (
'unix',
'/tmp/tractor/registry.sock',
)
@pytest.mark.parametrize(
'value, expected_type',
[
(('127.0.0.1', 1616), TCPAddress),
(['127.0.0.1', 1616], TCPAddress),
(('/tmp/tractor', 'registry.sock'), UDSAddress),
(['/tmp/tractor', 'registry.sock'], UDSAddress),
],
)
def test_decode_legacy_address_forms(
value,
expected_type: type,
):
'''
Existing callers, config, and older msgpack payloads still
provide untagged pairs. Keep tuple/list forms readable while
canonical tagged emission is introduced, proving the writer
migration does not break shipped input behavior.
'''
addr = wrap_address(value)
assert type(addr) is expected_type
assert addr.unwrap()[0] in {'tcp', 'unix'}
def test_tcp_from_native_ipv6_sockname():
'''
`socket.getsockname()` returns a four-item IPv6 sockaddr which is
neither a wire form nor a legacy two-item pair. Preserve it as an
OS compatibility boundary and intentionally ignore unsupported
flow-info/scope-id fields when constructing `TCPAddress`.
'''
addr = TCPAddress.from_addr(
('::1', 1616, 0, 0)
)
assert addr.unwrap() == ('tcp', '::1', 1616)
@pytest.mark.parametrize(
'legacy, canonical',
[
(
('127.0.0.1', 1616),
('tcp', '127.0.0.1', 1616),
),
(
('/tmp/tractor', 'registry.sock'),
('unix', '/tmp/tractor/registry.sock'),
),
],
)
def test_registrar_stores_canonical_addresses(
legacy: tuple,
canonical: tuple,
):
'''
Normalize registrar entries before stale-address eviction.
During the tagged-address migration an older actor can register
an untagged address before a newer actor reuses that endpoint with
its canonical tag. Store the first declaration canonically, then
register the tagged spelling under another uid. The old uid must
be evicted and the registry must retain exactly one canonical
address for the replacement actor.
'''
registrar = SimpleNamespace(
_registry={},
_waiters={},
)
old_uid = ('old', 'old-uid')
new_uid = ('new', 'new-uid')
async def register_both():
await Registrar.register_actor(
registrar,
old_uid,
legacy,
)
assert registrar._registry[old_uid] == [canonical]
await Registrar.register_actor(
registrar,
new_uid,
canonical,
)
trio.run(register_both)
assert registrar._registry == {
new_uid: [canonical],
}