Simplify actor sub-class declaration

- Make actor ref generic classes referencing to actors
 - Create actor ref interface directly with actor methods
 - Create a BaseActor abstract class for remote actors
 - Add ActorManager class
 - Implement the logic that allow to use a specialized
   Fixture manager for Actors

Change-Id: Idba2b903c1259dc924dd4d83fd7773c79bbb1e12
This commit is contained in:
Federico Ressi 2022-03-03 10:53:48 +01:00
parent 5e7ab1f6cf
commit 5177c1239e
10 changed files with 312 additions and 227 deletions

View File

@ -22,8 +22,8 @@ from tobiko.common import _deprecation
from tobiko.common import _detail
from tobiko.common import _exception
from tobiko.common import _fixture
from tobiko.common import _loader
from tobiko.common import _logging
from tobiko.common.managers import loader as loader_manager
from tobiko.common import _operation
from tobiko.common import _os
from tobiko.common import _retry
@ -84,8 +84,8 @@ RequiredFixture = _fixture.RequiredFixture
CaptureLogFixture = _logging.CaptureLogFixture
load_object = loader_manager.load_object
load_module = loader_manager.load_module
load_object = _loader.load_object
load_module = _loader.load_module
makedirs = _os.makedirs
open_output_file = _os.open_output_file

View File

@ -16,17 +16,19 @@
from __future__ import absolute_import
from tobiko.actors import _actor
from tobiko.actors import _manager
from tobiko.actors import _proxy
actor_method = _actor.actor_method
Actor = _actor.Actor
ActorRef = _actor.ActorRef
cleanup_actor = _manager.cleanup_actor
setup_actor = _manager.setup_actor
start_actor = _manager.start_actor
stop_actor = _manager.stop_actor
call_proxy = _proxy.create_call_proxy
call_proxy_class = _proxy.create_call_proxy_class
CallProxy = _proxy.CallProxy
CallProxyBase = _proxy.CallProxyBase
actor_method = _actor.actor_method
cleanup_actor = _actor.cleanup_actor
setup_actor = _actor.setup_actor
start_actor = _actor.start_actor
stop_actor = _actor.stop_actor
Actor = _actor.Actor
ActorRef = _actor.ActorRef

View File

