-
Notifications
You must be signed in to change notification settings - Fork 163
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
Conversation
Signed-off-by: Stephen Brawner <brawner@gmail.com>
c277cfe
to
44dbbed
Compare
It looks like there is a bit more I can do to boost coverage. I'll work on this more tomorrow. |
479aa12
to
db71eb1
Compare
db71eb1
to
66e036f
Compare
Signed-off-by: Stephen Brawner <brawner@gmail.com>
66e036f
to
70465b7
Compare
Much better coverage, but com_interface and default_state_machine are dragging it down.
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Some of the errors are better defined as RCL_RET INVALID_ARGUMENT. If you decide to change this you should change some error in the tests.
@@ -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; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
return RCL_RET_ERROR; | |
return RCL_RET_INVALID_ARGUMENT; |
@@ -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; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
return RCL_RET_ERROR; | |
return RCL_RET_INVALID_ARGUMENT; |
|
||
if (!label) { | ||
RCL_SET_ERROR_MSG("label pointer is null\n"); | ||
return RCL_RET_ERROR; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
return RCL_RET_ERROR; | |
return RCL_RET_INVALID_ARGUMENT; |
@@ -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; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
return RCL_RET_ERROR; | |
return RCL_RET_INVALID_ARGUMENT; |
@@ -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; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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; | ||
} | ||
if (!allocator) { | ||
RCL_SET_ERROR_MSG("can't initialize state machine, no allocator given\n"); | ||
return RCL_RET_ERROR; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
return RCL_RET_ERROR; | |
return RCL_RET_INVALID_ARGUMENT; |
@@ -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; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
return RCL_RET_ERROR; | |
return RCL_RET_INVALID_ARGUMENT; |
I'm not sure I can change the error codes of already existing functions this late in the game. While, I agree invalid_argument is better than error, I chose RCL_RET_ERROR to match the rest of the code. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I added an issue to follow up with the error messages #655
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good to me besides one comment.
@@ -691,6 +691,7 @@ rcl_lifecycle_init_default_state_machine( | |||
return ret; | |||
|
|||
fail: | |||
// if rcl_lifecycle_transition_map_fini() fails, it will clobber the error string here, twice |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
is that meant as a TODO here? If fini()
fails and sets its own error message, I think we can either reset it here or concatenate. I think overwriting the error message actually yields a warning.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No it was an issue I had found, and didn't have a great way of resolving it. I didn't know concatenation would work, I'll add that in.
It will indeed print a warning if the error message is set and then it proceeds to set another error message. However, I didn't want to lose the trace of errors messages as they fall from one failure to the next (so it would have been impossible to see the original failure case).
Signed-off-by: Stephen Brawner <stephenbrawner@verbsurgical.com>
7a3b0b9
to
93ca2ec
Compare
93ca2ec
to
71607bc
Compare
@@ -690,10 +690,27 @@ rcl_lifecycle_init_default_state_machine( | |||
|
|||
return ret; | |||
|
|||
fail: | |||
// Semicolon handles the "a label can only be part of a statement..." error | |||
fail:; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fail:; | |
fail: |
Or was that semicolon intended?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The compiler wouldn't let me declare current_error
directly after fail:
without it. The semicolon effectively creates a new statement. Should I move the variable declarations to the top?
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 = (rcl_error_is_set()) ? rcl_get_error_string().str : ""; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if I interpret this correctly this line will always result in an empty string, given that you reset the error message just above.
} else if (strcmp(current_error, "") != 0) { | ||
RCL_SET_ERROR_MSG(current_error); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry to iterate again over this, but I think that else if
branch is not needed. If I see this correctly, we'll fetch the error message before just to reset it again in this branch.
Why not just retrieving the existing error message within the first if
branch?
something like this:
if (rcl_lifecycle_transition_map_fini(&state_machine->transition_map, allocator) != RCL_RET_OK) {
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",
current_error, fini_error);
}
if (!rcl_error_is_set()) {
RCL_SET_ERROR_MSG("Unspecified error ..");
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The problem originally was that the error message was already set. But if rcl_lifecycle_transition_map_fini
failed, it would set its own message, and then the message would get set a third time inside this first if loop. So the goal was to keep track of current_message
, and append fini_error if it existed and throw a combined error that was hopefully more helpful than either of them individually.
However, if rcl_lifecycle_transition_map_fini
succeeds, we still want the user to know what caused the code to reach fail:
in the first place, which is in current_message
, but only if rcl_error_is_set() was true in line 697.
Originally when I submitted this, I just relied on rcutils to log the transition of error messages, since it warns if a new error message is set while a previous one had already existed. However, since I'm pretty new to the ROS 2 C codebase, I'm really not confident about any particular approach here 🤷♂️
Address @Karsten1987's feedback and am running CI again. |
Signed-off-by: Stephen Brawner <stephenbrawner@verbsurgical.com>
be69602
to
661b4b7
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this looks good to me now. Thanks for iterating with me over this.
Thank you for your reviews and help! |
@brawner @Karsten1987 this patch is causing test failures in |
Looks like a transition can be lazy initialized where start and goal state don't have to be present. Which means we should remove the nullptr checks for these two. |
As part of the push to Quality Level 1, this adds more more tests of the rcl_lifecycle API. While writing these tests, I found a few instances where more checks were needed.
If I didn't understand parts of the API and added checks where they weren't desired, feel free to make comments below. Alternatively, if there are more thorough checks that people feel are necessary I can also add those in. These were pretty much what I found passing around bad parameters.