Skip to content

Commit

Permalink
Add actions to ros loader
Browse files Browse the repository at this point in the history
  • Loading branch information
sea-bass committed Oct 30, 2023
1 parent 910163b commit 1d1cd6b
Show file tree
Hide file tree
Showing 3 changed files with 185 additions and 42 deletions.
1 change: 1 addition & 0 deletions rosbridge_library/package.xml
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
<test_depend>builtin_interfaces</test_depend>
<test_depend>diagnostic_msgs</test_depend>
<test_depend>example_interfaces</test_depend>
<test_depend>control_msgs</test_depend>
<test_depend>geometry_msgs</test_depend>
<test_depend>nav_msgs</test_depend>
<test_depend>sensor_msgs</test_depend>
Expand Down
61 changes: 31 additions & 30 deletions rosbridge_library/src/rosbridge_library/internal/ros_loader.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,10 @@
# Variable containing the loaded classes
_loaded_msgs = {}
_loaded_srvs = {}
_loaded_actions = {}
_msgs_lock = Lock()
_srvs_lock = Lock()
_actions_lock = Lock()


class InvalidTypeStringException(Exception):
Expand Down Expand Up @@ -75,14 +77,20 @@ def get_message_class(typestring):
"""Loads the message type specified.
Returns the loaded class, or throws exceptions on failure"""
return _get_msg_class(typestring)
return _get_interface_class(typestring, "msg", _loaded_msgs, _msgs_lock)


def get_service_class(typestring):
"""Loads the service type specified.
Returns the loaded class, or None on failure"""
return _get_srv_class(typestring)
return _get_interface_class(typestring, "srv", _loaded_srvs, _srvs_lock)


def get_action_class(typestring):
"""Loads the action type specified.
Returns the loaded class, or throws exceptions on failure"""
return _get_interface_class(typestring, "action", _loaded_actions, _actions_lock)


def get_message_instance(typestring):
Expand All @@ -102,48 +110,41 @@ def get_service_response_instance(typestring):
return cls.Response()


def _get_msg_class(typestring):
"""If not loaded, loads the specified msg class then returns an instance
of it
def get_action_goal_instance(typestring):
cls = get_action_class(typestring)
return cls.Goal()

Throws various exceptions if loading the msg class fails"""
global _loaded_msgs, _msgs_lock
try:
# The type string starts with the package and ends with the
# class and contains module subnames in between. For
# compatibility with ROS1 style types, we fall back to use a
# standard "msg" subname.
splits = [x for x in typestring.split("/") if x]
if len(splits) > 2:
subname = ".".join(splits[1:-1])
else:
subname = "msg"

return _get_class(typestring, subname, _loaded_msgs, _msgs_lock)
except (InvalidModuleException, InvalidClassException):
return _get_class(typestring, "msg", _loaded_msgs, _msgs_lock)
def get_action_feedback_instance(typestring):
cls = get_action_class(typestring)
return cls.Feedback()


def get_action_result_instance(typestring):
cls = get_action_class(typestring)
return cls.Result()

def _get_srv_class(typestring):
"""If not loaded, loads the specified srv class then returns an instance
of it

Throws various exceptions if loading the srv class fails"""
global _loaded_srvs, _srvs_lock
def _get_interface_class(typestring, intf_type, loaded_intfs, intf_lock):
"""
If not loaded, loads the specified ROS interface class then returns an instance of it.
Throws various exceptions if loading the interface class fails.
"""
try:
# The type string starts with the package and ends with the
# class and contains module subnames in between. For
# compatibility with ROS1 style types, we fall back to use a
# standard "srv" subname.
# compatibility with ROS 1 style types, we fall back to use a
# standard "msg" subname.
splits = [x for x in typestring.split("/") if x]
if len(splits) > 2:
subname = ".".join(splits[1:-1])
else:
subname = "srv"
subname = intf_type

return _get_class(typestring, subname, _loaded_srvs, _srvs_lock)
return _get_class(typestring, subname, loaded_intfs, intf_lock)
except (InvalidModuleException, InvalidClassException):
return _get_class(typestring, "srv", _loaded_srvs, _srvs_lock)
return _get_class(typestring, intf_type, loaded_intfs, intf_lock)


def _get_class(typestring, subname, cache, lock):
Expand Down
165 changes: 153 additions & 12 deletions rosbridge_library/test/internal/test_ros_loader.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,10 @@


