diff --git a/rclcpp_lifecycle/CMakeLists.txt b/rclcpp_lifecycle/CMakeLists.txt
index 953cd0d..91b300f 100644
--- a/rclcpp_lifecycle/CMakeLists.txt
+++ b/rclcpp_lifecycle/CMakeLists.txt
@@ -56,16 +56,27 @@ if(BUILD_TESTING)
ament_target_dependencies(test_lifecycle_node
"rcl_lifecycle"
"rclcpp"
+ "rcutils"
)
- target_link_libraries(test_lifecycle_node ${PROJECT_NAME})
+ target_link_libraries(test_lifecycle_node ${PROJECT_NAME} mimick)
+ endif()
+ ament_add_gtest(test_lifecycle_publisher test/test_lifecycle_publisher.cpp)
+ if(TARGET test_lifecycle_publisher)
+ ament_target_dependencies(test_lifecycle_publisher
+ "rcl_lifecycle"
+ "rclcpp"
+ "test_msgs"
+ )
+ target_link_libraries(test_lifecycle_publisher ${PROJECT_NAME})
endif()
ament_add_gtest(test_lifecycle_service_client test/test_lifecycle_service_client.cpp)
if(TARGET test_lifecycle_service_client)
ament_target_dependencies(test_lifecycle_service_client
"rcl_lifecycle"
"rclcpp"
+ "rcutils"
)
- target_link_libraries(test_lifecycle_service_client ${PROJECT_NAME})
+ target_link_libraries(test_lifecycle_service_client ${PROJECT_NAME} mimick)
endif()
ament_add_gtest(test_state_machine_info test/test_state_machine_info.cpp)
if(TARGET test_state_machine_info)
@@ -104,8 +115,12 @@ if(BUILD_TESTING)
ament_target_dependencies(test_transition_wrapper
"rcl_lifecycle"
"rclcpp"
+ "rcutils"
+ )
+ target_link_libraries(test_transition_wrapper ${PROJECT_NAME} mimick)
+ target_compile_definitions(test_transition_wrapper
+ PUBLIC RCUTILS_ENABLE_FAULT_INJECTION
)
- target_link_libraries(test_transition_wrapper ${PROJECT_NAME})
endif()
endif()
diff --git a/rclcpp_lifecycle/package.xml b/rclcpp_lifecycle/package.xml
index 0a315cb..2b3bc1f 100644
--- a/rclcpp_lifecycle/package.xml
+++ b/rclcpp_lifecycle/package.xml
@@ -24,6 +24,9 @@
ament_cmake_gtest
ament_lint_auto
ament_lint_common
+ mimick_vendor
+ rcutils
+ test_msgs
ament_cmake
diff --git a/rclcpp_lifecycle/test/mocking_utils/patch.hpp b/rclcpp_lifecycle/test/mocking_utils/patch.hpp
new file mode 100644
index 0000000..7551beb
--- /dev/null
+++ b/rclcpp_lifecycle/test/mocking_utils/patch.hpp
@@ -0,0 +1,527 @@
+// Copyright 2020 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.
+
+// Original file taken from:
+// https://github.com/ros2/rcutils/blob/master/test/mocking_utils/patch.hpp
+
+#ifndef MOCKING_UTILS__PATCH_HPP_
+#define MOCKING_UTILS__PATCH_HPP_
+
+#define MOCKING_UTILS_SUPPORT_VA_LIST
+#if (defined(__aarch64__) || defined(__arm__) || defined(_M_ARM) || defined(__thumb__))
+// In ARM machines, va_list does not define comparison operators
+// nor the compiler allows defining them via operator overloads.
+// Thus, Mimick argument matching code will not compile.
+#undef MOCKING_UTILS_SUPPORT_VA_LIST
+#endif
+
+#ifdef MOCKING_UTILS_SUPPORT_VA_LIST
+#include
+#endif
+
+#include
+#include
+#include
+#include
+
+#include "mimick/mimick.h"
+
+#include "rcutils/error_handling.h"
+#include "rcutils/macros.h"
+
+namespace mocking_utils
+{
+
+/// Mimick specific traits for each mocking_utils::Patch instance.
+/**
+ * \tparam ID Numerical identifier of the patch. Ought to be unique.
+ * \tparam SignatureT Type of the symbol to be patched.
+*/
+template
+struct PatchTraits;
+
+/// Traits specialization for ReturnT(void) free functions.
+/**
+ * \tparam ID Numerical identifier of the patch. Ought to be unique.
+ * \tparam ReturnT Return value type.
+ */
+template
+struct PatchTraits
+{
+ mmk_mock_define(mock_type, ReturnT);
+};
+
+/// Traits specialization for void(void) free functions.
+/**
+ * Necessary for Mimick macros to adjust accordingly when the return
+ * type is `void`.
+ *
+ * \tparam ID Numerical identifier of the patch. Ought to be unique.
+ */
+template
+struct PatchTraits
+{
+ mmk_mock_define(mock_type, void);
+};
+
+/// Traits specialization for ReturnT(ArgT0) free functions.
+/**
+ * \tparam ID Numerical identifier of the patch. Ought to be unique.
+ * \tparam ReturnT Return value type.
+ * \tparam ArgT0 Argument type.
+ */
+template
+struct PatchTraits
+{
+ mmk_mock_define(mock_type, ReturnT, ArgT0);
+};
+
+/// Traits specialization for void(ArgT0) free functions.
+/**
+ * Necessary for Mimick macros to adjust accordingly when the return
+ * type is `void`.
+ *
+ * \tparam ID Numerical identifier of the patch. Ought to be unique.
+ * \tparam ArgT0 Argument type.
+ */
+template
+struct PatchTraits
+{
+ mmk_mock_define(mock_type, void, ArgT0);
+};
+
+/// Traits specialization for ReturnT(ArgT0, ArgT1) free functions.
+/**
+ * \tparam ID Numerical identifier of the patch. Ought to be unique.
+ * \tparam ReturnT Return value type.
+ * \tparam ArgTx Argument types.
+ */
+template
+struct PatchTraits
+{
+ mmk_mock_define(mock_type, ReturnT, ArgT0, ArgT1);
+};
+
+/// Traits specialization for void(ArgT0, ArgT1) free functions.
+/**
+ * Necessary for Mimick macros to adjust accordingly when the return
+ * type is `void`.
+ *
+ * \tparam ID Numerical identifier of the patch. Ought to be unique.
+ * \tparam ArgTx Argument types.
+ */
+template
+struct PatchTraits
+{
+ mmk_mock_define(mock_type, void, ArgT0, ArgT1);
+};
+
+/// Traits specialization for ReturnT(ArgT0, ArgT1, ArgT2) free functions.
+/**
+ * \tparam ID Numerical identifier of the patch. Ought to be unique.
+ * \tparam ReturnT Return value type.
+ * \tparam ArgTx Argument types.
+ */
+template
+struct PatchTraits
+{
+ mmk_mock_define(mock_type, ReturnT, ArgT0, ArgT1, ArgT2);
+};
+
+/// Traits specialization for void(ArgT0, ArgT1, ArgT2) free functions.
+/**
+ * Necessary for Mimick macros to adjust accordingly when the return
+ * type is `void`.
+ *
+ * \tparam ID Numerical identifier of the patch. Ought to be unique.
+ * \tparam ArgTx Argument types.
+ */
+template
+struct PatchTraits
+{
+ mmk_mock_define(mock_type, void, ArgT0, ArgT1, ArgT2);
+};
+
+/// Traits specialization for ReturnT(ArgT0, ArgT1, ArgT2, ArgT3) free functions.
+/**
+ * \tparam ID Numerical identifier of the patch. Ought to be unique.
+ * \tparam ReturnT Return value type.
+ * \tparam ArgTx Argument types.
+ */
+template
+struct PatchTraits
+{
+ mmk_mock_define(mock_type, ReturnT, ArgT0, ArgT1, ArgT2, ArgT3);
+};
+
+/// Traits specialization for void(ArgT0, ArgT1, ArgT2, ArgT3) free functions.
+/**
+ * Necessary for Mimick macros to adjust accordingly when the return
+ * type is `void`.
+ *
+ * \tparam ID Numerical identifier of the patch. Ought to be unique.
+ * \tparam ArgTx Argument types.
+ */
+template
+struct PatchTraits
+{
+ mmk_mock_define(mock_type, void, ArgT0, ArgT1, ArgT2, ArgT3);
+};
+
+/// Traits specialization for ReturnT(ArgT0, ArgT1, ArgT2, ArgT3, ArgT4)
+/// free functions.
+/**
+ * \tparam ID Numerical identifier of the patch. Ought to be unique.
+ * \tparam ReturnT Return value type.
+ * \tparam ArgTx Argument types.
+ */
+template
+struct PatchTraits
+{
+ mmk_mock_define(mock_type, ReturnT, ArgT0, ArgT1, ArgT2, ArgT3, ArgT4);
+};
+
+/// Traits specialization for void(ArgT0, ArgT1, ArgT2, ArgT3, ArgT4)
+/// free functions.
+/**
+ * Necessary for Mimick macros to adjust accordingly when the return
+ * type is `void`.
+ *
+ * \tparam ID Numerical identifier of the patch. Ought to be unique.
+ * \tparam ArgTx Argument types.
+ */
+template
+struct PatchTraits
+{
+ mmk_mock_define(mock_type, void, ArgT0, ArgT1, ArgT2, ArgT3, ArgT4);
+};
+
+/// Traits specialization for ReturnT(ArgT0, ArgT1, ArgT2, ArgT3, ArgT4, ArgT5)
+/// free functions.
+/**
+ * \tparam ID Numerical identifier of the patch. Ought to be unique.
+ * \tparam ReturnT Return value type.
+ * \tparam ArgTx Argument types.
+ */
+template
+struct PatchTraits
+{
+ mmk_mock_define(
+ mock_type, ReturnT, ArgT0, ArgT1, ArgT2, ArgT3, ArgT4, ArgT5);
+};
+
+/// Traits specialization for void(ArgT0, ArgT1, ArgT2, ArgT3, ArgT4, ArgT5, ArgT6, ArgT7)
+/// free functions.
+/**
+ * Necessary for Mimick macros to adjust accordingly when the return
+ * type is `void`.
+ *
+ * \tparam ID Numerical identifier of the patch. Ought to be unique.
+ * \tparam ArgTx Argument types.
+ */
+template
+struct PatchTraits
+{
+ mmk_mock_define(
+ mock_type, ReturnT, ArgT0, ArgT1, ArgT2, ArgT3, ArgT4, ArgT5, ArgT6, ArgT7, ArgT8, ArgT9);
+};
+
+/// Traits specialization for void(ArgT0, ArgT1, ArgT2, ArgT3, ArgT4, ArgT5)
+/// free functions.
+/**
+ * Necessary for Mimick macros to adjust accordingly when the return
+ * type is `void`.
+ *
+ * \tparam ID Numerical identifier of the patch. Ought to be unique.
+ * \tparam ArgTx Argument types.
+ */
+template
+struct PatchTraits
+{
+ mmk_mock_define(
+ mock_type, void, ArgT0, ArgT1, ArgT2, ArgT3, ArgT4, ArgT5);
+};
+
+/// Generic trampoline to wrap generalized callables in plain functions.
+/**
+ * \tparam ID Numerical identifier of this trampoline. Ought to be unique.
+ * \tparam SignatureT Type of the symbol this trampoline replaces.
+ */
+template
+struct Trampoline;
+
+/// Trampoline specialization for free functions.
+template
+struct Trampoline
+{
+ static ReturnT base(ArgTs... args)
+ {
+ return target(std::forward(args)...);
+ }
+
+ static std::function target;
+};
+
+template
+std::function
+Trampoline::target;
+
+/// Setup trampoline with the given @p target.
+/**
+ * \param[in] target Callable that this trampoline will target.
+ * \return the plain base function of this trampoline.
+ *
+ * \tparam ID Numerical identifier of this trampoline. Ought to be unique.
+ * \tparam SignatureT Type of the symbol this trampoline replaces.
+ */
+template
+auto prepare_trampoline(std::function target)
+{
+ Trampoline::target = target;
+ return Trampoline::base;
+}
+
+/// Patch class for binary API mocking
+/**
+ * Built on top of Mimick, to enable symbol mocking on a per dynamically
+ * linked binary object basis.
+ *
+ * \tparam ID Numerical identifier for this patch. Ought to be unique.
+ * \tparam SignatureT Type of the symbol to be patched.
+ */
+template
+class Patch;
+
+/// Patch specialization for ReturnT(ArgTs...) free functions.
+/**
+ * \tparam ID Numerical identifier for this patch. Ought to be unique.
+ * \tparam ReturnT Return value type.
+ * \tparam ArgTs Argument types.
+ */
+template
+class Patch
+{
+public:
+ using mock_type = typename PatchTraits::mock_type;
+
+ /// Construct a patch.
+ /**
+ * \param[in] target Symbol target string, using Mimick syntax
+ * i.e. "symbol(@scope)?", where scope may be "self" to target the current
+ * binary, "lib:library_name" to target a given library, "file:path/to/library"
+ * to target a given file, or "sym:other_symbol" to target the first library
+ * that defines said symbol.
+ * \param[in] proxy An indirection to call the target function.
+ * This indirection must ensure this call goes through the function's
+ * trampoline, as setup by the dynamic linker.
+ * \return a mocking_utils::Patch instance.
+ */
+ explicit Patch(const std::string & target, std::function proxy)
+ : target_(target), proxy_(proxy)
+ {
+ }
+
+ // Copy construction and assignment are disabled.
+ Patch(const Patch &) = delete;
+ Patch & operator=(const Patch &) = delete;
+
+ Patch(Patch && other)
+ {
+ mock_ = other.mock_;
+ other.mock_ = nullptr;
+ }
+
+ Patch & operator=(Patch && other)
+ {
+ if (mock_) {
+ mmk_reset(mock_);
+ }
+ mock_ = other.mock_;
+ other.mock_ = nullptr;
+ }
+
+ ~Patch()
+ {
+ if (mock_) {
+ mmk_reset(mock_);
+ }
+ }
+
+ /// Inject a @p replacement for the patched function.
+ Patch & then_call(std::function replacement) &
+ {
+ replace_with(replacement);
+ return *this;
+ }
+
+ /// Inject a @p replacement for the patched function.
+ Patch && then_call(std::function replacement) &&
+ {
+ replace_with(replacement);
+ return std::move(*this);
+ }
+
+private:
+ // Helper for template parameter pack expansion using `mmk_any`
+ // macro as pattern.
+ template
+ T any() {return mmk_any(T);}
+
+ void replace_with(std::function replacement)
+ {
+ if (mock_) {
+ throw std::logic_error("Cannot configure patch more than once");
+ }
+ auto type_erased_trampoline =
+ reinterpret_cast(prepare_trampoline(replacement));
+ auto MMK_MANGLE(mock_type, create) =
+ PatchTraits::MMK_MANGLE(mock_type, create);
+ mock_ = mmk_mock(target_.c_str(), mock_type);
+ mmk_when(proxy_(any()...), .then_call = type_erased_trampoline);
+ }
+
+ mock_type mock_{nullptr};
+ std::string target_;
+ std::function proxy_;
+};
+
+/// Make a patch for a `target` function.
+/**
+ * Useful for type deduction during \ref mocking_utils::Patch construction.
+ *
+ * \param[in] target Symbol target string, using Mimick syntax.
+ * \param[in] proxy An indirection to call the target function.
+ * \return a mocking_utils::Patch instance.
+ *
+ * \tparam ID Numerical identifier for this patch. Ought to be unique.
+ * \tparam SignatureT Type of the function to be patched.
+ *
+ * \sa mocking_utils::Patch for further reference.
+ */
+template
+auto make_patch(const std::string & target, std::function proxy)
+{
+ return Patch(target, proxy);
+}
+
+/// Define a dummy operator `op` for a given `type`.
+/**
+ * Useful to enable patching functions that take arguments whose types
+ * do not define basic comparison operators, as required by Mimick.
+*/
+#define MOCKING_UTILS_BOOL_OPERATOR_RETURNS_FALSE(type_, op) \
+ template \
+ typename std::enable_if::value, bool>::type \
+ operator op(const T &, const T &) { \
+ return false; \
+ }
+
+/// Get the exact \ref mocking_utils::Patch type for a given `id` and `function`.
+/**
+ * Useful to avoid ignored attribute warnings when using the \b decltype operator.
+ */
+#define MOCKING_UTILS_PATCH_TYPE(id, function) \
+ decltype(mocking_utils::make_patch("", nullptr))
+
+/// A transparent forwarding proxy to a given `function`.
+/**
+ * Useful to ensure a call to `function` goes through its trampoline.
+ */
+#define MOCKING_UTILS_PATCH_PROXY(function) \
+ [] (auto && ... args)->decltype(auto) { \
+ return function(std::forward(args)...); \
+ }
+
+/// Compute a Mimick symbol target string based on which `function` is to be patched
+/// in which `scope`.
+#define MOCKING_UTILS_PATCH_TARGET(scope, function) \
+ (std::string(RCUTILS_STRINGIFY(function)) + "@" + (scope))
+
+/// Prepare a mocking_utils::Patch for patching a `function` in a given `scope`
+/// but defer applying any changes.
+#define prepare_patch(scope, function) \
+ make_patch<__COUNTER__, decltype(function)>( \
+ MOCKING_UTILS_PATCH_TARGET(scope, function), MOCKING_UTILS_PATCH_PROXY(function) \
+ )
+
+/// Patch a `function` with a used-provided `replacement` in a given `scope`.
+#define patch(scope, function, replacement) \
+ prepare_patch(scope, function).then_call(replacement)
+
+/// Patch a `function` to always yield a given `return_code` in a given `scope`.
+#define patch_and_return(scope, function, return_code) \
+ patch(scope, function, [&](auto && ...) {return return_code;})
+
+/// Patch a `function` to always yield a given `return_code` in a given `scope`.
+#define patch_to_fail(scope, function, error_message, return_code) \
+ patch( \
+ scope, function, [&](auto && ...) { \
+ RCUTILS_SET_ERROR_MSG(error_message); \
+ return return_code; \
+ })
+
+/// Patch a `function` to execute normally but always yield a given `return_code`
+/// in a given `scope`.
+/**
+ * \warning On some Linux distributions (e.g. CentOS), pointers to function
+ * reference their PLT trampolines. In such cases, it is not possible to
+ * call `function` from within the mock.
+ */
+#define inject_on_return(scope, function, return_code) \
+ patch( \
+ scope, function, ([&, base = function](auto && ... __args) { \
+ if (base != function) { \
+ static_cast(base(std::forward(__args)...)); \
+ } else { \
+ RCUTILS_SAFE_FWRITE_TO_STDERR( \
+ "[WARNING] mocking_utils::inject_on_return() cannot forward call to " \
+ "original '" RCUTILS_STRINGIFY(function) "' function before injection\n" \
+ " at " __FILE__ ":" RCUTILS_STRINGIFY(__LINE__) "\n"); \
+ } \
+ return return_code; \
+ }))
+
+} // namespace mocking_utils
+
+#ifdef MOCKING_UTILS_SUPPORT_VA_LIST
+// Define dummy comparison operators for C standard va_list type
+MOCKING_UTILS_BOOL_OPERATOR_RETURNS_FALSE(va_list, ==)
+MOCKING_UTILS_BOOL_OPERATOR_RETURNS_FALSE(va_list, !=)
+MOCKING_UTILS_BOOL_OPERATOR_RETURNS_FALSE(va_list, <)
+MOCKING_UTILS_BOOL_OPERATOR_RETURNS_FALSE(va_list, >)
+#endif
+
+#endif // MOCKING_UTILS__PATCH_HPP_
diff --git a/rclcpp_lifecycle/test/test_lifecycle_node.cpp b/rclcpp_lifecycle/test/test_lifecycle_node.cpp
index 93e4249..34674dd 100644
--- a/rclcpp_lifecycle/test/test_lifecycle_node.cpp
+++ b/rclcpp_lifecycle/test/test_lifecycle_node.cpp
@@ -24,9 +24,13 @@
#include "lifecycle_msgs/msg/state.hpp"
#include "lifecycle_msgs/msg/transition.hpp"
+#include "rcl_lifecycle/rcl_lifecycle.h"
+
#include "rclcpp/rclcpp.hpp"
#include "rclcpp_lifecycle/lifecycle_node.hpp"
+#include "./mocking_utils/patch.hpp"
+
using lifecycle_msgs::msg::State;
using lifecycle_msgs::msg::Transition;
@@ -37,13 +41,17 @@ protected:
{
rclcpp::init(0, nullptr);
}
+ static void TearDownTestCase()
+ {
+ rclcpp::shutdown();
+ }
};
class EmptyLifecycleNode : public rclcpp_lifecycle::LifecycleNode
{
public:
- explicit EmptyLifecycleNode(std::string node_name)
- : rclcpp_lifecycle::LifecycleNode(std::move(node_name))
+ explicit EmptyLifecycleNode(const std::string & node_name)
+ : rclcpp_lifecycle::LifecycleNode(node_name)
{}
};
@@ -142,6 +150,22 @@ TEST_F(TestDefaultStateMachine, empty_initializer) {
EXPECT_EQ(State::PRIMARY_STATE_UNCONFIGURED, test_node->get_current_state().id());
}
+TEST_F(TestDefaultStateMachine, empty_initializer_rcl_errors) {
+ {
+ auto patch = mocking_utils::patch_and_return(
+ "lib:rclcpp_lifecycle", rcl_lifecycle_state_machine_init, RCL_RET_ERROR);
+ EXPECT_THROW(
+ std::make_shared("testnode").reset(),
+ std::runtime_error);
+ }
+ {
+ auto test_node = std::make_shared("testnode");
+ auto patch = mocking_utils::inject_on_return(
+ "lib:rclcpp_lifecycle", rcl_lifecycle_state_machine_fini, RCL_RET_ERROR);
+ EXPECT_NO_THROW(test_node.reset());
+ }
+}
+
TEST_F(TestDefaultStateMachine, trigger_transition) {
auto test_node = std::make_shared("testnode");
@@ -163,6 +187,35 @@ TEST_F(TestDefaultStateMachine, trigger_transition) {
rclcpp_lifecycle::Transition(Transition::TRANSITION_UNCONFIGURED_SHUTDOWN)).id());
}
+TEST_F(TestDefaultStateMachine, trigger_transition_rcl_errors) {
+ auto test_node = std::make_shared("testnode");
+
+ {
+ auto mock = mocking_utils::patch_and_return(
+ "lib:rclcpp_lifecycle", rcl_lifecycle_state_machine_is_initialized, RCL_RET_ERROR);
+ EXPECT_EQ(
+ State::PRIMARY_STATE_UNCONFIGURED,
+ test_node->trigger_transition(
+ rclcpp_lifecycle::Transition(Transition::TRANSITION_CONFIGURE)).id());
+ }
+ {
+ auto mock = mocking_utils::patch_and_return(
+ "lib:rclcpp_lifecycle", rcl_lifecycle_trigger_transition_by_id, RCL_RET_ERROR);
+ EXPECT_EQ(
+ State::PRIMARY_STATE_UNCONFIGURED,
+ test_node->trigger_transition(
+ rclcpp_lifecycle::Transition(Transition::TRANSITION_CONFIGURE)).id());
+ }
+ {
+ auto mock = mocking_utils::patch_and_return(
+ "lib:rclcpp_lifecycle", rcl_lifecycle_trigger_transition_by_label, RCL_RET_ERROR);
+ EXPECT_EQ(
+ State::TRANSITION_STATE_CONFIGURING,
+ test_node->trigger_transition(
+ rclcpp_lifecycle::Transition(Transition::TRANSITION_CONFIGURE)).id());
+ }
+}
+
TEST_F(TestDefaultStateMachine, trigger_transition_with_error_code) {
auto test_node = std::make_shared("testnode");
auto success = rclcpp_lifecycle::node_interfaces::LifecycleNodeInterface::CallbackReturn::SUCCESS;
@@ -381,6 +434,8 @@ TEST_F(TestDefaultStateMachine, check_parameters) {
auto descriptors = test_node->describe_parameters(parameter_names);
EXPECT_EQ(descriptors.size(), parameter_names.size());
+ // This actually throws inside NodeParameters::describe_parameters(), so it's not currently
+ // possible to cover this method 100%.
EXPECT_THROW(
test_node->describe_parameter("not_a_real_parameter"),
rclcpp::exceptions::ParameterNotDeclaredException);
@@ -437,6 +492,12 @@ TEST_F(TestDefaultStateMachine, check_parameters) {
test_node->set_parameters({int_parameter});
EXPECT_EQ(parameters_set, 2u);
+ test_node->remove_on_set_parameters_callback(callback_handle.get());
+ rclcpp::Parameter bool_parameter2(bool_name, rclcpp::ParameterValue(true));
+ EXPECT_TRUE(test_node->set_parameter(bool_parameter2).successful);
+ EXPECT_EQ(parameters_set, 2u);
+
+
// List parameters
list_result = test_node->list_parameters({}, 0u);
EXPECT_EQ(list_result.names.size(), 3u);
@@ -489,7 +550,7 @@ TEST_F(TestDefaultStateMachine, test_getters) {
EXPECT_NE(nullptr, const_cast(test_node.get())->get_clock());
}
-TEST_F(TestDefaultStateMachine, test_graph) {
+TEST_F(TestDefaultStateMachine, test_graph_topics) {
auto test_node = std::make_shared("testnode");
auto names = test_node->get_node_names();
@@ -504,6 +565,19 @@ TEST_F(TestDefaultStateMachine, test_graph) {
topic_names_and_types["/testnode/transition_event"][0].c_str(),
"lifecycle_msgs/msg/TransitionEvent");
+ EXPECT_EQ(1u, test_node->count_publishers("/testnode/transition_event"));
+ EXPECT_EQ(0u, test_node->count_subscribers("/testnode/transition_event"));
+
+ auto publishers_info = test_node->get_publishers_info_by_topic("/testnode/transition_event");
+ EXPECT_EQ(1u, publishers_info.size());
+ auto subscriptions_info =
+ test_node->get_subscriptions_info_by_topic("/testnode/transition_event");
+ EXPECT_EQ(0u, subscriptions_info.size());
+}
+
+TEST_F(TestDefaultStateMachine, test_graph_services) {
+ auto test_node = std::make_shared("testnode");
+
auto service_names_and_types = test_node->get_service_names_and_types();
// These are specific to lifecycle nodes, other services are provided by rclcpp::Node
ASSERT_NE(
@@ -540,15 +614,48 @@ TEST_F(TestDefaultStateMachine, test_graph) {
EXPECT_STREQ(
service_names_and_types["/testnode/get_transition_graph"][0].c_str(),
"lifecycle_msgs/srv/GetAvailableTransitions");
+}
- EXPECT_EQ(1u, test_node->count_publishers("/testnode/transition_event"));
- EXPECT_EQ(0u, test_node->count_subscribers("/testnode/transition_event"));
+TEST_F(TestDefaultStateMachine, test_graph_services_by_node) {
+ auto test_node = std::make_shared("testnode");
- auto publishers_info = test_node->get_publishers_info_by_topic("/testnode/transition_event");
- EXPECT_EQ(publishers_info.size(), 1u);
- auto subscriptions_info =
- test_node->get_subscriptions_info_by_topic("/testnode/transition_event");
- EXPECT_EQ(subscriptions_info.size(), 0u);
+ auto service_names_and_types_by_node =
+ test_node->get_service_names_and_types_by_node("testnode", "");
+ // These are specific to lifecycle nodes, other services are provided by rclcpp::Node
+ ASSERT_NE(
+ service_names_and_types_by_node.end(),
+ service_names_and_types_by_node.find(std::string("/testnode/change_state")));
+ EXPECT_STREQ(
+ service_names_and_types_by_node["/testnode/change_state"][0].c_str(),
+ "lifecycle_msgs/srv/ChangeState");
+
+ ASSERT_NE(
+ service_names_and_types_by_node.end(),
+ service_names_and_types_by_node.find(std::string("/testnode/get_available_states")));
+ EXPECT_STREQ(
+ service_names_and_types_by_node["/testnode/get_available_states"][0].c_str(),
+ "lifecycle_msgs/srv/GetAvailableStates");
+
+ ASSERT_NE(
+ service_names_and_types_by_node.end(),
+ service_names_and_types_by_node.find(std::string("/testnode/get_available_transitions")));
+ EXPECT_STREQ(
+ service_names_and_types_by_node["/testnode/get_available_transitions"][0].c_str(),
+ "lifecycle_msgs/srv/GetAvailableTransitions");
+
+ ASSERT_NE(
+ service_names_and_types_by_node.end(),
+ service_names_and_types_by_node.find(std::string("/testnode/get_state")));
+ EXPECT_STREQ(
+ service_names_and_types_by_node["/testnode/get_state"][0].c_str(),
+ "lifecycle_msgs/srv/GetState");
+
+ ASSERT_NE(
+ service_names_and_types_by_node.end(),
+ service_names_and_types_by_node.find(std::string("/testnode/get_transition_graph")));
+ EXPECT_STREQ(
+ service_names_and_types_by_node["/testnode/get_transition_graph"][0].c_str(),
+ "lifecycle_msgs/srv/GetAvailableTransitions");
}
TEST_F(TestDefaultStateMachine, test_callback_groups) {
@@ -564,3 +671,16 @@ TEST_F(TestDefaultStateMachine, test_callback_groups) {
EXPECT_EQ(groups.size(), 2u);
EXPECT_EQ(groups[1].lock().get(), group.get());
}
+
+TEST_F(TestDefaultStateMachine, wait_for_graph_change)
+{
+ auto test_node = std::make_shared("testnode");
+ EXPECT_THROW(
+ test_node->wait_for_graph_change(nullptr, std::chrono::milliseconds(1)),
+ rclcpp::exceptions::InvalidEventError);
+
+ auto event = std::make_shared();
+ EXPECT_THROW(
+ test_node->wait_for_graph_change(event, std::chrono::milliseconds(0)),
+ rclcpp::exceptions::EventNotRegisteredError);
+}
diff --git a/rclcpp_lifecycle/test/test_lifecycle_publisher.cpp b/rclcpp_lifecycle/test/test_lifecycle_publisher.cpp
new file mode 100644
index 0000000..555354a
--- /dev/null
+++ b/rclcpp_lifecycle/test/test_lifecycle_publisher.cpp
@@ -0,0 +1,104 @@
+// Copyright 2020 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.
+
+
+#include
+#include
+#include
+#include
+
+#include "test_msgs/msg/empty.hpp"
+
+#include "rclcpp_lifecycle/lifecycle_node.hpp"
+#include "rclcpp_lifecycle/lifecycle_publisher.hpp"
+
+class TestDefaultStateMachine : public ::testing::Test
+{
+protected:
+ static void SetUpTestCase()
+ {
+ rclcpp::init(0, nullptr);
+ }
+ static void TearDownTestCase()
+ {
+ rclcpp::shutdown();
+ }
+};
+
+class EmptyLifecycleNode : public rclcpp_lifecycle::LifecycleNode
+{
+public:
+ explicit EmptyLifecycleNode(const std::string & node_name)
+ : rclcpp_lifecycle::LifecycleNode(node_name)
+ {
+ rclcpp::PublisherOptionsWithAllocator> options;
+ publisher_ =
+ std::make_shared>(
+ get_node_base_interface().get(), std::string("topic"), rclcpp::QoS(10), options);
+ add_publisher_handle(publisher_);
+
+ // For coverage this is being added here
+ auto timer = create_wall_timer(std::chrono::seconds(1), []() {});
+ add_timer_handle(timer);
+ }
+
+ std::shared_ptr> publisher()
+ {
+ return publisher_;
+ }
+
+private:
+ std::shared_ptr> publisher_;
+};
+
+class TestLifecyclePublisher : public ::testing::Test
+{
+public:
+ void SetUp()
+ {
+ rclcpp::init(0, nullptr);
+ node_ = std::make_shared("node");
+ }
+
+ void TearDown()
+ {
+ rclcpp::shutdown();
+ }
+
+protected:
+ std::shared_ptr node_;
+};
+
+TEST_F(TestLifecyclePublisher, publish) {
+ node_->publisher()->on_deactivate();
+ EXPECT_FALSE(node_->publisher()->is_activated());
+ {
+ auto msg_ptr = std::make_unique();
+ EXPECT_NO_THROW(node_->publisher()->publish(*msg_ptr));
+ }
+ {
+ auto msg_ptr = std::make_unique();
+ EXPECT_NO_THROW(node_->publisher()->publish(std::move(msg_ptr)));
+ }
+ node_->publisher()->on_activate();
+ EXPECT_TRUE(node_->publisher()->is_activated());
+ {
+ auto msg_ptr = std::make_unique();
+ EXPECT_NO_THROW(node_->publisher()->publish(*msg_ptr));
+ }
+ {
+ auto msg_ptr = std::make_unique();
+ EXPECT_NO_THROW(node_->publisher()->publish(std::move(msg_ptr)));
+ }
+}
diff --git a/rclcpp_lifecycle/test/test_lifecycle_service_client.cpp b/rclcpp_lifecycle/test/test_lifecycle_service_client.cpp
index 7ef4acc..ce19529 100644
--- a/rclcpp_lifecycle/test/test_lifecycle_service_client.cpp
+++ b/rclcpp_lifecycle/test/test_lifecycle_service_client.cpp
@@ -36,6 +36,8 @@
#include "rclcpp/rclcpp.hpp"
#include "rclcpp_lifecycle/lifecycle_node.hpp"
+#include "./mocking_utils/patch.hpp"
+
using namespace std::chrono_literals;
constexpr char const * lifecycle_node_name = "lc_talker";
@@ -393,3 +395,56 @@ TEST_F(TestLifecycleServiceClient, wait_for_graph_change)
node_graph->wait_for_graph_change(event, std::chrono::milliseconds(0)),
rclcpp::exceptions::EventNotRegisteredError);
}
+
+class TestLifecycleServiceClientRCLErrors : public ::testing::Test
+{
+protected:
+ void SetUp() override
+ {
+ rclcpp::init(0, nullptr);
+ }
+
+ void TearDown() override
+ {
+ rclcpp::shutdown();
+ }
+};
+
+TEST_F(TestLifecycleServiceClientRCLErrors, call_services_rcl_errors) {
+ auto lifecycle_node = std::make_shared();
+ auto lifecycle_client = std::make_shared("client_with_errors");
+
+ auto mock = mocking_utils::patch_and_return(
+ "lib:rclcpp_lifecycle", rcl_lifecycle_state_machine_is_initialized, RCL_RET_ERROR);
+
+ // on_change_state
+ lifecycle_client->change_state(
+ lifecycle_msgs::msg::Transition::TRANSITION_CONFIGURE);
+ rclcpp::spin_some(lifecycle_client);
+ EXPECT_THROW(
+ rclcpp::spin_some(lifecycle_node->get_node_base_interface()), std::runtime_error);
+
+ // on_get_state
+ lifecycle_client->get_state();
+ rclcpp::spin_some(lifecycle_client);
+ EXPECT_THROW(
+ rclcpp::spin_some(lifecycle_node->get_node_base_interface()), std::runtime_error);
+
+ // on_get_avilable_states
+ lifecycle_client->get_available_states();
+ rclcpp::spin_some(lifecycle_client);
+ EXPECT_THROW(
+ rclcpp::spin_some(lifecycle_node->get_node_base_interface()), std::runtime_error);
+
+ // on_get_available_transitions
+ lifecycle_client->get_available_transitions();
+ rclcpp::spin_some(lifecycle_client);
+ EXPECT_THROW(
+ rclcpp::spin_some(lifecycle_node->get_node_base_interface()), std::runtime_error);
+
+ // on_get_transition_graph
+ lifecycle_client->get_transition_graph();
+ rclcpp::spin_some(lifecycle_client);
+ EXPECT_THROW(
+ rclcpp::spin_some(lifecycle_node->get_node_base_interface()), std::runtime_error);
+}
diff --git a/rclcpp_lifecycle/test/test_transition_wrapper.cpp b/rclcpp_lifecycle/test/test_transition_wrapper.cpp
index 9895dc9..4f72d90 100644
--- a/rclcpp_lifecycle/test/test_transition_wrapper.cpp
+++ b/rclcpp_lifecycle/test/test_transition_wrapper.cpp
@@ -18,8 +18,12 @@
#include
#include
+#include "rcutils/testing/fault_injection.h"
+
#include "rclcpp_lifecycle/lifecycle_node.hpp"
+#include "./mocking_utils/patch.hpp"
+
class TestTransitionWrapper : public ::testing::Test
{
protected:
@@ -31,8 +35,11 @@ protected:
class TransitionDerived : public rclcpp_lifecycle::Transition
{
public:
+ TransitionDerived(const uint8_t id, const std::string & label)
+ : Transition(id, label) {}
+
TransitionDerived(
- uint8_t id, const std::string & label,
+ const uint8_t id, const std::string & label,
rclcpp_lifecycle::State && start, rclcpp_lifecycle::State && goal)
: Transition(id, label, std::move(start), std::move(goal)) {}
void expose_reset()
@@ -125,4 +132,80 @@ TEST_F(TestTransitionWrapper, exceptions) {
EXPECT_THROW(a->goal_state(), std::runtime_error);
EXPECT_THROW(a->id(), std::runtime_error);
EXPECT_THROW(a->label(), std::runtime_error);
+
+ {
+ auto mock = mocking_utils::patch_and_return(
+ "lib:rclcpp_lifecycle", rcl_lifecycle_transition_init, RCL_RET_ERROR);
+
+ EXPECT_THROW(
+ std::make_shared(1, "one").reset(),
+ std::runtime_error);
+
+ rclcpp_lifecycle::State state1(1, "start_state");
+ rclcpp_lifecycle::State state2(2, "goal_state");
+ EXPECT_THROW(
+ std::make_shared(
+ 2, "two", std::move(start_state), std::move(goal_state)).reset(),
+ std::runtime_error);
+ }
+ {
+ auto mock = mocking_utils::patch_and_return(
+ "lib:rclcpp_lifecycle", rcl_lifecycle_transition_fini, RCL_RET_ERROR);
+ auto transition1 = std::make_shared(1, "one");
+ EXPECT_NO_THROW(transition1->expose_reset());
+
+ rclcpp_lifecycle::State state1(1, "start_state");
+ rclcpp_lifecycle::State state2(2, "goal_state");
+ auto transition2 =
+ std::make_shared(2, "two", std::move(start_state), std::move(goal_state));
+ EXPECT_NO_THROW(transition2->expose_reset());
+ }
+
+ RCUTILS_FAULT_INJECTION_TEST(
+ {
+ std::shared_ptr transition = nullptr;
+ try {
+ transition = std::make_shared(1, "one");
+ } catch (...) {
+ }
+ if (nullptr != transition) {
+ EXPECT_NO_THROW(transition->expose_reset());
+ }
+ });
+
+ RCUTILS_FAULT_INJECTION_TEST(
+ {
+ std::shared_ptr transition = nullptr;
+ try {
+ {
+ // These will fail due to failed allocations
+ rclcpp_lifecycle::State state1(1, "start_state");
+ rclcpp_lifecycle::State state2(2, "goal_state");
+
+ // Failed allocations and failed rcl init functions
+ transition = std::make_shared(
+ 2, "two", std::move(state1), std::move(state2));
+ }
+ } catch (...) {
+ }
+
+ if (nullptr != transition) {
+ EXPECT_NO_THROW(transition->expose_reset());
+ }
+ });
+
+ RCUTILS_FAULT_INJECTION_TEST(
+ {
+ try {
+ // These will fail due to failed allocations
+ rclcpp_lifecycle::State state1(1, "start_state");
+ rclcpp_lifecycle::State state2(2, "goal_state");
+
+ // Failed allocations and failed rcl init functions
+ auto a = std::make_shared(2, "two", std::move(state1), std::move(state2));
+ auto b = std::make_shared(3, "three");
+ *b = *a;
+ } catch (...) {
+ }
+ });
}