-
Notifications
You must be signed in to change notification settings - Fork 145
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
Merged
Merged
Changes from all commits
Commits
Show all changes
17 commits
Select commit
Hold shift + click to select a range
2e954ce
Emits events.ExecutionComplete on Action completion.
hidmic 9398aba
Makes LaunchTestService handle non-process actions.
hidmic b9f3bae
Makes LaunchTestService handle fixture process actions.
hidmic f4faf91
Adds output checks to LaunchTestService.
hidmic 78a567d
Updates launch_testing package tests.
hidmic 3a65f65
Applies fixes after launch_testing refactor.
hidmic a1f7fb7
Adds OpaqueCoroutine action subclass.
hidmic 7a0ac0a
Apply suggestions from code review
wjwwood 33d15c2
Addresses peer review comments.
hidmic 07d55f8
Refactors launch_testing API a bit.
hidmic 34da363
Applies style fixes to launch_testing.
hidmic 6d04a9f
Deal with non zero exit on shutdown.
hidmic aec02bf
Avoids output tests' races.
hidmic ea034a2
Applies misc fixes after Windows triaging.
hidmic 39f1234
Applies more fixes after Windows triaging.
hidmic 2d9305a
Fixes linter issue.
hidmic d05379a
Addresses peer review comments.
hidmic File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,115 @@ | ||
# 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, | ||
*args, | ||
**kwargs | ||
): | ||
... | ||
|
||
if ignore_context is False on construction (currently the default), or | ||
|
||
.. code-block:: python | ||
|
||
async def coroutine( | ||
*args, | ||
**kwargs | ||
): | ||
... | ||
|
||
if ignore_context is True on construction. | ||
""" | ||
|
||
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 coroutine for 'coroutine', got '{}'".format( | ||
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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, | ||
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 ValueError("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_completion) | ||
|
||
@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)) | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
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.
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 comment
The reason will be displayed to describe this comment to others. Learn more.
Good point, we do not. Test added.