152 lines
5.0 KiB
Python
152 lines
5.0 KiB
Python
'''
|
|
Unit tests for the `tractor.devx.pformat` render helpers.
|
|
|
|
'''
|
|
from __future__ import annotations
|
|
|
|
import pytest
|
|
import trio
|
|
|
|
from tractor._exceptions import _mk_send_mte
|
|
from tractor.log import pformat_task_uid as log_task_uid
|
|
from tractor.devx.pformat import (
|
|
pformat_boxed_tb,
|
|
pformat_caller_frame,
|
|
pformat_task,
|
|
pformat_task_uid,
|
|
)
|
|
from tractor.msg._codec import _def_tractor_codec
|
|
|
|
|
|
def test_pformat_task_selects_another_tasks_identity() -> None:
|
|
'''
|
|
Describe the selected task rather than the diagnostic caller.
|
|
|
|
The final-Return regression needs to snapshot an endpoint and its
|
|
nursery owner from another task. Hold a real child in an active
|
|
nursery and inspect it from its parent, checking both identities
|
|
and public nursery relationships without requiring an actor.
|
|
Preserve the log header's decimal head/tail ID convention while
|
|
allowing that existing helper to select an explicit task too.
|
|
|
|
'''
|
|
async def main() -> None:
|
|
'''
|
|
Keep the inspected child alive through all report assertions.
|
|
|
|
'''
|
|
async def child(
|
|
task_status: trio.TaskStatus[trio.lowlevel.Task],
|
|
) -> None:
|
|
'''
|
|
Publish the real task before waiting for cancellation.
|
|
|
|
'''
|
|
task_status.started(trio.lowlevel.current_task())
|
|
await trio.sleep_forever()
|
|
|
|
owner: trio.lowlevel.Task = trio.lowlevel.current_task()
|
|
owner_id: str = str(id(owner))
|
|
assert pformat_task_uid() == f'{owner.name}[{owner_id[-6:]}]'
|
|
assert pformat_task_uid('head') == (
|
|
f'{owner.name}[{owner_id[:6]}]'
|
|
)
|
|
assert log_task_uid() == pformat_task_uid()
|
|
assert log_task_uid('head') == pformat_task_uid('head')
|
|
async with trio.open_nursery() as nursery:
|
|
task: trio.lowlevel.Task = await nursery.start(child)
|
|
task_id: str = str(id(task))
|
|
task_uid: str = f'{task.name}[{task_id[-6:]}]'
|
|
report: str = pformat_task(task)
|
|
assert pformat_task_uid(task=task) == task_uid
|
|
assert pformat_task_uid('head', task=task) == (
|
|
f'{task.name}[{task_id[:6]}]'
|
|
)
|
|
assert log_task_uid(task=task) == task_uid
|
|
assert log_task_uid('head', task=task) == (
|
|
pformat_task_uid('head', task=task)
|
|
)
|
|
assert f'uid={task_uid!r}' in report
|
|
assert f'task={task!r}' in report
|
|
assert f'coro={task.coro!r}' in report
|
|
assert f'parent_nursery: {nursery!r}' in report
|
|
assert f'parent_task={owner!r}' in report
|
|
assert 'child_nurseries: ()' in report
|
|
assert f'child_nurseries: ({nursery!r},)' in (
|
|
pformat_task(owner)
|
|
)
|
|
assert pformat_task() == pformat_task(owner)
|
|
nursery.cancel_scope.cancel()
|
|
|
|
trio.run(main)
|
|
|
|
|
|
@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='',
|
|
)
|
|
|
|
|
|
def test_send_mte_default_message_renders():
|
|
'''
|
|
The default send-side `MsgTypeError` must remain printable.
|
|
|
|
Once `pformat_caller_frame()` stopped failing first, this path
|
|
exposed two more formatter errors: `MsgCodec.msg_spec_str` passed
|
|
a type union where `pformat_msgspec()` requires a codec/decoder,
|
|
then `_mk_send_mte()` wrapped its message in a one-element tuple.
|
|
|
|
Construct the error without an override message to execute that
|
|
complete default path. Requiring a `str` message with the bad
|
|
value and valid spec, then rendering the exception, proves the
|
|
original IPC violation survives every formatter layer.
|
|
|
|
'''
|
|
bad_msg: dict[str, bool] = {'bad': True}
|
|
mte = _mk_send_mte(
|
|
msg=bad_msg,
|
|
codec=_def_tractor_codec,
|
|
)
|
|
|
|
assert isinstance(mte.message, str)
|
|
assert f'invalid msg -> {bad_msg}' in mte.message
|
|
assert 'Valid IPC msgs are:' in mte.message
|
|
|
|
report: str = repr(mte)
|
|
assert 'MsgTypeError' in report
|
|
assert f'invalid msg -> {bad_msg}' in report
|