Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Increase rcl_lifecycle test coverage and add more safety checks #649

Merged
merged 7 commits into from
May 22, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions rcl_lifecycle/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,16 @@ if(BUILD_TESTING)
)
target_link_libraries(test_multiple_instances ${PROJECT_NAME})
endif()
ament_add_gtest(test_rcl_lifecycle
test/test_rcl_lifecycle.cpp
)
if(TARGET test_rcl_lifecycle)
ament_target_dependencies(test_rcl_lifecycle
"rcl"
"osrf_testing_tools_cpp"
)
target_link_libraries(test_rcl_lifecycle ${PROJECT_NAME})
endif()
ament_add_gtest(test_transition_map
test/test_transition_map.cpp
)
Expand Down
24 changes: 22 additions & 2 deletions rcl_lifecycle/src/default_state_machine.c
Original file line number Diff line number Diff line change
Expand Up @@ -650,13 +650,15 @@ _register_transitions(
return ret;
}

// default implementation as despicted on
// default implementation as depicted on
// design.ros2.org
rcl_ret_t
rcl_lifecycle_init_default_state_machine(
rcl_lifecycle_state_machine_t * state_machine, const rcutils_allocator_t * allocator)
{
rcl_ret_t ret = RCL_RET_ERROR;
// Used for concatenating error messages in the fail: block.
const char * fail_error_message = "";

// ***************************
// register all primary states
Expand Down Expand Up @@ -691,8 +693,26 @@ rcl_lifecycle_init_default_state_machine(
return ret;

fail:
// If rcl_lifecycle_transition_map_fini() fails, it will clobber the error string here.
// Concatenate the error strings if that happens
if (rcl_error_is_set()) {
fail_error_message = rcl_get_error_string().str;
}

if (rcl_lifecycle_transition_map_fini(&state_machine->transition_map, allocator) != RCL_RET_OK) {
RCL_SET_ERROR_MSG("could not free lifecycle transition map. Leaking memory!\n");
const char * fini_error = "";
if (rcl_error_is_set()) {
fini_error = rcl_get_error_string().str;
rcl_reset_error();
}
RCL_SET_ERROR_MSG_WITH_FORMAT_STRING(
"Freeing transition map failed while handling a previous error. Leaking memory!"
"\nOriginal error:\n\t%s\nError encountered in rcl_lifecycle_transition_map_fini():\n\t%s\n",
fail_error_message, fini_error);
}

if (!rcl_error_is_set()) {
RCL_SET_ERROR_MSG("Unspecified error in default_state_machine _register_transitions()");
}
return RCL_RET_ERROR;
}
Expand Down
42 changes: 34 additions & 8 deletions rcl_lifecycle/src/rcl_lifecycle.c
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@ rcl_lifecycle_get_zero_initialized_state()
rcl_lifecycle_state_t state;
state.id = 0;
state.label = NULL;
state.valid_transitions = NULL;
state.valid_transition_size = 0;
return state;
}

Expand All @@ -58,6 +60,10 @@ rcl_lifecycle_state_init(
RCL_SET_ERROR_MSG("state pointer is null\n");
return RCL_RET_ERROR;
}
if (!label) {
RCL_SET_ERROR_MSG("State label is null\n");
return RCL_RET_ERROR;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
return RCL_RET_ERROR;
return RCL_RET_INVALID_ARGUMENT;

}