@ -29,31 +29,52 @@ from tobiko.actors import _proxy
from tobiko.actors import _request
P = typing.TypeVar('P', bound=abc.ABC)
A = typing.TypeVar('A', bound='ActorBase')
class ActorRef(_proxy.CallProxyBase, typing.Generic[P], abc.ABC):
class ActorRef(_proxy.CallProxyBase, _proxy.Generic[A]):
def __init__(self, actor_id: str,
requests: _request.ActorRequestQueue):
_actor_class: typing.Type[A]
def __class_getitem__(cls, item):
# pylint: disable=too-many-function-args
if isinstance(item, type):
if issubclass(item, ActorBase):
actor_class: typing.Type[ActorBase] = item
base_name = cls.__name__.split('[', 1)[0]
ref_class = _proxy.create_call_proxy_class(
protocols=(actor_class,),
class_name=f"{base_name}[{actor_class.__name__}]",
bases=(cls,),
namespace=dict(_actor_class=item),
predicate=is_actor_method)
ref_class.__module__ = cls.__module__
return ref_class
else:
raise TypeError(f'{item} is not subclass of {ActorBase}')
ref_class = cls
getitem = getattr(super(), '__class_getitem__')
if callable(getitem):
if inspect.ismethod(getitem):
ref_class = getitem(item)
else:
ref_class = getitem(cls, item)
return ref_class
def __init__(self, actor: A):
super().__init__()
self.actor_id = actor_id
self._requests = requests
def send_request(self, method: str, **arguments):
return self._requests.send_request(actor_id=self.actor_id,
method=method,
arguments=arguments)
self._actor = tobiko.check_valid_type(actor, self._actor_class)
def _handle_call(self, method: typing.Callable, *args, **kwargs) \
-> asyncio.Future:
arguments = inspect.signature(method).bind(
None, *args, **kwargs).arguments
arguments.pop('self', None)
return self.send_request(method.__name__, **arguments)
return self._actor.send_request(method.__name__, **arguments)
def ping_actor(self, data: typing.Any = None) -> typing.Any:
return self.send_request(method='ping_actor', data=data)
def __repr__(self):
return f'{type(self).__name__}({self._actor})'
def is_actor_method(obj):
@ -67,6 +88,9 @@ def actor_method(obj):
if not inspect.iscoroutinefunction(obj):
raise TypeError(f"Actor method {obj} is not async")
if not _proxy.is_public_function(obj):
raise TypeError(f"Actor method name {obj} can't start with '_'")
name = getattr(obj, '__name__', None)
if name is None or hasattr(ActorRef, name) or hasattr(Actor, name):
raise TypeError(f"Invalid method name: '{name}'")
@ -79,77 +103,111 @@ class _DummyActorProtocol(abc.ABC):
pass
class Actor(tobiko.SharedFixture, typing.Generic[P],
metaclass=_proxy.GenericMeta):
class ActorBase(tobiko.SharedFixture):
max_queue_size: int = 0
_actor_protocol = _DummyActorProtocol
_cancel_actor = False
_run_actor_task: asyncio.Task
# Class methods ----------------------------------------------------------
def __init_subclass__(cls,
*args,
**kwargs):
super().__init_subclass__(*args, **kwargs)
cls._actor_methods = dict(inspect.getmembers(cls, is_actor_method))
cls._actor_ref_class: typing.Type[ActorRef[P]] = (
ActorRef[cls._actor_protocol])
def __class_getitem__(cls, item: typing.Type[P]):
if isinstance(item, type):
return type(cls.__name__, (cls, item), dict(_actor_protocol=item))
else:
return cls
# Public instance methods ------------------------------------------------
cls._actor_ref_class = ActorRef[cls]
def __init__(self,
actor_id: str = None,
event_loop: asyncio.AbstractEventLoop = None,
loop: asyncio.AbstractEventLoop = None,
log: logging.LoggerAdapter = None,
requests: _request.ActorRequestQueue = None,
ref: ActorRef[P] = None):
requests: _request.ActorRequestQueue = None):
# pylint: disable=redefined-outer-name
super().__init__()
if actor_id is None:
actor_id = self._init_actor_id()
self.actor_id = actor_id
if event_loop is None:
event_loop = self._init_event_loop()
self.event_loop = event_loop
if log is None:
log = self._init_log()
self.log = log
if loop is None:
loop = self._init_loop()
self.loop = loop
if requests is None:
requests = self._init_actor_request_queue()
requests = self._init_requests()
self.requests = requests
self.setup_actor_future = self.loop.create_future()
self.cleanup_actor_future = self.loop.create_future()
self.cleanup_actor_future.set_result(None)
if ref is None:
ref = self._init_actor_ref()
self.ref = typing.cast(P, ref)
self.setup_future = event_loop.create_future()
self.cleanup_future = event_loop.create_future()
def setup_fixture(self):
if self.actor_id is None:
self.actor_id = self._setup_actor_id()
if self.setup_actor_future.done():
self.setup_actor_future.cancel()
self.setup_actor_future = self.loop.create_future()
if self.cleanup_actor_future.done():
self.cleanup_actor_future.cancel()
self.cleanup_actor_future = self.loop.create_future()
@property
def ref(self) -> 'ActorRef':
return self._actor_ref_class(actor=self)
@property
def actor_name(self) -> str:
return tobiko.get_fixture_name(self)
def send_request(self, method: str, **arguments) -> asyncio.Future:
if self.actor_id is None:
raise ValueError("Actor not set up yet")
return self.requests.send_request(actor_id=self.actor_id,
method=method,
arguments=arguments)
# Private instance methods -----------------------------------------------
def _init_log(self):
return log.getLogger(self.actor_name)
@staticmethod
def _init_loop() -> asyncio.AbstractEventLoop:
return asyncio.get_event_loop()
def _init_requests(self) -> _request.ActorRequestQueue:
return _request.create_request_queue(max_size=self.max_queue_size,
loop=self.loop)
@staticmethod
def _setup_actor_id() -> str:
return str(uuid.uuid4())
class Actor(ActorBase):
_stop_actor = False
_run_actor_task: asyncio.Task
# Class methods ----------------------------------------------------------
def __init_subclass__(cls, *args, **kwargs):
super().__init_subclass__(*args, **kwargs)
cls._actor_methods = dict(inspect.getmembers(cls, is_actor_method))
@classmethod
def get_fixture_manager(cls) -> tobiko.FixtureManager:
from tobiko.actors import _manager
return _manager.actor_manager()
# Public methods ---------------------------------------------------------
def setup_fixture(self):
self.setup_future.cancel()
self.setup_future = self.event_loop.create_future()
self._run_actor_task = self.event_loop.create_task(
super().setup_fixture()
self._run_actor_task = self.loop.create_task(
self._run_actor())
def cleanup_fixture(self):
self.cleanup_future.cancel()
self.cleanup_future = self.event_loop.create_future()
self._cancel_actor = True
self.ref.ping_actor('cleanup') # must weak up the actor with a message
super().cleanup_fixture()
self._stop_actor = True
if hasattr(self, '_run_actor_task'):
if not self._run_actor_task.done():
# must weak up the actor with a message
self.ref.ping_actor('cleanup')
async def setup_actor(self):
pass
@ -157,51 +215,20 @@ class Actor(tobiko.SharedFixture, typing.Generic[P],
async def cleanup_actor(self):
pass
async def on_request_error(
self, request: typing.Optional[_request.ActorRequest]):
pass
async def on_cleanup_error(self):
pass
async def ping_actor(self, data: typing.Any = None) -> typing.Any:
return data
ping_actor.__tobiko_actor_method__ = True # type: ignore[attr-defined]
# Private instance methods -----------------------------------------------
@staticmethod
def _init_actor_id() -> str:
return str(uuid.uuid4())
def _init_log(self):
return log.getLogger(self.actor_name)
@staticmethod
def _init_event_loop() -> asyncio.AbstractEventLoop:
return asyncio.get_event_loop()
def _init_actor_request_queue(self) -> _request.ActorRequestQueue:
return _request.create_request_queue(max_size=self.max_queue_size,
loop=self.event_loop)
def _init_actor_ref(self) -> ActorRef[P]:
return self._actor_ref_class(actor_id=self.actor_id,
requests=self.requests)
# Private methods --------------------------------------------------------
async def _run_actor(self):
try:
await self._setup_actor()
self._cancel_actor = False
while not self._cancel_actor:
request = None
try:
request = await self.requests.receive_request()
await self._receive_request(request)
except Exception:
await self.on_request_error(request=request)
finally:
with tobiko.exc_info(reraise=True):
await self._cleanup_actor()
await self._setup_actor()
self._stop_actor = False
while not self._stop_actor:
request = await self.requests.receive_request()
await self._receive_request(request)
with tobiko.exc_info(reraise=True):
await self._cleanup_actor()
async def _setup_actor(self):
try:
@ -211,9 +238,9 @@ class Actor(tobiko.SharedFixture, typing.Generic[P],
self.log.exception(
f'Failed Setting up actor: {self.actor_name} '
f'({self.actor_id})')
self.setup_future.set_exception(ex)
self.setup_actor_future.set_exception(ex)
else:
self.setup_future.set_result(self.ref)
self.setup_actor_future.set_result(None)
self.log.debug(f'Actor setup succeeded: {self.actor_name} '
f'({self.actor_id}).')
@ -223,12 +250,12 @@ class Actor(tobiko.SharedFixture, typing.Generic[P],
f'({self.actor_id}).')
await self.cleanup_actor()
except Exception as ex:
self.cleanup_future.set_exception(ex)
self.cleanup_actor_future.set_exception(ex)
self.log.exception(
f'Actor cleanup failed: {self.actor_name} '
f'({self.actor_id}).')
else:
self.cleanup_future.set_result(self.ref)
self.cleanup_actor_future.set_result(None)
self.log.debug(f'Actor cleanup succeeded: {self.actor_name} '
f'({self.actor_id}).')
finally:
@ -252,46 +279,3 @@ class Actor(tobiko.SharedFixture, typing.Generic[P],
if method is None:
raise ValueError(f"Invalid request method name: {name}")
return method
ActorType = typing.Union[Actor[P], typing.Type[Actor[P]]]
def start_actor(obj: ActorType,
fixture_id: typing.Optional[str] = None,
manager=None) -> ActorRef[P]:
return tobiko.setup_fixture(obj,
fixture_id=fixture_id,
manager=manager).ref
async def setup_actor(obj: ActorType,
fixture_id: typing.Optional[str] = None,
manager=None,
timeout: tobiko.Seconds = None) -> ActorRef[P]:
actor = tobiko.setup_fixture(obj,
fixture_id=fixture_id,
manager=manager)
await asyncio.wait_for(actor.setup_future,
timeout=timeout)
return actor.ref
async def stop_actor(obj: ActorType,
fixture_id: typing.Optional[str] = None,
manager=None) -> ActorRef[P]:
return tobiko.cleanup_fixture(obj,
fixture_id=fixture_id,
manager=manager).ref
async def cleanup_actor(obj: ActorType,
fixture_id: typing.Optional[str] = None,
manager=None,
timeout: tobiko.Seconds = None) -> ActorRef[P]:
actor = tobiko.cleanup_fixture(obj,
fixture_id=fixture_id,
manager=manager)
await asyncio.wait_for(actor.cleanup_future,
timeout=timeout)
return actor.ref

76
tobiko/actors/_manager.py Normal file
View File

@ -0,0 +1,76 @@
# Copyright 2022 Red Hat
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from __future__ import absolute_import
import asyncio
import typing
import tobiko
from tobiko.actors import _actor
A = typing.TypeVar('A', bound=_actor.ActorBase)
class ActorManager(tobiko.SharedFixture, tobiko.FixtureManager):
pass
def actor_manager(obj: ActorManager = None) -> ActorManager:
if obj is None:
return tobiko.get_fixture(ActorManager)
return tobiko.check_valid_type(obj, ActorManager)
ActorType = typing.Union[A, typing.Type[A]]
def start_actor(obj: ActorType,
fixture_id: typing.Optional[str] = None,
manager: ActorManager = None) -> _actor.ActorRef[A]:
return tobiko.setup_fixture(obj,
fixture_id=fixture_id,
manager=manager).ref
async def setup_actor(obj: ActorType,
fixture_id: typing.Optional[str] = None,
manager=None,
timeout: tobiko.Seconds = None) -> _actor.ActorRef[A]:
actor = tobiko.setup_fixture(obj,
fixture_id=fixture_id,
manager=manager)
await asyncio.wait_for(actor.setup_actor_future,
timeout=timeout)
return actor.ref
async def stop_actor(obj: ActorType,
fixture_id: typing.Optional[str] = None,
manager=None) -> _actor.ActorRef[A]:
return tobiko.cleanup_fixture(obj,
fixture_id=fixture_id,
manager=manager).ref
async def cleanup_actor(obj: ActorType,
fixture_id: typing.Optional[str] = None,
manager=None,
timeout: tobiko.Seconds = None) -> _actor.ActorRef[A]:
actor = tobiko.cleanup_fixture(obj,
fixture_id=fixture_id,
manager=manager)
await asyncio.wait_for(actor.cleanup_actor_future,
timeout=timeout)
return actor.ref

View File

@ -14,15 +14,15 @@
from __future__ import absolute_import
import abc
import functools
import inspect
import sys
import types
import typing
import decorator
P = typing.TypeVar('P', bound=abc.ABC)
P = typing.TypeVar('P')
GenericMetaBase = abc.ABCMeta
if hasattr(typing, 'GenericMeta'):
@ -34,6 +34,8 @@ if hasattr(typing, 'GenericMeta'):
class GenericMeta(GenericMetaBase):
@functools.lru_cache(maxsize=4096)
def __getitem__(self, item):
# pylint: disable=not-callable
cls = self
@ -49,11 +51,23 @@ class GenericMeta(GenericMetaBase):
return cls
def is_public_function(obj):
class Generic(typing.Generic[P], metaclass=GenericMeta):
pass
PredicateFunction = typing.Callable[[typing.Any], bool]
def is_public_function(obj) -> bool:
return (inspect.isfunction(obj) and
getattr(obj, '__name__', '_')[0] != '_')
def is_public_abstract_method(obj) -> bool:
return (is_public_function(obj) and
getattr(obj, "__isabstractmethod__", False))
class CallHandler(abc.ABC):
@abc.abstractmethod
@ -62,29 +76,26 @@ class CallHandler(abc.ABC):
raise NotImplementedError
class CallProxyBase(CallHandler, typing.Generic[P], abc.ABC,
metaclass=GenericMeta):
class CallProxyBase(CallHandler, Generic[P], abc.ABC):
def __class_getitem__(cls, item: typing.Type[P]):
if isinstance(item, type):
return create_call_proxy_class(protocols=(item,),
class_name=cls.__name__,
bases=(cls,))
bases=(cls, item),
predicate=cls._is_proxy_method)
else:
return cls
def __init_subclass__(cls,
*args,
**kwargs):
super().__init_subclass__(*args, **kwargs)
# On python < 3.8 must ensure __class_getitem__ is there
if sys.version_info < (3, 8):
cls.__class_getitem__ = CallProxyBase.__class_getitem__
@classmethod
def _is_proxy_method(cls, obj) -> bool:
return is_public_abstract_method(obj)
class CallProxy(CallProxyBase, typing.Generic[P]):
class CallProxy(CallProxyBase, Generic[P]):
def __init__(self, handle_call: typing.Callable):
super(CallProxy, self).__init__()
assert callable(handle_call)
self._handle_call = handle_call # type: ignore
@ -93,33 +104,35 @@ class CallProxy(CallProxyBase, typing.Generic[P]):
def create_call_proxy_class(
protocols: typing.Tuple[typing.Type[P], ...],
protocols: typing.Tuple[type, ...],
class_name: str,
bases: typing.Tuple[typing.Type, ...] = None,
namespace: dict = None) -> typing.Type[P]:
bases: typing.Tuple[type, ...] = None,
namespace: dict = None,
predicate: PredicateFunction = None) -> type:
if bases is None:
bases = tuple()
if predicate is None:
predicate = is_public_abstract_method
def exec_body(ns: typing.Dict[str, typing.Any]):
if namespace is not None:
ns.update(namespace)
for cls in protocols:
for member_name, member in list_abstract_methods(cls):
if member_name not in ns and is_public_function(member):
for member_name, member in inspect.getmembers(cls, predicate):
if member_name not in ns:
method = create_call_proxy_method(member)
ns[member_name] = method
proxy_class = types.new_class(name=class_name,
bases=bases + protocols,
exec_body=exec_body)
return typing.cast(typing.Type[P], proxy_class)
return types.new_class(name=class_name,
bases=bases,
exec_body=exec_body)
def create_call_proxy(handle_call: typing.Callable,
*protocols: typing.Type[P]) -> P:
cls = create_call_proxy_class(protocols=protocols,
class_name='CallProxy',
bases=(CallProxy,))
bases=(CallProxy,) + protocols)
return cls(handle_call) # type: ignore[call-arg]
@ -135,10 +148,9 @@ def list_abstract_classes(cls: typing.Type) \
def list_abstract_methods(cls: typing.Type) \
-> typing.List[typing.Tuple[str, typing.Callable]]:
methods: typing.List[typing.Tuple[str, typing.Callable]] = []
if inspect.isabstract(cls):
for name, member in inspect.getmembers(cls, inspect.isfunction):
if getattr(member, "__isabstractmethod__", False):
methods.append((name, member))
for name, member in inspect.getmembers(cls, inspect.isfunction):
if getattr(member, "__isabstractmethod__", False):
methods.append((name, member))
return methods

View File

@ -23,10 +23,10 @@ import fixtures
from oslo_log import log
import testtools
import tobiko
from tobiko.common import _detail
from tobiko.common import _exception
from tobiko.common import _testcase
from tobiko.common import _loader
LOG = log.getLogger(__name__)
@ -93,9 +93,8 @@ def get_fixture(obj: FixtureType,
"""
if isinstance(obj, fixtures.Fixture):
return typing.cast(F, obj)
if manager is None:
manager = FIXTURES
return manager.get_fixture(obj, fixture_id=fixture_id)
return fixture_manager(obj, manager).get_fixture(
obj, fixture_id=fixture_id)
def get_fixture_name(obj) -> str:
@ -113,7 +112,7 @@ def get_fixture_name(obj) -> str:
def get_fixture_class(obj: FixtureType) -> typing.Type[fixtures.Fixture]:
"""It gets fixture class"""
if isinstance(obj, str):
obj = tobiko.load_object(obj)
obj = _loader.load_object(obj)
if not inspect.isclass(obj):
obj = type(obj)
@ -145,8 +144,8 @@ def remove_fixture(obj: FixtureType,
fixture_id: typing.Any = None,
manager: 'FixtureManager' = None) -> typing.Optional[F]:
"""Unregister fixture identified by given :param obj: if any"""
manager = manager or FIXTURES
return manager.remove_fixture(obj, fixture_id=fixture_id)
return fixture_manager(obj, manager).remove_fixture(
obj, fixture_id=fixture_id)
@typing.overload
@ -277,7 +276,7 @@ def use_fixture(obj: FixtureType,
with on the fixture
"""
fixture = setup_fixture(obj, fixture_id=fixture_id, manager=manager)
_testcase.add_cleanup(tobiko.cleanup_fixture, fixture)
_testcase.add_cleanup(cleanup_fixture, fixture)
return fixture
@ -294,7 +293,7 @@ def get_name_and_object(obj: F) -> typing.Tuple[str, F]:
def get_name_and_object(obj: typing.Any) -> typing.Tuple[str, typing.Any]:
'''Get (name, obj) tuple identified by given :param obj:'''
if isinstance(obj, str):
return obj, tobiko.load_object(obj)
return obj, _loader.load_object(obj)
else:
return get_object_name(obj), obj
@ -459,7 +458,7 @@ def get_object_name(obj) -> str:
raise TypeError(f"Unable to get fixture name from object {obj!r}")
class FixtureManager(object):
class FixtureManager:
def __init__(self):
self.fixtures: typing.Dict[str, F] = {}
@ -497,6 +496,18 @@ class FixtureManager(object):
return self.fixtures.pop(name, None)
def fixture_manager(obj: FixtureType,
manager: FixtureManager = None) -> FixtureManager:
if manager is None:
if isinstance(obj, str):
obj = _loader.load_object(obj)
if hasattr(obj, 'get_fixture_manager'):
manager = obj.get_fixture_manager()
else:
manager = FIXTURES
return _exception.check_valid_type(manager, FixtureManager)
FIXTURES = FixtureManager()
@ -526,7 +537,12 @@ class SharedFixture(fixtures.Fixture):
__tobiko_fixture_name__: typing.Optional[str] = None
__tobiko_fixture_id__: typing.Any = None
@classmethod
def get_fixture_manager(cls) -> FixtureManager:
return FIXTURES
def __init__(self):
super().__init__()
# make sure class states can be used before setUp
self._clear_cleanups()
@ -586,7 +602,7 @@ class SharedFixture(fixtures.Fixture):
class FixtureProperty(property):
def __get__(self, instance, owner):
instance = instance or tobiko.get_fixture(owner)
instance = instance or get_fixture(owner)
return super(FixtureProperty, self).__get__(instance, owner)

View File

@ -15,7 +15,6 @@
# under the License.
from __future__ import absolute_import
import abc
import typing
import tobiko
@ -23,11 +22,26 @@ from tobiko.tests import unit
from tobiko import actors
class Greeter(abc.ABC):
class Greeter(actors.Actor):
@abc.abstractmethod
setup_called = False
cleanup_called = False
async def setup_actor(self):
self.setup_called = True
async def cleanup_actor(self):
self.cleanup_called = True
@actors.actor_method
async def greet(self, whom: str, greeted: 'Greeted'):
raise NotImplementedError
assert self.setup_called
assert not self.cleanup_called
if not whom:
raise ValueError("'whom' parameter can't be empty")
self.log.info(f"Hello {whom}!")
await greeted.greeted(whom=whom, greeter=self)
class Greeted:
@ -40,33 +54,9 @@ class Greeted:
self.whom = whom
class GreeterActor(actors.Actor[Greeter]):
setup_called = False
cleanup_called = False
async def setup_actor(self):
self.setup_called = True
async def cleanup_actor(self):
self.cleanup_called = True
@actors.actor_method
async def greet(self, whom: str, greeted: Greeted):
assert isinstance(self, Greeter)
assert isinstance(self, GreeterActor)
assert self.setup_called
assert not self.cleanup_called
if not whom:
raise ValueError("'whom' parameter can't be empty")
self.log.info(f"Hello {whom}!")
await greeted.greeted(whom=whom, greeter=self)
class ActorTest(unit.TobikoUnitTest):
actor = tobiko.required_fixture(GreeterActor, setup=False)
actor = tobiko.required_fixture(Greeter, setup=False)
async def test_setup_actor(self):
self.assertFalse(self.actor.setup_called)
@ -76,6 +66,13 @@ class ActorTest(unit.TobikoUnitTest):
self.assertFalse(self.actor.cleanup_called)
async def test_cleanup_actor(self):
self.assertFalse(self.actor.setup_called)
self.assertFalse(self.actor.cleanup_called)
await actors.cleanup_actor(self.actor)
self.assertFalse(self.actor.setup_called)
self.assertFalse(self.actor.cleanup_called)
async def test_cleanup_actor_after_setup(self):
await actors.setup_actor(self.actor)
self.assertTrue(self.actor.setup_called)
self.assertFalse(self.actor.cleanup_called)
@ -90,8 +87,7 @@ class ActorTest(unit.TobikoUnitTest):
async def test_async_request(self):
greeter = actors.start_actor(self.actor)
self.assertIsInstance(greeter, actors.ActorRef)
self.assertIsInstance(greeter, Greeter)
self.assertIsInstance(greeter, actors.ActorRef[Greeter])
greeted = Greeted()
await greeter.greet(whom=self.id(), greeted=greeted)
self.assertEqual(self.id(), greeted.whom)
@ -99,8 +95,7 @@ class ActorTest(unit.TobikoUnitTest):
async def test_async_request_failure(self):
greeter = actors.start_actor(self.actor)
self.assertIsInstance(greeter, actors.ActorRef)
self.assertIsInstance(greeter, Greeter)
self.assertIsInstance(greeter, actors.ActorRef[Greeter])
greeted = Greeted()
try:

View File

@ -16,7 +16,7 @@ from __future__ import absolute_import
import sys
import tobiko
from tobiko.common.managers import loader
from tobiko.common import _loader
from tobiko.tests.unit import TobikoUnitTest
@ -33,8 +33,8 @@ class TestLoader(TobikoUnitTest):
def setUp(self):
super(TestLoader, self).setUp()
self.manager = loader.LoaderManager()
self.patch(loader, 'LOADERS', self.manager)
self.manager = _loader.LoaderManager()
self.patch(_loader, 'LOADERS', self.manager)
def test_load_object_with_none(self):
object_id = '.'.join([__name__, 'SOME_NONE'])