Skip to content

Commit

Permalink
Protobuf typeadapter tests (#32)
Browse files Browse the repository at this point in the history

---------

Signed-off-by: Gonzo de Pedro <[email protected]>
Signed-off-by: Gonzalo de Pedro <[email protected]>
Co-authored-by: Gabriel <[email protected]>
Co-authored-by: gdiaz-guevara <[email protected]>
Co-authored-by: Tully Foote <[email protected]>
  • Loading branch information
4 people authored Jan 8, 2024
1 parent 669a2d9 commit 8148695
Show file tree
Hide file tree
Showing 25 changed files with 1,974 additions and 2 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/basic_ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,6 @@ jobs:
path: ros_ws/src
- uses: ros-tooling/[email protected]
with:
package-name: rosidl_adapter_proto rosidl_typesupport_protobuf rosidl_typesupport_protobuf_cpp rosidl_typesupport_protobuf_c
package-name: rosidl_adapter_proto rosidl_typesupport_protobuf rosidl_typesupport_protobuf_cpp rosidl_typesupport_protobuf_c rosidl_typeadapter_protobuf_test
target-ros2-distro: ${{ matrix.ros_distribution }}
vcs-repo-file-url: https://raw.githubusercontent.com/ros2/ros2/${{ matrix.ros_distribution }}/ros2.repos
15 changes: 15 additions & 0 deletions rosidl_adapter_proto/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,21 @@ project(rosidl_adapter_proto)
find_package(ament_cmake REQUIRED)
find_package(ament_cmake_python REQUIRED)

if(BUILD_TESTING)
find_package(ament_cmake_pytest REQUIRED)
find_package(rosidl_cmake REQUIRED)
ament_add_pytest_test(pytest test)
set(generator_arguments_file "${CMAKE_CURRENT_SOURCE_DIR}/test/msg/test_rosid_adapter_proto__arguments.json")
rosidl_write_generator_arguments(
"${generator_arguments_file}"
PACKAGE_NAME "${PROJECT_NAME}"
IDL_TUPLES "${CMAKE_CURRENT_SOURCE_DIR}/test:msg/BoolTest.idl"
OUTPUT_DIR "${CMAKE_CURRENT_SOURCE_DIR}/test"
TEMPLATE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/resource"
TARGET_DEPENDENCIES "${CMAKE_CURRENT_SOURCE_DIR}/test/msg/BoolTest.idl"
ADDITIONAL_FILES "")
endif()

ament_python_install_package(${PROJECT_NAME})

ament_package(CONFIG_EXTRAS "cmake/rosidl_adapter_proto-extras.cmake.in")
Expand Down
3 changes: 3 additions & 0 deletions rosidl_adapter_proto/package.xml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@
<exec_depend>rosidl_cli</exec_depend>
<exec_depend>rosidl_parser</exec_depend>

<test_depend>ament_cmake_pytest</test_depend>
<test_depend>rosidl_cmake</test_depend>

<member_of_group>rosidl_interface_packages</member_of_group>

<export>
Expand Down
2 changes: 2 additions & 0 deletions rosidl_adapter_proto/pytest.ini
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
[pytest]
junit_family=xunit2
7 changes: 7 additions & 0 deletions rosidl_adapter_proto/test/msg/BoolTest.idl
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
module rosidl_adapter_proto {
@verbatim (language="comment", text=
"This is for test purposes.")
struct Bool {
boolean data;
};
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{

}
172 changes: 172 additions & 0 deletions rosidl_adapter_proto/test/test_adapter_proto_message.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
# ================================= Apache 2.0 =================================
#
# Copyright (C) 2021 Continental
#
# 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.
#
# ================================= Apache 2.0 =================================

import pathlib

import pytest

from rosidl_adapter_proto import collect_proto_imports
from rosidl_adapter_proto import compute_proto_field_number
from rosidl_adapter_proto import generate_proto
from rosidl_adapter_proto import MSG_TYPE_TO_PROTO
from rosidl_adapter_proto import to_proto_import
from rosidl_parser.definition import IdlLocator
from rosidl_parser.definition import Message
from rosidl_parser.parser import parse_idl_file


MESSAGE_IDL_LOCATOR = IdlLocator(pathlib.Path(__file__).parent,
pathlib.Path('msg') / 'BoolTest.idl')


@pytest.fixture(scope='module')
def message_idl_file():
return parse_idl_file(MESSAGE_IDL_LOCATOR)


def search_word(file_path, word):
with open(file_path, 'r') as file:
content = file.read()
if word in content:
return True
return False


def get_member_name(message_idl_file):
messages = message_idl_file.content.get_elements_of_type(Message)
member = messages[0].structure.members[0]
return member.name


def test_message_proto_generated_invalid_argument():
with pytest.raises(Exception):
generate_file_argument = IdlLocator(
pathlib.Path(__file__).parent,
pathlib.Path('msg') / 'empty_document.json')
rc = generate_proto(generate_file_argument.get_absolute_path())

assert rc is None


def test_message_proto_generated_empty_file():
with pytest.raises(Exception):
generate_file_argument = IdlLocator(
pathlib.Path(__file__).parent,
pathlib.Path('msg') / 'test_rosidl_adapter__empty_args.json')
rc = generate_proto(generate_file_argument.get_absolute_path())

assert rc is None


def test_message_proto_generated(message_idl_file):
generate_file_argument = IdlLocator(
pathlib.Path(__file__).parent,
pathlib.Path('msg') / 'test_rosid_adapter_proto__arguments.json')
rc = generate_proto(generate_file_argument.get_absolute_path())

assert rc is not None

messages = message_idl_file.content.get_elements_of_type(Message)
member = messages[0].structure.members[0]
field_number = compute_proto_field_number(member.name)
proto_type = MSG_TYPE_TO_PROTO[member.type.typename]

proto_file_name = IdlLocator(
pathlib.Path(__file__).parent,
pathlib.Path('msg') / 'BoolTest.proto')

assert search_word(proto_file_name.get_absolute_path(), member.name) is True
assert search_word(proto_file_name.get_absolute_path(), str(field_number)) is True
assert search_word(proto_file_name.get_absolute_path(), proto_type) is True


def test_compute_proto_field_number_is_greter_than_0(message_idl_file):
member_name = get_member_name(message_idl_file)
field_number = compute_proto_field_number(member_name)

assert field_number > 0


def test_compute_proto_field_number_is_not_in_the_reserved_range(message_idl_file):
member_name = get_member_name(message_idl_file)
field_number = compute_proto_field_number(member_name)

assert field_number not in range(19000, 19999)


def test_compute_proto_field_number_repeated_with_same_member_name(message_idl_file):
member_name = get_member_name(message_idl_file)
field_number = compute_proto_field_number(member_name)
second_field_number = compute_proto_field_number('data')

assert field_number == second_field_number


def test_msg_type_to_proto_from_message_file(message_idl_file):
messages = message_idl_file.content.get_elements_of_type(Message)
member = messages[0].structure.members[0]
proto_type = MSG_TYPE_TO_PROTO[member.type.typename]
assert proto_type == 'bool'


def test_msg_type_to_proto_mapping():
idl_messages = ['boolean', 'octet', 'char', 'wchar', 'float',
'double', 'long double', 'uint8', 'int8',
'uint16', 'int16', 'uint32', 'int32', 'uint64',
'int64', 'string', 'wstring']
expected_proto_type_ = ['bool', 'uint32', 'uint32', 'uint32',
'float', 'double', 'double', 'uint32',
'int32', 'uint32', 'int32', 'fixed32',
'sfixed32', 'fixed64', 'sfixed64',
'string', 'bytes']
index = 0
for idl_message in idl_messages:
proto_type = MSG_TYPE_TO_PROTO[idl_message]
assert expected_proto_type_[index] == proto_type
index += 1


def test_msg_type_to_proto_invalid():
with pytest.raises(Exception):
MSG_TYPE_TO_PROTO['integer']


def test_to_proto_import(message_idl_file):
messages = message_idl_file.content.get_elements_of_type(Message)
namespace_type = messages[0].structure.namespaced_type
proto_import = to_proto_import(namespace_type)
assert proto_import == 'rosidl_adapter_proto/Bool.proto'


def test_to_proto_import_invalid_argument():
with pytest.raises(Exception):
to_proto_import('invalid argument')


def test_collect_proto_import(message_idl_file):
messages = message_idl_file.content.get_elements_of_type(Message)
proto_import_set = set()
for message in messages:
proto_import_set.update(collect_proto_imports(message))
for proto_file in proto_import_set:
assert proto_file == 'rosidl_adapter_proto/Bool.proto'


def test_collect_proto_import_invalid_argument():
with pytest.raises(Exception):
collect_proto_imports('string value')
148 changes: 148 additions & 0 deletions rosidl_typeadapter_protobuf_test/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
cmake_minimum_required(VERSION 3.5)

project(rosidl_typeadapter_protobuf_test)

# Default to C++17
if(NOT CMAKE_CXX_STANDARD)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
endif()
if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang")
add_compile_options(-Wall -Wextra -Wpedantic)
endif()
if(CMAKE_BUILD_TYPE STREQUAL "Debug" AND MSVC)
# /bigobj is needed to avoid error C1128:
# https://msdn.microsoft.com/en-us/library/8578y171.aspx
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /bigobj")
endif()

option(SKIP_SINGLE_RMW_TESTS
"Skip tests involving only a single RMW implementation" OFF)
option(SKIP_MULTI_RMW_TESTS
"Skip tests involving only multiple RMW implementations" OFF)

find_package(ament_cmake_auto REQUIRED)
ament_auto_find_build_dependencies()

if(BUILD_TESTING)
find_package(ament_cmake REQUIRED)
find_package(osrf_testing_tools_cpp REQUIRED)
find_package(rcpputils REQUIRED)
find_package(rcl REQUIRED)
find_package(rclcpp REQUIRED)
find_package(test_msgs REQUIRED)
find_package(rosidl_typesupport_protobuf_cpp REQUIRED)


include_directories(include)

ament_index_get_resource(interface_files "rosidl_interfaces" "test_msgs")
string(REPLACE "\n" ";" interface_files "${interface_files}")


set(message_files "")
set(service_files "")
set(action_files "")
foreach(interface_file ${interface_files})
get_filename_component(interface_ns "${interface_file}" DIRECTORY)
get_filename_component(interface_ns "${interface_ns}" NAME)
string_ends_with("${interface_file}" ".msg" is_message)
if(is_message AND interface_ns STREQUAL "msg")
list(APPEND message_files "${interface_file}")
continue()
endif()
string_ends_with("${interface_file}" ".srv" is_service)
if(is_service AND interface_ns STREQUAL "srv")
list(APPEND service_files "${interface_file}")
continue()
endif()
string_ends_with("${interface_file}" ".idl" is_action)
if(is_action AND interface_ns STREQUAL "action")
list(APPEND action_files "${interface_file}")
continue()
endif()
endforeach()

find_package(ament_lint_auto REQUIRED)
ament_lint_auto_find_test_dependencies()

# # Provides PYTHON_EXECUTABLE_DEBUG
# find_package(python_cmake_module REQUIRED)
# find_package(PythonExtra REQUIRED)

# get the rmw implementations ahead of time
find_package(rmw_implementation_cmake REQUIRED)
get_available_rmw_implementations(rmw_implementations2)
foreach(rmw_implementation ${rmw_implementations2})
find_package("${rmw_implementation}" REQUIRED)
endforeach()

function(custom_test target with_message_argument)
if(with_message_argument)
# adding test for each message type
foreach(message_file ${message_files})
get_filename_component(TEST_MESSAGE_TYPE "${message_file}" NAME_WE)
ament_add_test(
"${target}${target_suffix}__${TEST_MESSAGE_TYPE}"
COMMAND "$<TARGET_FILE:${target}>" "${TEST_MESSAGE_TYPE}"
TIMEOUT 15
GENERATE_RESULT_FOR_RETURN_CODE_ZERO
APPEND_LIBRARY_DIRS "${append_library_dirs}")
set_tests_properties(
"${target}${target_suffix}__${TEST_MESSAGE_TYPE}"
PROPERTIES REQUIRED_FILES "$<TARGET_FILE:${target}>"
)
endforeach()
else()
ament_add_test(
"${target}${target_suffix}"
COMMAND "$<TARGET_FILE:${target}>"
TIMEOUT 15
GENERATE_RESULT_FOR_RETURN_CODE_ZERO
APPEND_LIBRARY_DIRS "${append_library_dirs}")
set_tests_properties(
"${target}${target_suffix}"
PROPERTIES REQUIRED_FILES "$<TARGET_FILE:${target}>"
)
endif()
endfunction()

function(custom_executable target)
add_executable(${target} ${ARGN})
ament_target_dependencies(${target}
"rclcpp"
"rclcpp_action"
"test_msgs"
)
endfunction()

add_library(subscribe_types STATIC
"test/subscribe_array_types.cpp"
"test/subscribe_basic_types.cpp"
"test/subscribe_string_types.cpp")
ament_target_dependencies(subscribe_types
"rclcpp"
"test_msgs")

# publisher combined with a subscriber
custom_executable(test_proto_typeadapt_cpp
"test/test_proto_typeadapt.cpp")
target_link_libraries(test_proto_typeadapt_cpp subscribe_types rcpputils::rcpputils)


set(append_library_dirs "${CMAKE_CURRENT_BINARY_DIR}")
if(WIN32)
set(append_library_dirs "${append_library_dirs}/$<CONFIG>")
endif()

# finding gtest once in the highest scope
# prevents finding it repeatedly in each local scope
ament_find_gtest()

custom_test(test_proto_typeadapt_cpp TRUE)

endif() # BUILD_TESTING



ament_auto_package()
Loading

0 comments on commit 8148695

Please sign in to comment.