Skip to content
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

Add test actions #178

Merged
merged 16 commits into from
Mar 14, 2019
Merged

Add test actions #178

merged 16 commits into from
Mar 14, 2019

Conversation

ivanpauno
Copy link
Member

@ivanpauno ivanpauno commented Feb 7, 2019

Connected to #176. This pull request add two actions GTest and PyTest, to run tests.
I'm doing this over #167, as running the old test with the legacy API generated a run-time asyncio error ('event loop is closed'), because of bad test interaction.
Also migrated to ament_cmake package, in order to build the dummy cpp test (maybe it exists a better way of doing this).

@ivanpauno ivanpauno added the in review Waiting for review (Kanban column) label Feb 7, 2019
@ivanpauno ivanpauno self-assigned this Feb 7, 2019
hidmic
hidmic previously requested changes Feb 8, 2019
Copy link
Contributor

@hidmic hidmic left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Left some comments. Thanks @ivanpauno!

@@ -12,12 +12,8 @@
# See the License for the specific language governing permissions and
# limitations under the License.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ivanpauno please update the copyright year.

__all__ = [
'actions',
]

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ivanpauno I'm not sure we want all to behave like this (see here). Maybe we should add the class defined below and the output module as well?

@@ -0,0 +1,48 @@
# Copyright 2018 Open Source Robotics Foundation, Inc.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ivanpauno 2018 -> 2019