state->id = id;
state->label = rcutils_strndup(label, strlen(label), *allocator);
Expand Down Expand Up @@ -118,7 +124,22 @@ rcl_lifecycle_transition_init(

if (!transition) {
RCL_SET_ERROR_MSG("transition pointer is null\n");
return RCL_RET_OK;
return RCL_RET_ERROR;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
return RCL_RET_ERROR;
return RCL_RET_INVALID_ARGUMENT;

}

if (!label) {
RCL_SET_ERROR_MSG("label pointer is null\n");
return RCL_RET_ERROR;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
return RCL_RET_ERROR;
return RCL_RET_INVALID_ARGUMENT;

}

if (!start) {
RCL_SET_ERROR_MSG("start state pointer is null\n");
return RCL_RET_ERROR;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
return RCL_RET_ERROR;
return RCL_RET_INVALID_ARGUMENT;

}

if (!goal) {
RCL_SET_ERROR_MSG("goal state pointer is null\n");
return RCL_RET_ERROR;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
return RCL_RET_ERROR;
return RCL_RET_INVALID_ARGUMENT;

}

transition->start = start;
Expand All @@ -140,7 +161,7 @@ rcl_lifecycle_transition_fini(
const rcl_allocator_t * allocator)
{
if (!allocator) {
RCL_SET_ERROR_MSG("can't initialize transition, no allocator given\n");
RCL_SET_ERROR_MSG("can't finalize transition, no allocator given\n");
return RCL_RET_ERROR;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
return RCL_RET_ERROR;
return RCL_RET_INVALID_ARGUMENT;

}
// it is already NULL
Expand Down Expand Up @@ -192,6 +213,14 @@ rcl_lifecycle_state_machine_init(
bool default_states,
const rcl_allocator_t * allocator)
{
if (!state_machine) {
RCL_SET_ERROR_MSG("State machine is null\n");
return RCL_RET_ERROR;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
return RCL_RET_ERROR;
return RCL_RET_INVALID_ARGUMENT;

}
if (!node_handle) {
RCL_SET_ERROR_MSG("Node handle is null\n");
return RCL_RET_ERROR;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
return RCL_RET_ERROR;
return RCL_RET_INVALID_ARGUMENT;

}
if (!allocator) {
RCL_SET_ERROR_MSG("can't initialize state machine, no allocator given\n");
return RCL_RET_ERROR;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
return RCL_RET_ERROR;
return RCL_RET_INVALID_ARGUMENT;

Expand All @@ -207,15 +236,12 @@ rcl_lifecycle_state_machine_init(
}

if (default_states) {
rcl_ret_t ret =
rcl_lifecycle_init_default_state_machine(state_machine, allocator);
ret = rcl_lifecycle_init_default_state_machine(state_machine, allocator);
if (ret != RCL_RET_OK) {
// init default state machine might have allocated memory,
// so we have to call fini
if (rcl_lifecycle_state_machine_fini(state_machine, node_handle, allocator) != RCL_RET_OK) {
// error already set
return RCL_RET_ERROR;
}
ret = rcl_lifecycle_state_machine_fini(state_machine, node_handle, allocator);
return RCL_RET_ERROR;
}
}

Expand Down
27 changes: 20 additions & 7 deletions rcl_lifecycle/src/transition_map.c
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,11 @@ rcl_lifecycle_transition_map_fini(
rcl_lifecycle_transition_map_t * transition_map,
const rcutils_allocator_t * allocator)
{
if (!allocator) {
RCL_SET_ERROR_MSG("can't free transition map, no allocator given\n");
return RCL_RET_ERROR;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
return RCL_RET_ERROR;
return RCL_RET_INVALID_ARGUMENT;

}

rcl_ret_t fcn_ret = RCL_RET_OK;

// free valid transitions for all states
Expand Down Expand Up @@ -87,15 +92,16 @@ rcl_lifecycle_register_state(
allocator, "invalid allocator", return RCUTILS_RET_INVALID_ARGUMENT)

// add new primary state memory
transition_map->states_size += 1;
unsigned int new_states_size = transition_map->states_size + 1;
rcl_lifecycle_state_t * new_states = allocator->reallocate(
transition_map->states,
transition_map->states_size * sizeof(rcl_lifecycle_state_t),
new_states_size * sizeof(rcl_lifecycle_state_t),
allocator->state);
if (!new_states) {
RCL_SET_ERROR_MSG("failed to reallocate memory for new states");
return RCL_RET_ERROR;
}
transition_map->states_size = new_states_size;
transition_map->states = new_states;
transition_map->states[transition_map->states_size - 1] = state;

Expand All @@ -117,32 +123,39 @@ rcl_lifecycle_register_transition(
return RCL_RET_ERROR;
}

// we add a new transition, so increase the size
transition_map->transitions_size += 1;
rcl_lifecycle_state_t * goal = rcl_lifecycle_get_state(transition_map, transition.goal->id);
if (!goal) {
RCL_SET_ERROR_MSG_WITH_FORMAT_STRING("state %u is not registered\n", transition.goal->id);
return RCL_RET_ERROR;
}
// Attempt to add new transition, don't update map if it fails
unsigned int new_transitions_size = transition_map->transitions_size + 1;
rcl_lifecycle_transition_t * new_transitions = allocator->reallocate(
transition_map->transitions,
transition_map->transitions_size * sizeof(rcl_lifecycle_transition_t),
new_transitions_size * sizeof(rcl_lifecycle_transition_t),
allocator->state);
if (!new_transitions) {
RCL_SET_ERROR_MSG("failed to reallocate memory for new transitions");
return RCL_RET_BAD_ALLOC;
}
transition_map->transitions_size = new_transitions_size;
transition_map->transitions = new_transitions;
// finally set the new transition to the end of the array
transition_map->transitions[transition_map->transitions_size - 1] = transition;

// we have to copy the transitons here once more to the actual state
// as we can't assign only the pointer. This pointer gets invalidated whenever
// we add a new transition and re-shuffle/re-allocate new memory for it.
state->valid_transition_size += 1;
unsigned int new_valid_transitions_size = state->valid_transition_size + 1;
rcl_lifecycle_transition_t * new_valid_transitions = allocator->reallocate(
state->valid_transitions,
state->valid_transition_size * sizeof(rcl_lifecycle_transition_t),
new_valid_transitions_size * sizeof(rcl_lifecycle_transition_t),
allocator->state);
if (!new_valid_transitions) {
RCL_SET_ERROR_MSG("failed to reallocate memory for new transitions on state");
return RCL_RET_ERROR;
}
state->valid_transition_size = new_valid_transitions_size;
state->valid_transitions = new_valid_transitions;

state->valid_transitions[state->valid_transition_size - 1] = transition;
Expand Down
8 changes: 7 additions & 1 deletion rcl_lifecycle/test/test_default_state_machine.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,13 @@ TEST_F(TestDefaultStateMachine, zero_init) {
TEST_F(TestDefaultStateMachine, default_init) {
rcl_lifecycle_state_machine_t state_machine = rcl_lifecycle_get_zero_initialized_state_machine();

auto ret = rcl_lifecycle_init_default_state_machine(&state_machine, this->allocator);
// Because this init method is so complex, the succession of failures caused by a null
// allocator will result in several error messages overwriting themselves.
auto ret = rcl_lifecycle_init_default_state_machine(&state_machine, nullptr);
EXPECT_EQ(RCL_RET_ERROR, ret);
rcutils_reset_error();

ret = rcl_lifecycle_init_default_state_machine(&state_machine, this->allocator);
EXPECT_EQ(RCL_RET_OK, ret) << rcl_get_error_string().str;

ret = rcl_lifecycle_state_machine_fini(&state_machine, this->node_ptr, this->allocator);
Expand Down
Loading