Skip to content

Commit 69dbd41

Browse files
Simplify tests (backport of #849)
* Remove duplication of launch description in tests * Move launch and interfacing boilerplate to common file * Move all test logs to python logging This makes it easier to differentiate between ROS and test framework messages * Move waiting for a controller to test_common * Move robot starting to dashboard interface * Remove unused request definition (cherry picked from commit b28a870) Co-authored-by: RobertWilbrandt <[email protected]>
1 parent 9976bd4 commit 69dbd41

File tree

4 files changed

+452
-581
lines changed

4 files changed

+452
-581
lines changed

ur_robot_driver/test/dashboard_client.py

Lines changed: 11 additions & 129 deletions
Original file line numberDiff line numberDiff line change
@@ -26,84 +26,23 @@
2626
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
2727
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
2828
# POSSIBILITY OF SUCH DAMAGE.
29-
30-
29+
import os
30+
import sys
3131
import time
3232
import unittest
3333

3434
import pytest
3535
import rclpy
36-
from launch import LaunchDescription
37-
from launch.actions import DeclareLaunchArgument, ExecuteProcess, IncludeLaunchDescription
38-
from launch.launch_description_sources import PythonLaunchDescriptionSource
39-
from launch.substitutions import LaunchConfiguration, PathJoinSubstitution
40-
from launch_ros.substitutions import FindPackagePrefix, FindPackageShare
41-
from launch_testing.actions import ReadyToTest
4236
from rclpy.node import Node
43-
from std_srvs.srv import Trigger
4437
from ur_dashboard_msgs.msg import RobotMode
45-
from ur_dashboard_msgs.srv import (
46-
GetLoadedProgram,
47-
GetProgramState,
48-
GetRobotMode,
49-
IsProgramRunning,
50-
Load,
51-
)
5238

53-
TIMEOUT_WAIT_SERVICE = 10
54-
# If we download the docker image simultaneously to the tests, it can take quite some time until the
55-
# dashboard server is reachable and usable.
56-
TIMEOUT_WAIT_SERVICE_INITIAL = 120
39+
sys.path.append(os.path.dirname(__file__))
40+
from test_common import DashboardInterface, generate_dashboard_test_description # noqa: E402
5741

5842

5943
@pytest.mark.launch_test
6044
def generate_test_description():
61-
declared_arguments = []
62-
63-
declared_arguments.append(
64-
DeclareLaunchArgument(
65-
"ur_type",
66-
default_value="ur5e",
67-
description="Type/series of used UR robot.",
68-
choices=["ur3", "ur3e", "ur5", "ur5e", "ur10", "ur10e", "ur16e", "ur20"],
69-
)
70-
)
71-
72-
ur_type = LaunchConfiguration("ur_type")
73-
74-
dashboard_client = IncludeLaunchDescription(
75-
PythonLaunchDescriptionSource(
76-
PathJoinSubstitution(
77-
[
78-
FindPackageShare("ur_robot_driver"),
79-
"launch",
80-
"ur_dashboard_client.launch.py",
81-
]
82-
)
83-
),
84-
launch_arguments={
85-
"robot_ip": "192.168.56.101",
86-
}.items(),
87-
)
88-
ursim = ExecuteProcess(
89-
cmd=[
90-
PathJoinSubstitution(
91-
[
92-
FindPackagePrefix("ur_client_library"),
93-
"lib",
94-
"ur_client_library",
95-
"start_ursim.sh",
96-
]
97-
),
98-
" ",
99-
"-m ",
100-
ur_type,
101-
],
102-
name="start_ursim",
103-
output="screen",
104-
)
105-
106-
return LaunchDescription(declared_arguments + [ReadyToTest(), dashboard_client, ursim])
45+
return generate_dashboard_test_description()
10746

10847

10948
class DashboardClientTest(unittest.TestCase):
@@ -121,39 +60,7 @@ def tearDownClass(cls):
12160
rclpy.shutdown()
12261

12362
def init_robot(self):
124-
125-
# We wait longer for the first client, as the robot is still starting up
126-
power_on_client = waitForService(
127-
self.node, "/dashboard_client/power_on", Trigger, timeout=TIMEOUT_WAIT_SERVICE_INITIAL
128-
)
129-
130-
# Connect to all other expected services
131-
dashboard_interfaces = {
132-
"power_off": Trigger,
133-
"brake_release": Trigger,
134-
"unlock_protective_stop": Trigger,
135-
"restart_safety": Trigger,
136-
"get_robot_mode": GetRobotMode,
137-
"load_installation": Load,
138-
"load_program": Load,
139-
"close_popup": Trigger,
140-
"get_loaded_program": GetLoadedProgram,
141-
"program_state": GetProgramState,
142-
"program_running": IsProgramRunning,
143-
"play": Trigger,
144-
"stop": Trigger,
145-
}
146-
self.dashboard_clients = {
147-
srv_name: waitForService(self.node, f"/dashboard_client/{srv_name}", srv_type)
148-
for (srv_name, srv_type) in dashboard_interfaces.items()
149-
}
150-
151-
# Add first client to dict
152-
self.dashboard_clients["power_on"] = power_on_client
153-
154-
#
155-
# Test functions
156-
#
63+
self._dashboard_interface = DashboardInterface(self.node)
15764

15865
def test_switch_on(self):
15966
"""Test power on a robot."""
@@ -162,59 +69,34 @@ def test_switch_on(self):
16269
mode = RobotMode.DISCONNECTED
16370
while mode != RobotMode.POWER_OFF and time.time() < end_time:
16471
time.sleep(0.1)
165-
result = self.call_dashboard_service("get_robot_mode", GetRobotMode.Request())
72+
result = self._dashboard_interface.get_robot_mode()
16673
self.assertTrue(result.success)
16774
mode = result.robot_mode.mode
16875

16976
# Power on robot
170-
self.assertTrue(self.call_dashboard_service("power_on", Trigger.Request()).success)
77+
self.assertTrue(self._dashboard_interface.power_on().success)
17178

17279
# Wait until robot mode changes
17380
end_time = time.time() + 10
17481
mode = RobotMode.DISCONNECTED
17582
while mode not in (RobotMode.IDLE, RobotMode.RUNNING) and time.time() < end_time:
17683
time.sleep(0.1)
177-
result = self.call_dashboard_service("get_robot_mode", GetRobotMode.Request())
84+
result = self._dashboard_interface.get_robot_mode()
17885
self.assertTrue(result.success)
17986
mode = result.robot_mode.mode
18087

18188
self.assertIn(mode, (RobotMode.IDLE, RobotMode.RUNNING))
18289

18390
# Release robot brakes
184-
self.assertTrue(self.call_dashboard_service("brake_release", Trigger.Request()).success)
91+
self.assertTrue(self._dashboard_interface.brake_release().success)
18592

18693
# Wait until robot mode is RUNNING
18794
end_time = time.time() + 10
18895
mode = RobotMode.DISCONNECTED
18996
while mode != RobotMode.RUNNING and time.time() < end_time:
19097
time.sleep(0.1)
191-
result = self.call_dashboard_service("get_robot_mode", GetRobotMode.Request())
98+
result = self._dashboard_interface.get_robot_mode()
19299
self.assertTrue(result.success)
193100
mode = result.robot_mode.mode
194101

195102
self.assertEqual(mode, RobotMode.RUNNING)
196-
197-
#
198-
# Utility functions
199-
#
200-
201-
def call_dashboard_service(self, srv_name, request):
202-
self.node.get_logger().info(
203-
f"Calling dashboard service '{srv_name}' with request {request}"
204-
)
205-
future = self.dashboard_clients[srv_name].call_async(request)
206-
rclpy.spin_until_future_complete(self.node, future)
207-
if future.result() is not None:
208-
self.node.get_logger().info(f"Received result {future.result()}")
209-
return future.result()
210-
else:
211-
raise Exception(f"Exception while calling service: {future.exception()}")
212-
213-
214-
def waitForService(node, srv_name, srv_type, timeout=TIMEOUT_WAIT_SERVICE):
215-
client = node.create_client(srv_type, srv_name)
216-
if client.wait_for_service(timeout) is False:
217-
raise Exception(f"Could not reach service '{srv_name}' within timeout of {timeout}")
218-
219-
node.get_logger().info(f"Successfully connected to service '{srv_name}'")
220-
return client

0 commit comments

Comments
 (0)