Skip to content

Commit 8ba98de

Browse files
authored
Add integration test (#28)
* Add ruff/ruff format to pre-commit & Add integration test * Set joint_states_ to initial_value as well
1 parent f04a9fb commit 8ba98de

13 files changed

Lines changed: 604 additions & 46 deletions

.pre-commit-config.yaml

Lines changed: 13 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,3 @@
1-
# To use:
2-
#
3-
# pre-commit run -a
4-
#
5-
# Or:
6-
#
7-
# pre-commit install # (runs every time you commit in git)
8-
#
9-
# To update this file:
10-
#
11-
# pre-commit autoupdate
12-
#
13-
# See https://github.com/pre-commit/pre-commit
14-
151
repos:
162
# Standard hooks
173
- repo: https://github.com/pre-commit/pre-commit-hooks
@@ -43,10 +29,12 @@ repos:
4329
- id: requirements-txt-fixer
4430
- id: sort-simple-yaml
4531
- id: trailing-whitespace
46-
- repo: https://github.com/psf/black
47-
rev: 25.1.0
32+
- repo: https://github.com/astral-sh/ruff-pre-commit
33+
rev: v0.6.9
4834
hooks:
49-
- id: black
35+
- id: ruff
36+
args: ['--output-format=full', '--fix', '--config', 'pyproject.toml']
37+
- id: ruff-format
5038
- repo: https://github.com/codespell-project/codespell
5139
rev: v2.4.1
5240
hooks:
@@ -71,3 +59,11 @@ repos:
7159
rev: v2.13.1-beta
7260
hooks:
7361
- id: hadolint-docker
62+
- repo: https://github.com/pre-commit/mirrors-prettier
63+
rev: "v3.1.0"
64+
hooks:
65+
- id: prettier
66+
additional_dependencies:
67+
- "prettier@3.1.0"
68+
- "@prettier/plugin-xml@3.3.1"
69+
files: \.(xml|xacro|srdf)$

.prettierrc.cjs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
module.exports = {
2+
plugins: [require.resolve("@prettier/plugin-xml")],
3+
xmlWhitespaceSensitivity: "ignore",
4+
xmlQuoteAttributes: "double"
5+
}

CMakeLists.txt

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,9 +56,12 @@ if(BUILD_TESTING)
5656
find_package(ament_lint_auto REQUIRED)
5757
find_package(ament_cmake_pytest REQUIRED)
5858
find_package(ros2_control_test_assets REQUIRED)
59+
find_package(ros_testing REQUIRED)
5960

6061
# ROS2 linters, but disable copyright test. PickNik's copyright's may not conform
6162
# to this test
63+
set(ament_cmake_flake8_FOUND TRUE)
64+
set(ament_cmake_pep257_FOUND TRUE)
6265
set(ament_cmake_cpplint_FOUND TRUE)
6366
set(ament_cmake_uncrustify_FOUND TRUE)
6467
ament_lint_auto_find_test_dependencies()
@@ -68,6 +71,14 @@ if(BUILD_TESTING)
6871
target_link_libraries(topic_based_system_test
6972
${PROJECT_NAME})
7073
ament_target_dependencies(topic_based_system_test ${THIS_PACKAGE_INCLUDE_DEPENDS} ros2_control_test_assets)
74+
75+
# Integration tests
76+
add_ros_test(
77+
test/ros2_control.test.py
78+
TIMEOUT
79+
120
80+
ARGS
81+
test_file:=${CMAKE_CURRENT_SOURCE_DIR}/test/test_topic_based_robot.py)
7182
endif()
7283

7384
pluginlib_export_plugin_description_file(hardware_interface topic_based_ros2_control_plugin_description.xml)

package.xml

Lines changed: 39 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,48 @@
1-
<?xml version="1.0"?>
1+
<?xml version="1.0" ?>
22
<package format="3">
3-
<name>topic_based_ros2_control</name>
4-
<version>0.2.0</version>
5-
<description>ros2 control hardware interface for topic_based sim</description>
3+
<name>topic_based_ros2_control</name>
4+
<version>0.2.0</version>
5+
<description>ros2 control hardware interface for topic_based sim</description>
66

7-
<maintainer email="marq.razz@gmail.com">Marq Rasmussen</maintainer>
8-
<maintainer email="jafar@picknik.ai">Jafar</maintainer>
9-
<author email="jafar@picknik.ai">Jafar</author>
7+
<maintainer email="marq.razz@gmail.com">Marq Rasmussen</maintainer>
8+
<maintainer email="jafar@picknik.ai">Jafar</maintainer>
9+
<author email="jafar@picknik.ai">Jafar</author>
1010

11-
<license>BSD</license>
11+
<license>BSD</license>
1212

13-
<url type="website">https://github.com/PickNikRobotics/topic_based_ros2_control</url>
14-
<url type="bugtracker">https://github.com/PickNikRobotics/topic_based_ros2_control/issues</url>
15-
<url type="repository">https://github.com/PickNikRobotics/topic_based_ros2_control/</url>
13+
<url type="website">
14+
https://github.com/PickNikRobotics/topic_based_ros2_control
15+
</url>
16+
<url type="bugtracker">
17+
https://github.com/PickNikRobotics/topic_based_ros2_control/issues
18+
</url>
19+
<url type="repository">
20+
https://github.com/PickNikRobotics/topic_based_ros2_control/
21+
</url>
1622

17-
<buildtool_depend>ament_cmake</buildtool_depend>s
23+
<buildtool_depend>ament_cmake</buildtool_depend>
1824

19-
<depend>angles</depend>
20-
<depend>rclcpp</depend>
21-
<depend>hardware_interface</depend>
22-
<depend>sensor_msgs</depend>
25+
<depend>angles</depend>
26+
<depend>rclcpp</depend>
27+
<depend>hardware_interface</depend>
28+
<depend>sensor_msgs</depend>
2329

24-
<test_depend>ament_lint_auto</test_depend>
25-
<test_depend>picknik_ament_copyright</test_depend>
26-
<test_depend>ros2_control_test_assets</test_depend>
27-
<test_depend>ament_lint_common</test_depend>
30+
<test_depend>ament_lint_auto</test_depend>
31+
<test_depend>ament_lint_common</test_depend>
32+
<test_depend>controller_manager</test_depend>
33+
<test_depend>joint_state_broadcaster</test_depend>
34+
<test_depend>joint_trajectory_controller</test_depend>
35+
<test_depend>launch</test_depend>
36+
<test_depend>launch_ros</test_depend>
37+
<test_depend>launch_testing</test_depend>
38+
<test_depend>picknik_ament_copyright</test_depend>
39+
<test_depend>rclpy</test_depend>
40+
<test_depend>robot_state_publisher</test_depend>
41+
<test_depend>ros2_control_test_assets</test_depend>
42+
<test_depend>ros_testing</test_depend>
43+
<test_depend>xacro</test_depend>
2844

29-
<export>
30-
<build_type>ament_cmake</build_type>
31-
</export>
45+
<export>
46+
<build_type>ament_cmake</build_type>
47+
</export>
3248
</package>

pyproject.toml

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
[tool.ruff]
2+
extend-select = [
3+
# Enabled by default
4+
# pyflakes
5+
# "F",
6+
# pycodestyle
7+
# "E",
8+
"W",
9+
# isort
10+
"I",
11+
# NumPy-specific rules
12+
"NPY",
13+
# Ruff-specific rules
14+
"RUF",
15+
]
16+
# line-length = 88
17+
ignore = ["E501"]
18+
target-version = "py310"
19+
[tool.ruff.pydocstyle]
20+
convention = "google"

src/topic_based_system.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,7 @@ CallbackReturn TopicBasedSystem::on_init(const hardware_interface::HardwareInfo&
9191
// Check the initial_value param is used
9292
if (!interface.initial_value.empty())
9393
{
94+
joint_states_[index][i] = std::stod(interface.initial_value);
9495
joint_commands_[index][i] = std::stod(interface.initial_value);
9596
}
9697
}

test/control.launch.py

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
# -*- coding: utf-8 -*-
2+
3+
# Copyright 2024 PickNik Inc.
4+
#
5+
# Redistribution and use in source and binary forms, with or without
6+
# modification, are permitted provided that the following conditions are met:
7+
#
8+
# * Redistributions of source code must retain the above copyright
9+
# notice, this list of conditions and the following disclaimer.
10+
#
11+
# * Redistributions in binary form must reproduce the above copyright
12+
# notice, this list of conditions and the following disclaimer in the
13+
# documentation and/or other materials provided with the distribution.
14+
#
15+
# * Neither the name of the PickNik Inc. nor the names of its
16+
# contributors may be used to endorse or promote products derived from
17+
# this software without specific prior written permission.
18+
#
19+
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
20+
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21+
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
22+
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
23+
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
24+
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
25+
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
26+
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
27+
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
28+
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
29+
# POSSIBILITY OF SUCH DAMAGE.
30+
31+
import os
32+
from pathlib import Path
33+
34+
import xacro
35+
from launch import LaunchDescription
36+
from launch_ros.actions import Node
37+
38+
SCRIPT_PATH = Path(os.path.realpath(__file__)).parent
39+
40+
41+
def generate_launch_description():
42+
ros2_controllers_file = Path(SCRIPT_PATH / "ros2_controllers.yaml")
43+
robot_description = {
44+
"robot_description": xacro.process_file(SCRIPT_PATH / "rrr.urdf.xacro").toxml(),
45+
}
46+
controllers = ["joint_state_broadcaster", "joint_trajectory_controller"]
47+
return LaunchDescription(
48+
[
49+
Node(
50+
package="robot_state_publisher",
51+
executable="robot_state_publisher",
52+
name="robot_state_publisher",
53+
parameters=[robot_description],
54+
),
55+
Node(
56+
package="controller_manager",
57+
executable="ros2_control_node",
58+
parameters=[robot_description, ros2_controllers_file],
59+
output="screen",
60+
),
61+
]
62+
+ [
63+
Node(
64+
package="controller_manager",
65+
executable="spawner",
66+
arguments=[controller],
67+
)
68+
for controller in controllers
69+
],
70+
)

test/ros2_control.test.py

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
# -*- coding: utf-8 -*-
2+
3+
# Copyright 2024 PickNik Inc.
4+
#
5+
# Redistribution and use in source and binary forms, with or without
6+
# modification, are permitted provided that the following conditions are met:
7+
#
8+
# * Redistributions of source code must retain the above copyright
9+
# notice, this list of conditions and the following disclaimer.
10+
#
11+
# * Redistributions in binary form must reproduce the above copyright
12+
# notice, this list of conditions and the following disclaimer in the
13+
# documentation and/or other materials provided with the distribution.
14+
#
15+
# * Neither the name of the s nor the names of its
16+
# contributors may be used to endorse or promote products derived from
17+
# this software without specific prior written permission.
18+
#
19+
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
20+
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21+
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
22+
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
23+
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
24+
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
25+
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
26+
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
27+
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
28+
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
29+
# POSSIBILITY OF SUCH DAMAGE.
30+
31+
32+
import os
33+
import unittest
34+
from pathlib import Path
35+
36+
import launch_testing
37+
from launch import LaunchDescription
38+
from launch.actions import (
39+
DeclareLaunchArgument,
40+
IncludeLaunchDescription,
41+
TimerAction,
42+
)
43+
from launch.launch_description_sources import PythonLaunchDescriptionSource
44+
from launch.substitutions import (
45+
LaunchConfiguration,
46+
PathJoinSubstitution,
47+
)
48+
from launch_ros.actions import Node
49+
50+
SCRIPT_PATH = Path(os.path.realpath(__file__)).parent
51+
52+
53+
def generate_test_description():
54+
test_node = Node(
55+
executable=LaunchConfiguration("test_file"),
56+
)
57+
58+
return LaunchDescription(
59+
[
60+
IncludeLaunchDescription(
61+
PythonLaunchDescriptionSource(
62+
[
63+
PathJoinSubstitution(
64+
[
65+
str(SCRIPT_PATH),
66+
"control.launch.py",
67+
],
68+
),
69+
],
70+
),
71+
),
72+
DeclareLaunchArgument("test_file"),
73+
TimerAction(period=2.0, actions=[test_node]),
74+
launch_testing.util.KeepAliveProc(),
75+
launch_testing.actions.ReadyToTest(),
76+
],
77+
), {
78+
"test_node": test_node,
79+
}
80+
81+
82+
class TestWaitForCompletion(unittest.TestCase):
83+
# Waits for test to complete, then waits a bit to make sure result files are generated
84+
def test_run_complete(self, test_node):
85+
self.proc_info.assertWaitForShutdown(test_node, timeout=4000.0)
86+
87+
88+
@launch_testing.post_shutdown_test()
89+
class TestProcessPostShutdown(unittest.TestCase):
90+
# Checks if the test has been completed with acceptable exit codes (successful codes)
91+
def test_pass(self, proc_info, test_node):
92+
launch_testing.asserts.assertExitCodes(proc_info, process=test_node)

test/ros2_controllers.yaml

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
controller_manager:
2+
ros__parameters:
3+
update_rate: 50
4+
joint_state_broadcaster:
5+
type: joint_state_broadcaster/JointStateBroadcaster
6+
joint_trajectory_controller:
7+
type: joint_trajectory_controller/JointTrajectoryController
8+
9+
joint_trajectory_controller:
10+
ros__parameters:
11+
joints:
12+
- joint_1
13+
- joint_2
14+
- joint_3
15+
command_interfaces:
16+
- position
17+
state_interfaces:
18+
- position
19+
- velocity
20+
state_publish_rate: 100.0
21+
action_monitor_rate: 20.0
22+
allow_partial_joints_goal: false
23+
constraints:
24+
stopped_velocity_tolerance: 0.0
25+
goal_time: 0.0

0 commit comments

Comments
 (0)