diff --git a/rosidl_parser/rosidl_parser/idl_to_message_spec.py b/rosidl_parser/rosidl_parser/idl_to_message_spec.py new file mode 100644 index 000000000..376cfc156 --- /dev/null +++ b/rosidl_parser/rosidl_parser/idl_to_message_spec.py @@ -0,0 +1,157 @@ +from pathlib import Path +from types import NoneType +from typing import Any, List, Type + +from ament_index_python.packages import get_package_share_directory + +from rosidl_parser.parser import parse_idl_file +import rosidl_parser.definition as idl_def +import rosidl_adapter.parser as rosidl_parser +from rosidl_adapter.msg import MSG_TYPE_TO_IDL + +IDL_TYPE_TO_MSG = {v: k for k, v in MSG_TYPE_TO_IDL.items()} + +BOUNDED_TYPES = [idl_def.BoundedSequence, idl_def.Array] +BRACKET_TYPES = BOUNDED_TYPES + [idl_def.UnboundedSequence, idl_def.Array] +STRING_BOUND_TYPES = [idl_def.BoundedString, idl_def.BoundedWString] + +UNSUPPORTED_ROSIDL_TYPES = { + 'long double': 'float64' +} + + +def find_value(haystack: List, name: str) -> Any: + for a in haystack: + if a.name == name: + return a.value + + return None + + +def find_annotation_value(annotations: List, name: str, value_key="value") -> Any: + for a in annotations: + if a.name == name: + return a.value[value_key] + + return None + + +def resolve_typename(member_type: Type[idl_def.AbstractType]) -> str: + if isinstance(member_type, idl_def.BasicType): + return member_type.typename + elif isinstance(member_type, idl_def.AbstractWString): + return "wstring" + elif isinstance(member_type, idl_def.AbstractString): + return "string" + elif isinstance(member_type, idl_def.NamedType): + return member_type.name + elif isinstance(member_type, idl_def.AbstractNestedType): + return resolve_typename(member_type.value_type) + else: + return member_type.name + + +def build_type_string(member_type: Type[idl_def.AbstractType], constants: List[idl_def.Constant]) -> str: + type_string = resolve_typename(member_type) + + if type_string in IDL_TYPE_TO_MSG: + type_string = IDL_TYPE_TO_MSG[type_string] + elif type_string in UNSUPPORTED_ROSIDL_TYPES: + print(f"WARNING: Unsupported type could result in in loss of precision '{type_string}' --> '{UNSUPPORTED_ROSIDL_TYPES[type_string]}'") + type_string = UNSUPPORTED_ROSIDL_TYPES[type_string] + + has_string_bounds = any(isinstance(member_type, t) for t in STRING_BOUND_TYPES) + string_upper_bound = member_type.maximum_size if has_string_bounds else None + has_brackets = any(isinstance(member_type, t) for t in BRACKET_TYPES) + + if type_string == 'wchar': + # hack to support wchar as a wstring with length 1 + type_string = 'wstring' + has_string_bounds = True + string_upper_bound = 1 + + if has_string_bounds: + if type(string_upper_bound) == str: + # constant bounded type needs to be resolved to the value + string_upper_bound = find_value(constants, string_upper_bound) + assert string_upper_bound is not None + + type_string += f"{rosidl_parser.STRING_UPPER_BOUND_TOKEN}{string_upper_bound}" + + if has_brackets: + bounds = '' + if isinstance(member_type, idl_def.BoundedSequence): + bounds = f"{rosidl_parser.ARRAY_UPPER_BOUND_TOKEN}{member_type.maximum_size}" + if isinstance(member_type, idl_def.Array): + bounds = f"{member_type.size}" + type_string += f"[{bounds}]" + + return type_string + + +def process_constants(constants: List[idl_def.Constant]) -> List[rosidl_parser.Constant]: + out_constants = [] + for c in constants: + if isinstance(c.type, idl_def.BoundedString): + typename = "string" + elif isinstance(c.type, idl_def.BoundedWString): + typename = "wstring" + else: + typename = IDL_TYPE_TO_MSG[c.type.typename] + out_constants.append(rosidl_parser.Constant(typename, c.name, str(c.value))) + return out_constants + + +def process_members(members: List[idl_def.Member], constants: List[idl_def.Constant]) -> List[rosidl_parser.Field]: + fields = [] + for m in members: + type_pkg_name = None + type_string = build_type_string(m.type, constants) + + if isinstance(m.type, idl_def.NamespacedType): + type_pkg_name = m.type.namespaces[0] + + field_type = rosidl_parser.Type(type_string, type_pkg_name) + + default_value_str = find_annotation_value(m.annotations, 'default') + if type(default_value_str) not in [NoneType, str]: + default_value_str = str(default_value_str) + + fields.append(rosidl_parser.Field(field_type, m.name, default_value_str)) + + return fields + + +def parse_idl_message(pkg_name: str, msg_name: str, idl_msg: idl_def.Message) -> rosidl_parser.MessageSpecification: + fields = process_members(idl_msg.structure.members, idl_msg.constants) + constants = process_constants(idl_msg.constants) + + msg = rosidl_parser.MessageSpecification(pkg_name, msg_name, fields, constants) + # TODO add comments + # msg.annotations['comment'] = message_comments + + return msg + + +def parse_idl_to_message_spec(pkg_name: str, interface_file_path: str) -> rosidl_parser.MessageSpecification: + path = Path(interface_file_path) + # share_dir = get_package_share_directory(pkg_name) + share_dir = path.parent + msg_name = path.stem + + idl_file = parse_idl_file(idl_def.IdlLocator(share_dir, path.relative_to(share_dir))) + idl_msg = idl_file.content.get_elements_of_type(idl_def.Message)[0] + return parse_idl_message(pkg_name, msg_name, idl_msg) + + +def parse_idl_to_service_spec(pkg_name: str, interface_file_path: str) -> rosidl_parser.ServiceSpecification: + path = Path(interface_file_path) + share_dir = get_package_share_directory(pkg_name) + srv_name = path.stem + + idl_file = parse_idl_file(idl_def.IdlLocator(share_dir, path.relative_to(share_dir))) + idl_srv = idl_file.content.get_elements_of_type(idl_def.Service)[0] + request_message = parse_idl_message(pkg_name, srv_name + rosidl_parser.SERVICE_REQUEST_MESSAGE_SUFFIX, idl_srv.request_message) + response_message = parse_idl_message(pkg_name, srv_name + rosidl_parser.SERVICE_RESPONSE_MESSAGE_SUFFIX, idl_srv.response_message) + + return rosidl_parser.ServiceSpecification(pkg_name, srv_name, request_message, response_message) diff --git a/rosidl_parser/test/msg/MyMessage.msg b/rosidl_parser/test/msg/MyMessage.msg new file mode 100644 index 000000000..6f3bd1ba1 --- /dev/null +++ b/rosidl_parser/test/msg/MyMessage.msg @@ -0,0 +1,53 @@ +# tmp_msgs/MyMessage +int16 SHORT_CONSTANT=-23 +uint32 UNSIGNED_LONG_CONSTANT=42 +float32 FLOAT_CONSTANT=1.25 +bool BOOLEAN_CONSTANT=True +string STRING_CONSTANT='string_value' +wstring WSTRING_CONSTANT='wstring_value_\u2122' +string EMPTY_STRING_CONSTANT='' +int16 short_value +int16 short_value2 +uint16 unsigned_short_value 123 +int32 long_value +uint32 unsigned_long_value +int64 long_long_value +uint64 unsigned_long_long_value +float32 float_value +float64 double_value +float64 long_double_value +char char_value +wstring<=1 wchar_value +bool boolean_value +byte octet_value +int8 int8_value +uint8 uint8_value +int16 int16_value +uint16 uint16_value +int32 int32_value +uint32 uint32_value +int64 int64_value +uint64 uint64_value +string string_value +string<=5 bounded_string_value +wstring wstring_value +wstring<=23 bounded_wstring_value +wstring<=42 constant_bounded_wstring_value +int16[] unbounded_short_values +int16[<=5] bounded_short_values +string[] unbounded_values_of_bounded_strings +string[<=4] bounded_values_of_bounded_strings +int16[23] array_short_values +float32 int_and_frac_with_positive_scientific 19000000000.0 +float32 int_and_frac_with_explicit_positive_scientific 19000000000.0 +float32 int_and_frac_with_negative_scientific 1.1e-10 +float32 int_and_frac 9e-05 +float32 int_with_empty_frac 1.0 +float32 frac_only 0.1 +float32 int_with_positive_scientific 900000.0 +float32 int_with_explicit_positive_scientific 900000.0 +float32 int_with_negative_scientific 9e-05 +float32 fixed_int_and_frac 8.7 +float32 fixed_int_with_dot_only 4.0 +float32 fixed_frac_only 0.3 +float32 fixed_int_only 7.0 \ No newline at end of file diff --git a/rosidl_parser/test/test_msg_spec_translate.py b/rosidl_parser/test/test_msg_spec_translate.py new file mode 100644 index 000000000..0c2bc995f --- /dev/null +++ b/rosidl_parser/test/test_msg_spec_translate.py @@ -0,0 +1,418 @@ +# Copyright 2022 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 pathlib + +import pytest + +from rosidl_parser.idl_to_message_spec import parse_idl_to_message_spec, parse_idl_to_service_spec + +MESSAGE_IDL_PATH = pathlib.Path(__file__).parent / pathlib.Path('msg') / 'MyMessage.idl' +MESSAGE_ROSIDL_PATH = pathlib.Path(__file__).parent / pathlib.Path('msg') / 'MyMessage.msg' + +# SERVICE_IDL_LOCATOR = IdlLocator( +# pathlib.Path(__file__).parent, pathlib.Path('srv') / 'MyService.idl') +# ACTION_IDL_LOCATOR = IdlLocator( +# pathlib.Path(__file__).parent, pathlib.Path('action') / 'MyAction.idl') + + +@pytest.fixture(scope='module') +def rosidl_msg_spec(): + return parse_idl_to_message_spec('tmp_msgs', MESSAGE_IDL_PATH) + + +def test_idl_to_rosidl_translation(rosidl_msg_spec): + assert str(rosidl_msg_spec) == MESSAGE_ROSIDL_PATH.read_text() + + +# def test_message_parser_structure(message_rosidl_file): +# messages = message_idl_file.content.get_elements_of_type(Message) +# assert len(messages) == 1 +# +# constants = messages[0].constants +# assert len(constants) == 7 +# +# assert constants[0].name == 'SHORT_CONSTANT' +# assert isinstance(constants[0].type, BasicType) +# assert constants[0].type.typename == 'int16' +# assert constants[0].value == -23 +# +# assert constants[1].name == 'UNSIGNED_LONG_CONSTANT' +# assert isinstance(constants[1].type, BasicType) +# assert constants[1].type.typename == 'uint32' +# assert constants[1].value == 42 +# +# assert constants[2].name == 'FLOAT_CONSTANT' +# assert isinstance(constants[2].type, BasicType) +# assert constants[2].type.typename == 'float' +# assert constants[2].value == 1.25 +# +# assert constants[3].name == 'BOOLEAN_CONSTANT' +# assert isinstance(constants[3].type, BasicType) +# assert constants[3].type.typename == 'boolean' +# assert constants[3].value is True +# +# assert constants[4].name == 'STRING_CONSTANT' +# assert isinstance(constants[4].type, BoundedString) +# assert constants[4].value == 'string_value' +# +# assert constants[5].name == 'WSTRING_CONSTANT' +# assert isinstance(constants[5].type, BoundedWString) +# assert constants[5].value == 'wstring_value_\\u2122' +# +# assert constants[6].name == 'EMPTY_STRING_CONSTANT' +# assert isinstance(constants[6].type, BoundedString) +# assert constants[6].value == '' +# +# structure = messages[0].structure +# assert structure.namespaced_type.namespaces == ['rosidl_parser', 'msg'] +# assert structure.namespaced_type.name == 'MyMessage' +# assert len(structure.members) == 45 +# +# assert isinstance(structure.members[0].type, BasicType) +# assert structure.members[0].type.typename == 'int16' +# assert structure.members[0].name == 'short_value' +# assert isinstance(structure.members[1].type, BasicType) +# assert structure.members[1].type.typename == 'int16' +# assert structure.members[1].name == 'short_value2' +# +# assert isinstance(structure.members[22].type, UnboundedString) +# assert structure.members[22].name == 'string_value' +# assert isinstance(structure.members[23].type, BoundedString) +# assert structure.members[23].type.maximum_size == 5 +# assert structure.members[23].name == 'bounded_string_value' +# +# assert isinstance(structure.members[24].type, UnboundedWString) +# assert structure.members[24].name == 'wstring_value' +# assert isinstance(structure.members[25].type, BoundedWString) +# assert structure.members[25].type.maximum_size == 23 +# assert structure.members[25].name == 'bounded_wstring_value' +# assert isinstance(structure.members[26].type, BoundedWString) +# assert structure.members[26].type.maximum_size == 'UNSIGNED_LONG_CONSTANT' +# assert structure.members[26].name == 'constant_bounded_wstring_value' +# +# assert isinstance(structure.members[27].type, UnboundedSequence) +# assert isinstance(structure.members[27].type.value_type, BasicType) +# assert structure.members[27].type.value_type.typename == 'int16' +# assert structure.members[27].name == 'unbounded_short_values' +# assert isinstance(structure.members[28].type, BoundedSequence) +# assert isinstance(structure.members[28].type.value_type, BasicType) +# assert structure.members[28].type.value_type.typename == 'int16' +# assert structure.members[28].type.maximum_size == 5 +# assert structure.members[28].name == 'bounded_short_values' +# +# assert isinstance(structure.members[29].type, UnboundedSequence) +# assert isinstance(structure.members[29].type.value_type, BoundedString) +# assert structure.members[29].type.value_type.maximum_size == 3 +# assert structure.members[29].name == 'unbounded_values_of_bounded_strings' +# +# assert isinstance(structure.members[30].type, BoundedSequence) +# assert isinstance(structure.members[30].type.value_type, BoundedString) +# assert structure.members[30].type.value_type.maximum_size == 3 +# assert structure.members[30].type.maximum_size == 4 +# assert structure.members[30].name == 'bounded_values_of_bounded_strings' +# +# assert isinstance(structure.members[31].type, Array) +# assert isinstance(structure.members[31].type.value_type, BasicType) +# assert structure.members[31].type.value_type.typename == 'int16' +# assert structure.members[31].type.size == 23 +# assert structure.members[31].name == 'array_short_values' +# +# +# def test_message_parser_annotations(message_idl_file): +# messages = message_idl_file.content.get_elements_of_type(Message) +# assert len(messages) == 1 +# structure = messages[0].structure +# +# assert len(structure.annotations) == 2 +# assert structure.annotations[0].name == 'verbatim' +# assert len(structure.annotations[0].value) == 2 +# assert 'language' in structure.annotations[0].value +# assert structure.annotations[0].value['language'] == 'comment' +# assert 'text' in structure.annotations[0].value +# assert structure.annotations[0].value['text'] == \ +# 'Documentation of MyMessage.Adjacent string literal.' +# +# assert structure.annotations[1].name == 'transfer_mode' +# assert structure.annotations[1].value == 'SHMEM_REF' +# +# assert len(structure.members[2].annotations) == 1 +# +# assert structure.members[2].annotations[0].name == 'default' +# assert len(structure.members[2].annotations[0].value) == 1 +# assert 'value' in structure.members[2].annotations[0].value +# assert structure.members[2].annotations[0].value['value'] == 123 +# +# assert len(structure.members[3].annotations) == 2 +# +# assert structure.members[3].annotations[0].name == 'key' +# assert structure.members[3].annotations[0].value is None +# +# assert structure.members[3].annotations[1].name == 'range' +# assert len(structure.members[3].annotations[1].value) == 2 +# assert 'min' in structure.members[3].annotations[1].value +# assert structure.members[3].annotations[1].value['min'] == -10 +# assert 'max' in structure.members[3].annotations[1].value +# assert structure.members[3].annotations[1].value['max'] == 10 +# +# assert isinstance(structure.members[32].type, BasicType) +# assert structure.members[32].type.typename == 'float' +# assert structure.members[32].name == 'int_and_frac_with_positive_scientific' +# assert len(structure.members[32].annotations) == 1 +# assert structure.members[32].annotations[0].name == 'default' +# +# assert isinstance(structure.members[33].type, BasicType) +# assert structure.members[33].type.typename == 'float' +# assert structure.members[33].name == 'int_and_frac_with_explicit_positive_scientific' +# assert len(structure.members[33].annotations) == 1 +# assert structure.members[33].annotations[0].name == 'default' +# +# assert isinstance(structure.members[34].type, BasicType) +# assert structure.members[34].type.typename == 'float' +# assert structure.members[34].name == 'int_and_frac_with_negative_scientific' +# assert len(structure.members[34].annotations) == 1 +# assert structure.members[34].annotations[0].name == 'default' +# +# assert isinstance(structure.members[35].type, BasicType) +# assert structure.members[35].type.typename == 'float' +# assert structure.members[35].name == 'int_and_frac' +# assert len(structure.members[35].annotations) == 1 +# assert structure.members[35].annotations[0].name == 'default' +# +# assert isinstance(structure.members[36].type, BasicType) +# assert structure.members[36].type.typename == 'float' +# assert structure.members[36].name == 'int_with_empty_frac' +# assert len(structure.members[36].annotations) == 1 +# assert structure.members[36].annotations[0].name == 'default' +# +# assert isinstance(structure.members[37].type, BasicType) +# assert structure.members[37].type.typename == 'float' +# assert structure.members[37].name == 'frac_only' +# assert len(structure.members[37].annotations) == 1 +# assert structure.members[37].annotations[0].name == 'default' +# +# assert isinstance(structure.members[38].type, BasicType) +# assert structure.members[38].type.typename == 'float' +# assert structure.members[38].name == 'int_with_positive_scientific' +# assert len(structure.members[38].annotations) == 1 +# assert structure.members[38].annotations[0].name == 'default' +# +# assert isinstance(structure.members[39].type, BasicType) +# assert structure.members[39].type.typename == 'float' +# assert structure.members[39].name == 'int_with_explicit_positive_scientific' +# assert len(structure.members[39].annotations) == 1 +# assert structure.members[39].annotations[0].name == 'default' +# +# assert isinstance(structure.members[40].type, BasicType) +# assert structure.members[40].type.typename == 'float' +# assert structure.members[40].name == 'int_with_negative_scientific' +# assert len(structure.members[40].annotations) == 1 +# assert structure.members[40].annotations[0].name == 'default' +# +# assert isinstance(structure.members[41].type, BasicType) +# assert structure.members[41].type.typename == 'float' +# assert structure.members[41].name == 'fixed_int_and_frac' +# assert len(structure.members[41].annotations) == 1 +# assert structure.members[41].annotations[0].name == 'default' +# +# assert isinstance(structure.members[42].type, BasicType) +# assert structure.members[42].type.typename == 'float' +# assert structure.members[42].name == 'fixed_int_with_dot_only' +# assert len(structure.members[42].annotations) == 1 +# assert structure.members[42].annotations[0].name == 'default' +# +# assert isinstance(structure.members[43].type, BasicType) +# assert structure.members[43].type.typename == 'float' +# assert structure.members[43].name == 'fixed_frac_only' +# assert len(structure.members[43].annotations) == 1 +# assert structure.members[43].annotations[0].name == 'default' +# +# assert isinstance(structure.members[44].type, BasicType) +# assert structure.members[44].type.typename == 'float' +# assert structure.members[44].name == 'fixed_int_only' +# assert len(structure.members[44].annotations) == 1 +# assert structure.members[44].annotations[0].name == 'default' + + +# @pytest.fixture(scope='module') +# def service_rosidl_file(): +# return parse_idl_to_service_spec('tmp_msgs', SERVICE_IDL_PATH) + + +# def test_service_parser(service_rosidl_file): +# services = service_idl_file.content.get_elements_of_type(Service) +# assert len(services) == 1 +# +# srv = services[0] +# assert isinstance(srv, Service) +# assert srv.namespaced_type.namespaces == ['rosidl_parser', 'srv'] +# assert srv.namespaced_type.name == 'MyService' +# assert len(srv.request_message.structure.members) == 2 +# assert len(srv.response_message.structure.members) == 1 +# +# constants = srv.request_message.constants +# assert len(constants) == 1 +# +# assert constants[0].name == 'SHORT_CONSTANT' +# assert isinstance(constants[0].type, BasicType) +# assert constants[0].type.typename == 'int16' +# assert constants[0].value == -23 +# +# constants = srv.response_message.constants +# assert len(constants) == 1 +# +# assert constants[0].name == 'UNSIGNED_LONG_CONSTANT' +# assert isinstance(constants[0].type, BasicType) +# assert constants[0].type.typename == 'uint32' +# assert constants[0].value == 42 +# +# +# @pytest.fixture(scope='module') +# def action_idl_file(): +# return parse_idl_file(ACTION_IDL_LOCATOR) +# +# +# def test_action_parser(action_idl_file): +# actions = action_idl_file.content.get_elements_of_type(Action) +# assert len(actions) == 1 +# +# action = actions[0] +# assert isinstance(action, Action) +# assert action.namespaced_type.namespaces == ['rosidl_parser', 'action'] +# assert action.namespaced_type.name == 'MyAction' +# +# # check messages defined in the idl file +# constants = action.goal.constants +# assert len(constants) == 1 +# assert constants[0].name == 'SHORT_CONSTANT' +# assert isinstance(constants[0].type, BasicType) +# assert constants[0].type.typename == 'int16' +# assert constants[0].value == -23 +# +# structure = action.goal.structure +# assert structure.namespaced_type.namespaces == ['rosidl_parser', 'action'] +# assert structure.namespaced_type.name == 'MyAction_Goal' +# assert len(structure.members) == 1 +# assert isinstance(structure.members[0].type, BasicType) +# assert structure.members[0].type.typename == 'int32' +# assert structure.members[0].name == 'input_value' +# +# constants = action.result.constants +# assert len(constants) == 1 +# assert constants[0].name == 'UNSIGNED_LONG_CONSTANT' +# assert isinstance(constants[0].type, BasicType) +# assert constants[0].type.typename == 'uint32' +# assert constants[0].value == 42 +# +# structure = action.result.structure +# assert structure.namespaced_type.namespaces == ['rosidl_parser', 'action'] +# assert structure.namespaced_type.name == 'MyAction_Result' +# assert len(structure.members) == 1 +# assert isinstance(structure.members[0].type, BasicType) +# assert structure.members[0].type.typename == 'uint32' +# assert structure.members[0].name == 'output_value' +# +# constants = action.feedback.constants +# assert len(constants) == 1 +# assert constants[0].name == 'FLOAT_CONSTANT' +# assert isinstance(constants[0].type, BasicType) +# assert constants[0].type.typename == 'float' +# assert constants[0].value == 1.25 +# +# structure = action.feedback.structure +# assert structure.namespaced_type.namespaces == ['rosidl_parser', 'action'] +# assert structure.namespaced_type.name == 'MyAction_Feedback' +# assert len(structure.members) == 1 +# assert isinstance(structure.members[0].type, BasicType) +# assert structure.members[0].type.typename == 'float' +# assert structure.members[0].name == 'progress_value' +# +# # check derived goal service +# namespaced_type = action.send_goal_service.namespaced_type +# assert namespaced_type.namespaces == ['rosidl_parser', 'action'] +# assert namespaced_type.name == 'MyAction_SendGoal' +# +# structure = action.send_goal_service.request_message.structure +# assert len(structure.members) == 2 +# +# assert isinstance(structure.members[0].type, NamespacedType) +# assert structure.members[0].type.namespaces == [ +# 'unique_identifier_msgs', 'msg'] +# assert structure.members[0].type.name == 'UUID' +# assert structure.members[0].name == 'goal_id' +# +# assert isinstance(structure.members[1].type, NamespacedType) +# assert structure.members[1].type.namespaces == \ +# action.goal.structure.namespaced_type.namespaces +# assert structure.members[1].type.name == \ +# action.goal.structure.namespaced_type.name +# +# structure = action.send_goal_service.response_message.structure +# assert len(structure.members) == 2 +# +# assert isinstance(structure.members[0].type, BasicType) +# assert structure.members[0].type.typename == 'boolean' +# assert structure.members[0].name == 'accepted' +# +# assert isinstance(structure.members[1].type, NamespacedType) +# assert structure.members[1].type.namespaces == [ +# 'builtin_interfaces', 'msg'] +# assert structure.members[1].type.name == 'Time' +# assert structure.members[1].name == 'stamp' +# +# # check derived result service +# namespaced_type = action.get_result_service.namespaced_type +# assert namespaced_type.namespaces == ['rosidl_parser', 'action'] +# assert namespaced_type.name == 'MyAction_GetResult' +# +# structure = action.get_result_service.request_message.structure +# assert len(structure.members) == 1 +# +# assert isinstance(structure.members[0].type, NamespacedType) +# assert structure.members[0].type.namespaces == [ +# 'unique_identifier_msgs', 'msg'] +# assert structure.members[0].type.name == 'UUID' +# assert structure.members[0].name == 'goal_id' +# +# structure = action.get_result_service.response_message.structure +# assert len(structure.members) == 2 +# +# assert isinstance(structure.members[0].type, BasicType) +# assert structure.members[0].type.typename == 'int8' +# assert structure.members[0].name == 'status' +# +# assert isinstance(structure.members[1].type, NamespacedType) +# assert structure.members[1].type.namespaces == \ +# action.result.structure.namespaced_type.namespaces +# assert structure.members[1].type.name == \ +# action.result.structure.namespaced_type.name +# +# # check derived feedback message +# structure = action.feedback_message.structure +# assert structure.namespaced_type.namespaces == ['rosidl_parser', 'action'] +# assert structure.namespaced_type.name == 'MyAction_FeedbackMessage' +# +# assert len(structure.members) == 2 +# +# assert isinstance(structure.members[0].type, NamespacedType) +# assert structure.members[0].type.namespaces == [ +# 'unique_identifier_msgs', 'msg'] +# assert structure.members[0].type.name == 'UUID' +# assert structure.members[0].name == 'goal_id' +# +# assert isinstance(structure.members[1].type, NamespacedType) +# assert structure.members[1].type.namespaces == \ +# action.feedback.structure.namespaced_type.namespaces +# assert structure.members[1].type.name == \ +# action.feedback.structure.namespaced_type.name