forked from goodboy/tractor
Add tests for import-time failures
parent
06c908f285
commit
d2f0537850
|
@ -1,6 +1,8 @@
|
||||||
"""
|
"""
|
||||||
RPC related
|
RPC related
|
||||||
"""
|
"""
|
||||||
|
import itertools
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
import tractor
|
import tractor
|
||||||
import trio
|
import trio
|
||||||
|
@ -12,17 +14,20 @@ async def sleep_back_actor(
|
||||||
func_defined,
|
func_defined,
|
||||||
exposed_mods,
|
exposed_mods,
|
||||||
):
|
):
|
||||||
async with tractor.find_actor(actor_name) as portal:
|
if actor_name:
|
||||||
try:
|
async with tractor.find_actor(actor_name) as portal:
|
||||||
await portal.run(__name__, func_name)
|
try:
|
||||||
except tractor.RemoteActorError as err:
|
await portal.run(__name__, func_name)
|
||||||
if not func_defined:
|
except tractor.RemoteActorError as err:
|
||||||
expect = AttributeError
|
if not func_defined:
|
||||||
if not exposed_mods:
|
expect = AttributeError
|
||||||
expect = tractor.ModuleNotExposed
|
if not exposed_mods:
|
||||||
|
expect = tractor.ModuleNotExposed
|
||||||
|
|
||||||
assert err.type is expect
|
assert err.type is expect
|
||||||
raise
|
raise
|
||||||
|
else:
|
||||||
|
await trio.sleep(float('inf'))
|
||||||
|
|
||||||
|
|
||||||
async def short_sleep():
|
async def short_sleep():
|
||||||
|
@ -31,19 +36,40 @@ async def short_sleep():
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
'to_call', [
|
'to_call', [
|
||||||
([], 'short_sleep'),
|
([], 'short_sleep', tractor.RemoteActorError),
|
||||||
([__name__], 'short_sleep'),
|
([__name__], 'short_sleep', tractor.RemoteActorError),
|
||||||
([__name__], 'fake_func'),
|
([__name__], 'fake_func', tractor.RemoteActorError),
|
||||||
|
(['tmp_mod'], 'import doggy', ModuleNotFoundError),
|
||||||
|
(['tmp_mod'], '4doggy', SyntaxError),
|
||||||
],
|
],
|
||||||
ids=['no_mods', 'this_mod', 'this_mod_bad_func'],
|
ids=['no_mods', 'this_mod', 'this_mod_bad_func', 'fail_to_import',
|
||||||
|
'fail_on_syntax'],
|
||||||
)
|
)
|
||||||
def test_rpc_errors(arb_addr, to_call):
|
def test_rpc_errors(arb_addr, to_call, testdir):
|
||||||
"""Test errors when making various RPC requests to an actor
|
"""Test errors when making various RPC requests to an actor
|
||||||
that either doesn't have the requested module exposed or doesn't define
|
that either doesn't have the requested module exposed or doesn't define
|
||||||
the named function.
|
the named function.
|
||||||
"""
|
"""
|
||||||
exposed_mods, funcname = to_call
|
exposed_mods, funcname, inside_err = to_call
|
||||||
|
subactor_exposed_mods = []
|
||||||
func_defined = globals().get(funcname, False)
|
func_defined = globals().get(funcname, False)
|
||||||
|
subactor_requests_to = 'arbiter'
|
||||||
|
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 cause raise a ModuleNotFoundError at import
|
||||||
|
testdir.makefile('.py', tmp_mod=funcname)
|
||||||
|
|
||||||
|
# no need to exposed module to the subactor
|
||||||
|
subactor_exposed_mods = exposed_mods
|
||||||
|
exposed_mods = []
|
||||||
|
func_defined = False
|
||||||
|
# subactor should not try to invoke anything
|
||||||
|
subactor_requests_to = None
|
||||||
|
remote_err = trio.MultiError
|
||||||
|
|
||||||
async def main():
|
async def main():
|
||||||
actor = tractor.current_actor()
|
actor = tractor.current_actor()
|
||||||
|
@ -54,12 +80,13 @@ def test_rpc_errors(arb_addr, to_call):
|
||||||
await n.run_in_actor(
|
await n.run_in_actor(
|
||||||
'subactor',
|
'subactor',
|
||||||
sleep_back_actor,
|
sleep_back_actor,
|
||||||
actor_name=actor.name,
|
actor_name=subactor_requests_to,
|
||||||
# function from this module the subactor will invoke
|
# function from the local exposed module space
|
||||||
# when it RPCs back to this actor
|
# the subactor will invoke when it RPCs back to this actor
|
||||||
func_name=funcname,
|
func_name=funcname,
|
||||||
exposed_mods=exposed_mods,
|
exposed_mods=exposed_mods,
|
||||||
func_defined=True if func_defined else False,
|
func_defined=True if func_defined else False,
|
||||||
|
rpc_module_paths=subactor_exposed_mods,
|
||||||
)
|
)
|
||||||
|
|
||||||
def run():
|
def run():
|
||||||
|
@ -73,8 +100,18 @@ def test_rpc_errors(arb_addr, to_call):
|
||||||
if exposed_mods and func_defined:
|
if exposed_mods and func_defined:
|
||||||
run()
|
run()
|
||||||
else:
|
else:
|
||||||
# underlying errors are propogated upwards (yet)
|
# underlying errors are propagated upwards (yet)
|
||||||
with pytest.raises(tractor.RemoteActorError) as err:
|
with pytest.raises(remote_err) as err:
|
||||||
run()
|
run()
|
||||||
|
|
||||||
assert err.value.type is tractor.RemoteActorError
|
# 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
|
||||||
|
))
|
||||||
|
|
||||||
|
assert value.type is inside_err
|
||||||
|
|
Loading…
Reference in New Issue