-
Notifications
You must be signed in to change notification settings - Fork 142
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Migrates legacy launch API tests #167
Changes from 9 commits
2e954ce
9398aba
b9f3bae
f4faf91
78a567d
3a65f65
a1f7fb7
7a0ac0a
33d15c2
07d55f8
34da363
6d04a9f
aec02bf
ea034a2
39f1234
2d9305a
d05379a
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,103 @@ | ||
# Copyright 2019 Open Source Robotics Foundation, Inc. | ||
# | ||
# 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. | ||
|
||
"""Module for the OpaqueCoroutine action.""" | ||
|
||
import asyncio | ||
import collections.abc | ||
from typing import Any | ||
from typing import Coroutine | ||
from typing import Dict | ||
from typing import Iterable | ||
from typing import List | ||
from typing import Optional | ||
from typing import Text | ||
|
||
from ..action import Action | ||
from ..event import Event | ||
from ..event_handlers import OnShutdown | ||
from ..launch_context import LaunchContext | ||
from ..some_actions_type import SomeActionsType | ||
from ..utilities import ensure_argument_type | ||
|
||
|
||
class OpaqueCoroutine(Action): | ||
""" | ||
Action that adds a Python coroutine to the launch run loop. | ||
|
||
The signature of a coroutine should be: | ||
|
||
.. code-block:: python | ||
|
||
async def coroutine( | ||
context: LaunchContext, # iff ignore_context is False | ||
*args, | ||
**kwargs | ||
): | ||
... | ||
""" | ||
|
||
def __init__( | ||
self, *, | ||
coroutine: Coroutine, | ||
args: Optional[Iterable[Any]] = None, | ||
kwargs: Optional[Dict[Text, Any]] = None, | ||
ignore_context: bool = False, | ||
**left_over_kwargs | ||
) -> None: | ||
"""Constructor.""" | ||
super().__init__(**left_over_kwargs) | ||
if not asyncio.iscoroutinefunction(coroutine): | ||
raise TypeError( | ||
"OpaqueCoroutine expected a couroutine for 'couroutine', got '{}'".format( | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. couroutine -> coroutine There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Oops, good catch! |
||
type(coroutine) | ||
) | ||
) | ||
ensure_argument_type( | ||
args, (collections.abc.Iterable, type(None)), 'args', 'OpaqueCoroutine' | ||
) | ||
ensure_argument_type(kwargs, (dict, type(None)), 'kwargs', 'OpaqueCoroutine') | ||
ensure_argument_type(ignore_context, bool, 'ignore_context', 'OpaqueCoroutine') | ||
self.__coroutine = coroutine | ||
self.__args = [] # type: Iterable | ||
if args is not None: | ||
self.__args = args | ||
self.__kwargs = {} # type: Dict[Text, Any] | ||
if kwargs is not None: | ||
self.__kwargs = kwargs | ||
self.__ignore_context = ignore_context # type: bool | ||
self.__future = None # type: Optional[asyncio.Future] | ||
|
||
def __on_shutdown(self, event: Event, context: LaunchContext) -> Optional[SomeActionsType]: | ||
"""Cancel ongoing coroutine upon shutdown.""" | ||
if self.__future is not None: | ||
self.__future.cancel() | ||
return None | ||
|
||
def execute(self, context: LaunchContext) -> Optional[List[Action]]: | ||
"""Execute the action.""" | ||
args = self.__args | ||
if not self.__ignore_context: | ||
args = [context, *self.__args] | ||
self.__future = context.asyncio_loop.create_task( | ||
self.__coroutine(*args, **self.__kwargs) | ||
) | ||
context.register_event_handler( | ||
OnShutdown(on_shutdown=self.__on_shutdown) | ||
) | ||
return None | ||
|
||
def get_asyncio_future(self) -> Optional[asyncio.Future]: | ||
"""Return an asyncio Future, used to let the launch system know when we're done.""" | ||
return self.__future |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,122 @@ | ||
# Copyright 2019 Open Source Robotics Foundation, Inc. | ||
# | ||
# 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. | ||
|
||
import collections.abc | ||
from typing import Callable | ||
from typing import cast | ||
from typing import List # noqa | ||
from typing import Optional | ||
from typing import overload | ||
from typing import Text | ||
|
||
from ..event import Event | ||
from ..event_handler import EventHandler | ||
from ..events import ExecutionComplete | ||
from ..launch_context import LaunchContext | ||
from ..launch_description_entity import LaunchDescriptionEntity | ||
from ..some_actions_type import SomeActionsType | ||
|
||
|
||
class OnExecutionComplete(EventHandler): | ||
""" | ||
Convenience class for handling an action completion event. | ||
|
||
It may be configured to only handle the completion of a specific action, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do we have an example or test for handling specific vs all? I can't seem to find it. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Good point, we do not. Test added. |
||
or to handle them all. | ||
""" | ||
|
||
@overload | ||
def __init__( | ||
self, *, | ||
target_action: Optional['Action'] = None, | ||
on_completion: SomeActionsType, | ||
**kwargs | ||
) -> None: | ||
"""Overload which takes just actions.""" | ||
... | ||
|
||
@overload # noqa: F811 | ||
def __init__( | ||
self, | ||
*, | ||
target_action: Optional['Action'] = None, | ||
on_completion: Callable[[int], Optional[SomeActionsType]], | ||
**kwargs | ||
) -> None: | ||
"""Overload which takes a callable to handle completion.""" | ||
... | ||
|
||
def __init__(self, *, target_action=None, on_completion, **kwargs) -> None: # noqa: F811 | ||
"""Constructor.""" | ||
from ..action import Action # noqa | ||
if not isinstance(target_action, (Action, type(None))): | ||
raise RuntimeError("OnExecutionComplete requires an 'Action' as the target") | ||
super().__init__( | ||
matcher=( | ||
lambda event: ( | ||
isinstance(event, ExecutionComplete) and ( | ||
target_action is None or | ||
event.action == target_action | ||
) | ||
) | ||
), | ||
entities=None, | ||
**kwargs, | ||
) | ||
self.__target_action = target_action | ||
# TODO(wjwwood) check that it is not only callable, but also a callable that matches | ||
# the correct signature for a handler in this case | ||
self.__on_completion = on_completion | ||
self.__actions_on_completion = [] # type: List[LaunchDescriptionEntity] | ||
if callable(on_completion): | ||
# Then on_completion is a function or lambda, so we can just call it, but | ||
# we don't put anything in self.__actions_on_completion because we cannot | ||
# know what the function will return. | ||
pass | ||
else: | ||
# Otherwise, setup self.__actions_on_completion | ||
if isinstance(on_completion, collections.abc.Iterable): | ||
for entity in on_completion: | ||
if not isinstance(entity, LaunchDescriptionEntity): | ||
raise ValueError( | ||
"expected all items in 'on_completion' iterable to be of type " | ||
"'LaunchDescriptionEntity' but got '{}'".format(type(entity))) | ||
self.__actions_on_completion = list(on_completion) | ||
else: | ||
self.__actions_on_completion = [on_completion] | ||
# Then return it from a lambda and use that as the self.__on_completion callback. | ||
self.__on_completion = lambda event, context: self.__actions_on_completion | ||
|
||
def handle(self, event: Event, context: LaunchContext) -> Optional[SomeActionsType]: | ||
"""Handle the given event.""" | ||
return self.__on_completion(cast(ExecutionComplete, event), context) | ||
|
||
@property | ||
def handler_description(self) -> Text: | ||
"""Return the string description of the handler.""" | ||
# TODO(jacobperron): revisit how to describe known actions that are passed in. | ||
# It would be nice if the parent class could output their description | ||
# via the 'entities' property. | ||
if self.__actions_on_completion: | ||
return '<actions>' | ||
return '{}'.format(self.__on_exit) | ||
|
||
@property | ||
def matcher_description(self) -> Text: | ||
"""Return the string description of the matcher.""" | ||
if self.__target_action is None: | ||
return 'event == ExecutionComplete' | ||
return 'event == ExecutionComplete and event.action == Action({})'.format( | ||
hex(id(self.__target_action)) | ||
) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
# Copyright 2019 Open Source Robotics Foundation, Inc. | ||
# | ||
# 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. | ||
|
||
"""Module for ExecutionComplete event.""" | ||
|
||
from ..action import Action | ||
from ..event import Event | ||
|
||
|
||
class ExecutionComplete(Event): | ||
"""Event that is emitted on action execution completion.""" | ||
|
||
name = 'launch.events.ExecutionComplete' | ||
|
||
def __init__(self, *, action: 'Action') -> None: | ||
"""Constructor.""" | ||
self.__action = action | ||
|
||
@property | ||
def action(self): | ||
"""Getter for action.""" | ||
return self.__action |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
# Copyright 2018 Open Source Robotics Foundation, Inc. | ||
# | ||
# 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. | ||
|
||
"""Module for standard "matchers", which are used with Events.""" | ||
|
||
from typing import Callable | ||
|
||
from ..action import Action | ||
|
||
|
||
def matches_action(target_action: 'Action') -> Callable[['Action'], bool]: | ||
"""Return a matcher which matches based on an exact given ExecuteProcess action.""" | ||
return lambda action: action == target_action |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I understand what
iff
is in this context, but it may be helpful to be a little bit more explicit. It may also be worth updating the docs forOpaqueFunction
so that they are consistent in this regard.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That makes sense. I've updated the documentation, but I'm not sure what you mean about changing
OpaqueFunction
docs.