forked from goodboy/tractor
1
0
Fork 0
tractor/tests/test_rpc.py

145 lines
4.3 KiB
Python
Raw Permalink Normal View History

2019-01-01 21:12:29 +00:00
"""
RPC related
"""
2019-01-12 22:56:39 +00:00
import itertools
2019-01-01 21:12:29 +00:00
import pytest
import tractor
import trio
async def sleep_back_actor(
actor_name,
func_name,
func_defined,
exposed_mods,
*,
reg_addr: tuple,
2019-01-01 21:12:29 +00:00
):
2019-01-12 22:56:39 +00:00
if actor_name:
async with tractor.find_actor(
actor_name,
# NOTE: must be set manually since
# the subactor doesn't have the reg_addr
# fixture code run in it!
# TODO: maybe we should just set this once in the
# _state mod and derive to all children?
registry_addrs=[reg_addr],
) as portal:
2019-01-12 22:56:39 +00:00
try:
await portal.run(__name__, func_name)
except tractor.RemoteActorError as err:
if not func_defined:
expect = AttributeError
if not exposed_mods:
expect = tractor.ModuleNotExposed
2019-01-01 21:12:29 +00:00
2019-01-12 22:56:39 +00:00
assert err.type is expect
raise
else:
await trio.sleep(float('inf'))
2019-01-01 21:12:29 +00:00
async def short_sleep():
await trio.sleep(0)
@pytest.mark.parametrize(
'to_call', [
2019-01-12 22:56:39 +00:00
([], 'short_sleep', tractor.RemoteActorError),
([__name__], 'short_sleep', tractor.RemoteActorError),
([__name__], 'fake_func', tractor.RemoteActorError),
(['tmp_mod'], 'import doggy', ModuleNotFoundError),
(['tmp_mod'], '4doggy', SyntaxError),
2019-01-01 21:12:29 +00:00
],
2019-01-12 22:56:39 +00:00
ids=['no_mods', 'this_mod', 'this_mod_bad_func', 'fail_to_import',
'fail_on_syntax'],
2019-01-01 21:12:29 +00:00
)
def test_rpc_errors(
reg_addr,
to_call,
testdir,
):
'''
Test errors when making various RPC requests to an actor
2019-01-01 21:12:29 +00:00
that either doesn't have the requested module exposed or doesn't define
the named function.
'''
2019-01-12 22:56:39 +00:00
exposed_mods, funcname, inside_err = to_call
subactor_exposed_mods = []
2019-01-01 21:12:29 +00:00
func_defined = globals().get(funcname, False)
subactor_requests_to = 'root'
2019-01-12 22:56:39 +00:00
remote_err = tractor.RemoteActorError
# remote module that fails at import time
if exposed_mods == ['tmp_mod']:
# create an importable module with a bad import
testdir.syspathinsert()
# module should raise a ModuleNotFoundError at import
2019-01-12 22:56:39 +00:00
testdir.makefile('.py', tmp_mod=funcname)
# no need to expose module to the subactor
2019-01-12 22:56:39 +00:00
subactor_exposed_mods = exposed_mods
exposed_mods = []
func_defined = False
# subactor should not try to invoke anything
subactor_requests_to = None
# the module will be attempted to be imported locally but will
# fail in the initial local instance of the actor
remote_err = inside_err
2019-01-01 21:12:29 +00:00
async def main():
# spawn a subactor which calls us back
async with tractor.open_nursery(
registry_addrs=[reg_addr],
enable_modules=exposed_mods.copy(),
# NOTE: will halt test in REPL if uncommented, so only
# do that if actually debugging subactor but keep it
# disabled for the test.
# debug_mode=True,
) as n:
actor = tractor.current_actor()
assert actor.is_arbiter
2019-01-01 21:12:29 +00:00
await n.run_in_actor(
sleep_back_actor,
2019-01-12 22:56:39 +00:00
actor_name=subactor_requests_to,
name='subactor',
2019-01-12 22:56:39 +00:00
# function from the local exposed module space
# the subactor will invoke when it RPCs back to this actor
2019-01-01 21:12:29 +00:00
func_name=funcname,
exposed_mods=exposed_mods,
func_defined=True if func_defined else False,
2021-02-24 23:46:33 +00:00
enable_modules=subactor_exposed_mods,
reg_addr=reg_addr,
2019-01-01 21:12:29 +00:00
)
def run():
trio.run(main)
2019-01-01 21:12:29 +00:00
# handle both parameterized cases
if exposed_mods and func_defined:
run()
else:
# underlying errors aren't propagated upwards (yet)
2019-01-12 22:56:39 +00:00
with pytest.raises(remote_err) as err:
2019-01-01 21:12:29 +00:00
run()
2019-01-12 22:56:39 +00:00
# get raw instance from pytest wrapper
value = err.value
# might get multiple `trio.Cancelled`s as well inside an inception
if isinstance(value, trio.MultiError):
value = next(itertools.dropwhile(
lambda exc: not isinstance(exc, tractor.RemoteActorError),
value.exceptions
))
if getattr(value, 'type', None):
assert value.type is inside_err