diff --git a/rosidl_generator_py/resource/_msg_support.c.em b/rosidl_generator_py/resource/_msg_support.c.em index 62941b29..802435df 100644 --- a/rosidl_generator_py/resource/_msg_support.c.em +++ b/rosidl_generator_py/resource/_msg_support.c.em @@ -678,25 +678,15 @@ if isinstance(type_, AbstractNestedType): @(bi) Py_DECREF(field); @(bi) return NULL; @(bi) } -@(bi) // clear the array, poor approach to remove potential default values +@(bi) // clear the array to remove potential default values @(bi) Py_ssize_t length = PyObject_Length(field); @(bi) if (-1 == length) { @(bi) Py_DECREF(field); @(bi) return NULL; @(bi) } -@(bi) if (length > 0) { -@(bi) PyObject * pop = PyObject_GetAttrString(field, "pop"); -@(bi) assert(pop != NULL); -@(bi) for (Py_ssize_t i = 0; i < length; ++i) { -@(bi) PyObject * ret = PyObject_CallFunctionObjArgs(pop, NULL); -@(bi) if (!ret) { -@(bi) Py_DECREF(pop); -@(bi) Py_DECREF(field); -@(bi) return NULL; -@(bi) } -@(bi) Py_DECREF(ret); -@(bi) } -@(bi) Py_DECREF(pop); +@(bi) if (PySequence_DelSlice(field, 0, length) == -1) { +@(bi) Py_DECREF(field); +@(bi) return NULL; @(bi) } @(bi) if (ros_message->@(member.name).size > 0) { @(bi) // populating the array.array using the frombytes method diff --git a/rosidl_generator_py/test/test_interfaces.py b/rosidl_generator_py/test/test_interfaces.py index 64225d9c..1ad54361 100644 --- a/rosidl_generator_py/test/test_interfaces.py +++ b/rosidl_generator_py/test/test_interfaces.py @@ -13,8 +13,10 @@ # limitations under the License. import array +import ctypes import math import sys +from typing import Any import numpy import pytest @@ -39,6 +41,48 @@ from rosidl_parser.definition import UnboundedString +_PY_CAPSULE_GET_POINTER = ctypes.pythonapi.PyCapsule_GetPointer +_PY_CAPSULE_GET_POINTER.argtypes = [ctypes.py_object, ctypes.c_char_p] +_PY_CAPSULE_GET_POINTER.restype = ctypes.c_void_p + + +def _get_capsule_pointer(capsule: object) -> int: + pointer = _PY_CAPSULE_GET_POINTER(capsule, None) + assert pointer is not None + return pointer + + +def _convert_through_type_support(message_type: Any, message: Any) -> Any: + message_type.__import_type_support__() + assert message_type._CREATE_ROS_MESSAGE is not None + assert message_type._CONVERT_FROM_PY is not None + assert message_type._CONVERT_TO_PY is not None + assert message_type._DESTROY_ROS_MESSAGE is not None + + create_ros_message_type = ctypes.PYFUNCTYPE(ctypes.c_void_p) + convert_from_py_type = ctypes.PYFUNCTYPE( + ctypes.c_bool, ctypes.py_object, ctypes.c_void_p) + convert_to_py_type = ctypes.PYFUNCTYPE(ctypes.py_object, ctypes.c_void_p) + destroy_ros_message_type = ctypes.PYFUNCTYPE(None, ctypes.c_void_p) + + create_ros_message = create_ros_message_type( + _get_capsule_pointer(message_type._CREATE_ROS_MESSAGE)) + convert_from_py = convert_from_py_type( + _get_capsule_pointer(message_type._CONVERT_FROM_PY)) + convert_to_py = convert_to_py_type( + _get_capsule_pointer(message_type._CONVERT_TO_PY)) + destroy_ros_message = destroy_ros_message_type( + _get_capsule_pointer(message_type._DESTROY_ROS_MESSAGE)) + + ros_message = create_ros_message() + assert ros_message is not None + try: + assert convert_from_py(message, ros_message) + return convert_to_py(ros_message) + finally: + destroy_ros_message(ros_message) + + def test_basic_types() -> None: msg = BasicTypes(check_fields=True) @@ -765,6 +809,33 @@ def test_bounded_sequences() -> None: assert msg.bool_values == [True, False] +@pytest.mark.parametrize( + ('message_type', 'field_name', 'type_code', 'values'), + ( + (BoundedSequences, 'char_values', 'B', [0, 1, 255]), + (BoundedSequences, 'float32_values', 'f', [0.0, 1.25, -2.5]), + (BoundedSequences, 'int16_values', 'h', [0, -32768, 32767]), + (UnboundedSequences, 'uint16_values', 'H', [0, 1, 65535]), + (UnboundedSequences, 'int32_values', 'l', [0, -2147483648, 2147483647]), + (UnboundedSequences, 'uint64_values', 'Q', [0, 1, 18446744073709551615]), + ), +) +def test_primitive_sequences_convert_to_py( + message_type: Any, + field_name: str, + type_code: str, + values: list[Any], +) -> None: + msg = message_type(check_fields=True) + setattr(msg, field_name, values) + + converted_msg = _convert_through_type_support(message_type, msg) + converted_field = getattr(converted_msg, field_name) + + assert converted_field.typecode == type_code + assert converted_field == array.array(type_code, values) + + def test_unbounded_sequences() -> None: msg = UnboundedSequences(check_fields=True)