Drop `wrapt` for `tractor_test`, revert to `functools`
Realized a bit late that (pretty sure) i already tried this using `wrapt` idea and waay back and found the same "issue" XD The `wrapt.decorator` transparently proxies `__code__` from the async test fn, fooling `pytest`'s coroutine detection into skipping wrapped tests as "unhandled coroutines". `functools.wraps` preserves the sig for fixture injection via `__wrapped__` without leaking the async nature. So i let `claude` rework the latest code to go back to using the old stdlib wrapping again.. Deats, - `functools.partial` replaces `wrapt.PartialCallableObjectProxy`. - wrapper takes plain `**kwargs`; runtime settings extracted via `kwargs.get()` in `_main()`. - `iscoroutinefunction()` guard moved before wrapper definition. - drop all `*args` passing (fixture kwargs only). (this patch was generated in some part by [`claude-code`][claude-code-gh]) [claude-code-gh]: https://github.com/anthropics/claude-codesubint_spawner_backend
parent
f47010d7e9
commit
452a32fb23
|
|
@ -21,18 +21,17 @@ and applications.
|
||||||
'''
|
'''
|
||||||
from functools import (
|
from functools import (
|
||||||
partial,
|
partial,
|
||||||
|
wraps,
|
||||||
)
|
)
|
||||||
import inspect
|
import inspect
|
||||||
import platform
|
import platform
|
||||||
from typing import (
|
from typing import (
|
||||||
Callable,
|
Callable,
|
||||||
Type,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
import tractor
|
import tractor
|
||||||
import trio
|
import trio
|
||||||
import wrapt
|
|
||||||
|
|
||||||
|
|
||||||
def tractor_test(
|
def tractor_test(
|
||||||
|
|
@ -89,26 +88,34 @@ def tractor_test(
|
||||||
'''
|
'''
|
||||||
__tracebackhide__: bool = hide_tb
|
__tracebackhide__: bool = hide_tb
|
||||||
|
|
||||||
# handle the decorator not called with () case.
|
# handle @tractor_test (no parens) vs @tractor_test(timeout=10)
|
||||||
# i.e. in `wrapt` support a deco-with-optional-args,
|
|
||||||
# https://wrapt.readthedocs.io/en/master/decorators.html#decorators-with-optional-arguments
|
|
||||||
if wrapped is None:
|
if wrapped is None:
|
||||||
return wrapt.PartialCallableObjectProxy(
|
return partial(
|
||||||
tractor_test,
|
tractor_test,
|
||||||
timeout=timeout,
|
timeout=timeout,
|
||||||
hide_tb=hide_tb
|
hide_tb=hide_tb,
|
||||||
)
|
)
|
||||||
|
|
||||||
@wrapt.decorator
|
funcname: str = wrapped.__name__
|
||||||
def wrapper(
|
if not inspect.iscoroutinefunction(wrapped):
|
||||||
wrapped: Callable,
|
raise TypeError(
|
||||||
instance: object|Type|None,
|
f'Test-fn {funcname!r} must be an async-function !!'
|
||||||
args: tuple,
|
)
|
||||||
kwargs: dict,
|
|
||||||
):
|
# NOTE: we intentionally use `functools.wraps` instead of
|
||||||
|
# `@wrapt.decorator` here bc wrapt's transparent proxy makes
|
||||||
|
# `inspect.iscoroutinefunction(wrapper)` return `True` (it
|
||||||
|
# proxies `__code__` from the wrapped async fn), which causes
|
||||||
|
# pytest to skip the test as an "unhandled coroutine".
|
||||||
|
# `functools.wraps` preserves the signature for fixture
|
||||||
|
# injection (via `__wrapped__`) without leaking the async
|
||||||
|
# nature.
|
||||||
|
@wraps(wrapped)
|
||||||
|
def wrapper(**kwargs):
|
||||||
__tracebackhide__: bool = hide_tb
|
__tracebackhide__: bool = hide_tb
|
||||||
|
|
||||||
# NOTE, ensure we inject any test-fn declared fixture names.
|
# NOTE, ensure we inject any test-fn declared fixture
|
||||||
|
# names.
|
||||||
for kw in [
|
for kw in [
|
||||||
'reg_addr',
|
'reg_addr',
|
||||||
'loglevel',
|
'loglevel',
|
||||||
|
|
@ -121,7 +128,7 @@ def tractor_test(
|
||||||
assert kw in kwargs
|
assert kw in kwargs
|
||||||
|
|
||||||
start_method = kwargs.get('start_method')
|
start_method = kwargs.get('start_method')
|
||||||
if platform.system() == "Windows":
|
if platform.system() == 'Windows':
|
||||||
if start_method is None:
|
if start_method is None:
|
||||||
kwargs['start_method'] = 'trio'
|
kwargs['start_method'] = 'trio'
|
||||||
elif start_method != 'trio':
|
elif start_method != 'trio':
|
||||||
|
|
@ -129,60 +136,43 @@ def tractor_test(
|
||||||
'ONLY the `start_method="trio"` is supported on Windows.'
|
'ONLY the `start_method="trio"` is supported on Windows.'
|
||||||
)
|
)
|
||||||
|
|
||||||
# open a root-actor, passing certain
|
# Open a root-actor, passing certain runtime-settings
|
||||||
# `tractor`-runtime-settings, then invoke the test-fn body as
|
# extracted from the fixture kwargs, then invoke the
|
||||||
# the root-most task.
|
# test-fn body as the root-most task.
|
||||||
#
|
#
|
||||||
# https://wrapt.readthedocs.io/en/master/decorators.html#processing-function-arguments
|
# NOTE: we use `kwargs.get()` (not named params) so that
|
||||||
async def _main(
|
# the fixture values remain in `kwargs` and are forwarded
|
||||||
*args,
|
# to `wrapped()` — the test fn may declare the same
|
||||||
|
# fixtures in its own signature.
|
||||||
# runtime-settings
|
async def _main(**kwargs):
|
||||||
loglevel:str|None = None,
|
|
||||||
reg_addr:tuple|None = None,
|
|
||||||
start_method: str|None = None,
|
|
||||||
debug_mode: bool = False,
|
|
||||||
tpt_proto: str|None = None,
|
|
||||||
|
|
||||||
**kwargs,
|
|
||||||
):
|
|
||||||
__tracebackhide__: bool = hide_tb
|
__tracebackhide__: bool = hide_tb
|
||||||
|
|
||||||
|
reg_addr = kwargs.get('reg_addr')
|
||||||
with trio.fail_after(timeout):
|
with trio.fail_after(timeout):
|
||||||
async with tractor.open_root_actor(
|
async with tractor.open_root_actor(
|
||||||
registry_addrs=[reg_addr] if reg_addr else None,
|
registry_addrs=(
|
||||||
loglevel=loglevel,
|
[reg_addr] if reg_addr else None
|
||||||
start_method=start_method,
|
),
|
||||||
|
loglevel=kwargs.get('loglevel'),
|
||||||
|
start_method=kwargs.get('start_method'),
|
||||||
|
|
||||||
# TODO: only enable when pytest is passed --pdb
|
# TODO: only enable when pytest is passed
|
||||||
debug_mode=debug_mode,
|
# --pdb
|
||||||
|
debug_mode=kwargs.get('debug_mode', False),
|
||||||
|
|
||||||
):
|
):
|
||||||
# invoke test-fn body IN THIS task
|
# invoke test-fn body IN THIS task
|
||||||
await wrapped(
|
await wrapped(**kwargs)
|
||||||
*args,
|
|
||||||
**kwargs,
|
|
||||||
)
|
|
||||||
|
|
||||||
funcname = wrapped.__name__
|
|
||||||
if not inspect.iscoroutinefunction(wrapped):
|
|
||||||
raise TypeError(
|
|
||||||
f"Test-fn {funcname!r} must be an async-function !!"
|
|
||||||
)
|
|
||||||
|
|
||||||
# invoke runtime via a root task.
|
# invoke runtime via a root task.
|
||||||
return trio.run(
|
return trio.run(
|
||||||
partial(
|
partial(
|
||||||
_main,
|
_main,
|
||||||
*args,
|
|
||||||
**kwargs,
|
**kwargs,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
return wrapper
|
||||||
return wrapper(
|
|
||||||
wrapped,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def pytest_addoption(
|
def pytest_addoption(
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue