Pin `Endpoint` addr-reconciliation for tcp/uds

Guard test for `.start_listener()`s post-bind
`getsockname()`-vs-`.addr` round-trip, landed *before* that
reconciliation gets gated on an opt-out `ClassVar`.

- tcp: a `port=0` bind MUST still learn the kernel-picked
  port, since the reconciliation is the only path that ever
  does.
- uds: the sock-file path must survive the `.from_addr()`
  round-trip unchanged.

(this patch was generated in some part by `claude-code` using `claude-opus-5` (`anthropic`))
wkt/pr493_review
Gud Boi 2026-08-14 09:34:41 -04:00
parent f9f98eeb06
commit d4737e957f
1 changed files with 69 additions and 0 deletions

View File

@ -14,6 +14,7 @@ from tractor import (
from tractor._testing.addr import ( from tractor._testing.addr import (
get_rando_addr, get_rando_addr,
) )
from tractor.ipc._tcp import TCPAddress
# TODO, use/check-roundtripping with some of these wrapper types? # TODO, use/check-roundtripping with some of these wrapper types?
# #
# from .._addr import Address # from .._addr import Address
@ -70,3 +71,71 @@ def test_basic_ipc_server(
pdb=debug_mode, pdb=debug_mode,
): ):
trio.run(main) trio.run(main)
@pytest.mark.parametrize(
'_tpt_proto',
['uds', 'tcp']
)
def test_ep_addr_reconciled_from_sockname(
_tpt_proto: str,
debug_mode: bool,
):
'''
Guard `Endpoint.start_listener()`'s post-bind reconciliation of
`.addr` against the listener's `socket.getsockname()`.
For `tcp` that reconciliation is the ONLY way a kernel-assigned
port (from a `port=0` bind) is ever learned, so it must keep
firing; for `uds` the sock-file path must survive the
round-trip through `.from_addr()` unchanged.
Both are pinned here *before* the reconciliation gets gated on
an `Address.rebind_from_sockname` opt-out (for backends whose
`getsockname()` reports something other than what was bound).
'''
async def main():
async with ipc._server.open_ipc_server() as server:
accept_addr: tuple[str, int|str]
match _tpt_proto:
# XXX the whole point: ask the kernel to pick.
case 'tcp':
accept_addr = (
TCPAddress.def_bindspace,
0,
)
case 'uds':
accept_addr = get_rando_addr(
tpt_proto=_tpt_proto,
)
eps: list[ipc._server.Endpoint] = await server.listen_on(
accept_addrs=[accept_addr],
stream_handler_nursery=None,
)
assert len(eps) == 1
ep: ipc._server.Endpoint = eps[0]
sockname = ep._listener.socket.getsockname()
match _tpt_proto:
case 'tcp':
# the bind req was for "any port"..
assert accept_addr[1] == 0
# ..and the ep learned the real one.
assert ep.addr._port != 0
assert ep.addr.unwrap() == tuple(sockname[:2])
case 'uds':
# sock-file path is stable across the
# `.from_addr()` round-trip.
assert ep.addr.unwrap() == accept_addr
assert str(ep.addr.sockpath) == sockname
server._parent_tn.cancel_scope.cancel()
with devx.maybe_open_crash_handler(
pdb=debug_mode,
):
trio.run(main)