class TestROSLoader(unittest.TestCase):
def test_bad_msgnames(self):
#################
# Message Tests #
#################
def test_bad_msg_names(self):
bad = [
"",
"/",
Expand All @@ -33,7 +36,7 @@ def test_bad_msgnames(self):
ros_loader.InvalidTypeStringException, ros_loader.get_message_instance, x
)

def test_irregular_msgnames(self):
def test_irregular_msg_names(self):
irregular = [
"std_msgs//String",
"//std_msgs/String",
Expand All @@ -49,7 +52,7 @@ def test_irregular_msgnames(self):
self.assertNotEqual(ros_loader.get_message_class(x), None)
self.assertNotEqual(ros_loader.get_message_instance(x), None)

def test_std_msgnames(self):
def test_std_msg_names(self):
stdmsgs = [
"std_msgs/Bool",
"std_msgs/Byte",
Expand Down Expand Up @@ -124,7 +127,7 @@ def test_msg_cache(self):
self.assertEqual(get_message(x), type(inst))
self.assertTrue(x in ros_loader._loaded_msgs)

def test_assorted_msgnames(self):
def test_assorted_msg_names(self):
assortedmsgs = [
"geometry_msgs/Pose",
"actionlib_msgs/GoalStatus",
Expand All @@ -145,7 +148,7 @@ def test_assorted_msgnames(self):
self.assertNotEqual(inst, None)
self.assertEqual(get_message(x), type(inst))

def test_invalid_msgnames_primitives(self):
def test_invalid_msg_names_primitives(self):
invalid = [
"bool",
"int8",
Expand All @@ -172,7 +175,7 @@ def test_invalid_msgnames_primitives(self):
x,
)

def test_nonexistent_packagenames(self):
def test_nonexistent_package_names(self):
nonexistent = [
"wangle_msgs/Jam",
"whistleblower_msgs/Document",
Expand All @@ -195,7 +198,7 @@ def test_packages_without_msgs(self):
self.assertRaises(ros_loader.InvalidModuleException, ros_loader.get_message_class, x)
self.assertRaises(ros_loader.InvalidModuleException, ros_loader.get_message_instance, x)

def test_nonexistent_msg_classnames(self):
def test_nonexistent_msg_class_names(self):
nonexistent = [
"rcl_interfaces/Time",
"rcl_interfaces/Duration",
Expand All @@ -208,7 +211,10 @@ def test_nonexistent_msg_classnames(self):
self.assertRaises(ros_loader.InvalidClassException, ros_loader.get_message_class, x)
self.assertRaises(ros_loader.InvalidClassException, ros_loader.get_message_instance, x)

def test_bad_servicenames(self):
#################
# Service Tests #
#################
def test_bad_service_names(self):
bad = [
"",
"/",
Expand Down Expand Up @@ -241,7 +247,7 @@ def test_bad_servicenames(self):
ros_loader.InvalidTypeStringException, ros_loader.get_service_response_instance, x
)

def test_irregular_servicenames(self):
def test_irregular_service_names(self):
irregular = [
"rcl_interfaces//GetParameters",
"/rcl_interfaces/GetParameters/",
Expand All @@ -258,7 +264,7 @@ def test_irregular_servicenames(self):
self.assertNotEqual(ros_loader.get_service_request_instance(x), None)
self.assertNotEqual(ros_loader.get_service_response_instance(x), None)

def test_common_servicenames(self):
def test_common_service_names(self):
common = [
"rcl_interfaces/GetParameters",
"rcl_interfaces/SetParameters",
Expand Down Expand Up @@ -302,7 +308,7 @@ def test_packages_without_srvs(self):
ros_loader.InvalidModuleException, ros_loader.get_service_response_instance, x
)

def test_nonexistent_service_packagenames(self):
def test_nonexistent_service_package_names(self):
nonexistent = [
"butler_srvs/FetchDrink",
"money_srvs/MoreMoney",
Expand All @@ -318,7 +324,7 @@ def test_nonexistent_service_packagenames(self):
ros_loader.InvalidModuleException, ros_loader.get_service_response_instance, x
)

def test_nonexistent_service_classnames(self):
def test_nonexistent_service_class_names(self):
nonexistent = [
"std_srvs/Reboot",
"std_srvs/Full",
Expand All @@ -332,3 +338,138 @@ def test_nonexistent_service_classnames(self):
self.assertRaises(
ros_loader.InvalidClassException, ros_loader.get_service_response_instance, x
)

################
# Action Tests #
################
def test_bad_action_names(self):
bad = [
"",
"/",
"//",
"///",
"////",
"/////",
"bad",
"stillbad",
"not/better/even/still",
"not//better//even//still",
"not///better///even///still",
"better/",
"better//",
"better///",
"/better",
"//better",
"///better",
r"this\isbad",
"\\",
]
for x in bad:
self.assertRaises(ros_loader.InvalidTypeStringException, ros_loader.get_action_class, x)
self.assertRaises(
ros_loader.InvalidTypeStringException, ros_loader.get_action_goal_instance, x
)
self.assertRaises(
ros_loader.InvalidTypeStringException, ros_loader.get_action_feedback_instance, x
)
self.assertRaises(
ros_loader.InvalidTypeStringException, ros_loader.get_action_result_instance, x
)

def test_irregular_action_names(self):
irregular = [
"example_interfaces//Fibonacci",
"/example_interfaces/Fibonacci/",
"/example_interfaces/Fibonacci",
"//example_interfaces/Fibonacci",
"/example_interfaces//Fibonacci",
"example_interfaces/Fibonacci//",
"/example_interfaces/Fibonacci//",
"example_interfaces/Fibonacci/",
"example_interfaces//Fibonacci//",
]
for x in irregular:
self.assertNotEqual(ros_loader.get_action_class(x), None)
self.assertNotEqual(ros_loader.get_action_goal_instance(x), None)
self.assertNotEqual(ros_loader.get_action_feedback_instance(x), None)
self.assertNotEqual(ros_loader.get_action_result_instance(x), None)

def test_common_action_names(self):
common = [
"control_msgs/FollowJointTrajectory",
"tf2_msgs/LookupTransform",
"example_interfaces/Fibonacci",
]
for x in common:
self.assertNotEqual(ros_loader.get_action_class(x), None)
self.assertNotEqual(ros_loader.get_action_goal_instance(x), None)
self.assertNotEqual(ros_loader.get_action_feedback_instance(x), None)
self.assertNotEqual(ros_loader.get_action_result_instance(x), None)

def test_action_cache(self):
common = [
"control_msgs/FollowJointTrajectory",
"tf2_msgs/LookupTransform",
"example_interfaces/Fibonacci",
]
for x in common:
self.assertNotEqual(ros_loader.get_action_class(x), None)
self.assertNotEqual(ros_loader.get_action_goal_instance(x), None)
self.assertNotEqual(ros_loader.get_action_feedback_instance(x), None)
self.assertNotEqual(ros_loader.get_action_result_instance(x), None)
self.assertTrue(x in ros_loader._loaded_actions)

def test_packages_without_actions(self):
no_msgs = ["roslib/A", "roslib/B", "roslib/C", "std_msgs/CuriousSrv"]
for x in no_msgs:
self.assertRaises(ros_loader.InvalidModuleException, ros_loader.get_action_class, x)
self.assertRaises(
ros_loader.InvalidModuleException, ros_loader.get_action_goal_instance, x
)
self.assertRaises(
ros_loader.InvalidModuleException, ros_loader.get_action_feedback_instance, x
)
self.assertRaises(
ros_loader.InvalidModuleException, ros_loader.get_action_result_instance, x
)

def test_nonexistent_action_package_names(self):
nonexistent = [
"butler_srvs/SetTable",
"money_srvs/WithdrawMoreMoney",
"snoopdogg_actions/LayBackWithMyMindOnMyMoneyAndMyMoneyOnMyMind",
"revenge_actions/PlotRevenge",
]
for x in nonexistent:
self.assertRaises(ros_loader.InvalidModuleException, ros_loader.get_action_class, x)
self.assertRaises(
ros_loader.InvalidModuleException, ros_loader.get_action_goal_instance, x
)
self.assertRaises(
ros_loader.InvalidModuleException, ros_loader.get_action_feedback_instance, x
)
self.assertRaises(
ros_loader.InvalidModuleException, ros_loader.get_action_result_instance, x
)

def test_nonexistent_action_class_names(self):
nonexistent = [
"control_msgs/ControlFusionReactor",
"tf2_msgs/GetDualQuaternionRepresentation",
"example_interfaces/DoNonexistentAction",
]
for x in nonexistent:
self.assertRaises(ros_loader.InvalidClassException, ros_loader.get_action_class, x)
self.assertRaises(
ros_loader.InvalidClassException, ros_loader.get_action_goal_instance, x
)
self.assertRaises(
ros_loader.InvalidClassException, ros_loader.get_action_feedback_instance, x
)
self.assertRaises(
ros_loader.InvalidClassException, ros_loader.get_action_result_instance, x
)


if __name__ == "__main__":
unittest.main()

0 comments on commit 1d1cd6b

Please sign in to comment.