diff --git a/launch_testing/launch_testing_examples/README.md b/launch_testing/launch_testing_examples/README.md new file mode 100644 index 00000000..b08d1c41 --- /dev/null +++ b/launch_testing/launch_testing_examples/README.md @@ -0,0 +1,55 @@ +# Launch testing examples + +This package contains simple use cases for the ``launch`` and ``launch_testing`` packages. +These are designed to help beginners get started with these packages and help them understand the concepts. + +## Examples + +### `check_node_launch_test.py` + +Usage: + +```sh +launch_test launch_testing_examples/check_node_launch_test.py +``` + +There might be situations where nodes, once launched, take some time to actually start and we need to wait for the node to start to perform some action. +We can simulate this using ``launch.actions.TimerAction``. This example shows one way to detect when a node has been launched. +We delay the launch by 5 seconds, and wait for the node to start with a timeout of 8 seconds. + +### `check_msgs_launch_test.py` + +Usage: + +```sh +launch_test launch_testing_examples/check_msgs_launch_test.py +``` + +Consider a problem statement where you need to launch a node and check if messages are published on a particular topic. +This example demonstrates how to do that, using a talker node. +It uses the ``Event`` object to end the test as soon as the first message is received on the chatter topic, with a timeout of 5 seconds. + +### `set_param_launch_test.py` + +Usage: + +```sh +launch_test launch_testing_examples/set_param_launch_test.py +``` + +This example demonstrates how to launch a node, set a parameter in it and check if that was successful. + +### `hello_world_launch_test.py` + +Usage: + +```sh +launch_test launch_testing_examples/hello_world_launch_test.py +``` + +This test is a simple example on how to use the ``launch_testing``. + +It launches a process and asserts that it prints "hello_world" to ``stdout`` using ``proc_output.assertWaitFor()``. +Finally, it checks if the process exits normally (zero exit code). + +The ``@launch_testing.markers.keep_alive`` decorator ensures that the launch process stays alive long enough for the tests to run. diff --git a/launch_testing/launch_testing_examples/launch_testing_examples/__init__.py b/launch_testing/launch_testing_examples/launch_testing_examples/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/launch_testing/launch_testing_examples/launch_testing_examples/check_msgs_launch_test.py b/launch_testing/launch_testing_examples/launch_testing_examples/check_msgs_launch_test.py new file mode 100644 index 00000000..6c6d9c2e --- /dev/null +++ b/launch_testing/launch_testing_examples/launch_testing_examples/check_msgs_launch_test.py @@ -0,0 +1,44 @@ +# Copyright 2021 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 unittest + +import launch +import launch.actions +import launch_ros.actions +import launch_testing.actions +import launch_testing.markers +from launch_testing_ros import WaitForTopics +import pytest +from std_msgs.msg import String + + +@pytest.mark.launch_test +@launch_testing.markers.keep_alive +def generate_test_description(): + return launch.LaunchDescription([ + launch_ros.actions.Node( + executable='talker', + package='demo_nodes_cpp', + name='demo_node_1' + ), + launch_testing.actions.ReadyToTest() + ]) + + +class TestFixture(unittest.TestCase): + + def test_check_if_msgs_published(self): + with WaitForTopics([('chatter', String)], timeout=5.0): + print('Topic received messages !') diff --git a/launch_testing/launch_testing_examples/launch_testing_examples/check_node_launch_test.py b/launch_testing/launch_testing_examples/launch_testing_examples/check_node_launch_test.py new file mode 100644 index 00000000..76a1568e --- /dev/null +++ b/launch_testing/launch_testing_examples/launch_testing_examples/check_node_launch_test.py @@ -0,0 +1,62 @@ +# Copyright 2021 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 time +import unittest + +import launch +import launch.actions +import launch_ros.actions +import launch_testing.actions +import launch_testing.markers +import pytest +import rclpy +from rclpy.node import Node + + +@pytest.mark.launch_test +@launch_testing.markers.keep_alive +def generate_test_description(): + return launch.LaunchDescription([ + launch.actions.TimerAction( + period=5.0, + actions=[ + launch_ros.actions.Node( + executable='talker', + package='demo_nodes_cpp', + name='demo_node_1' + ), + ]), + launch_testing.actions.ReadyToTest() + ]) + + +class TestFixture(unittest.TestCase): + + def test_node_start(self, proc_output): + rclpy.init() + node = Node('test_node') + assert wait_for_node(node, 'demo_node_1', 8.0), 'Node not found !' + rclpy.shutdown() + + +def wait_for_node(dummy_node, node_name, timeout=8.0): + start = time.time() + flag = False + print('Waiting for node...') + while time.time() - start < timeout and not flag: + flag = node_name in dummy_node.get_node_names() + time.sleep(0.1) + + return flag diff --git a/launch_testing/launch_testing_examples/launch_testing_examples/hello_world_launch_test.py b/launch_testing/launch_testing_examples/launch_testing_examples/hello_world_launch_test.py new file mode 100644 index 00000000..9ee17958 --- /dev/null +++ b/launch_testing/launch_testing_examples/launch_testing_examples/hello_world_launch_test.py @@ -0,0 +1,58 @@ +# Copyright 2021 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 unittest + +import launch +import launch.actions +import launch_testing.actions +import launch_testing.markers +import pytest + + +# This function specifies the processes to be run for our test +@pytest.mark.launch_test +@launch_testing.markers.keep_alive +def generate_test_description(): + """Launch a simple process to print 'hello_world'.""" + return launch.LaunchDescription([ + # Launch a process to test + launch.actions.ExecuteProcess( + cmd=['echo', 'hello_world'], + shell=True + ), + # Tell launch to start the test + launch_testing.actions.ReadyToTest() + ]) + + +# This is our test fixture. Each method is a test case. +# These run alongside the processes specified in generate_test_description() +class TestHelloWorldProcess(unittest.TestCase): + + def test_read_stdout(self, proc_output): + """Check if 'hello_world' was found in the stdout.""" + # 'proc_output' is an object added automatically by the launch_testing framework. + # It captures the outputs of the processes launched in generate_test_description() + # Refer to the documentation for further details. + proc_output.assertWaitFor('hello_world', timeout=10, stream='stdout') + + +# These tests are run after the processes in generate_test_description() have shutdown. +@launch_testing.post_shutdown_test() +class TestHelloWorldShutdown(unittest.TestCase): + + def test_exit_codes(self, proc_info): + """Check if the processes exited normally.""" + launch_testing.asserts.assertExitCodes(proc_info) diff --git a/launch_testing/launch_testing_examples/launch_testing_examples/set_param_launch_test.py b/launch_testing/launch_testing_examples/launch_testing_examples/set_param_launch_test.py new file mode 100644 index 00000000..e1b5fc34 --- /dev/null +++ b/launch_testing/launch_testing_examples/launch_testing_examples/set_param_launch_test.py @@ -0,0 +1,65 @@ +# Copyright 2021 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 unittest + +import launch +import launch.actions +import launch_ros.actions +import launch_testing.actions +import launch_testing.markers +import pytest +from rcl_interfaces.srv import SetParameters +import rclpy +from rclpy.node import Node + + +@pytest.mark.launch_test +@launch_testing.markers.keep_alive +def generate_test_description(): + return launch.LaunchDescription([ + launch_ros.actions.Node( + executable='parameter_blackboard', + package='demo_nodes_cpp', + name='demo_node_1' + ), + launch_testing.actions.ReadyToTest() + ]) + + +class TestFixture(unittest.TestCase): + + def test_set_parameter(self, proc_output): + rclpy.init() + node = Node('test_node') + response = set_parameter(node, value=True) + assert response.successful, 'Could not set parameter!' + rclpy.shutdown() + + +def set_parameter(dummy_node, value=True, timeout=5.0): + parameters = [rclpy.Parameter('demo_parameter_1', value=value).to_parameter_msg()] + + client = dummy_node.create_client(SetParameters, 'demo_node_1/set_parameters') + ready = client.wait_for_service(timeout_sec=timeout) + if not ready: + raise RuntimeError('Wait for service timed out') + + request = SetParameters.Request() + request.parameters = parameters + future = client.call_async(request) + rclpy.spin_until_future_complete(dummy_node, future, timeout_sec=timeout) + + response = future.result() + return response.results[0] diff --git a/launch_testing/launch_testing_examples/package.xml b/launch_testing/launch_testing_examples/package.xml new file mode 100644 index 00000000..70504c94 --- /dev/null +++ b/launch_testing/launch_testing_examples/package.xml @@ -0,0 +1,28 @@ + + + + launch_testing_examples + 0.12.0 + Examples of simple launch tests + Mabel Zhang + Shane Loretz + Apache License 2.0 + + Aditya Pande + + ament_copyright + ament_flake8 + ament_pep257 + python3-pytest + launch + launch_ros + launch_testing + launch_testing_ros + rclpy + std_msgs + demo_nodes_cpp + + + ament_python + + diff --git a/launch_testing/launch_testing_examples/resource/launch_testing_examples b/launch_testing/launch_testing_examples/resource/launch_testing_examples new file mode 100644 index 00000000..e69de29b diff --git a/launch_testing/launch_testing_examples/setup.cfg b/launch_testing/launch_testing_examples/setup.cfg new file mode 100644 index 00000000..f820dc95 --- /dev/null +++ b/launch_testing/launch_testing_examples/setup.cfg @@ -0,0 +1,4 @@ +[develop] +script_dir=$base/lib/launch_testing_examples +[install] +install_scripts=$base/lib/launch_testing_examples diff --git a/launch_testing/launch_testing_examples/setup.py b/launch_testing/launch_testing_examples/setup.py new file mode 100644 index 00000000..bb109beb --- /dev/null +++ b/launch_testing/launch_testing_examples/setup.py @@ -0,0 +1,25 @@ +from setuptools import setup + +package_name = 'launch_testing_examples' + +setup( + name=package_name, + version='0.12.0', + packages=[package_name], + data_files=[ + ('share/ament_index/resource_index/packages', + ['resource/' + package_name]), + ('share/' + package_name, ['package.xml']), + ], + install_requires=['setuptools'], + zip_safe=True, + maintainer='aditya', + maintainer_email='aditya.pande@openrobotics.org', + description='Examples of simple launch tests', + license='Apache License 2.0', + tests_require=['pytest'], + entry_points={ + 'console_scripts': [ + ], + }, +) diff --git a/launch_testing/launch_testing_examples/test/test_copyright.py b/launch_testing/launch_testing_examples/test/test_copyright.py new file mode 100644 index 00000000..cc8ff03f --- /dev/null +++ b/launch_testing/launch_testing_examples/test/test_copyright.py @@ -0,0 +1,23 @@ +# Copyright 2015 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. + +from ament_copyright.main import main +import pytest + + +@pytest.mark.copyright +@pytest.mark.linter +def test_copyright(): + rc = main(argv=['.', 'test']) + assert rc == 0, 'Found errors' diff --git a/launch_testing/launch_testing_examples/test/test_flake8.py b/launch_testing/launch_testing_examples/test/test_flake8.py new file mode 100644 index 00000000..27ee1078 --- /dev/null +++ b/launch_testing/launch_testing_examples/test/test_flake8.py @@ -0,0 +1,25 @@ +# Copyright 2017 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. + +from ament_flake8.main import main_with_errors +import pytest + + +@pytest.mark.flake8 +@pytest.mark.linter +def test_flake8(): + rc, errors = main_with_errors(argv=[]) + assert rc == 0, \ + 'Found %d code style errors / warnings:\n' % len(errors) + \ + '\n'.join(errors) diff --git a/launch_testing/launch_testing_examples/test/test_pep257.py b/launch_testing/launch_testing_examples/test/test_pep257.py new file mode 100644 index 00000000..b234a384 --- /dev/null +++ b/launch_testing/launch_testing_examples/test/test_pep257.py @@ -0,0 +1,23 @@ +# Copyright 2015 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. + +from ament_pep257.main import main +import pytest + + +@pytest.mark.linter +@pytest.mark.pep257 +def test_pep257(): + rc = main(argv=['.', 'test']) + assert rc == 0, 'Found code style errors / warnings'