Share actor-context test helpers
The context and one-shot suites duplicated cancellation file markers and filtering of registrar-owned runtime contexts. Move those mechanics into `tests._helpers` while retaining each endpoint's distinct startup handshake. Also update the startup-cancel `Channel.send()` mock to accept and forward the new `send_deadline` arg. Caught-during: review remediation Found-via: `/run-tests` test_cancel_during_context_startup[trio] Review: PR #481 (goodboy) https://github.com/goodboy/tractor/pull/481#pullrequestreview-5012942328 (this patch was generated in some part by `opencode` using `gpt-5.6-sol` (`openai`))wkt/to_actor_subpkg
parent
617ca1de43
commit
0a580df63d
|
|
@ -0,0 +1,50 @@
|
|||
'''
|
||||
Shared helpers for actor-runtime test suites.
|
||||
|
||||
'''
|
||||
from pathlib import Path
|
||||
from types import TracebackType
|
||||
|
||||
import tractor
|
||||
|
||||
|
||||
class CancellationMarkers:
|
||||
'''
|
||||
Mark a test endpoint's lifetime without cleanup checkpoints.
|
||||
|
||||
'''
|
||||
def __init__(
|
||||
self,
|
||||
started_path: str,
|
||||
cancelled_path: str,
|
||||
) -> None:
|
||||
self.started_path = started_path
|
||||
self.cancelled_path = cancelled_path
|
||||
|
||||
def __enter__(self) -> None:
|
||||
Path(self.started_path).touch()
|
||||
|
||||
def __exit__(
|
||||
self,
|
||||
exc_type: type[BaseException]|None,
|
||||
exc_value: BaseException|None,
|
||||
traceback: TracebackType|None,
|
||||
) -> None:
|
||||
Path(self.cancelled_path).touch()
|
||||
|
||||
|
||||
def non_registration_contexts(
|
||||
actor: tractor.Actor,
|
||||
) -> dict[tuple, str]:
|
||||
'''
|
||||
Snapshot application contexts without registrar-service traffic.
|
||||
|
||||
'''
|
||||
return {
|
||||
key: str(ctx._nsf)
|
||||
for key, ctx in actor._contexts.items()
|
||||
if str(ctx._nsf) != (
|
||||
'tractor.discovery._registry:'
|
||||
'Registrar.register_actor'
|
||||
)
|
||||
}
|
||||
|
|
@ -40,6 +40,11 @@ from tractor._testing import (
|
|||
expect_ctxc,
|
||||
)
|
||||
|
||||
from ._helpers import (
|
||||
CancellationMarkers,
|
||||
non_registration_contexts,
|
||||
)
|
||||
|
||||
# ``Context`` semantics are as follows,
|
||||
# ------------------------------------
|
||||
|
||||
|
|
@ -164,31 +169,18 @@ def test_overrun_error_send_tolerates_transport_close(
|
|||
_state: bool = False
|
||||
|
||||
|
||||
def _non_registration_contexts(
|
||||
actor: Actor,
|
||||
) -> dict[tuple, str]:
|
||||
return {
|
||||
key: str(ctx._nsf)
|
||||
for key, ctx in actor._contexts.items()
|
||||
if str(ctx._nsf) != (
|
||||
'tractor.discovery._registry:'
|
||||
'Registrar.register_actor'
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@tractor.context
|
||||
async def startup_cancel_target(
|
||||
ctx: Context,
|
||||
started_path: str,
|
||||
cancelled_path: str,
|
||||
) -> None:
|
||||
Path(started_path).touch()
|
||||
try:
|
||||
with CancellationMarkers(
|
||||
started_path,
|
||||
cancelled_path,
|
||||
):
|
||||
await ctx.started()
|
||||
await trio.sleep_forever()
|
||||
finally:
|
||||
Path(cancelled_path).touch()
|
||||
|
||||
|
||||
async def return_one() -> int:
|
||||
|
|
@ -317,11 +309,13 @@ async def test_cancel_during_context_startup(
|
|||
chan: tractor.Channel,
|
||||
payload: object,
|
||||
hide_tb: bool = False,
|
||||
send_deadline: float = float('inf'),
|
||||
) -> None:
|
||||
await original_send(
|
||||
chan,
|
||||
payload,
|
||||
hide_tb=hide_tb,
|
||||
send_deadline=send_deadline,
|
||||
)
|
||||
if isinstance(payload, tractor.msg.Start):
|
||||
if payload.func == 'startup_cancel_target':
|
||||
|
|
@ -344,7 +338,7 @@ async def test_cancel_during_context_startup(
|
|||
'startup_cancel_worker',
|
||||
enable_modules=[__name__],
|
||||
)
|
||||
contexts_before = _non_registration_contexts(actor)
|
||||
contexts_before = non_registration_contexts(actor)
|
||||
monkeypatch.setattr(
|
||||
tractor.Channel,
|
||||
'send',
|
||||
|
|
@ -365,12 +359,12 @@ async def test_cancel_during_context_startup(
|
|||
original_send,
|
||||
)
|
||||
assert cancelled_path.exists()
|
||||
assert _non_registration_contexts(actor) == contexts_before
|
||||
assert non_registration_contexts(actor) == contexts_before
|
||||
assert await portal.run_from_ns(
|
||||
__name__,
|
||||
'return_one',
|
||||
) == 1
|
||||
assert _non_registration_contexts(actor) == contexts_before
|
||||
assert non_registration_contexts(actor) == contexts_before
|
||||
await portal.cancel_actor()
|
||||
|
||||
|
||||
|
|
@ -396,7 +390,7 @@ async def test_start_serialization_error_cleans_context(
|
|||
'serialization_error_worker',
|
||||
enable_modules=[__name__],
|
||||
)
|
||||
contexts_before = _non_registration_contexts(actor)
|
||||
contexts_before = non_registration_contexts(actor)
|
||||
with pytest.raises(tractor.MsgTypeError):
|
||||
async with portal.open_context(
|
||||
simple_setup_teardown,
|
||||
|
|
@ -404,7 +398,7 @@ async def test_start_serialization_error_cleans_context(
|
|||
):
|
||||
raise AssertionError('invalid `Start` was accepted')
|
||||
|
||||
assert _non_registration_contexts(actor) == contexts_before
|
||||
assert non_registration_contexts(actor) == contexts_before
|
||||
async with portal.open_context(
|
||||
simple_setup_teardown,
|
||||
data=1,
|
||||
|
|
@ -412,7 +406,7 @@ async def test_start_serialization_error_cleans_context(
|
|||
assert started == 2
|
||||
assert await ctx.wait_for_result() == 'yo'
|
||||
|
||||
assert _non_registration_contexts(actor) == contexts_before
|
||||
assert non_registration_contexts(actor) == contexts_before
|
||||
await portal.cancel_actor()
|
||||
|
||||
|
||||
|
|
@ -437,7 +431,7 @@ async def test_start_module_error_cleans_context(
|
|||
portal: tractor.Portal = await an.start_actor(
|
||||
'module_error_worker',
|
||||
)
|
||||
contexts_before = _non_registration_contexts(actor)
|
||||
contexts_before = non_registration_contexts(actor)
|
||||
with pytest.raises(tractor.RemoteActorError) as excinfo:
|
||||
async with portal.open_context(
|
||||
simple_setup_teardown,
|
||||
|
|
@ -446,7 +440,7 @@ async def test_start_module_error_cleans_context(
|
|||
raise AssertionError('unexposed context was started')
|
||||
|
||||
assert excinfo.value.boxed_type is tractor.ModuleNotExposed
|
||||
assert _non_registration_contexts(actor) == contexts_before
|
||||
assert non_registration_contexts(actor) == contexts_before
|
||||
await portal.cancel_actor()
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -25,6 +25,11 @@ from tractor.msg.ptr import NamespacePath
|
|||
from tractor.spawn import _mp as mp_spawn
|
||||
from tractor.to_actor import _api as to_actor_api
|
||||
|
||||
from ._helpers import (
|
||||
CancellationMarkers,
|
||||
non_registration_contexts,
|
||||
)
|
||||
|
||||
|
||||
async def add_one(
|
||||
n: int,
|
||||
|
|
@ -58,11 +63,11 @@ async def mark_task_cancellation(
|
|||
started_path: str,
|
||||
cancelled_path: str,
|
||||
) -> None:
|
||||
Path(started_path).touch()
|
||||
try:
|
||||
with CancellationMarkers(
|
||||
started_path,
|
||||
cancelled_path,
|
||||
):
|
||||
await trio.sleep_forever()
|
||||
finally:
|
||||
Path(cancelled_path).touch()
|
||||
|
||||
|
||||
async def echo_startup_control(
|
||||
|
|
@ -84,19 +89,6 @@ async def collect_call(
|
|||
return args, kwargs
|
||||
|
||||
|
||||
def _non_registration_contexts(
|
||||
actor: tractor.Actor,
|
||||
) -> dict[tuple, str]:
|
||||
return {
|
||||
key: str(ctx._nsf)
|
||||
for key, ctx in actor._contexts.items()
|
||||
if str(ctx._nsf) != (
|
||||
'tractor.discovery._registry:'
|
||||
'Registrar.register_actor'
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
def test_namespace_path_retains_target_ref(
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
):
|
||||
|
|
@ -588,7 +580,7 @@ async def test_reuse_existing_actor_via_portal(
|
|||
to_actor.MODULE,
|
||||
],
|
||||
)
|
||||
contexts_before = _non_registration_contexts(actor)
|
||||
contexts_before = non_registration_contexts(actor)
|
||||
for i in range(3):
|
||||
assert await to_actor.run(
|
||||
add_one,
|
||||
|
|
@ -601,7 +593,7 @@ async def test_reuse_existing_actor_via_portal(
|
|||
'echo_startup_control',
|
||||
_cancel_on_startup='target_value',
|
||||
) == 'target_value'
|
||||
assert _non_registration_contexts(actor) == contexts_before
|
||||
assert non_registration_contexts(actor) == contexts_before
|
||||
|
||||
# still alive: caller owns the actor's lifetime.
|
||||
await portal.cancel_actor()
|
||||
|
|
@ -887,7 +879,7 @@ async def test_portal_task_cancelled_with_local_caller(
|
|||
to_actor.MODULE,
|
||||
],
|
||||
)
|
||||
contexts_before = _non_registration_contexts(actor)
|
||||
contexts_before = non_registration_contexts(actor)
|
||||
|
||||
async with trio.open_nursery() as tn:
|
||||
tn.start_soon(
|
||||
|
|
@ -905,13 +897,13 @@ async def test_portal_task_cancelled_with_local_caller(
|
|||
tn.cancel_scope.cancel()
|
||||
|
||||
assert cancelled_path.exists()
|
||||
assert _non_registration_contexts(actor) == contexts_before
|
||||
assert non_registration_contexts(actor) == contexts_before
|
||||
assert await to_actor.run(
|
||||
add_one,
|
||||
1,
|
||||
portal=portal,
|
||||
) == 2
|
||||
assert _non_registration_contexts(actor) == contexts_before
|
||||
assert non_registration_contexts(actor) == contexts_before
|
||||
|
||||
await portal.cancel_actor()
|
||||
|
||||
|
|
@ -937,7 +929,7 @@ async def test_context_trampoline_preserves_module_allowlist(
|
|||
'restricted_context_worker',
|
||||
enable_modules=[to_actor.MODULE],
|
||||
)
|
||||
contexts_before = _non_registration_contexts(actor)
|
||||
contexts_before = non_registration_contexts(actor)
|
||||
with pytest.raises(RemoteActorError) as excinfo:
|
||||
await to_actor.run(
|
||||
add_one,
|
||||
|
|
@ -946,7 +938,7 @@ async def test_context_trampoline_preserves_module_allowlist(
|
|||
)
|
||||
|
||||
assert excinfo.value.boxed_type is tractor.ModuleNotExposed
|
||||
assert _non_registration_contexts(actor) == contexts_before
|
||||
assert non_registration_contexts(actor) == contexts_before
|
||||
await portal.cancel_actor()
|
||||
|
||||
|
||||
|
|
@ -970,7 +962,7 @@ async def test_portal_requires_context_trampoline(
|
|||
'no_context_trampoline_worker',
|
||||
enable_modules=[__name__],
|
||||
)
|
||||
contexts_before = _non_registration_contexts(actor)
|
||||
contexts_before = non_registration_contexts(actor)
|
||||
with pytest.raises(RemoteActorError) as excinfo:
|
||||
await to_actor.run(
|
||||
add_one,
|
||||
|
|
@ -981,5 +973,5 @@ async def test_portal_requires_context_trampoline(
|
|||
err = excinfo.value
|
||||
assert err.boxed_type is tractor.ModuleNotExposed
|
||||
assert to_actor.MODULE in str(err)
|
||||
assert _non_registration_contexts(actor) == contexts_before
|
||||
assert non_registration_contexts(actor) == contexts_before
|
||||
await portal.cancel_actor()
|
||||
|
|
|
|||
Loading…
Reference in New Issue