:param: path the path of the test to be executed.
"""
cmd = [FindExecutable(name='python3'), '-m pytest', path]
super().__init__(cmd=cmd, shell=True, **kwargs)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ivanpauno I think that using FindExecutable precludes the need for shell=True.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It fails without shell=True

# Setup a timer to send us a SIGKILL if the test locks
sigkill_timer = TimerAction(period=self.__timeout, actions=[
EmitEvent(event=SignalProcess(
signal_number=signal.SIGKILL,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ivanpauno maybe we could send a SIGINT instead? It's slightly more polite :)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sounds good, but maybe it should be escalated to SIGKILL after an extra timeout. WDYT?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's ExecuteProcess action behavior upon shutdown already. Maybe we could try to reuse its __shutdown_process() method somehow?


ament_python_install_package(${PROJECT_NAME})

add_executable(locking dummy_tests/locking.cpp)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ivanpauno consider relocating your dummy applications below test/.

from launch_testing.actions import GTest


def test_gtest():
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ivanpauno consider adding tests for real googletest and pytest instances, here and below respectively.

@ivanpauno
Copy link
Member Author

ivanpauno commented Feb 13, 2019

@wjwwood I'm having a problem here, in a test that is locking forever.
I'm adding two actions to run gtest and pytest, which after a timeout are killed. I'm reusing the on_shutdown actions of ExecuteProcess, in order to not re-implement the same thing again.
The first timer is ending fine, SIGINT is being emited, and the gtest action is ending fine. Then the two other timers (for escalating to SIGTERM and SIGKILL) are cancelled, but the loop continues waiting for them and never ends. Is this the expected behavior?

How to repro:

  • I added an extra on_process_exit handler to avoid the error in test_gtest.py, comment it:
    RegisterEventHandler(OnProcessExit(
    on_exit=on_gtest_exited,
    target_action=gtest_action
  • It's better only running the test_gtest.py, and not all the tests.
  • Also, I added some extra printing before the _is_iddle LaunchService method for debugging
    print(number_of_entity_future_pairs) print(self._entity_future_pairs) print(self.__context._completion_futures)

Outputs:

  • Output commenting out the extra on_process_exit handler (pytest -s test_gtest.py)
platform linux -- Python 3.6.7, pytest-4.2.0, py-1.7.0, pluggy-0.8.1
rootdir: /home/ivanpauno/ros2_ws/src/ros2/launch/launch_testing/test/launch_testing, inifile:
plugins: rerunfailures-6.0, repeat-0.7.0, cov-2.6.1
collected 1 item                                                                                                                                                                                           

test_gtest.py 0
[]
[]
processing event: '<launch.events.include_launch_description.IncludeLaunchDescription object at 0x7ffbda5fcf28>'
processing event: '<launch.events.include_launch_description.IncludeLaunchDescription object at 0x7ffbda5fcf28>' ✓ '<launch.event_handlers.on_include_launch_description.OnIncludeLaunchDescription object at 0x7ffbda5fce80>'
2
[(<launch_testing.actions.gtest.GTest object at 0x7ffbda5fccf8>, <Future pending created at /usr/lib/python3.6/asyncio/base_events.py:284>), (<launch.actions.timer_action.TimerAction object at 0x7ffbda58a4a8>, <Future pending created at /usr/lib/python3.6/asyncio/base_events.py:284>)]
[]
processing event: '<launch.events.process.process_started.ProcessStarted object at 0x7ffbda4b20b8>'
2
[(<launch_testing.actions.gtest.GTest object at 0x7ffbda5fccf8>, <Future pending created at /usr/lib/python3.6/asyncio/base_events.py:284>), (<launch.actions.timer_action.TimerAction object at 0x7ffbda58a4a8>, <Future pending created at /usr/lib/python3.6/asyncio/base_events.py:284>)]
[]
processing event: '<launch.events.timer_event.TimerEvent object at 0x7ffbda58aa20>'
processing event: '<launch.events.timer_event.TimerEvent object at 0x7ffbda58aa20>' ✓ '<launch.event_handler.EventHandler object at 0x7ffbda58aa90>'
3
[(<launch_testing.actions.gtest.GTest object at 0x7ffbda5fccf8>, <Future pending created at /usr/lib/python3.6/asyncio/base_events.py:284>), (<launch.actions.timer_action.TimerAction object at 0x7ffbda4b23c8>, <Future pending created at /usr/lib/python3.6/asyncio/base_events.py:284>), (<launch.actions.timer_action.TimerAction object at 0x7ffbda4b24a8>, <Future pending created at /usr/lib/python3.6/asyncio/base_events.py:284>)]
[]
processing event: '<launch.events.process.signal_process.SignalProcess object at 0x7ffbda4b24e0>'
processing event: '<launch.events.process.signal_process.SignalProcess object at 0x7ffbda4b24e0>' ✓ '<launch.event_handler.EventHandler object at 0x7ffbda58a320>'
3
[(<launch_testing.actions.gtest.GTest object at 0x7ffbda5fccf8>, <Future pending created at /usr/lib/python3.6/asyncio/base_events.py:284>), (<launch.actions.timer_action.TimerAction object at 0x7ffbda4b23c8>, <Future pending created at /usr/lib/python3.6/asyncio/base_events.py:284>), (<launch.actions.timer_action.TimerAction object at 0x7ffbda4b24a8>, <Future pending created at /usr/lib/python3.6/asyncio/base_events.py:284>)]
[]
processing event: '<launch.events.process.process_exited.ProcessExited object at 0x7ffbda4b24e0>'
processing event: '<launch.events.process.process_exited.ProcessExited object at 0x7ffbda4b24e0>' ✓ '<launch.event_handlers.on_process_exit.OnProcessExit object at 0x7ffbda58a908>'
2
[(<launch.actions.timer_action.TimerAction object at 0x7ffbda4b23c8>, <Future pending created at /usr/lib/python3.6/asyncio/base_events.py:284>), (<launch.actions.timer_action.TimerAction object at 0x7ffbda4b24a8>, <Future pending created at /usr/lib/python3.6/asyncio/base_events.py:284>)]
[]
  • With the extra on_process_exit handler (pytest -s test_gtest.py)
platform linux -- Python 3.6.7, pytest-4.2.0, py-1.7.0, pluggy-0.8.1
rootdir: /home/ivanpauno/ros2_ws/src/ros2/launch/launch_testing/test/launch_testing, inifile:
plugins: rerunfailures-6.0, repeat-0.7.0, cov-2.6.1
collected 1 item                                                                                                                                                                                           

test_gtest.py 0
[]
[]
processing event: '<launch.events.include_launch_description.IncludeLaunchDescription object at 0x7f25d5074048>'
processing event: '<launch.events.include_launch_description.IncludeLaunchDescription object at 0x7f25d5074048>' ✓ '<launch.event_handlers.on_include_launch_description.OnIncludeLaunchDescription object at 0x7f25d510f978>'
2
[(<launch_testing.actions.gtest.GTest object at 0x7f25d513ff60>, <Future pending created at /usr/lib/python3.6/asyncio/base_events.py:284>), (<launch.actions.timer_action.TimerAction object at 0x7f25d50de668>, <Future pending created at /usr/lib/python3.6/asyncio/base_events.py:284>)]
[]
processing event: '<launch.events.process.process_started.ProcessStarted object at 0x7f25d5020160>'
2
[(<launch_testing.actions.gtest.GTest object at 0x7f25d513ff60>, <Future pending created at /usr/lib/python3.6/asyncio/base_events.py:284>), (<launch.actions.timer_action.TimerAction object at 0x7f25d50de668>, <Future pending created at /usr/lib/python3.6/asyncio/base_events.py:284>)]
[]
processing event: '<launch.events.timer_event.TimerEvent object at 0x7f25d50debe0>'
processing event: '<launch.events.timer_event.TimerEvent object at 0x7f25d50debe0>' ✓ '<launch.event_handler.EventHandler object at 0x7f25d50dec50>'
3
[(<launch_testing.actions.gtest.GTest object at 0x7f25d513ff60>, <Future pending created at /usr/lib/python3.6/asyncio/base_events.py:284>), (<launch.actions.timer_action.TimerAction object at 0x7f25d5020588>, <Future pending created at /usr/lib/python3.6/asyncio/base_events.py:284>), (<launch.actions.timer_action.TimerAction object at 0x7f25d5020668>, <Future pending created at /usr/lib/python3.6/asyncio/base_events.py:284>)]
[]
processing event: '<launch.events.process.signal_process.SignalProcess object at 0x7f25d50206a0>'
processing event: '<launch.events.process.signal_process.SignalProcess object at 0x7f25d50206a0>' ✓ '<launch.event_handler.EventHandler object at 0x7f25d50de4e0>'
3
[(<launch_testing.actions.gtest.GTest object at 0x7f25d513ff60>, <Future pending created at /usr/lib/python3.6/asyncio/base_events.py:284>), (<launch.actions.timer_action.TimerAction object at 0x7f25d5020588>, <Future pending created at /usr/lib/python3.6/asyncio/base_events.py:284>), (<launch.actions.timer_action.TimerAction object at 0x7f25d5020668>, <Future pending created at /usr/lib/python3.6/asyncio/base_events.py:284>)]
[]
processing event: '<launch.events.process.process_exited.ProcessExited object at 0x7f25d50206a0>'
processing event: '<launch.events.process.process_exited.ProcessExited object at 0x7f25d50206a0>' ✓ '<launch.event_handlers.on_process_exit.OnProcessExit object at 0x7f25d510feb8>'
processing event: '<launch.events.process.process_exited.ProcessExited object at 0x7f25d50206a0>' ✓ '<launch.event_handlers.on_process_exit.OnProcessExit object at 0x7f25d50deac8>'
2
[(<launch.actions.timer_action.TimerAction object at 0x7f25d5020588>, <Future pending created at /usr/lib/python3.6/asyncio/base_events.py:284>), (<launch.actions.timer_action.TimerAction object at 0x7f25d5020668>, <Future pending created at /usr/lib/python3.6/asyncio/base_events.py:284>)]
[]
processing event: '<launch.events.shutdown.Shutdown object at 0x7f25d5020160>'
processing event: '<launch.events.shutdown.Shutdown object at 0x7f25d5020160>' ✓ '<launch.event_handlers.on_shutdown.OnShutdown object at 0x7f25d50de588>'
processing event: '<launch.events.shutdown.Shutdown object at 0x7f25d5020160>' ✓ '<launch.event_handlers.on_shutdown.OnShutdown object at 0x7f25d510ffd0>'
0
[]
[]

@wjwwood
Copy link
Member

wjwwood commented Feb 14, 2019

Those timers do need to hold up shutdown, but not all timers need to do so (see: #181), but ExecuteProcess action cancels those timers when the process exits before they expire, preventing them from holding up shutdown when the process exits quickly. Perhaps you're missing that part.

I haven't had time to fully review this, so I can't speak to how you're reusing the parts of execute process, though I'm a bit curious why you're not inheriting from it if you're both running a subprocess and wanting it to be killed gracefully (with escalating signals).

ivanpauno and others added 12 commits March 6, 2019 13:35
Signed-off-by: ivanpauno <ivanpauno@ekumenlabs.com>
Signed-off-by: ivanpauno <ivanpauno@ekumenlabs.com>
Signed-off-by: ivanpauno <ivanpauno@ekumenlabs.com>
Signed-off-by: ivanpauno <ivanpauno@ekumenlabs.com>
Signed-off-by: ivanpauno <ivanpauno@ekumenlabs.com>
Signed-off-by: ivanpauno <ivanpauno@ekumenlabs.com>
Signed-off-by: ivanpauno <ivanpauno@ekumenlabs.com>
Signed-off-by: ivanpauno <ivanpauno@ekumenlabs.com>
Signed-off-by: ivanpauno <ivanpauno@ekumenlabs.com>
Signed-off-by: ivanpauno <ivanpauno@ekumenlabs.com>
Signed-off-by: ivanpauno <ivanpauno@ekumenlabs.com>
Signed-off-by: Michel Hidalgo <michel@ekumenlabs.com>
@hidmic hidmic force-pushed the ivanpauno/add-test-actions branch from 74cd5a7 to 0b2b07c Compare March 6, 2019 17:18
@hidmic hidmic dismissed their stale review March 6, 2019 17:21

I've pushed changes, can't be a reviewer anymore.

@hidmic
Copy link
Contributor

hidmic commented Mar 6, 2019

@wjwwood the added Test action and its subclasses do inherit from ExecuteProcess. @ivanpauno showed me the issue he's describing above. It's not that timers are holding up the loop, but the shutdown_on_idle feature that (it seems to me) has a race:

  • The executable exits and a ProcessExit event is sent.
  • The timers are canceled (i.e. their cancelling future is resolved).
  • The main loop awakes first and handles the event sent.
  • The main loop goes on and awaits for more events since the timers' completion futures have not been resolved yet.
  • The timers' coroutines are awaken and their completion futures resolved.
  • The main loop never wakes up because no event was emitted nor will be emitted.

@hidmic
Copy link
Contributor

hidmic commented Mar 6, 2019

On a side note, may I ask you guys @mjcarroll @sloretz (and maybe @wjwwood if you feel like it) to review this? I've touched it, so I can't review it anymore.

@wjwwood
Copy link
Member

wjwwood commented Mar 6, 2019

All the waiting occurs here:

while not done:
done, pending = await asyncio.wait(
entity_futures,
loop=self.__loop_from_run_thread,
timeout=1.0,
return_when=asyncio.FIRST_COMPLETED)
if not done:
_logger.debug('still waiting on futures: {}'.format(entity_futures))

Do you see debug output for "still waiting on ..."? I'm not initially convinced by your bullet point scenario above that there is a race, but it's been a while since I've steeped myself in this code, so maybe.

@hidmic
Copy link
Contributor

hidmic commented Mar 6, 2019

But that's if you've received a shutdown event. In the case described above, we were hoping the loop would trigger the shutdown itself once it detects it's idle:

is_idle = self._is_idle() # self._entity_future_pairs is pruned here
if not self.__shutting_down and self.__shutdown_when_idle and is_idle:
coroutine = self._shutdown(reason='idle', due_to_sigint=False)
await coroutine
continue

But it seems to be getting stuck in:

await process_one_event_task

@hidmic
Copy link
Contributor

hidmic commented Mar 8, 2019

@mjcarroll ping

@hidmic
Copy link
Contributor

hidmic commented Mar 12, 2019

@mjcarroll can we get this one in? I need the test action concept for ApexAI/apex_rostest#19.

Running CI just in case:

  • Linux Build Status
  • Linux-aarch64 Build Status
  • macOS Build Status
  • Windows Build Status

Signed-off-by: Michel Hidalgo <michel@ekumenlabs.com>
…encies.

Signed-off-by: Michel Hidalgo <michel@ekumenlabs.com>
@hidmic
Copy link
Contributor

hidmic commented Mar 12, 2019

And that's why running CI is important :)

Copy link
Member

@mjcarroll mjcarroll left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not super well-versed in this bit of code, but with green CI it LGTM.

Signed-off-by: Michel Hidalgo <michel@ekumenlabs.com>
Signed-off-by: Michel Hidalgo <michel@ekumenlabs.com>
@hidmic hidmic force-pushed the ivanpauno/add-test-actions branch from 5c96ac0 to 35551f6 Compare March 13, 2019 19:47
@hidmic
Copy link
Contributor

hidmic commented Mar 14, 2019

Alright, CI is passing. Merging! Thanks for taking a look @mjcarroll !

@hidmic hidmic merged commit 7611f7b into master Mar 14, 2019
@hidmic hidmic deleted the ivanpauno/add-test-actions branch March 14, 2019 11:50
@hidmic hidmic removed the in review Waiting for review (Kanban column) label Mar 14, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants