Skip to content

Commit e3acc91

Browse files
authored
Add test_utils file for transition tests (backport #3048) (#3216)
1 parent 3560a5f commit e3acc91

3 files changed

Lines changed: 483 additions & 0 deletions

File tree

controller_interface/CMakeLists.txt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,11 @@ if(BUILD_TESTING)
125125
target_link_libraries(test_controller_tf_prefix
126126
controller_interface
127127
)
128+
129+
ament_add_gmock(test_test_utils test/test_test_utils.cpp)
130+
target_link_libraries(test_test_utils
131+
controller_interface
132+
)
128133
endif()
129134

130135
install(
Lines changed: 172 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,172 @@
1+
// Copyright 2026 AIT - Austrian Institute of Technology GmbH
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
#ifndef CONTROLLER_INTERFACE__TEST_UTILS_HPP_
16+
#define CONTROLLER_INTERFACE__TEST_UTILS_HPP_
17+
18+
#include <memory>
19+
#include <stdexcept>
20+
#include <string>
21+
22+
#include "lifecycle_msgs/msg/state.hpp"
23+
24+
namespace controller_interface
25+
{
26+
using lifecycle_msgs::msg::State;
27+
28+
/**
29+
* @brief Triggers the controller's configure transition and checks success
30+
*
31+
* @note Intentionally calls controller->configure() instead of get_node()->configure() because
32+
* ControllerInterfaceBase::configure() contains controller-specific configure logic and parameter
33+
* handling before driving the lifecycle transition.
34+
*
35+
* @param controller The controller to test
36+
* @return true if the controller successfully transitions to the expected state, false if it fails
37+
* to
38+
*
39+
* @throws std::runtime_error if the controller transitions to an unexpected state
40+
*/
41+
template <typename T>
42+
bool configure_succeeds(const std::unique_ptr<T> & controller)
43+
{
44+
auto state = controller->configure();
45+
46+
switch (state.id())
47+
{
48+
case State::PRIMARY_STATE_INACTIVE:
49+
return true;
50+
case State::PRIMARY_STATE_UNCONFIGURED:
51+
return false;
52+
default:
53+
throw std::runtime_error(
54+
"Unexpected controller state in configure_succeeds: " + std::to_string(state.id()));
55+
}
56+
}
57+
58+
/**
59+
* @brief Triggers the controller's activate transition and checks success
60+
*
61+
* @param controller The controller to test
62+
* @return true if the controller successfully transitions to the expected state, false if it fails
63+
* to
64+
*
65+
* @throws std::runtime_error if the controller transitions to an unexpected state
66+
*/
67+
template <typename T>
68+
bool activate_succeeds(const std::unique_ptr<T> & controller)
69+
{
70+
auto state = controller->get_node()->activate();
71+
72+
switch (state.id())
73+
{
74+
case State::PRIMARY_STATE_ACTIVE:
75+
return true;
76+
case State::PRIMARY_STATE_INACTIVE:
77+
return false;
78+
default:
79+
// if transition returns error, it will go to on_error transition. Depending on its success,
80+
// it will either land in unconfigured or finalized state
81+
throw std::runtime_error(
82+
"Unexpected controller state in activate_succeeds: " + std::to_string(state.id()));
83+
}
84+
}
85+
86+
/**
87+
* @brief Triggers the controller's cleanup transition and checks success
88+
*
89+
* @param controller The controller to test
90+
* @return true if the controller successfully transitions to the expected state, false if it fails
91+
* to
92+
*
93+
* @throws std::runtime_error if the controller transitions to an unexpected state
94+
*/
95+
template <typename T>
96+
bool deactivate_succeeds(const std::unique_ptr<T> & controller)
97+
{
98+
auto state = controller->get_node()->deactivate();
99+
100+
switch (state.id())
101+
{
102+
case State::PRIMARY_STATE_INACTIVE:
103+
return true;
104+
case State::PRIMARY_STATE_ACTIVE:
105+
return false;
106+
default:
107+
throw std::runtime_error(
108+
"Unexpected controller state in deactivate_succeeds: " + std::to_string(state.id()));
109+
}
110+
}
111+
112+
/**
113+
* @brief Triggers the controller's cleanup transition and checks success
114+
*
115+
* @param controller The controller to test
116+
* @return true if the controller successfully transitions to the expected state, false if it fails
117+
* to
118+
*
119+
* @throws std::runtime_error if the controller transitions to an unexpected state
120+
*/
121+
template <typename T>
122+
bool cleanup_succeeds(const std::unique_ptr<T> & controller)
123+
{
124+
auto state = controller->get_node()->cleanup();
125+
126+
switch (state.id())
127+
{
128+
case State::PRIMARY_STATE_UNCONFIGURED:
129+
return true;
130+
case State::PRIMARY_STATE_INACTIVE:
131+
return false;
132+
default:
133+
throw std::runtime_error(
134+
"Unexpected controller state in cleanup_succeeds: " + std::to_string(state.id()));
135+
}
136+
}
137+
138+
/**
139+
* @brief Triggers the controller's shutdown transition and checks success
140+
*
141+
* @param controller The controller to test
142+
* @return true if the controller successfully transitions to the expected state
143+
*
144+
* @throws std::runtime_error if the controller transitions to an unexpected state
145+
*/
146+
template <typename T>
147+
bool shutdown_succeeds(const std::unique_ptr<T> & controller)
148+
{
149+
auto state = controller->get_node()->shutdown();
150+
151+
switch (state.id())
152+
{
153+
// if shutdown transition returns success or failure, it will anyways end up in the finalized
154+
// state
155+
// The observed behavior is not as described in
156+
// https://design.ros2.org/articles/node_lifecycle.html
157+
// See https://github.com/ros2/rclcpp/issues/1763 for more information
158+
// if shutdown transition returns error, it will go to on_error transition:
159+
// If on_error returns failure, it will end up in finalized state
160+
case State::PRIMARY_STATE_FINALIZED:
161+
return true;
162+
default:
163+
// if shutdowntransition returns error, it will go to on_error transition:
164+
// If on_error returns success, it will end up in unconfigured state
165+
throw std::runtime_error(
166+
"Unexpected controller state in shutdown_succeeds: " + std::to_string(state.id()));
167+
}
168+
}
169+
170+
} // namespace controller_interface
171+
172+
#endif // CONTROLLER_INTERFACE__TEST_UTILS_HPP_

0 commit comments

Comments
 (0)