Skip to content
Draft
Show file tree
Hide file tree
Changes from 9 commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 52 additions & 0 deletions tests/devx/test_pformat.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
'''
Unit tests for the `tractor.devx.pformat` render helpers.

'''
from __future__ import annotations

import pytest

from tractor.devx.pformat import (
pformat_boxed_tb,
pformat_caller_frame,
)


@pytest.mark.parametrize(
'box_tb',
[True, False],
ids=['boxed', 'bare'],
)
def test_pformat_caller_frame_renders(box_tb: bool):
'''
`pformat_caller_frame()` must render, not raise.

XXX the `box_tb=True` branch was passing an `indent=''` kwarg
that `pformat_boxed_tb()` never accepted, so it blew up with
a `TypeError`. Nothing in the test suite covered it, and the
only caller is `_mk_send_mte()` β€” i.e. EVERY send-side
`MsgTypeError` died while formatting itself, masking the real
msg-spec violation behind a bogus `TypeError`.

'''
report: str = pformat_caller_frame(
stack_limit=3,
box_tb=box_tb,
)
assert isinstance(report, str)
assert 'test_pformat_caller_frame_renders' in report


def test_pformat_boxed_tb_rejects_unknown_kwargs():
'''
Pin the signature so a future typo'd kwarg fails loudly at the
call site rather than only when some rare error path runs.

'''
assert pformat_boxed_tb(tb_str='doggy\n')

with pytest.raises(TypeError):
pformat_boxed_tb(
tb_str='doggy\n',
indent='',
)
29 changes: 29 additions & 0 deletions tests/discovery/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,35 @@ def _wait_for_daemon_ready(
timeout=poll_interval,
):
return

elif tpt_proto == 'tipc':
# TIPC β€” `reg_addr` is the proto-keyed
# `('tipc', stype, instance, scope)` per
# `tractor.ipc._tipc.TIPCAddress.unwrap()`.
#
# NOTE, connecting *by name* IS the readiness
# probe: until the daemon `.bind()`s (i.e.
# publishes) the name, the kernel answers
# `EHOSTUNREACH` immediately β€” no timeout wait.
from tractor.ipc._tipc import (
AF_TIPC,
TIPC_ADDR_NAME,
)
_, stype, instance, scope = reg_addr
sock = socket.socket(AF_TIPC, socket.SOCK_STREAM)
try:
sock.settimeout(poll_interval)
sock.connect((
TIPC_ADDR_NAME,
stype,
instance,
0, # domain: 0 == "anywhere in scope"
scope,
))
return
finally:
sock.close()

else:
# UDS β€” `reg_addr` is a `(filedir, sockname)`
# tuple per `tractor.ipc._uds.UDSAddress.unwrap`.
Expand Down
19 changes: 15 additions & 4 deletions tests/discovery/test_multiaddr.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,10 @@
_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():
Expand All @@ -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():
Expand Down Expand Up @@ -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():
Expand Down
69 changes: 69 additions & 0 deletions tests/ipc/test_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
from tractor._testing.addr import (
get_rando_addr,
)
from tractor.ipc._tcp import TCPAddress
# TODO, use/check-roundtripping with some of these wrapper types?
#
# from .._addr import Address
Expand Down Expand Up @@ -70,3 +71,71 @@ async def main():
pdb=debug_mode,
):
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)
Loading
Loading