From 153f42e9ed331f02e78ac811b69f02327ffe6edb Mon Sep 17 00:00:00 2001 From: Stephen Brawner Date: Tue, 30 Jun 2020 19:01:18 -0700 Subject: [PATCH 01/15] Initial commit of fault injection macros and functionality Signed-off-by: Stephen Brawner --- CMakeLists.txt | 1 + Doxyfile | 1 + include/rcutils/error_handling.h | 1 + include/rcutils/macros.h | 49 ++++++++++++ include/rcutils/testing/fault_injection.h | 90 +++++++++++++++++++++++ src/testing/fault_injection.c | 44 +++++++++++ 6 files changed, 186 insertions(+) create mode 100644 include/rcutils/testing/fault_injection.h create mode 100644 src/testing/fault_injection.c diff --git a/CMakeLists.txt b/CMakeLists.txt index 68092033..2a0d769b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -51,6 +51,7 @@ set(rcutils_sources src/strerror.c src/string_array.c src/string_map.c + src/testing/fault_injection.c src/time.c ${time_impl_c} src/uint8_array.c diff --git a/Doxyfile b/Doxyfile index e0df4833..828da77e 100644 --- a/Doxyfile +++ b/Doxyfile @@ -24,6 +24,7 @@ EXPAND_ONLY_PREDEF = YES PREDEFINED += RCUTILS_PUBLIC= PREDEFINED += RCUTILS_PUBLIC_TYPE= PREDEFINED += RCUTILS_WARN_UNUSED= +PREDEFINED += RCUTILS_ENABLE_FAULT_INJECTION= # Tag files that do not exist will produce a warning and cross-project linking will not work. TAGFILES += "../../../doxygen_tag_files/cppreference-doxygen-web.tag.xml=http://en.cppreference.com/w/" diff --git a/include/rcutils/error_handling.h b/include/rcutils/error_handling.h index 9f19b834..940bf06b 100644 --- a/include/rcutils/error_handling.h +++ b/include/rcutils/error_handling.h @@ -36,6 +36,7 @@ extern "C" #include "rcutils/allocator.h" #include "rcutils/macros.h" #include "rcutils/snprintf.h" +#include "rcutils/testing/fault_injection.h" #include "rcutils/types/rcutils_ret.h" #include "rcutils/visibility_control.h" diff --git a/include/rcutils/macros.h b/include/rcutils/macros.h index 6618119b..c6bcb1eb 100644 --- a/include/rcutils/macros.h +++ b/include/rcutils/macros.h @@ -131,6 +131,55 @@ extern "C" # define RCUTILS_UNLIKELY(x) (x) #endif // _WIN32 +#if defined RCUTILS_ENABLE_FAULT_INJECTION +#include "rcutils/testing/fault_injection.h" + +/** + * \def RCUTILS_CAN_RETURN_WITH_ERROR_OF + * Indicating macro that the function intends to return possible error value. + * + * Put this macro as the first line in the function. For example: + * + * int rcutils_function_that_can_fail() { + * RCUTILS_CAN_RETURN_WITH_ERROR_OF(RCUTILS_RET_INVALID_ARGUMENT); + * ... // rest of function + * } + * + * For now, this macro just simply calls `RCUTILS_MAYBE_RETURN_ERROR` if fault injection is + * enabled. However, for source code, the macro annotation `RCUTILS_CAN_RETURN_WITH_ERROR_OF` helps + * clarify that a function may return a value signifying an error and what those are. + * + * In general, you should only include a return value that originates in the function you're + * annotating instead of one that is merely passed on from a called function already annotated with + *`RCUTILS_CAN_RETURN_WITH_ERROR_OF`. If you are passing on return values from a called function, + * but that function is not annotated with `RCUTILS_CAN_RETURN_WITH_ERROR_OF`, then you might + * consider annotating that function first. If for some reason that is not desired or possible, + * then annotate your function as if the return values you are passing on originated from your + * function. + * + * If the function can return multiple return values indicating separate failure types, each one + * should go on a separate line. + * + * If in your function, there are expected effects on output parameters that occur during + * the failure case, then it will introduce a discrepency between fault injection testing and + * production operation. This is because the fault injection will cause the function to return + * where this macro is used, not at the location the error values are typically returned. To help + * protect against this scenario you may consider adding unit tests that checks your function does + * not modify output parameters when it actually returns a failing error code. + * + * If your function is void, this macro can be used without parameters. However, for the above + * reasoning, there should be no side effects on output parameters for all possible early returns. + * + * \param error_return_value the value returned as a result of an error. It does not need to be + * a rcutils_ret_t type. It could also be NULL, -1, a string error message, etc + */ +# define RCUTILS_CAN_RETURN_WITH_ERROR_OF(error_return_value) \ + RCUTILS_MAYBE_RETURN_ERROR(error_return_value); + +#else +# define RCUTILS_CAN_RETURN_WITH_ERROR_OF(error_return_value) +#endif // defined RCUTILS_ENABLE_FAULT_INJECTION + #ifdef __cplusplus } #endif diff --git a/include/rcutils/testing/fault_injection.h b/include/rcutils/testing/fault_injection.h new file mode 100644 index 00000000..ab357a59 --- /dev/null +++ b/include/rcutils/testing/fault_injection.h @@ -0,0 +1,90 @@ +// 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. + +#ifndef RCUTILS__TESTING_MACROS_H_ +#define RCUTILS__TESTING_MACROS_H_ +#include + +#ifdef __cplusplus +extern "C" +{ +#endif + +void _rcutils_set_fault_injection_count(int count); + +int _rcutils_maybe_fail(); + +#if defined RCUTILS_ENABLE_FAULT_INJECTION + +/** + * \def RCUTILS_MAYBE_RETURN_ERROR + * \brief This macro checks and decrements a static global variable atomic counter and returns + * `return_value_on_error` if 0. + * + * This macro is not a function itself, so when this macro returns it will cause the calling + * function to return with the return value. + * + * Set the counter with `RCUTILS_SET_FAULT_INJECTION_COUNT`. If the count is less than 0, then + * `RCUTILS_MAYBE_RETURN_ERROR` will not cause an early return. + * + * This macro is thread-safe, and ensures that at most one invocation results in a failure for each + * time the fault injection counter is set with `RCUTILS_SET_FAULT_INJECTION_COUNT` + * + * \param return_value_on_error the value to return in the case of fault injected failure. + */ +#define RCUTILS_MAYBE_RETURN_ERROR(return_value_on_error) \ + if (0 == _rcutils_maybe_fail()) { \ + return return_value_on_error; \ + } + +/** + * \def RCUTILS_SET_FAULT_INJECTION_COUNT + * \brief Atomically set the fault injection counter. + * + * There will be at most one fault injected failure per call to RCUTILS_SET_FAULT_INJECTION_COUNT. + * To test all reachable fault injection locations, call this macro inside a for loop with + * sufficient iterations setting count to the loop iteration variable. For example: + * + * for (int i = 0; i < SUFFICIENTLY_LARGE_ITERATION_COUNT; ++i) { + * RCUTILS_SET_FAULT_INJECTION_COUNT(i); + * ... // Call function under test + * } + * + * Where SUFFICIENTLY_LARGE_ITERATION_COUNT is a value larger than the maximum expected calls to + * `RCUTILS_MAYBE_RETURN_ERROR`. In your fault injection unit test, it is recommended to run one + * last iteration with the fault injection counter set to this maximum value and validate that the + * results of the call to the function under test would result in the same thing as if no fault + * injection was used. This will help ensure that this maximum value is suitable and will call + * attention to maintainers if it needs to be increased because more instances of + * RCUTILS_MAYBE_RETURN_ERROR were introduced. + * + * \param count The count to set the fault injection counter to. If count is negative, then fault + * injection errors will be disabled. The counter is globally initialized to -1. + */ +#define RCUTILS_SET_FAULT_INJECTION_COUNT(count) \ + _rcutils_set_fault_injection_count(count); + +#else // RCUTILS_ENABLE_FAULT_INJECTION + +#define RCUTILS_SET_FAULT_INJECTION_COUNT(count) + +#define RCUTILS_MAYBE_RETURN_ERROR(msg, error_statement) + +#endif // defined RCUTILS_ENABLE_FAULT_INJECTION + +#ifdef __cplusplus +} +#endif + +#endif // RCUTILS__TESTING_MACROS_H_ diff --git a/src/testing/fault_injection.c b/src/testing/fault_injection.c new file mode 100644 index 00000000..592cc1bb --- /dev/null +++ b/src/testing/fault_injection.c @@ -0,0 +1,44 @@ +// 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 "rcutils/testing/fault_injection.h" + +#include "rcutils/stdatomic_helper.h" + +static atomic_int_least64_t g_rcutils_fault_injection_count = -1; + +int _rcutils_maybe_fail() +{ + bool set_atomic_success = false; + int_least64_t current_count = -1; + rcutils_atomic_load(&g_rcutils_fault_injection_count, current_count); + do { + // A fault_injection_count less than 0 means that maybe_fail doesn't fail, so just return. + if (current_count < 0) { + return current_count; + } + + // Otherwise decrement by one, but do so in a thread-safe manner so that exactly one calling + // thread gets the 0 case. + int_least64_t desired_count = current_count - 1; + rcutils_atomic_compare_exchange_strong( + &g_rcutils_fault_injection_count, set_atomic_success, ¤t_count, desired_count); + } while (!set_atomic_success); + return current_count; +} + +void _rcutils_set_fault_injection_count(int count) +{ + rcutils_atomic_store(&g_rcutils_fault_injection_count, count); +} From e9cc01984bc6871b3133782f21c8f2ee70010822 Mon Sep 17 00:00:00 2001 From: Stephen Brawner Date: Tue, 30 Jun 2020 19:01:45 -0700 Subject: [PATCH 02/15] Add can_return_with_failure to rcutils_vsnprintf Signed-off-by: Stephen Brawner --- src/snprintf.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/snprintf.c b/src/snprintf.c index afe0be6e..d8f1b01f 100644 --- a/src/snprintf.c +++ b/src/snprintf.c @@ -39,6 +39,8 @@ rcutils_snprintf(char * buffer, size_t buffer_size, const char * format, ...) int rcutils_vsnprintf(char * buffer, size_t buffer_size, const char * format, va_list args) { + RCUTILS_CAN_RETURN_WITH_ERROR_OF(-1); + if (NULL == format) { errno = EINVAL; return -1; From 97dc3694370de9b6e30e7f05bde3f4764855cb4b Mon Sep 17 00:00:00 2001 From: Stephen Brawner Date: Tue, 7 Jul 2020 13:54:26 -0700 Subject: [PATCH 03/15] Add getter for fault injection count Signed-off-by: Stephen Brawner --- include/rcutils/testing/fault_injection.h | 49 ++++++++++++++++++----- src/testing/fault_injection.c | 13 ++++-- 2 files changed, 48 insertions(+), 14 deletions(-) diff --git a/include/rcutils/testing/fault_injection.h b/include/rcutils/testing/fault_injection.h index ab357a59..b585f2ac 100644 --- a/include/rcutils/testing/fault_injection.h +++ b/include/rcutils/testing/fault_injection.h @@ -16,13 +16,21 @@ #define RCUTILS__TESTING_MACROS_H_ #include +#include "rcutils/stdatomic_helper.h" + #ifdef __cplusplus extern "C" { #endif +#define RCUTILS_FAULT_INJECTION_NEVER_FAIL -1 + +#define RCUTILS_FAULT_INJECTION_FAIL_NOW 0 + void _rcutils_set_fault_injection_count(int count); +int_least64_t _rcutils_get_fault_injection_count(); + int _rcutils_maybe_fail(); #if defined RCUTILS_ENABLE_FAULT_INJECTION @@ -44,7 +52,7 @@ int _rcutils_maybe_fail(); * \param return_value_on_error the value to return in the case of fault injected failure. */ #define RCUTILS_MAYBE_RETURN_ERROR(return_value_on_error) \ - if (0 == _rcutils_maybe_fail()) { \ + if (RCUTILS_FAULT_INJECTION_FAIL_NOW == _rcutils_maybe_fail()) { \ return return_value_on_error; \ } @@ -53,32 +61,53 @@ int _rcutils_maybe_fail(); * \brief Atomically set the fault injection counter. * * There will be at most one fault injected failure per call to RCUTILS_SET_FAULT_INJECTION_COUNT. - * To test all reachable fault injection locations, call this macro inside a for loop with - * sufficient iterations setting count to the loop iteration variable. For example: + * To test all reachable fault injection locations, call this macro inside a loop and set the count + * to an incrementing count variable. * * for (int i = 0; i < SUFFICIENTLY_LARGE_ITERATION_COUNT; ++i) { * RCUTILS_SET_FAULT_INJECTION_COUNT(i); * ... // Call function under test * } + * ASSERT_LT(RCUTILS_FAULT_INJECTION_NEVER_FAIL, RCUTILS_GET_FAULT_INJECTION_COUNT()); * * Where SUFFICIENTLY_LARGE_ITERATION_COUNT is a value larger than the maximum expected calls to - * `RCUTILS_MAYBE_RETURN_ERROR`. In your fault injection unit test, it is recommended to run one - * last iteration with the fault injection counter set to this maximum value and validate that the - * results of the call to the function under test would result in the same thing as if no fault - * injection was used. This will help ensure that this maximum value is suitable and will call - * attention to maintainers if it needs to be increased because more instances of - * RCUTILS_MAYBE_RETURN_ERROR were introduced. + * `RCUTILS_MAYBE_RETURN_ERROR`. This last assertion just insures that your choice for + * SUFFICIENTLY_LARGE_ITERATION_COUNT was large enough. To avoid having to choose this count + * yourself, you can use a do-while loop. + * + * int i = 0; + * do { + * RCUTILS_SET_FAULT_INJECTION_COUNT(i++); + * ... // Call function under test + * } while (RCUTILS_GET_FAULT_INJECTION_COUNT() <= RCUTILS_FAULT_INJECTION_NEVER_FAIL); * * \param count The count to set the fault injection counter to. If count is negative, then fault - * injection errors will be disabled. The counter is globally initialized to -1. + * injection errors will be disabled. The counter is globally initialized to + * RCUTILS_FAULT_INJECTION_NEVER_FAIL. */ #define RCUTILS_SET_FAULT_INJECTION_COUNT(count) \ _rcutils_set_fault_injection_count(count); +/** + * \def RCUTILS_GET_FAULT_INJECTION_COUNT + * \brief Atomically get the fault injection counter value + * + * Use this macro after running the code under test to check whether the counter reached a negative + * value. This is helpful so you can verify that you ran the fault injection test in a loop a + * sufficient number of times. Likewise, if the code under test returned with an error, but the + * count value was greater or equal to 0, then the failure was not caused by the fault injection + * counter. + */ +#define RCUTILS_GET_FAULT_INJECTION_COUNT() \ + _rcutils_get_fault_injection_count(); + #else // RCUTILS_ENABLE_FAULT_INJECTION #define RCUTILS_SET_FAULT_INJECTION_COUNT(count) +// This needs to be set to an int for compatibility +#define RCUTILS_GET_FAULT_INJECTION_COUNT() RCUTILS_FAULT_INJECTION_NEVER_FAIL + #define RCUTILS_MAYBE_RETURN_ERROR(msg, error_statement) #endif // defined RCUTILS_ENABLE_FAULT_INJECTION diff --git a/src/testing/fault_injection.c b/src/testing/fault_injection.c index 592cc1bb..50a30803 100644 --- a/src/testing/fault_injection.c +++ b/src/testing/fault_injection.c @@ -14,18 +14,16 @@ #include "rcutils/testing/fault_injection.h" -#include "rcutils/stdatomic_helper.h" - static atomic_int_least64_t g_rcutils_fault_injection_count = -1; int _rcutils_maybe_fail() { bool set_atomic_success = false; - int_least64_t current_count = -1; + int_least64_t current_count = RCUTILS_FAULT_INJECTION_NEVER_FAIL; rcutils_atomic_load(&g_rcutils_fault_injection_count, current_count); do { // A fault_injection_count less than 0 means that maybe_fail doesn't fail, so just return. - if (current_count < 0) { + if (current_count <= RCUTILS_FAULT_INJECTION_NEVER_FAIL) { return current_count; } @@ -42,3 +40,10 @@ void _rcutils_set_fault_injection_count(int count) { rcutils_atomic_store(&g_rcutils_fault_injection_count, count); } + +int_least64_t _rcutils_get_fault_injection_count() +{ + int_least64_t count = 0; + rcutils_atomic_load(&g_rcutils_fault_injection_count, count); + return count; +} From a4f74aeacb442d2022511a03e888d86ad93f60ce Mon Sep 17 00:00:00 2001 From: Stephen Brawner Date: Tue, 7 Jul 2020 14:06:38 -0700 Subject: [PATCH 04/15] Adjust headers Signed-off-by: Stephen Brawner --- include/rcutils/testing/fault_injection.h | 6 ++---- src/testing/fault_injection.c | 2 ++ 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/include/rcutils/testing/fault_injection.h b/include/rcutils/testing/fault_injection.h index b585f2ac..3f410d3b 100644 --- a/include/rcutils/testing/fault_injection.h +++ b/include/rcutils/testing/fault_injection.h @@ -15,9 +15,7 @@ #ifndef RCUTILS__TESTING_MACROS_H_ #define RCUTILS__TESTING_MACROS_H_ #include - -#include "rcutils/stdatomic_helper.h" - +#include #ifdef __cplusplus extern "C" { @@ -94,7 +92,7 @@ int _rcutils_maybe_fail(); * * Use this macro after running the code under test to check whether the counter reached a negative * value. This is helpful so you can verify that you ran the fault injection test in a loop a - * sufficient number of times. Likewise, if the code under test returned with an error, but the + * sufficient number of times. Likewise, if the code under test returned with an error, but the * count value was greater or equal to 0, then the failure was not caused by the fault injection * counter. */ diff --git a/src/testing/fault_injection.c b/src/testing/fault_injection.c index 50a30803..d6b40b86 100644 --- a/src/testing/fault_injection.c +++ b/src/testing/fault_injection.c @@ -14,6 +14,8 @@ #include "rcutils/testing/fault_injection.h" +#include "rcutils/stdatomic_helper.h" + static atomic_int_least64_t g_rcutils_fault_injection_count = -1; int _rcutils_maybe_fail() From adad3bb81bb9181c215d105c120babc2a837c788 Mon Sep 17 00:00:00 2001 From: Stephen Brawner Date: Tue, 28 Jul 2020 15:47:59 -0700 Subject: [PATCH 05/15] Adding more fault injection locations Signed-off-by: Stephen Brawner --- include/rcutils/testing/fault_injection.h | 11 ++++++++--- src/shared_library.c | 5 +++++ src/strdup.c | 9 +++++++-- src/string_array.c | 5 +++++ src/testing/fault_injection.c | 9 +++++++++ 5 files changed, 34 insertions(+), 5 deletions(-) diff --git a/include/rcutils/testing/fault_injection.h b/include/rcutils/testing/fault_injection.h index 3f410d3b..30373375 100644 --- a/include/rcutils/testing/fault_injection.h +++ b/include/rcutils/testing/fault_injection.h @@ -12,8 +12,9 @@ // See the License for the specific language governing permissions and // limitations under the License. -#ifndef RCUTILS__TESTING_MACROS_H_ -#define RCUTILS__TESTING_MACROS_H_ +#ifndef RCUTILS__TESTING__FAULT_INJECTION_H_ +#define RCUTILS__TESTING__FAULT_INJECTION_H_ +#include #include #include #ifdef __cplusplus @@ -25,6 +26,8 @@ extern "C" #define RCUTILS_FAULT_INJECTION_FAIL_NOW 0 +bool rcutils_fault_injection_is_test_complete(); + void _rcutils_set_fault_injection_count(int count); int_least64_t _rcutils_get_fault_injection_count(); @@ -51,6 +54,8 @@ int _rcutils_maybe_fail(); */ #define RCUTILS_MAYBE_RETURN_ERROR(return_value_on_error) \ if (RCUTILS_FAULT_INJECTION_FAIL_NOW == _rcutils_maybe_fail()) { \ + printf( \ + "%s:%d Injecting fault and returning " #return_value_on_error "\n", __FILE__, __LINE__); \ return return_value_on_error; \ } @@ -114,4 +119,4 @@ int _rcutils_maybe_fail(); } #endif -#endif // RCUTILS__TESTING_MACROS_H_ +#endif // RCUTILS__TESTING__FAULT_INJECTION_H_ diff --git a/src/shared_library.c b/src/shared_library.c index a0ce9a7e..52b1d0de 100644 --- a/src/shared_library.c +++ b/src/shared_library.c @@ -27,6 +27,7 @@ C_ASSERT(sizeof(void *) == sizeof(HINSTANCE)); #endif // _WIN32 #include "rcutils/error_handling.h" +#include "rcutils/macros.h" #include "rcutils/shared_library.h" #include "rcutils/strdup.h" @@ -46,6 +47,10 @@ rcutils_load_shared_library( const char * library_path, rcutils_allocator_t allocator) { + RCUTILS_CAN_RETURN_WITH_ERROR_OF(RCUTILS_RET_INVALID_ARGUMENT); + RCUTILS_CAN_RETURN_WITH_ERROR_OF(RCUTILS_RET_BAD_ALLOC); + RCUTILS_CAN_RETURN_WITH_ERROR_OF(RCUTILS_RET_ERROR); + RCUTILS_CHECK_ARGUMENT_FOR_NULL(lib, RCUTILS_RET_INVALID_ARGUMENT); RCUTILS_CHECK_ARGUMENT_FOR_NULL(library_path, RCUTILS_RET_INVALID_ARGUMENT); RCUTILS_CHECK_ALLOCATOR(&allocator, return RCUTILS_RET_INVALID_ARGUMENT); diff --git a/src/strdup.c b/src/strdup.c index bc52b189..1029b64b 100644 --- a/src/strdup.c +++ b/src/strdup.c @@ -17,16 +17,19 @@ extern "C" { #endif -#include "rcutils/strdup.h" - #include #include #include "./common.h" +#include "rcutils/macros.h" +#include "rcutils/strdup.h" + char * rcutils_strdup(const char * str, rcutils_allocator_t allocator) { + RCUTILS_CAN_RETURN_WITH_ERROR_OF(NULL); + if (NULL == str) { return NULL; } @@ -36,6 +39,8 @@ rcutils_strdup(const char * str, rcutils_allocator_t allocator) char * rcutils_strndup(const char * str, size_t string_length, rcutils_allocator_t allocator) { + RCUTILS_CAN_RETURN_WITH_ERROR_OF(NULL); + if (NULL == str) { return NULL; } diff --git a/src/string_array.c b/src/string_array.c index 759950fa..40adb54d 100644 --- a/src/string_array.c +++ b/src/string_array.c @@ -42,6 +42,9 @@ rcutils_string_array_init( size_t size, const rcutils_allocator_t * allocator) { + RCUTILS_CAN_RETURN_WITH_ERROR_OF(RCUTILS_RET_INVALID_ARGUMENT); + RCUTILS_CAN_RETURN_WITH_ERROR_OF(RCUTILS_RET_BAD_ALLOC); + if (NULL == allocator) { RCUTILS_SET_ERROR_MSG("allocator is null"); return RCUTILS_RET_INVALID_ARGUMENT; @@ -63,6 +66,8 @@ rcutils_string_array_init( rcutils_ret_t rcutils_string_array_fini(rcutils_string_array_t * string_array) { + RCUTILS_CAN_RETURN_WITH_ERROR_OF(RCUTILS_RET_INVALID_ARGUMENT); + if (NULL == string_array) { RCUTILS_SET_ERROR_MSG("string_array is null"); return RCUTILS_RET_INVALID_ARGUMENT; diff --git a/src/testing/fault_injection.c b/src/testing/fault_injection.c index d6b40b86..b55dac72 100644 --- a/src/testing/fault_injection.c +++ b/src/testing/fault_injection.c @@ -18,6 +18,15 @@ static atomic_int_least64_t g_rcutils_fault_injection_count = -1; +bool rcutils_fault_injection_is_test_complete() +{ +#ifndef RCUTILS_ENABLE_FAULT_INJECTION + return true; +#endif // RCUTILS_ENABLE_FAULT_INJECTION + + return _rcutils_get_fault_injection_count() > RCUTILS_FAULT_INJECTION_NEVER_FAIL; +} + int _rcutils_maybe_fail() { bool set_atomic_success = false; From e86483aa11e3b443beab7f51e487ce1c586e3a25 Mon Sep 17 00:00:00 2001 From: Stephen Brawner Date: Tue, 28 Jul 2020 15:53:20 -0700 Subject: [PATCH 06/15] Revert header strdup Signed-off-by: Stephen Brawner --- src/strdup.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/strdup.c b/src/strdup.c index 1029b64b..2513c35d 100644 --- a/src/strdup.c +++ b/src/strdup.c @@ -17,12 +17,13 @@ extern "C" { #endif +#include "rcutils/strdup.h" + #include #include #include "./common.h" #include "rcutils/macros.h" -#include "rcutils/strdup.h" char * From e083a0d727a0c77106fefe54c36c16af2cdf8635 Mon Sep 17 00:00:00 2001 From: Stephen Brawner Date: Tue, 28 Jul 2020 16:25:47 -0700 Subject: [PATCH 07/15] Adjust names Signed-off-by: Stephen Brawner --- include/rcutils/macros.h | 9 +-- include/rcutils/testing/fault_injection.h | 69 +++++++++++++++-------- src/testing/fault_injection.c | 8 +-- 3 files changed, 54 insertions(+), 32 deletions(-) diff --git a/include/rcutils/macros.h b/include/rcutils/macros.h index c6bcb1eb..1b4e9eac 100644 --- a/include/rcutils/macros.h +++ b/include/rcutils/macros.h @@ -145,9 +145,10 @@ extern "C" * ... // rest of function * } * - * For now, this macro just simply calls `RCUTILS_MAYBE_RETURN_ERROR` if fault injection is - * enabled. However, for source code, the macro annotation `RCUTILS_CAN_RETURN_WITH_ERROR_OF` helps - * clarify that a function may return a value signifying an error and what those are. + * For now, this macro just simply calls `RCUTILS_FAULT_INJECTION_MAYBE_RETURN_ERROR` if fault + * injection is enabled. However, for source code, the macro annotation + * `RCUTILS_CAN_RETURN_WITH_ERROR_OF` helps clarify that a function may return a value signifying + * an error and what those are. * * In general, you should only include a return value that originates in the function you're * annotating instead of one that is merely passed on from a called function already annotated with @@ -174,7 +175,7 @@ extern "C" * a rcutils_ret_t type. It could also be NULL, -1, a string error message, etc */ # define RCUTILS_CAN_RETURN_WITH_ERROR_OF(error_return_value) \ - RCUTILS_MAYBE_RETURN_ERROR(error_return_value); + RCUTILS_FAULT_INJECTION_MAYBE_RETURN_ERROR(error_return_value); #else # define RCUTILS_CAN_RETURN_WITH_ERROR_OF(error_return_value) diff --git a/include/rcutils/testing/fault_injection.h b/include/rcutils/testing/fault_injection.h index 30373375..55344197 100644 --- a/include/rcutils/testing/fault_injection.h +++ b/include/rcutils/testing/fault_injection.h @@ -28,71 +28,71 @@ extern "C" bool rcutils_fault_injection_is_test_complete(); -void _rcutils_set_fault_injection_count(int count); +void _rcutils_fault_injection_set_count(int count); -int_least64_t _rcutils_get_fault_injection_count(); +int_least64_t _rcutils_fault_injection_get_count(); -int _rcutils_maybe_fail(); +int _rcutils_fault_injection_maybe_fail(); #if defined RCUTILS_ENABLE_FAULT_INJECTION /** - * \def RCUTILS_MAYBE_RETURN_ERROR + * \def RCUTILS_FAULT_INJECTION_MAYBE_RETURN_ERROR * \brief This macro checks and decrements a static global variable atomic counter and returns * `return_value_on_error` if 0. * * This macro is not a function itself, so when this macro returns it will cause the calling * function to return with the return value. * - * Set the counter with `RCUTILS_SET_FAULT_INJECTION_COUNT`. If the count is less than 0, then - * `RCUTILS_MAYBE_RETURN_ERROR` will not cause an early return. + * Set the counter with `RCUTILS_FAULT_INJECTION_SET_COUNT`. If the count is less than 0, then + * `RCUTILS_FAULT_INJECTION_MAYBE_RETURN_ERROR` will not cause an early return. * * This macro is thread-safe, and ensures that at most one invocation results in a failure for each - * time the fault injection counter is set with `RCUTILS_SET_FAULT_INJECTION_COUNT` + * time the fault injection counter is set with `RCUTILS_FAULT_INJECTION_SET_COUNT` * * \param return_value_on_error the value to return in the case of fault injected failure. */ -#define RCUTILS_MAYBE_RETURN_ERROR(return_value_on_error) \ - if (RCUTILS_FAULT_INJECTION_FAIL_NOW == _rcutils_maybe_fail()) { \ +#define RCUTILS_FAULT_INJECTION_MAYBE_RETURN_ERROR(return_value_on_error) \ + if (RCUTILS_FAULT_INJECTION_FAIL_NOW == _rcutils_fault_injection_maybe_fail()) { \ printf( \ "%s:%d Injecting fault and returning " #return_value_on_error "\n", __FILE__, __LINE__); \ return return_value_on_error; \ } /** - * \def RCUTILS_SET_FAULT_INJECTION_COUNT + * \def RCUTILS_FAULT_INJECTION_SET_COUNT * \brief Atomically set the fault injection counter. * - * There will be at most one fault injected failure per call to RCUTILS_SET_FAULT_INJECTION_COUNT. + * There will be at most one fault injected failure per call to RCUTILS_FAULT_INJECTION_SET_COUNT. * To test all reachable fault injection locations, call this macro inside a loop and set the count * to an incrementing count variable. * * for (int i = 0; i < SUFFICIENTLY_LARGE_ITERATION_COUNT; ++i) { - * RCUTILS_SET_FAULT_INJECTION_COUNT(i); + * RCUTILS_FAULT_INJECTION_SET_COUNT(i); * ... // Call function under test * } - * ASSERT_LT(RCUTILS_FAULT_INJECTION_NEVER_FAIL, RCUTILS_GET_FAULT_INJECTION_COUNT()); + * ASSERT_LT(RCUTILS_FAULT_INJECTION_NEVER_FAIL, RCUTILS_FAULT_INJECTION_GET_COUNT()); * * Where SUFFICIENTLY_LARGE_ITERATION_COUNT is a value larger than the maximum expected calls to - * `RCUTILS_MAYBE_RETURN_ERROR`. This last assertion just insures that your choice for + * `RCUTILS_FAULT_INJECTION_MAYBE_RETURN_ERROR`. This last assertion just insures that your choice for * SUFFICIENTLY_LARGE_ITERATION_COUNT was large enough. To avoid having to choose this count * yourself, you can use a do-while loop. * * int i = 0; * do { - * RCUTILS_SET_FAULT_INJECTION_COUNT(i++); + * RCUTILS_FAULT_INJECTION_SET_COUNT(i++); * ... // Call function under test - * } while (RCUTILS_GET_FAULT_INJECTION_COUNT() <= RCUTILS_FAULT_INJECTION_NEVER_FAIL); + * } while (RCUTILS_FAULT_INJECTION_GET_COUNT() <= RCUTILS_FAULT_INJECTION_NEVER_FAIL); * * \param count The count to set the fault injection counter to. If count is negative, then fault * injection errors will be disabled. The counter is globally initialized to * RCUTILS_FAULT_INJECTION_NEVER_FAIL. */ -#define RCUTILS_SET_FAULT_INJECTION_COUNT(count) \ - _rcutils_set_fault_injection_count(count); +#define RCUTILS_FAULT_INJECTION_SET_COUNT(count) \ + _rcutils_fault_injection_set_count(count); /** - * \def RCUTILS_GET_FAULT_INJECTION_COUNT + * \def RCUTILS_FAULT_INJECTION_GET_COUNT * \brief Atomically get the fault injection counter value * * Use this macro after running the code under test to check whether the counter reached a negative @@ -101,17 +101,38 @@ int _rcutils_maybe_fail(); * count value was greater or equal to 0, then the failure was not caused by the fault injection * counter. */ -#define RCUTILS_GET_FAULT_INJECTION_COUNT() \ - _rcutils_get_fault_injection_count(); +#define RCUTILS_FAULT_INJECTION_GET_COUNT() \ + _rcutils_fault_injection_get_count(); + +#define RCUTILS_FAULT_INJECTION_INIT() \ + RCUTILS_FAULT_INJECTION_SET_COUNT(RCUTILS_FAULT_INJECTION_NEVER_FAIL) + +#define RCUTILS_FAULT_INJECTION_FINI() \ + RCUTILS_FAULT_INJECTION_SET_COUNT(RCUTILS_FAULT_INJECTION_NEVER_FAIL) + +#define RCUTILS_FAULT_INJECTION_TEST(code) \ + do { \ + int fault_injection_count = 0; \ + do { \ + RCUTILS_FAULT_INJECTION_SET_COUNT(fault_injection_count++); \ + code; \ + } while (!rcutils_fault_injection_is_test_complete()); \ + } while(0) #else // RCUTILS_ENABLE_FAULT_INJECTION -#define RCUTILS_SET_FAULT_INJECTION_COUNT(count) +#define RCUTILS_FAULT_INJECTION_SET_COUNT(count) // This needs to be set to an int for compatibility -#define RCUTILS_GET_FAULT_INJECTION_COUNT() RCUTILS_FAULT_INJECTION_NEVER_FAIL +#define RCUTILS_FAULT_INJECTION_GET_COUNT() RCUTILS_FAULT_INJECTION_NEVER_FAIL + +#define RCUTILS_FAULT_INJECTION_MAYBE_RETURN_ERROR(msg, error_statement) + +#define RCUTILS_FAULT_INJECTION_INIT() \ + printf("Fault injection is disabled, skipping\n"); \ + return; -#define RCUTILS_MAYBE_RETURN_ERROR(msg, error_statement) +#define RCUTILS_FAULT_INJECTION_FINI() #endif // defined RCUTILS_ENABLE_FAULT_INJECTION diff --git a/src/testing/fault_injection.c b/src/testing/fault_injection.c index b55dac72..71806984 100644 --- a/src/testing/fault_injection.c +++ b/src/testing/fault_injection.c @@ -24,10 +24,10 @@ bool rcutils_fault_injection_is_test_complete() return true; #endif // RCUTILS_ENABLE_FAULT_INJECTION - return _rcutils_get_fault_injection_count() > RCUTILS_FAULT_INJECTION_NEVER_FAIL; + return _rcutils_fault_injection_get_count() > RCUTILS_FAULT_INJECTION_NEVER_FAIL; } -int _rcutils_maybe_fail() +int _rcutils_fault_injection_maybe_fail() { bool set_atomic_success = false; int_least64_t current_count = RCUTILS_FAULT_INJECTION_NEVER_FAIL; @@ -47,12 +47,12 @@ int _rcutils_maybe_fail() return current_count; } -void _rcutils_set_fault_injection_count(int count) +void _rcutils_fault_injection_set_count(int count) { rcutils_atomic_store(&g_rcutils_fault_injection_count, count); } -int_least64_t _rcutils_get_fault_injection_count() +int_least64_t _rcutils_fault_injection_get_count() { int_least64_t count = 0; rcutils_atomic_load(&g_rcutils_fault_injection_count, count); From 203747d38a5add493e923e33c3a321c314a30864 Mon Sep 17 00:00:00 2001 From: Stephen Brawner Date: Wed, 29 Jul 2020 12:18:39 -0700 Subject: [PATCH 08/15] Remove init/fini macros Signed-off-by: Stephen Brawner --- include/rcutils/testing/fault_injection.h | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/include/rcutils/testing/fault_injection.h b/include/rcutils/testing/fault_injection.h index 55344197..04b1edcb 100644 --- a/include/rcutils/testing/fault_injection.h +++ b/include/rcutils/testing/fault_injection.h @@ -104,12 +104,6 @@ int _rcutils_fault_injection_maybe_fail(); #define RCUTILS_FAULT_INJECTION_GET_COUNT() \ _rcutils_fault_injection_get_count(); -#define RCUTILS_FAULT_INJECTION_INIT() \ - RCUTILS_FAULT_INJECTION_SET_COUNT(RCUTILS_FAULT_INJECTION_NEVER_FAIL) - -#define RCUTILS_FAULT_INJECTION_FINI() \ - RCUTILS_FAULT_INJECTION_SET_COUNT(RCUTILS_FAULT_INJECTION_NEVER_FAIL) - #define RCUTILS_FAULT_INJECTION_TEST(code) \ do { \ int fault_injection_count = 0; \ @@ -117,6 +111,7 @@ int _rcutils_fault_injection_maybe_fail(); RCUTILS_FAULT_INJECTION_SET_COUNT(fault_injection_count++); \ code; \ } while (!rcutils_fault_injection_is_test_complete()); \ + RCUTILS_FAULT_INJECTION_SET_COUNT(RCUTILS_FAULT_INJECTION_NEVER_FAIL); \ } while(0) #else // RCUTILS_ENABLE_FAULT_INJECTION @@ -128,11 +123,7 @@ int _rcutils_fault_injection_maybe_fail(); #define RCUTILS_FAULT_INJECTION_MAYBE_RETURN_ERROR(msg, error_statement) -#define RCUTILS_FAULT_INJECTION_INIT() \ - printf("Fault injection is disabled, skipping\n"); \ - return; - -#define RCUTILS_FAULT_INJECTION_FINI() +#define RCUTILS_FAULT_INJECTION_TEST(code) return; #endif // defined RCUTILS_ENABLE_FAULT_INJECTION From 9a72ee04b5576bfea0fe18c5910e9c701033c928 Mon Sep 17 00:00:00 2001 From: Stephen Brawner Date: Wed, 29 Jul 2020 14:12:45 -0700 Subject: [PATCH 09/15] uncrustify fix Signed-off-by: Stephen Brawner --- include/rcutils/testing/fault_injection.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/rcutils/testing/fault_injection.h b/include/rcutils/testing/fault_injection.h index 04b1edcb..504cd1d6 100644 --- a/include/rcutils/testing/fault_injection.h +++ b/include/rcutils/testing/fault_injection.h @@ -112,7 +112,7 @@ int _rcutils_fault_injection_maybe_fail(); code; \ } while (!rcutils_fault_injection_is_test_complete()); \ RCUTILS_FAULT_INJECTION_SET_COUNT(RCUTILS_FAULT_INJECTION_NEVER_FAIL); \ - } while(0) + } while (0) #else // RCUTILS_ENABLE_FAULT_INJECTION From f5bb491eaacea5b3226382a66eaefc2656243b88 Mon Sep 17 00:00:00 2001 From: Stephen Brawner Date: Fri, 7 Aug 2020 15:31:50 -0700 Subject: [PATCH 10/15] Update definitions Signed-off-by: Stephen Brawner --- CMakeLists.txt | 4 ++++ include/rcutils/testing/fault_injection.h | 16 +--------------- src/testing/fault_injection.c | 4 ++-- 3 files changed, 7 insertions(+), 17 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 2a0d769b..ff96fcff 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -106,6 +106,10 @@ target_include_directories(${PROJECT_NAME} PUBLIC # which is appropriate when building the dll but not consuming it. target_compile_definitions(${PROJECT_NAME} PRIVATE "RCUTILS_BUILDING_DLL") +if(BUILD_TESTING AND NOT RCUTILS_DISABLE_FAULT_INJECTION) + target_compile_definitions(${PROJECT_NAME} PUBLIC RCUTILS_ENABLE_FAULT_INJECTION) +endif() + target_link_libraries(${PROJECT_NAME} ${CMAKE_DL_LIBS}) # Needed if pthread is used for thread local storage. diff --git a/include/rcutils/testing/fault_injection.h b/include/rcutils/testing/fault_injection.h index 504cd1d6..85504e80 100644 --- a/include/rcutils/testing/fault_injection.h +++ b/include/rcutils/testing/fault_injection.h @@ -17,6 +17,7 @@ #include #include #include + #ifdef __cplusplus extern "C" { @@ -34,8 +35,6 @@ int_least64_t _rcutils_fault_injection_get_count(); int _rcutils_fault_injection_maybe_fail(); -#if defined RCUTILS_ENABLE_FAULT_INJECTION - /** * \def RCUTILS_FAULT_INJECTION_MAYBE_RETURN_ERROR * \brief This macro checks and decrements a static global variable atomic counter and returns @@ -114,19 +113,6 @@ int _rcutils_fault_injection_maybe_fail(); RCUTILS_FAULT_INJECTION_SET_COUNT(RCUTILS_FAULT_INJECTION_NEVER_FAIL); \ } while (0) -#else // RCUTILS_ENABLE_FAULT_INJECTION - -#define RCUTILS_FAULT_INJECTION_SET_COUNT(count) - -// This needs to be set to an int for compatibility -#define RCUTILS_FAULT_INJECTION_GET_COUNT() RCUTILS_FAULT_INJECTION_NEVER_FAIL - -#define RCUTILS_FAULT_INJECTION_MAYBE_RETURN_ERROR(msg, error_statement) - -#define RCUTILS_FAULT_INJECTION_TEST(code) return; - -#endif // defined RCUTILS_ENABLE_FAULT_INJECTION - #ifdef __cplusplus } #endif diff --git a/src/testing/fault_injection.c b/src/testing/fault_injection.c index 71806984..5cb795d4 100644 --- a/src/testing/fault_injection.c +++ b/src/testing/fault_injection.c @@ -22,9 +22,9 @@ bool rcutils_fault_injection_is_test_complete() { #ifndef RCUTILS_ENABLE_FAULT_INJECTION return true; -#endif // RCUTILS_ENABLE_FAULT_INJECTION - +#else // RCUTILS_ENABLE_FAULT_INJECTION return _rcutils_fault_injection_get_count() > RCUTILS_FAULT_INJECTION_NEVER_FAIL; +#endif // RCUTILS_ENABLE_FAULT_INJECTION } int _rcutils_fault_injection_maybe_fail() From 36050cab5904ae541edad5d8edabaab30bbeebed Mon Sep 17 00:00:00 2001 From: Stephen Brawner Date: Wed, 12 Aug 2020 16:25:19 -0700 Subject: [PATCH 11/15] Change maybe_fail int int_least64 Signed-off-by: Stephen Brawner --- include/rcutils/testing/fault_injection.h | 2 +- src/testing/fault_injection.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/include/rcutils/testing/fault_injection.h b/include/rcutils/testing/fault_injection.h index 85504e80..aeb5dfe5 100644 --- a/include/rcutils/testing/fault_injection.h +++ b/include/rcutils/testing/fault_injection.h @@ -33,7 +33,7 @@ void _rcutils_fault_injection_set_count(int count); int_least64_t _rcutils_fault_injection_get_count(); -int _rcutils_fault_injection_maybe_fail(); +int_least64_t _rcutils_fault_injection_maybe_fail(); /** * \def RCUTILS_FAULT_INJECTION_MAYBE_RETURN_ERROR diff --git a/src/testing/fault_injection.c b/src/testing/fault_injection.c index 5cb795d4..d404d478 100644 --- a/src/testing/fault_injection.c +++ b/src/testing/fault_injection.c @@ -27,7 +27,7 @@ bool rcutils_fault_injection_is_test_complete() #endif // RCUTILS_ENABLE_FAULT_INJECTION } -int _rcutils_fault_injection_maybe_fail() +int_least64_t _rcutils_fault_injection_maybe_fail() { bool set_atomic_success = false; int_least64_t current_count = RCUTILS_FAULT_INJECTION_NEVER_FAIL; From 2e99f4c28e05f05aa19844c7dc36837320337ddd Mon Sep 17 00:00:00 2001 From: Stephen Brawner Date: Wed, 12 Aug 2020 16:29:27 -0700 Subject: [PATCH 12/15] ATOMIC_VAR_INIT Signed-off-by: Stephen Brawner --- src/testing/fault_injection.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/testing/fault_injection.c b/src/testing/fault_injection.c index d404d478..b8fda2f8 100644 --- a/src/testing/fault_injection.c +++ b/src/testing/fault_injection.c @@ -16,7 +16,7 @@ #include "rcutils/stdatomic_helper.h" -static atomic_int_least64_t g_rcutils_fault_injection_count = -1; +static atomic_int_least64_t g_rcutils_fault_injection_count = ATOMIC_VAR_INIT(-1); bool rcutils_fault_injection_is_test_complete() { From 75009d9828cbec34ed7936778f8da54e93603109 Mon Sep 17 00:00:00 2001 From: Stephen Brawner Date: Wed, 12 Aug 2020 17:13:08 -0700 Subject: [PATCH 13/15] Move get/set to public functions Signed-off-by: Stephen Brawner --- include/rcutils/testing/fault_injection.h | 80 +++++++++++++---------- src/testing/fault_injection.c | 29 ++++---- 2 files changed, 58 insertions(+), 51 deletions(-) diff --git a/include/rcutils/testing/fault_injection.h b/include/rcutils/testing/fault_injection.h index aeb5dfe5..dd958507 100644 --- a/include/rcutils/testing/fault_injection.h +++ b/include/rcutils/testing/fault_injection.h @@ -18,6 +18,9 @@ #include #include +#include "rcutils/macros.h" +#include "rcutils/visibility_control.h" + #ifdef __cplusplus extern "C" { @@ -27,36 +30,10 @@ extern "C" #define RCUTILS_FAULT_INJECTION_FAIL_NOW 0 -bool rcutils_fault_injection_is_test_complete(); - -void _rcutils_fault_injection_set_count(int count); - -int_least64_t _rcutils_fault_injection_get_count(); - -int_least64_t _rcutils_fault_injection_maybe_fail(); - -/** - * \def RCUTILS_FAULT_INJECTION_MAYBE_RETURN_ERROR - * \brief This macro checks and decrements a static global variable atomic counter and returns - * `return_value_on_error` if 0. - * - * This macro is not a function itself, so when this macro returns it will cause the calling - * function to return with the return value. - * - * Set the counter with `RCUTILS_FAULT_INJECTION_SET_COUNT`. If the count is less than 0, then - * `RCUTILS_FAULT_INJECTION_MAYBE_RETURN_ERROR` will not cause an early return. - * - * This macro is thread-safe, and ensures that at most one invocation results in a failure for each - * time the fault injection counter is set with `RCUTILS_FAULT_INJECTION_SET_COUNT` - * - * \param return_value_on_error the value to return in the case of fault injected failure. - */ -#define RCUTILS_FAULT_INJECTION_MAYBE_RETURN_ERROR(return_value_on_error) \ - if (RCUTILS_FAULT_INJECTION_FAIL_NOW == _rcutils_fault_injection_maybe_fail()) { \ - printf( \ - "%s:%d Injecting fault and returning " #return_value_on_error "\n", __FILE__, __LINE__); \ - return return_value_on_error; \ - } +RCUTILS_PUBLIC +RCUTILS_WARN_UNUSED +bool +rcutils_fault_injection_is_test_complete(void); /** * \def RCUTILS_FAULT_INJECTION_SET_COUNT @@ -87,8 +64,9 @@ int_least64_t _rcutils_fault_injection_maybe_fail(); * injection errors will be disabled. The counter is globally initialized to * RCUTILS_FAULT_INJECTION_NEVER_FAIL. */ -#define RCUTILS_FAULT_INJECTION_SET_COUNT(count) \ - _rcutils_fault_injection_set_count(count); +RCUTILS_PUBLIC +void +rcutils_fault_injection_set_count(int count); /** * \def RCUTILS_FAULT_INJECTION_GET_COUNT @@ -100,17 +78,47 @@ int_least64_t _rcutils_fault_injection_maybe_fail(); * count value was greater or equal to 0, then the failure was not caused by the fault injection * counter. */ -#define RCUTILS_FAULT_INJECTION_GET_COUNT() \ - _rcutils_fault_injection_get_count(); +RCUTILS_PUBLIC +RCUTILS_WARN_UNUSED +int_least64_t +rcutils_fault_injection_get_count(void); + +RCUTILS_PUBLIC +RCUTILS_WARN_UNUSED +int_least64_t +_rcutils_fault_injection_maybe_fail(void); + +/** + * \def RCUTILS_FAULT_INJECTION_MAYBE_RETURN_ERROR + * \brief This macro checks and decrements a static global variable atomic counter and returns + * `return_value_on_error` if 0. + * + * This macro is not a function itself, so when this macro returns it will cause the calling + * function to return with the return value. + * + * Set the counter with `RCUTILS_FAULT_INJECTION_SET_COUNT`. If the count is less than 0, then + * `RCUTILS_FAULT_INJECTION_MAYBE_RETURN_ERROR` will not cause an early return. + * + * This macro is thread-safe, and ensures that at most one invocation results in a failure for each + * time the fault injection counter is set with `RCUTILS_FAULT_INJECTION_SET_COUNT` + * + * \param return_value_on_error the value to return in the case of fault injected failure. + */ +#define RCUTILS_FAULT_INJECTION_MAYBE_RETURN_ERROR(return_value_on_error) \ + if (RCUTILS_FAULT_INJECTION_FAIL_NOW == _rcutils_fault_injection_maybe_fail()) { \ + printf( \ + "%s:%d Injecting fault and returning " #return_value_on_error "\n", __FILE__, __LINE__); \ + return return_value_on_error; \ + } #define RCUTILS_FAULT_INJECTION_TEST(code) \ do { \ int fault_injection_count = 0; \ do { \ - RCUTILS_FAULT_INJECTION_SET_COUNT(fault_injection_count++); \ + rcutils_fault_injection_set_count(fault_injection_count++); \ code; \ } while (!rcutils_fault_injection_is_test_complete()); \ - RCUTILS_FAULT_INJECTION_SET_COUNT(RCUTILS_FAULT_INJECTION_NEVER_FAIL); \ + rcutils_fault_injection_set_count(RCUTILS_FAULT_INJECTION_NEVER_FAIL); \ } while (0) #ifdef __cplusplus diff --git a/src/testing/fault_injection.c b/src/testing/fault_injection.c index b8fda2f8..6f1c8547 100644 --- a/src/testing/fault_injection.c +++ b/src/testing/fault_injection.c @@ -18,20 +18,31 @@ static atomic_int_least64_t g_rcutils_fault_injection_count = ATOMIC_VAR_INIT(-1); +void rcutils_fault_injection_set_count(int count) +{ + rcutils_atomic_store(&g_rcutils_fault_injection_count, count); +} + +int_least64_t rcutils_fault_injection_get_count() +{ + int_least64_t count = 0; + rcutils_atomic_load(&g_rcutils_fault_injection_count, count); + return count; +} + bool rcutils_fault_injection_is_test_complete() { #ifndef RCUTILS_ENABLE_FAULT_INJECTION return true; #else // RCUTILS_ENABLE_FAULT_INJECTION - return _rcutils_fault_injection_get_count() > RCUTILS_FAULT_INJECTION_NEVER_FAIL; + return rcutils_fault_injection_get_count() > RCUTILS_FAULT_INJECTION_NEVER_FAIL; #endif // RCUTILS_ENABLE_FAULT_INJECTION } int_least64_t _rcutils_fault_injection_maybe_fail() { bool set_atomic_success = false; - int_least64_t current_count = RCUTILS_FAULT_INJECTION_NEVER_FAIL; - rcutils_atomic_load(&g_rcutils_fault_injection_count, current_count); + int_least64_t current_count = rcutils_fault_injection_get_count(); do { // A fault_injection_count less than 0 means that maybe_fail doesn't fail, so just return. if (current_count <= RCUTILS_FAULT_INJECTION_NEVER_FAIL) { @@ -46,15 +57,3 @@ int_least64_t _rcutils_fault_injection_maybe_fail() } while (!set_atomic_success); return current_count; } - -void _rcutils_fault_injection_set_count(int count) -{ - rcutils_atomic_store(&g_rcutils_fault_injection_count, count); -} - -int_least64_t _rcutils_fault_injection_get_count() -{ - int_least64_t count = 0; - rcutils_atomic_load(&g_rcutils_fault_injection_count, count); - return count; -} From b03d89e167b4e0a7a20f0b15ce234a7d336a1df3 Mon Sep 17 00:00:00 2001 From: Stephen Brawner Date: Fri, 14 Aug 2020 17:05:52 -0700 Subject: [PATCH 14/15] Add RCUTILS_CAN_FAIL_WITH Signed-off-by: Stephen Brawner --- include/rcutils/macros.h | 25 +++++++++++++++++++--- include/rcutils/testing/fault_injection.h | 26 ++++++++++++++++++++++- src/snprintf.c | 2 +- 3 files changed, 48 insertions(+), 5 deletions(-) diff --git a/include/rcutils/macros.h b/include/rcutils/macros.h index 1b4e9eac..86040602 100644 --- a/include/rcutils/macros.h +++ b/include/rcutils/macros.h @@ -162,11 +162,12 @@ extern "C" * should go on a separate line. * * If in your function, there are expected effects on output parameters that occur during - * the failure case, then it will introduce a discrepency between fault injection testing and + * the failure case, then it will introduce a discrepancy between fault injection testing and * production operation. This is because the fault injection will cause the function to return * where this macro is used, not at the location the error values are typically returned. To help - * protect against this scenario you may consider adding unit tests that checks your function does - * not modify output parameters when it actually returns a failing error code. + * protect against this scenario you may consider adding unit tests that check your function does + * not modify output parameters when it actually returns a failing error code if it's possible for + * your code. * * If your function is void, this macro can be used without parameters. However, for the above * reasoning, there should be no side effects on output parameters for all possible early returns. @@ -177,8 +178,26 @@ extern "C" # define RCUTILS_CAN_RETURN_WITH_ERROR_OF(error_return_value) \ RCUTILS_FAULT_INJECTION_MAYBE_RETURN_ERROR(error_return_value); +/** + * \def RCUTILS_CAN_FAIL_WITH + * Indicating macro similar to RCUTILS_CAN_RETURN_WITH_ERROR_OF but for use with more complicated + * statements. + * + * The `failure_code` will be executed inside a scoped if block, so any variables declared within + * will not be available outside of the macro. + * + * One example where you might need this version, is if a side-effect may occur within a function. + * For example, in snprintf, rcutils_snprintf needs to set both errno and return -1 on failure. + * This macro is used to capture both effects. + * + * \param failure_code Code that is representative of the failure case in this function. + */ +# define RCUTILS_CAN_FAIL_WITH(failure_code) \ + RCUTILS_FAULT_INJECTION_MAYBE_FAIL(failure_code); + #else # define RCUTILS_CAN_RETURN_WITH_ERROR_OF(error_return_value) +# define RCUTILS_CAN_FAIL_WITH(failure_code) #endif // defined RCUTILS_ENABLE_FAULT_INJECTION #ifdef __cplusplus diff --git a/include/rcutils/testing/fault_injection.h b/include/rcutils/testing/fault_injection.h index dd958507..4b5c2126 100644 --- a/include/rcutils/testing/fault_injection.h +++ b/include/rcutils/testing/fault_injection.h @@ -50,7 +50,7 @@ rcutils_fault_injection_is_test_complete(void); * ASSERT_LT(RCUTILS_FAULT_INJECTION_NEVER_FAIL, RCUTILS_FAULT_INJECTION_GET_COUNT()); * * Where SUFFICIENTLY_LARGE_ITERATION_COUNT is a value larger than the maximum expected calls to - * `RCUTILS_FAULT_INJECTION_MAYBE_RETURN_ERROR`. This last assertion just insures that your choice for + * `RCUTILS_FAULT_INJECTION_MAYBE_RETURN_ERROR`. This last assertion just ensures that your choice for * SUFFICIENTLY_LARGE_ITERATION_COUNT was large enough. To avoid having to choose this count * yourself, you can use a do-while loop. * @@ -111,6 +111,30 @@ _rcutils_fault_injection_maybe_fail(void); return return_value_on_error; \ } +/** + * \def RCUTILS_FAULT_INJECTION_MAYBE_FAIL + * \brief This macro checks and decrements a static global variable atomic counter and executes + * `failure_code` if the counter is 0 inside a scoped block (any variables declared in failure_code) + * will not be avaliable outside of this scoped block. + * + * This macro is not a function itself, so it will cause the calling function to execute the code + * from within an if loop. + * + * Set the counter with `RCUTILS_FAULT_INJECTION_SET_COUNT`. If the count is less than 0, then + * `RCUTILS_FAULT_INJECTION_MAYBE_FAIL` will not execute the failure code. + * + * This macro is thread-safe, and ensures that at most one invocation results in a failure for each + * time the fault injection counter is set with `RCUTILS_FAULT_INJECTION_SET_COUNT` + * + * \param failure_code the code to execute in the case of fault injected failure. + */ +#define RCUTILS_FAULT_INJECTION_MAYBE_FAIL(failure_code) \ + if (RCUTILS_FAULT_INJECTION_FAIL_NOW == _rcutils_fault_injection_maybe_fail()) { \ + printf( \ + "%s:%d Injecting fault and executing " #failure_code "\n", __FILE__, __LINE__); \ + failure_code; \ + } + #define RCUTILS_FAULT_INJECTION_TEST(code) \ do { \ int fault_injection_count = 0; \ diff --git a/src/snprintf.c b/src/snprintf.c index d8f1b01f..3bc3a11e 100644 --- a/src/snprintf.c +++ b/src/snprintf.c @@ -39,7 +39,7 @@ rcutils_snprintf(char * buffer, size_t buffer_size, const char * format, ...) int rcutils_vsnprintf(char * buffer, size_t buffer_size, const char * format, va_list args) { - RCUTILS_CAN_RETURN_WITH_ERROR_OF(-1); + RCUTILS_CAN_FAIL_WITH({errno = EINVAL; return -1;}); if (NULL == format) { errno = EINVAL; From 6a5daad9dd0b21140387b5371773d0bca1dd4a66 Mon Sep 17 00:00:00 2001 From: Stephen Brawner Date: Mon, 17 Aug 2020 11:52:40 -0700 Subject: [PATCH 15/15] Updating comments Signed-off-by: Stephen Brawner --- include/rcutils/testing/fault_injection.h | 76 +++++++++++++++-------- 1 file changed, 49 insertions(+), 27 deletions(-) diff --git a/include/rcutils/testing/fault_injection.h b/include/rcutils/testing/fault_injection.h index 4b5c2126..6cb83fe2 100644 --- a/include/rcutils/testing/fault_injection.h +++ b/include/rcutils/testing/fault_injection.h @@ -36,29 +36,22 @@ bool rcutils_fault_injection_is_test_complete(void); /** - * \def RCUTILS_FAULT_INJECTION_SET_COUNT * \brief Atomically set the fault injection counter. * - * There will be at most one fault injected failure per call to RCUTILS_FAULT_INJECTION_SET_COUNT. - * To test all reachable fault injection locations, call this macro inside a loop and set the count - * to an incrementing count variable. + * This is typically not the preferred method of interacting directly with the fault injection + * logic, instead use `RCUTILS_FAULT_INJECTION_TEST` instead. * - * for (int i = 0; i < SUFFICIENTLY_LARGE_ITERATION_COUNT; ++i) { - * RCUTILS_FAULT_INJECTION_SET_COUNT(i); - * ... // Call function under test - * } - * ASSERT_LT(RCUTILS_FAULT_INJECTION_NEVER_FAIL, RCUTILS_FAULT_INJECTION_GET_COUNT()); + * This function may also be used for pausing code inside of a `RCUTILS_FAULT_INJECTION_TEST` with + * something like the following: * - * Where SUFFICIENTLY_LARGE_ITERATION_COUNT is a value larger than the maximum expected calls to - * `RCUTILS_FAULT_INJECTION_MAYBE_RETURN_ERROR`. This last assertion just ensures that your choice for - * SUFFICIENTLY_LARGE_ITERATION_COUNT was large enough. To avoid having to choose this count - * yourself, you can use a do-while loop. - * - * int i = 0; - * do { - * RCUTILS_FAULT_INJECTION_SET_COUNT(i++); - * ... // Call function under test - * } while (RCUTILS_FAULT_INJECTION_GET_COUNT() <= RCUTILS_FAULT_INJECTION_NEVER_FAIL); + * RCUTILS_FAULT_INJECTION_TEST({ + * ... // code to run with fault injection + * int64_t count = rcutils_fault_injection_get_count(); + * rcutils_fault_injection_set_count(RCUTILS_FAULT_INJECTION_NEVER_FAIL); + * ... // code to run without fault injection + * rcutils_fault_injection_set_count(count); + * ... // code to run with fault injection + * }); * * \param count The count to set the fault injection counter to. If count is negative, then fault * injection errors will be disabled. The counter is globally initialized to @@ -69,20 +62,22 @@ void rcutils_fault_injection_set_count(int count); /** - * \def RCUTILS_FAULT_INJECTION_GET_COUNT * \brief Atomically get the fault injection counter value * - * Use this macro after running the code under test to check whether the counter reached a negative - * value. This is helpful so you can verify that you ran the fault injection test in a loop a - * sufficient number of times. Likewise, if the code under test returned with an error, but the - * count value was greater or equal to 0, then the failure was not caused by the fault injection - * counter. + * This function is typically not used directly but instead indirectly inside an + * `RCUTILS_FAULT_INJECTION_TEST` */ RCUTILS_PUBLIC RCUTILS_WARN_UNUSED int_least64_t rcutils_fault_injection_get_count(void); +/** + * \brief Implementation of fault injection decrementer + * + * This is included inside of macros, so it needs to be exported as a public function, but it + * should not be used directly. + */ RCUTILS_PUBLIC RCUTILS_WARN_UNUSED int_least64_t @@ -114,8 +109,8 @@ _rcutils_fault_injection_maybe_fail(void); /** * \def RCUTILS_FAULT_INJECTION_MAYBE_FAIL * \brief This macro checks and decrements a static global variable atomic counter and executes - * `failure_code` if the counter is 0 inside a scoped block (any variables declared in failure_code) - * will not be avaliable outside of this scoped block. + * `failure_code` if the counter is 0 inside a scoped block (any variables declared in + * failure_code) will not be avaliable outside of this scoped block. * * This macro is not a function itself, so it will cause the calling function to execute the code * from within an if loop. @@ -135,6 +130,33 @@ _rcutils_fault_injection_maybe_fail(void); failure_code; \ } +/** + * \def RCUTILS_FAULT_INJECTION_TEST + * + * The fault injection macro for use with unit tests to check that `code` can tolerate injected + * failures at all points along the execution path where the indicating macros + * `RCUTILS_CAN_RETURN_WITH_ERROR_OF` and `RCUTILS_CAN_FAIL_WITH` are located. + * + * This macro is intended to be used within a gtest function macro like 'TEST', 'TEST_F', etc. + * + * `code` is executed within a do-while loop and therefore any variables declared within are in + * their own scope block. + * + * Here's a simple example: + * RCUTILS_FAULT_INJECTION_TEST( + * rcl_ret_t ret = rcl_init(argc, argv, options, context); + * if (RCL_RET_OK == ret) + * { + * ret = rcl_shutdown(context); + * } + * }); + * + * In this example, you will need have conditional execution based on the return value of + * `rcl_init`. If it failed, then it wouldn't make sense to call rcl_shutdown. In your own test, + * there might be similar logic that requires conditional checks. The goal of writing this test + * is less about checking the behavior is consistent, but instead that failures do not cause + * program crashes, memory errors, or unnecessary memory leaks. + */ #define RCUTILS_FAULT_INJECTION_TEST(code) \ do { \ int fault_injection_count = 0; \