From 895cb6af50b7efc2dfe2912beed55a80c268fbf3 Mon Sep 17 00:00:00 2001 From: burekn Date: Thu, 6 Dec 2018 18:06:26 -0800 Subject: [PATCH] Adding an ArrayList and HashMap data structures along with tests. --- CMakeLists.txt | 16 + include/rcutils/types/array_list.h | 208 ++++++++++ include/rcutils/types/hash_map.h | 342 +++++++++++++++++ include/rcutils/types/rcutils_ret.h | 8 + src/array_list.c | 221 +++++++++++ src/hash_map.c | 562 ++++++++++++++++++++++++++++ test/test_array_list.cpp | 355 ++++++++++++++++++ test/test_hash_map.cpp | 452 ++++++++++++++++++++++ 8 files changed, 2164 insertions(+) create mode 100644 include/rcutils/types/array_list.h create mode 100644 include/rcutils/types/hash_map.h create mode 100644 src/array_list.c create mode 100644 src/hash_map.c create mode 100644 test/test_array_list.cpp create mode 100644 test/test_hash_map.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 619d80c4..c6723769 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -30,6 +30,7 @@ endif() set(rcutils_sources src/allocator.c + src/array_list.c src/char_array.c src/cmdline_parser.c src/error_handling.c @@ -37,6 +38,7 @@ set(rcutils_sources src/find.c src/format_string.c src/get_env.c + src/hash_map.c src/logging.c src/repl_str.c src/snprintf.c @@ -312,6 +314,20 @@ if(BUILD_TESTING) if(TARGET test_uint8_array) target_link_libraries(test_uint8_array ${PROJECT_NAME}) endif() + + rcutils_custom_add_gtest(test_array_list + test/test_array_list.cpp + ) + if(TARGET test_array_list) + target_link_libraries(test_array_list ${PROJECT_NAME}) + endif() + + rcutils_custom_add_gtest(test_hash_map + test/test_hash_map.cpp + ) + if(TARGET test_hash_map) + target_link_libraries(test_hash_map ${PROJECT_NAME}) + endif() endif() ament_export_dependencies(ament_cmake) diff --git a/include/rcutils/types/array_list.h b/include/rcutils/types/array_list.h new file mode 100644 index 00000000..9fc97b2a --- /dev/null +++ b/include/rcutils/types/array_list.h @@ -0,0 +1,208 @@ +// Copyright 2017 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__TYPES__ARRAY_LIST_H_ +#define RCUTILS__TYPES__ARRAY_LIST_H_ + +#ifdef __cplusplus +extern "C" +{ +#endif + +#include + +#include "rcutils/allocator.h" +#include "rcutils/macros.h" +#include "rcutils/types/rcutils_ret.h" +#include "rcutils/visibility_control.h" + +struct rcutils_array_list_impl_t; + +typedef struct RCUTILS_PUBLIC_TYPE rcutils_array_list_t +{ + struct rcutils_array_list_impl_t * impl; +} rcutils_array_list_t; + +/// Return an empty array_list struct. +/** + * This function returns an empty and zero initialized array_list struct. + * Calling rcutils_array_list_fini() on any non-initialized instance leads + * to undefined behavior. + * Every instance of array_list_t has to either be zero_initialized with this + * function or manually allocated. + * + * Example: + * + * ```c + * rcutils_array_list_t foo; + * rcutils_array_list_fini(&foo); // undefined behavior! + * + * rcutils_array_list_t bar = rcutils_get_zero_initialized_array_list(); + * rcutils_array_list_fini(&bar); // ok + * ``` + */ +RCUTILS_PUBLIC +rcutils_array_list_t +rcutils_get_zero_initialized_array_list(void); + +/// Initialize an array liast with a given initial capacity. +/** + * This function will initialize a given, zero initialized, string array to + * a given size. + * + * Note that putting a string into the array gives owenship to the array. + * + * Example: + * + * ```c + * rcutils_allocator_t allocator = rcutils_get_default_allocator(); + * rcutils_array_list_t array_list = rcutils_get_zero_initialized_array_list(); + * rcutils_ret_t ret = rcutils_array_list_init(&array_list, 2, &allocator); + * if (ret != RCUTILS_RET_OK) { + * // ... error handling + * } + * array_list.data[0] = rcutils_strdup("Hello", &allocator); + * array_list.data[1] = rcutils_strdup("World", &allocator); + * ret = rcutils_array_list_fini(&array_list); + * + * \param[inout] array_list object to be initialized + * \param[in] initial_capacity the initial capacity to allocate in the list + * \param[in] data_size the size (in bytes) of the data object being stored in the list + * \param[in] allocator to be used to allocate and deallocate memory + * \return `RCUTILS_RET_OK` if successful, or + * \return `RCUTILS_RET_INVALID_ARGUMENT` for invalid arguments, or + * \return `RCUTILS_RET_BAD_ALLOC` if memory allocation fails, or + * \return `RCUTILS_RET_ERROR` if an unknown error occurs + * ``` + */ +RCUTILS_PUBLIC +rcutils_ret_t +rcutils_array_list_init( + rcutils_array_list_t * array_list, + size_t initial_capacity, + size_t data_size, + const rcutils_allocator_t * allocator); + +/// Finalize a string array, reclaiming all resources. +/** + * This function reclaims any memory owned by the string array, including the + * strings it references. + * + * The allocator used to initialize the string array is used to deallocate each + * string in the array and the array of strings itself. + * + * \param[inout] array_list object to be finalized + * \return `RCUTILS_RET_OK` if successful, or + * \return `RCUTILS_RET_INVALID_ARGUMENT` for invalid arguments, or + * \return `RCUTILS_RET_ERROR` if an unknown error occurs + */ +RCUTILS_PUBLIC +RCUTILS_WARN_UNUSED +rcutils_ret_t +rcutils_array_list_fini(rcutils_array_list_t * array_list); + +/// Adds an entry to the list +/** + * This functions adds the provided data to the end of the list. A copy of + * the provided data is made to store in the list instead of just storing + * the pointer to the provided data. + * + * + * \param[in] array_list to add the data to + * \param[in] data a pointer to the data to add to the list + * \return `RCUTILS_RET_OK` if successful, or + * \return `RCUTILS_RET_INVALID_ARGUMENT` for invalid arguments, or + * \return `RCUTILS_RET_BAD_ALLOC` if memory allocation fails, or + * \return `RCUTILS_RET_ERROR` if an unknown error occurs + */ +RCUTILS_PUBLIC +RCUTILS_WARN_UNUSED +rcutils_ret_t +rcutils_array_list_add(rcutils_array_list_t * array_list, const void * data); + +/// Sets an entry in the list to the provided data +/** + * This functions sets the provided data at the specified index in the list. A copy of + * the provided data is made to store in the list instead of just storing + * the pointer to the provided data. + * + * + * \param[in] array_list to add the data to + * \param[in] index the position in the list to set the data + * \param[in] data a pointer to the data that will be set in the list + * \return `RCUTILS_RET_OK` if successful, or + * \return `RCUTILS_RET_INVALID_ARGUMENT` for invalid arguments, or + * \return `RCUTILS_RET_BAD_ALLOC` if memory allocation fails, or + * \return `RCUTILS_RET_ERROR` if an unknown error occurs + */ +RCUTILS_PUBLIC +RCUTILS_WARN_UNUSED +rcutils_ret_t +rcutils_array_list_set(rcutils_array_list_t * array_list, size_t index, const void * data); + +/// Removes an entry in the list at the provided index +/** + * This functions removes data from the list at the specified index. + * + * + * \param[in] array_list to add the data to + * \return `RCUTILS_RET_OK` if successful, or + * \return `RCUTILS_RET_INVALID_ARGUMENT` for invalid arguments, or + * \return `RCUTILS_RET_BAD_ALLOC` if memory allocation fails, or + * \return `RCUTILS_RET_ERROR` if an unknown error occurs + */ +RCUTILS_PUBLIC +RCUTILS_WARN_UNUSED +rcutils_ret_t +rcutils_array_list_remove(rcutils_array_list_t * array_list, size_t index); + +/// Retrieves an entry in the list at the provided index +/** + * This functions retrieves a copy of the data stored in the list. + * + * + * \param[in] array_list to add the data to + * \param[in] index the index at which to get the data + * \param[out] data a copy of the data stored in the list + * \return `RCUTILS_RET_OK` if successful, or + * \return `RCUTILS_RET_INVALID_ARGUMENT` for invalid arguments, or + * \return `RCUTILS_RET_BAD_ALLOC` if memory allocation fails, or + * \return `RCUTILS_RET_ERROR` if an unknown error occurs + */ +RCUTILS_PUBLIC +RCUTILS_WARN_UNUSED +rcutils_ret_t +rcutils_array_list_get(const rcutils_array_list_t * array_list, size_t index, void * data); + +/// Retrieves the size of the provided array_list +/** + * This functions retrieves the size of the provided array list + * + * + * \param[in] array_list list to get the size of + * \param[out] size The number of items currently stored in the list + * \return `RCUTILS_RET_OK` if successful, or + * \return `RCUTILS_RET_INVALID_ARGUMENT` for invalid arguments, or + * \return `RCUTILS_RET_ERROR` if an unknown error occurs + */ +RCUTILS_PUBLIC +RCUTILS_WARN_UNUSED +rcutils_ret_t +rcutils_array_list_get_size(const rcutils_array_list_t * array_list, size_t * size); + +#ifdef __cplusplus +} +#endif + +#endif // RCUTILS__TYPES__ARRAY_LIST_H_ diff --git a/include/rcutils/types/hash_map.h b/include/rcutils/types/hash_map.h new file mode 100644 index 00000000..6b71a47f --- /dev/null +++ b/include/rcutils/types/hash_map.h @@ -0,0 +1,342 @@ +// Copyright 2017 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__TYPES__HASH_MAP_H_ +#define RCUTILS__TYPES__HASH_MAP_H_ + +#ifdef __cplusplus +extern "C" +{ +#endif + +#include + +#include "rcutils/allocator.h" +#include "rcutils/types/rcutils_ret.h" +#include "rcutils/macros.h" +#include "rcutils/visibility_control.h" + +struct rcutils_hash_map_impl_t; + +typedef struct RCUTILS_PUBLIC_TYPE rcutils_hash_map_t +{ + struct rcutils_hash_map_impl_t * impl; +} rcutils_hash_map_t; + +/// The function signature for a key hashing function. +/** + * \param key The key that needs to be hashed + * \return A hash value for the provided string + */ +typedef size_t (* rcutils_hash_map_key_hasher_t)( + const void * // key to hash +); + +/// The function signature for a key comparison function. +/** + * \param val1 The first value to compare + * \param val2 The second value to compare + * \return A negative number if val1 < val2, or + * \return A positve number if val1 > val2, or + * \return Zero if val1 == val2, or + */ +typedef int (* rcutils_hash_map_key_cmp_t)( + const void *, // val1 + const void * // val2 +); + +/// A hashing function for a null terminated c string. +/** + * A hashing function for a null terminated c string. + * Should be used when your key is just a pointer to a c-string + */ +RCUTILS_PUBLIC +size_t +rcutils_hash_map_string_hash_func(const void * key_str); + +/// A comparison function for a null terminated c string. +/** + * A comparison function for a null terminated c string. + * Should be used when your key is just a pointer to a c-string + */ +RCUTILS_PUBLIC +int +rcutils_hash_map_string_cmp_func(const void * val1, const void * val2); + +/// Return an empty hash_map struct. +/* + * This function returns an empty and zero initialized hash_map struct. + * + * Example: + * + * ```c + * // Do not do this: + * // rcutils_hash_map_t foo; + * // rcutils_hash_map_fini(&foo); // undefined behavior! + * + * // Do this instead: + * rcutils_hash_map_t bar = rcutils_get_zero_initialized_hash_map(); + * rcutils_hash_map_fini(&bar); // ok + * ``` + * */ +RCUTILS_PUBLIC +RCUTILS_WARN_UNUSED +rcutils_hash_map_t +rcutils_get_zero_initialized_hash_map(); + +/// Initialize a rcutils_hash_map_t, allocating space for given capacity. +/** + * This function initializes the rcutils_hash_map_t with a given initial + * capacity for entries. + * Note this does not allocate space for keys or values in the hash_map, just the + * arrays of pointers to the keys and values. + * rcutils_hash_map_set() should still be used when assigning values. + * + * The hash_map argument should point to allocated memory and should have + * been zero initialized with rcutils_get_zero_initialized_hash_map(). + * For example: + * + * ```c + * rcutils_hash_map_t hash_map = rcutils_get_zero_initialized_hash_map(); + * rcutils_ret_t ret = + * rcutils_hash_map_init(&hash_map, 10, rcutils_get_default_allocator()); + * if (ret != RCUTILS_RET_OK) { + * // ... do error handling + * } + * // ... use the hash_map and when done: + * ret = rcutils_hash_map_fini(&hash_map); + * if (ret != RCUTILS_RET_OK) { + * // ... do error handling + * } + * ``` + * + * \param[inout] hash_map rcutils_hash_map_t to be initialized + * \param[in] initial_capacity the amount of initial capacity for the hash_map + * \param[in] key_size the size (in bytes) of the key used to index the data + * \param[in] data_size the size (in bytes) of the data being stored + * \param[in] key_hashing_func a function that returns a hashed value for a key + * \param[in] key_cmp_func a function used to compare keys + * \param[in] allocator the allocator to use through out the lifetime of the hash_map + * \return `RCUTILS_RET_OK` if successful, or + * \return `RCUTILS_RET_INVALID_ARGUMENT` for invalid arguments, or + * \return `RCUTILS_RET_BAD_ALLOC` if memory allocation fails, or + * \return `RCUTILS_RET_STRING_MAP_ALREADY_INIT` if already initialized, or + * \return `RCUTILS_RET_ERROR` if an unknown error occurs + */ +RCUTILS_PUBLIC +RCUTILS_WARN_UNUSED +rcutils_ret_t +rcutils_hash_map_init( + rcutils_hash_map_t * hash_map, + size_t initial_capacity, + size_t key_size, + size_t data_size, + rcutils_hash_map_key_hasher_t key_hashing_func, + rcutils_hash_map_key_cmp_t key_cmp_func, + const rcutils_allocator_t * allocator); + +/// Finalize the previously initialized hash_map struct. +/** + * This function will free any resources which were created when initializing + * or when calling rcutils_hash_map_set(). + * + * \param[inout] hash_map rcutils_hash_map_t to be finalized + * \return `RCUTILS_RET_OK` if successful, or + * \return `RCUTILS_RET_INVALID_ARGUMENT` for invalid arguments, or + * \return `RCUTILS_RET_ERROR` if an unknown error occurs + */ +RCUTILS_PUBLIC +RCUTILS_WARN_UNUSED +rcutils_ret_t +rcutils_hash_map_fini(rcutils_hash_map_t * hash_map); + +/// Get the current capacity of the hash_map. +/** + * This function will return the internal capacity of the hash_map, which is the + * maximum number of key value pairs the hash_map could hold. + * The capacity can be set initially with rcutils_hash_map_init() or + * with rcutils_hash_map_reserve(). + * The capacity does not indicate how many key value paris are stored in the + * hash_map, the rcutils_hash_map_get_size() function can provide that. + * + * \param[in] hash_map rcutils_hash_map_t to be queried + * \param[out] capacity capacity of the hash_map + * \return `RCUTILS_RET_OK` if successful, or + * \return `RCUTILS_RET_INVALID_ARGUMENT` for invalid arguments, or + * \return `RCUTILS_RET_NOT_INITIALIZED` if the hash_map is invalid, or + * \return `RCUTILS_RET_ERROR` if an unknown error occurs + */ +RCUTILS_PUBLIC +RCUTILS_WARN_UNUSED +rcutils_ret_t +rcutils_hash_map_get_capacity(const rcutils_hash_map_t * hash_map, size_t * capacity); + +/// Get the current size of the hash_map. +/** + * This function will return the internal size of the hash_map, which is the + * current number of key value pairs in the hash_map. + * The size is changed when calling rcutils_hash_map_set_no_resize(), + * rcutils_hash_map_set(), or rcutils_hash_map_unset(). + * + * \param[in] hash_map rcutils_hash_map_t to be queried + * \param[out] size size of the hash_map + * \return `RCUTILS_RET_OK` if successful, or + * \return `RCUTILS_RET_INVALID_ARGUMENT` for invalid arguments, or + * \return `RCUTILS_RET_NOT_INITIALIZED` if the hash_map is invalid, or + * \return `RCUTILS_RET_ERROR` if an unknown error occurs + */ +RCUTILS_PUBLIC +RCUTILS_WARN_UNUSED +rcutils_ret_t +rcutils_hash_map_get_size(const rcutils_hash_map_t * hash_map, size_t * size); + +/// Set a key value pair in the hash_map, increasing capacity if necessary. +/** + * The capacity will be increased if needed using rcutils_hash_map_reserve(). + * Otherwise it is the same as rcutils_hash_map_set_no_resize(). + * + * \see rcutils_hash_map_set_no_resize() + * + * \param[inout] hash_map rcutils_hash_map_t to be updated + * \param[in] key hash_map key + * \param[in] value value for given hash_map key + * \return `RCUTILS_RET_OK` if successful, or + * \return `RCUTILS_RET_INVALID_ARGUMENT` for invalid arguments, or + * \return `RCUTILS_RET_BAD_ALLOC` if memory allocation fails, or + * \return `RCUTILS_RET_NOT_INITIALIZED` if the hash_map is invalid, or + * \return `RCUTILS_RET_ERROR` if an unknown error occurs + */ +RCUTILS_PUBLIC +RCUTILS_WARN_UNUSED +rcutils_ret_t +rcutils_hash_map_set(rcutils_hash_map_t * hash_map, const void * key, const void * value); + +/// Unset a key value pair in the hash_map. +/** + * The key needs to be a null terminated c string. + * If the given key is not found, RCUTILS_RET_STRING_KEY_NOT_FOUND is returned. + * + * \param[inout] hash_map rcutils_hash_map_t to be updated + * \param[in] key hash_map key, must be null terminated c string + * \return `RCUTILS_RET_OK` if successful, or + * \return `RCUTILS_RET_INVALID_ARGUMENT` for invalid arguments, or + * \return `RCUTILS_RET_NOT_INITIALIZED` if the hash_map is invalid, or + * \return `RCUTILS_RET_ERROR` if an unknown error occurs + */ +RCUTILS_PUBLIC +RCUTILS_WARN_UNUSED +rcutils_ret_t +rcutils_hash_map_unset(rcutils_hash_map_t * hash_map, const void * key); + +/// Get whether or not a key exists. +/** + * The key needs to be a null terminated c string. + * + * This function can fail and return false if the key is not found, + * or the hash_map is NULL or invalid, or if the key is NULL. + * In all cases no error message is set. + * + * \param[in] hash_map rcutils_hash_map_t to be searched + * \param[in] key hash_map key, must be null terminated c string + * \return `true` if key is in the hash_map, or + * \return `false` if key is not in the hash_map, or + * \return `false` for invalid arguments, or + * \return `false` if the hash_map is invalid + */ +RCUTILS_PUBLIC +bool +rcutils_hash_map_key_exists(const rcutils_hash_map_t * hash_map, const void * key); + +/// Get value given a key. +/** + * This function can fail, and therefore return NULL, if the key is not found, + * or the hash_map is NULL or invalid, or if the key is NULL. + * In all cases no error message is set. + * + * The returned value is still owned by the hash_map, and it should not be + * modified or free'd. + * This also means that the value pointer becomes invalid if either + * rcutils_hash_map_clear() or rcutils_hash_map_fini() are called or if + * the key value pair is updated or removed with one of rcutils_hash_map_set() + * or rcutils_hash_map_set_no_resize() or rcutils_hash_map_unset(). + * + * \param[in] hash_map rcutils_hash_map_t to be searched + * \param[in] key hash_map key to look up the data for + * \param[out] data A copy of the data stored in the map + * \return `RCUTILS_RET_OK` if successful, or + * \return `RCUTILS_RET_INVALID_ARGUMENT` for invalid arguments, or + * \return `RCUTILS_RET_NOT_INITIALIZED` if the hash_map is invalid, or + * \return `RCUTILS_RET_NOT_FOUND` if the key doesn't exist in the map, or + * \return `RCUTILS_RET_ERROR` if an unknown error occurs + */ +RCUTILS_PUBLIC +rcutils_ret_t +rcutils_hash_map_get(const rcutils_hash_map_t * hash_map, const void * key, void * data); + +/// Get the next key in the hash_map, unless NULL is given, then get the first key. +/** + * This function allows you iteratively get each key in the hash_map. + * + * If NULL is given for the key, then the first key in the hash_map is returned. + * If that returned key if given to the this function, then the next key in the + * hash_map is returned. + * If there are no more keys in the hash_map or if the given key is not in the hash_map, + * NULL is returned. + * + * The order of the keys in the hash_map is arbitrary and if the hash_map is modified + * between calls to this function the behavior is undefined. + * If the hash_map is modifeid then iteration should begin again by passing NULL to + * get the first key again. + * + * This function operates based on the address of the pointer, you cannot pass + * a copy of a key to get the next key. + * + * Example: + * + * ```c + * printf("keys in the hash_map:\n"); + * const void * current_key = rcutils_hash_map_get_next_key(&hash_map, NULL); + * while (current_key) { + * current_key = rcutils_hash_map_get_next_key(&hash_map, current_key); + * } + * ``` + * + * NULL can also be returned if NULL is given for the hash_map or if the + * hash_map is invalid. + * + * \param[in] hash_map rcutils_hash_map_t to be queried + * \param[in] previous_key NULL to get the first key or the previous key to get the next for + * \param[out] key A copy of the next key in the sequence + * \param[out] data A copy of the next data in the sequence + * \return `RCUTILS_RET_OK` if successful, or + * \return `RCUTILS_RET_INVALID_ARGUMENT` for invalid arguments, or + * \return `RCUTILS_RET_NOT_INITIALIZED` if the hash_map is invalid, or + * \return `RCUTILS_RET_NOT_FOUND` if the previous_key doesn't exist in the map, or + * \return `RCUTILS_RET_HASH_MAP_NO_MORE_ENTRIES` if there is no more data beyound the previous_key, or + * \return `RCUTILS_RET_ERROR` if an unknown error occurs + */ +RCUTILS_PUBLIC +rcutils_ret_t +rcutils_hash_map_get_next_key_and_data( + const rcutils_hash_map_t * hash_map, + const void * previous_key, + void * key, + void * data); + + +#ifdef __cplusplus +} +#endif + +#endif // RCUTILS__TYPES__HASH_MAP_H_ diff --git a/include/rcutils/types/rcutils_ret.h b/include/rcutils/types/rcutils_ret.h index 624407f3..598856da 100644 --- a/include/rcutils/types/rcutils_ret.h +++ b/include/rcutils/types/rcutils_ret.h @@ -31,6 +31,10 @@ typedef int rcutils_ret_t; #define RCUTILS_RET_INVALID_ARGUMENT 11 /// Not enough storage to do operation. #define RCUTILS_RET_NOT_ENOUGH_SPACE 12 +/// Resource is not initialized +#define RCUTILS_RET_NOT_INITIALIZED 13 +/// Resource for request not found +#define RCUTILS_RET_NOT_FOUND 14 /// Given string map was either already initialized or was not zero initialized. #define RCUTILS_RET_STRING_MAP_ALREADY_INIT 30 @@ -44,6 +48,10 @@ typedef int rcutils_ret_t; /// String representation of a severity is invalid. #define RCUTILS_RET_LOGGING_SEVERITY_STRING_INVALID 41 +/// There are no more entires beyond the last one in the map +#define RCUTILS_RET_HASH_MAP_NO_MORE_ENTRIES 50 + + #ifdef __cplusplus } #endif diff --git a/src/array_list.c b/src/array_list.c new file mode 100644 index 00000000..e9602557 --- /dev/null +++ b/src/array_list.c @@ -0,0 +1,221 @@ +// Copyright 2017 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. + +#ifdef __cplusplus +extern "C" +{ +#endif + +#include + +#include "rcutils/allocator.h" +#include "rcutils/error_handling.h" +#include "rcutils/macros.h" +#include "rcutils/types/array_list.h" +#include "rcutils/types/rcutils_ret.h" +#include "rcutils/visibility_control.h" + +#define ARRAY_LIST_VALIDATE_ARRAY_LIST(array_list) \ + if (NULL == array_list) { \ + RCUTILS_SET_ERROR_MSG("array_list is null"); \ + return RCUTILS_RET_INVALID_ARGUMENT; \ + } else if (NULL == array_list->impl) { \ + RCUTILS_SET_ERROR_MSG("array_list is not initialized"); \ + return RCUTILS_RET_NOT_INITIALIZED; \ + } + +#define ARRAY_LIST_VALIDATE_ARGUMENT_NOT_NULL(arg) \ + if (NULL == arg) { \ + RCUTILS_SET_ERROR_MSG("argument is null"); \ + return RCUTILS_RET_INVALID_ARGUMENT; \ + } + +#define ARRAY_LIST_VALIDATE_INDEX_IN_BOUNDS(array_list, index) \ + if (array_list->impl->size <= index) { \ + RCUTILS_SET_ERROR_MSG("index is out of bounds of the list"); \ + return RCUTILS_RET_INVALID_ARGUMENT; \ + } + +typedef struct rcutils_array_list_impl_t +{ + size_t size; + size_t capacity; + void * list; + size_t data_size; + rcutils_allocator_t allocator; +} rcutils_array_list_impl_t; + +rcutils_array_list_t +rcutils_get_zero_initialized_array_list(void) +{ + static rcutils_array_list_t zero_initialized_array_list; + zero_initialized_array_list.impl = NULL; + return zero_initialized_array_list; +} + +rcutils_ret_t +rcutils_array_list_init( + rcutils_array_list_t * array_list, + size_t initial_capacity, + size_t data_size, + const rcutils_allocator_t * allocator) +{ + ARRAY_LIST_VALIDATE_ARGUMENT_NOT_NULL(array_list); + ARRAY_LIST_VALIDATE_ARGUMENT_NOT_NULL(allocator); + if (NULL != array_list->impl) { + RCUTILS_SET_ERROR_MSG("array_list is already initialized"); + return RCUTILS_RET_INVALID_ARGUMENT; + } else if (1 > initial_capacity) { + RCUTILS_SET_ERROR_MSG("initial_capacity cannot be less than 1"); + return RCUTILS_RET_INVALID_ARGUMENT; + } else if (1 > data_size) { + RCUTILS_SET_ERROR_MSG("data_size cannot be less than 1"); + return RCUTILS_RET_INVALID_ARGUMENT; + } + + array_list->impl = allocator->allocate(sizeof(rcutils_array_list_impl_t), allocator->state); + if (NULL == array_list->impl) { + RCUTILS_SET_ERROR_MSG("failed to allocate memory for array list impl"); + return RCUTILS_RET_BAD_ALLOC; + } + + array_list->impl->capacity = initial_capacity; + array_list->impl->size = 0; + array_list->impl->data_size = data_size; + array_list->impl->list = allocator->allocate(initial_capacity * data_size, allocator->state); + if (NULL == array_list->impl->list) { + allocator->deallocate(array_list->impl, allocator->state); + array_list->impl = NULL; + RCUTILS_SET_ERROR_MSG("failed to allocate memory for array list data"); + return RCUTILS_RET_BAD_ALLOC; + } + array_list->impl->allocator = *allocator; + + return RCUTILS_RET_OK; +} + +rcutils_ret_t +rcutils_array_list_fini(rcutils_array_list_t * array_list) +{ + ARRAY_LIST_VALIDATE_ARRAY_LIST(array_list); + + array_list->impl->allocator.deallocate(array_list->impl->list, array_list->impl->allocator.state); + array_list->impl->allocator.deallocate(array_list->impl, array_list->impl->allocator.state); + array_list->impl = NULL; + + return RCUTILS_RET_OK; +} + +static rcutils_ret_t rcutils_array_list_increase_capacity(rcutils_array_list_t * array_list) +{ + size_t new_capacity = 2 * array_list->impl->capacity; + size_t new_size = array_list->impl->data_size * new_capacity; + void * new_list = array_list->impl->allocator.reallocate( + array_list->impl->list, + new_size, + array_list->impl->allocator.state); + if (NULL == new_list) { + return RCUTILS_RET_BAD_ALLOC; + } + array_list->impl->list = new_list; + array_list->impl->capacity = new_capacity; + return RCUTILS_RET_OK; +} + +static uint8_t * rcutils_array_list_get_pointer_for_index( + const rcutils_array_list_t * array_list, + size_t index) +{ + uint8_t * list_start = array_list->impl->list; + uint8_t * index_ptr = list_start + (array_list->impl->data_size * index); + return index_ptr; +} + +rcutils_ret_t +rcutils_array_list_add(rcutils_array_list_t * array_list, const void * data) +{ + ARRAY_LIST_VALIDATE_ARRAY_LIST(array_list); + ARRAY_LIST_VALIDATE_ARGUMENT_NOT_NULL(data); + rcutils_ret_t ret = RCUTILS_RET_OK; + + if (array_list->impl->size + 1 > array_list->impl->capacity) { + ret = rcutils_array_list_increase_capacity(array_list); + if (RCUTILS_RET_OK != ret) { + return ret; + } + } + + uint8_t * index_ptr = + rcutils_array_list_get_pointer_for_index(array_list, array_list->impl->size); + memcpy(index_ptr, data, array_list->impl->data_size); + + array_list->impl->size++; + return ret; +} + +rcutils_ret_t +rcutils_array_list_set(rcutils_array_list_t * array_list, size_t index, const void * data) +{ + ARRAY_LIST_VALIDATE_ARRAY_LIST(array_list); + ARRAY_LIST_VALIDATE_ARGUMENT_NOT_NULL(data); + ARRAY_LIST_VALIDATE_INDEX_IN_BOUNDS(array_list, index); + + uint8_t * index_ptr = rcutils_array_list_get_pointer_for_index(array_list, index); + memcpy(index_ptr, data, array_list->impl->data_size); + + return RCUTILS_RET_OK; +} + +rcutils_ret_t +rcutils_array_list_remove(rcutils_array_list_t * array_list, size_t index) +{ + ARRAY_LIST_VALIDATE_ARRAY_LIST(array_list); + ARRAY_LIST_VALIDATE_INDEX_IN_BOUNDS(array_list, index); + + // Shift all the data in the list to replace the missing data + for (size_t i = index; i < (array_list->impl->size - 1); ++i) { + uint8_t * dst_ptr = rcutils_array_list_get_pointer_for_index(array_list, i); + uint8_t * src_ptr = rcutils_array_list_get_pointer_for_index(array_list, i + 1); + memcpy(dst_ptr, src_ptr, array_list->impl->data_size); + } + + array_list->impl->size--; + return RCUTILS_RET_OK; +} + +rcutils_ret_t +rcutils_array_list_get(const rcutils_array_list_t * array_list, size_t index, void * data) +{ + ARRAY_LIST_VALIDATE_ARRAY_LIST(array_list); + ARRAY_LIST_VALIDATE_ARGUMENT_NOT_NULL(data); + ARRAY_LIST_VALIDATE_INDEX_IN_BOUNDS(array_list, index); + + uint8_t * index_ptr = rcutils_array_list_get_pointer_for_index(array_list, index); + memcpy(data, index_ptr, array_list->impl->data_size); + + return RCUTILS_RET_OK; +} + +rcutils_ret_t +rcutils_array_list_get_size(const rcutils_array_list_t * array_list, size_t * size) +{ + ARRAY_LIST_VALIDATE_ARRAY_LIST(array_list); + ARRAY_LIST_VALIDATE_ARGUMENT_NOT_NULL(size); + *size = array_list->impl->size; + return RCUTILS_RET_OK; +} + +#ifdef __cplusplus +} +#endif diff --git a/src/hash_map.c b/src/hash_map.c new file mode 100644 index 00000000..1abe9801 --- /dev/null +++ b/src/hash_map.c @@ -0,0 +1,562 @@ +// Copyright 2017 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. + +#ifdef __cplusplus +extern "C" +{ +#endif + +#include +#include + +#include "rcutils/allocator.h" +#include "rcutils/error_handling.h" +#include "rcutils/logging_macros.h" +#include "rcutils/types/array_list.h" +#include "rcutils/types/hash_map.h" +#include "rcutils/types/rcutils_ret.h" +#include "rcutils/macros.h" +#include "rcutils/visibility_control.h" + +#define LOAD_FACTOR (0.75) +#define BUCKET_INITIAL_CAP ((size_t)2) + +#define HASH_MAP_VALIDATE_HASH_MAP(map) \ + if (NULL == map) { \ + RCUTILS_SET_ERROR_MSG("map is null"); \ + return RCUTILS_RET_INVALID_ARGUMENT; \ + } else if (NULL == map->impl) { \ + RCUTILS_SET_ERROR_MSG("map is not initialized"); \ + return RCUTILS_RET_NOT_INITIALIZED; \ + } + +#define HASH_MAP_VALIDATE_ARGUMENT_NOT_NULL(arg) \ + if (NULL == arg) { \ + RCUTILS_SET_ERROR_MSG("argument is null"); \ + return RCUTILS_RET_INVALID_ARGUMENT; \ + } + +typedef struct rcutils_hash_map_entry_t +{ + size_t hashed_key; + void * key; + void * value; +} rcutils_hash_map_entry_t; + +typedef struct rcutils_hash_map_impl_t +{ + // This is the array of buckets that will store the keypairs + rcutils_array_list_t * map; + size_t capacity; + size_t size; + size_t key_size; + size_t data_size; + rcutils_hash_map_key_hasher_t key_hashing_func; + rcutils_hash_map_key_cmp_t key_cmp_func; + rcutils_allocator_t allocator; +} rcutils_hash_map_impl_t; + +// djb2 hash function +size_t rcutils_hash_map_string_hash_func(const void * key_str) +{ + const char ** ckey_ptr = (const char **) key_str; + const char * ckey_str = *ckey_ptr; + size_t hash = 5381; + + while ('\0' != *ckey_str) { + int c = *(ckey_str++); + hash = ((hash << 5) + hash) + c; /* hash * 33 + c */ + } + + return hash; +} + +int rcutils_hash_map_string_cmp_func(const void * val1, const void * val2) +{ + const char ** cval1 = (const char **) val1; + const char ** cval2 = (const char **) val2; + return strcmp(*cval1, *cval2); +} + +rcutils_hash_map_t +rcutils_get_zero_initialized_hash_map() +{ + static rcutils_hash_map_t zero_initialized_hash_map; + zero_initialized_hash_map.impl = NULL; + return zero_initialized_hash_map; +} + +// Allocates a new zero initialized hashmap +static rcutils_ret_t hash_map_allocate_new_map( + rcutils_array_list_t ** map, size_t capacity, + const rcutils_allocator_t * allocator) +{ + *map = allocator->allocate(capacity * sizeof(rcutils_hash_map_impl_t), allocator->state); + if (NULL == *map) { + return RCUTILS_RET_BAD_ALLOC; + } + + // Make sure the list for ever bucket is zero initialized + rcutils_array_list_t zero_list = rcutils_get_zero_initialized_array_list(); + for (size_t i = 0; i < capacity; ++i) { + (*map)[i] = zero_list; + } + + return RCUTILS_RET_OK; +} + +// Deallocates the memory for a map entry +static void hash_map_deallocate_entry( + rcutils_allocator_t * allocator, + rcutils_hash_map_entry_t * entry) +{ + if (NULL != entry) { + if (NULL != entry->key) { + allocator->deallocate(entry->key, allocator->state); + } + if (NULL != entry->value) { + allocator->deallocate(entry->value, allocator->state); + } + allocator->deallocate(entry, allocator->state); + } +} + +// Deallocates an existing hashmap +static rcutils_ret_t hash_map_deallocate_map( + rcutils_array_list_t * map, size_t capacity, + rcutils_allocator_t * allocator, bool dealloc_map_entries) +{ + rcutils_ret_t ret = RCUTILS_RET_OK; + for (size_t i = 0; i < capacity && RCUTILS_RET_OK == ret; ++i) { + // If an array list is allocated for this bucket then clean it up + rcutils_array_list_t * bucket = &(map[i]); + if (NULL != bucket->impl) { + // if selected then deallocate the memory for the actual entries as well + if (dealloc_map_entries) { + size_t bucket_size = 0; + ret = rcutils_array_list_get_size(bucket, &bucket_size); + for (size_t b_i = 0; b_i < bucket_size && RCUTILS_RET_OK == ret; ++b_i) { + rcutils_hash_map_entry_t * entry; + ret = rcutils_array_list_get(bucket, b_i, &entry); + if (RCUTILS_RET_OK == ret) { + hash_map_deallocate_entry(allocator, entry); + } + } + } + + // Cleanup the actual list itself + if (RCUTILS_RET_OK == ret) { + ret = rcutils_array_list_fini(bucket); + } + } + } + + // Cleanup the memory allocated for the array of buckets + if (RCUTILS_RET_OK == ret) { + allocator->deallocate(map, allocator->state); + } + + return ret; +} + +// Inserts a new entry into a bucket +static rcutils_ret_t hash_map_insert_entry( + rcutils_array_list_t * map, + size_t bucket_index, + const rcutils_hash_map_entry_t * entry, + rcutils_allocator_t * allocator) +{ + rcutils_array_list_t * bucket = &(map[bucket_index]); + rcutils_ret_t ret = RCUTILS_RET_OK; + + // If we have initialized this bucket yet then do so + if (NULL == bucket->impl) { + ret = rcutils_array_list_init(bucket, BUCKET_INITIAL_CAP, sizeof(rcutils_hash_map_entry_t *), + allocator); + } + + if (RCUTILS_RET_OK == ret) { + ret = rcutils_array_list_add(bucket, &entry); + } + + return ret; +} + +// Checks if map is already past its load factor and grows it if so +static rcutils_ret_t hash_map_check_and_grow_map(rcutils_hash_map_t * hash_map) +{ + rcutils_ret_t ret = RCUTILS_RET_OK; + if (hash_map->impl->size >= (LOAD_FACTOR * hash_map->impl->capacity)) { + size_t new_capacity = 2 * hash_map->impl->capacity; + rcutils_array_list_t * new_map = NULL; + + ret = hash_map_allocate_new_map(&new_map, new_capacity, &hash_map->impl->allocator); + if (RCUTILS_RET_OK != ret) { + return ret; + } + + for (size_t map_index = 0; + map_index < hash_map->impl->capacity && RCUTILS_RET_OK == ret; + ++map_index) + { + rcutils_array_list_t * bucket = &(hash_map->impl->map[map_index]); + // Is this a valid bucket with entries + if (NULL != bucket->impl) { + size_t bucket_size = 0; + ret = rcutils_array_list_get_size(bucket, &bucket_size); + if (RCUTILS_RET_OK != ret) { + return ret; + } + + for (size_t bucket_index = 0; + bucket_index < bucket_size && RCUTILS_RET_OK == ret; + ++bucket_index) + { + rcutils_hash_map_entry_t * entry = NULL; + ret = rcutils_array_list_get(bucket, bucket_index, &entry); + if (RCUTILS_RET_OK == ret) { + size_t new_index = entry->hashed_key % new_capacity; + ret = hash_map_insert_entry(new_map, new_index, entry, &hash_map->impl->allocator); + } + } + } + } + + // Something went wrong above after we allocated the new map. Try to clean it up + if (RCUTILS_RET_OK != ret) { + hash_map_deallocate_map(new_map, new_capacity, &hash_map->impl->allocator, false); + return ret; + } + + // Cleanup the old map and swap in the new one + ret = hash_map_deallocate_map(hash_map->impl->map, hash_map->impl->capacity, + &hash_map->impl->allocator, false); + // everything worked up to this point, so if we fail to dealloc the old map still set the new + hash_map->impl->map = new_map; + hash_map->impl->capacity = new_capacity; + } + + return ret; +} + +rcutils_ret_t +rcutils_hash_map_init( + rcutils_hash_map_t * hash_map, + size_t initial_capacity, + size_t key_size, + size_t data_size, + rcutils_hash_map_key_hasher_t key_hashing_func, + rcutils_hash_map_key_cmp_t key_cmp_func, + const rcutils_allocator_t * allocator) +{ + HASH_MAP_VALIDATE_ARGUMENT_NOT_NULL(hash_map); + HASH_MAP_VALIDATE_ARGUMENT_NOT_NULL(key_hashing_func); + HASH_MAP_VALIDATE_ARGUMENT_NOT_NULL(key_cmp_func); + HASH_MAP_VALIDATE_ARGUMENT_NOT_NULL(allocator); + if (1 > initial_capacity) { + RCUTILS_SET_ERROR_MSG("initial_capacity cannot be less than 1"); + return RCUTILS_RET_INVALID_ARGUMENT; + } else if (1 > key_size) { + RCUTILS_SET_ERROR_MSG("key_size cannot be less than 1"); + return RCUTILS_RET_INVALID_ARGUMENT; + } else if (1 > data_size) { + RCUTILS_SET_ERROR_MSG("data_size cannot be less than 1"); + return RCUTILS_RET_INVALID_ARGUMENT; + } + + hash_map->impl = allocator->allocate(sizeof(rcutils_hash_map_impl_t), allocator->state); + if (NULL == hash_map->impl) { + RCUTILS_SET_ERROR_MSG("failed to allocate memory for hash map impl"); + return RCUTILS_RET_BAD_ALLOC; + } + + hash_map->impl->capacity = initial_capacity; + hash_map->impl->size = 0; + hash_map->impl->key_size = key_size; + hash_map->impl->data_size = data_size; + hash_map->impl->key_hashing_func = key_hashing_func; + hash_map->impl->key_cmp_func = key_cmp_func; + + rcutils_ret_t ret = hash_map_allocate_new_map(&hash_map->impl->map, initial_capacity, allocator); + if (RCUTILS_RET_OK != ret) { + // Cleanup allocated memory before we return failure + allocator->deallocate(hash_map->impl, allocator->state); + hash_map->impl = NULL; + RCUTILS_SET_ERROR_MSG("failed to allocate memory for map data"); + return ret; + } + + hash_map->impl->allocator = *allocator; + + return RCUTILS_RET_OK; +} + +rcutils_ret_t +rcutils_hash_map_fini(rcutils_hash_map_t * hash_map) +{ + HASH_MAP_VALIDATE_HASH_MAP(hash_map); + rcutils_ret_t ret = hash_map_deallocate_map(hash_map->impl->map, hash_map->impl->capacity, + &hash_map->impl->allocator, true); + + if (RCUTILS_RET_OK == ret) { + hash_map->impl->allocator.deallocate(hash_map->impl, hash_map->impl->allocator.state); + hash_map->impl = NULL; + } + + return ret; +} + +rcutils_ret_t +rcutils_hash_map_get_capacity(const rcutils_hash_map_t * hash_map, size_t * capacity) +{ + HASH_MAP_VALIDATE_HASH_MAP(hash_map); + HASH_MAP_VALIDATE_ARGUMENT_NOT_NULL(capacity); + *capacity = hash_map->impl->capacity; + return RCUTILS_RET_OK; +} + +rcutils_ret_t +rcutils_hash_map_get_size(const rcutils_hash_map_t * hash_map, size_t * size) +{ + HASH_MAP_VALIDATE_HASH_MAP(hash_map); + HASH_MAP_VALIDATE_ARGUMENT_NOT_NULL(size); + *size = hash_map->impl->size; + return RCUTILS_RET_OK; +} + +/// Returns true if found or false if it doesn't exist. +/// key_hash and map_index will always be set correctly +static bool hash_map_find( + const rcutils_hash_map_t * hash_map, // [in] The hash_map to look up in + const void * key, // [in] The key to lookup + size_t * key_hash, // [out] The key's hashed value + size_t * map_index, // [out] The index into the array of buckets + size_t * bucket_index, // [out] The index of the entry in its bucket + rcutils_hash_map_entry_t ** entry) // [out] Will be set to a pointer to the entry's data +{ + size_t bucket_size = 0; + rcutils_hash_map_entry_t * bucket_entry = NULL; + + *key_hash = hash_map->impl->key_hashing_func(key); + *map_index = (*key_hash) % hash_map->impl->capacity; + + // Find the bucket the entry should be in check that it's valid + rcutils_array_list_t * bucket = &(hash_map->impl->map[*map_index]); + if (NULL == bucket->impl) { + return false; + } + if (RCUTILS_RET_OK != rcutils_array_list_get_size(bucket, &bucket_size)) { + return false; + } + for (size_t i = 0; i < bucket_size; ++i) { + if (RCUTILS_RET_OK != rcutils_array_list_get(bucket, i, &bucket_entry)) { + return false; + } + // Check that the hashes match first as that will be the quicker comparison to quick fail on + if (bucket_entry->hashed_key == *key_hash && + (0 == hash_map->impl->key_cmp_func(bucket_entry->key, key))) + { + *bucket_index = i; + *entry = bucket_entry; + return true; + } + } + + return false; +} + +rcutils_ret_t +rcutils_hash_map_set(rcutils_hash_map_t * hash_map, const void * key, const void * value) +{ + HASH_MAP_VALIDATE_HASH_MAP(hash_map); + HASH_MAP_VALIDATE_ARGUMENT_NOT_NULL(key); + HASH_MAP_VALIDATE_ARGUMENT_NOT_NULL(value); + + size_t key_hash = 0, map_index = 0, bucket_index = 0; + bool already_exists = false; + rcutils_hash_map_entry_t * entry = NULL; + rcutils_ret_t ret = RCUTILS_RET_OK; + + already_exists = hash_map_find(hash_map, key, &key_hash, &map_index, &bucket_index, &entry); + + if (already_exists) { + // Just update the existing value to match the new value + memcpy(entry->value, value, hash_map->impl->data_size); + } else { + // We need to create a new entry in the map + rcutils_allocator_t * allocator = &hash_map->impl->allocator; + + // Start by trying to allocate the memory we need for the new entry + entry = allocator->allocate(sizeof(rcutils_hash_map_entry_t), allocator->state); + if (NULL == entry) { + return RCUTILS_RET_BAD_ALLOC; + } + entry->key = allocator->allocate(hash_map->impl->key_size, allocator->state); + entry->value = allocator->allocate(hash_map->impl->data_size, allocator->state); + if (NULL == entry->key || NULL == entry->value) { + ret = RCUTILS_RET_BAD_ALLOC; + } + + // Set the entry data and try to insert into the bucket + if (RCUTILS_RET_OK == ret) { + entry->hashed_key = key_hash; + memcpy(entry->value, value, hash_map->impl->data_size); + memcpy(entry->key, key, hash_map->impl->key_size); + + bucket_index = key_hash % hash_map->impl->capacity; + ret = hash_map_insert_entry(hash_map->impl->map, bucket_index, entry, allocator); + } + + if (RCUTILS_RET_OK != ret) { + // If somethign went wrong somewhere then cleanup the memory we've allocated + hash_map_deallocate_entry(allocator, entry); + return ret; + } else { + hash_map->impl->size++; + } + } + + // Time to check if we've exceeded our Load Factor and grow the map if so + ret = hash_map_check_and_grow_map(hash_map); + // Just log on this failure because the map can continue to operate with degraded performance + RCUTILS_LOG_ERROR_EXPRESSION(RCUTILS_RET_OK != ret, "Failed to grow hash_map. Reason: %d", ret); + + return RCUTILS_RET_OK; +} + +rcutils_ret_t +rcutils_hash_map_unset(rcutils_hash_map_t * hash_map, const void * key) +{ + HASH_MAP_VALIDATE_HASH_MAP(hash_map); + HASH_MAP_VALIDATE_ARGUMENT_NOT_NULL(key); + + size_t key_hash = 0, map_index = 0, bucket_index = 0; + bool already_exists = false; + rcutils_hash_map_entry_t * entry = NULL; + + already_exists = hash_map_find(hash_map, key, &key_hash, &map_index, &bucket_index, &entry); + + if (!already_exists) { + // The entry isn't in the map, so just exit + return RCUTILS_RET_OK; + } + + // Remove the entry from its bucket and deallocate it + rcutils_array_list_t * bucket = &(hash_map->impl->map[map_index]); + if (RCUTILS_RET_OK == rcutils_array_list_remove(bucket, bucket_index)) { + hash_map->impl->size--; + hash_map_deallocate_entry(&hash_map->impl->allocator, entry); + } + + return RCUTILS_RET_OK; +} + +bool +rcutils_hash_map_key_exists(const rcutils_hash_map_t * hash_map, const void * key) +{ + // Verify input + if (NULL == hash_map) { + return false; + } else if (NULL == hash_map->impl) { + return false; + } else if (NULL == key) { + return false; + } + + size_t key_hash = 0, map_index = 0, bucket_index = 0; + bool already_exists = false; + rcutils_hash_map_entry_t * entry = NULL; + + already_exists = hash_map_find(hash_map, key, &key_hash, &map_index, &bucket_index, &entry); + + return already_exists; +} + +rcutils_ret_t +rcutils_hash_map_get(const rcutils_hash_map_t * hash_map, const void * key, void * data) +{ + HASH_MAP_VALIDATE_HASH_MAP(hash_map); + HASH_MAP_VALIDATE_ARGUMENT_NOT_NULL(key); + HASH_MAP_VALIDATE_ARGUMENT_NOT_NULL(data); + + size_t key_hash = 0, map_index = 0, bucket_index = 0; + bool already_exists = false; + rcutils_hash_map_entry_t * entry = NULL; + + already_exists = hash_map_find(hash_map, key, &key_hash, &map_index, &bucket_index, &entry); + + if (already_exists) { + memcpy(data, entry->value, hash_map->impl->data_size); + return RCUTILS_RET_OK; + } + + return RCUTILS_RET_NOT_FOUND; +} + +rcutils_ret_t +rcutils_hash_map_get_next_key_and_data( + const rcutils_hash_map_t * hash_map, + const void * previous_key, + void * key, + void * data) +{ + HASH_MAP_VALIDATE_HASH_MAP(hash_map); + HASH_MAP_VALIDATE_ARGUMENT_NOT_NULL(key); + HASH_MAP_VALIDATE_ARGUMENT_NOT_NULL(data); + + size_t key_hash = 0, map_index = 0, bucket_index = 0; + bool already_exists = false; + rcutils_hash_map_entry_t * entry = NULL; + rcutils_ret_t ret = RCUTILS_RET_OK; + + if (NULL != previous_key) { + already_exists = hash_map_find(hash_map, key, &key_hash, &map_index, &bucket_index, &entry); + if (!already_exists) { + return RCUTILS_RET_NOT_FOUND; + } + bucket_index++; // We want to start our search from the next object + } + + for (; map_index < hash_map->impl->capacity; ++map_index) { + rcutils_array_list_t * bucket = &(hash_map->impl->map[map_index]); + if (NULL != bucket->impl) { + size_t bucket_size = 0; + ret = rcutils_array_list_get_size(bucket, &bucket_size); + if (RCUTILS_RET_OK != ret) { + return ret; + } + + // Check if the next index in this bucket is valid and if so we've found the next item + if (bucket_index < bucket_size) { + rcutils_hash_map_entry_t * bucket_entry = NULL; + ret = rcutils_array_list_get(bucket, bucket_index, &bucket_entry); + if (RCUTILS_RET_OK == ret) { + memcpy(key, bucket_entry->key, hash_map->impl->key_size); + memcpy(data, bucket_entry->value, hash_map->impl->data_size); + } + + return ret; + } + } + // After the first bucket the next entry must be at the start of the next bucket with entries + bucket_index = 0; + } + + return RCUTILS_RET_HASH_MAP_NO_MORE_ENTRIES; +} + + +#ifdef __cplusplus +} +#endif diff --git a/test/test_array_list.cpp b/test/test_array_list.cpp new file mode 100644 index 00000000..5312a155 --- /dev/null +++ b/test/test_array_list.cpp @@ -0,0 +1,355 @@ +// Copyright 2017 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 "./allocator_testing_utils.h" +#include "rcutils/allocator.h" +#include "rcutils/error_handling.h" +#include "rcutils/types/array_list.h" + +auto failing_allocator = get_failing_allocator(); + +class ArrayListTest : public ::testing::Test +{ +protected: + void SetUp() override + { + allocator = rcutils_get_default_allocator(); + list = rcutils_get_zero_initialized_array_list(); + rcutils_reset_error(); + } + + // void TearDown() override {} + + rcutils_allocator_t allocator; + rcutils_array_list_t list; +}; + +// This fixture is used for test that want the list pre initialized +class ArrayListPreInitTest : public ::testing::Test +{ +protected: + void SetUp() override + { + allocator = rcutils_get_default_allocator(); + list = rcutils_get_zero_initialized_array_list(); + rcutils_ret_t ret = rcutils_array_list_init(&list, 2, sizeof(uint32_t), &allocator); + EXPECT_EQ(RCUTILS_RET_OK, ret) << rcutils_get_error_string().str; + rcutils_reset_error(); + } + + void TearDown() override + { + rcutils_ret_t ret = rcutils_array_list_fini(&list); + EXPECT_EQ(RCUTILS_RET_OK, ret) << rcutils_get_error_string().str; + } + + rcutils_allocator_t allocator; + rcutils_array_list_t list; +}; + +TEST_F(ArrayListTest, init_list_null_fails) { + rcutils_ret_t ret = rcutils_array_list_init(NULL, 2, sizeof(uint32_t), &allocator); + EXPECT_EQ(RCUTILS_RET_INVALID_ARGUMENT, ret) << rcutils_get_error_string().str; +} + +TEST_F(ArrayListTest, init_initial_capacity_zero_fails) { + rcutils_ret_t ret = rcutils_array_list_init(&list, 0, sizeof(uint32_t), &allocator); + EXPECT_EQ(RCUTILS_RET_INVALID_ARGUMENT, ret) << rcutils_get_error_string().str; +} + +TEST_F(ArrayListTest, init_data_size_zero_fails) { + rcutils_ret_t ret = rcutils_array_list_init(&list, 2, 0, &allocator); + EXPECT_EQ(RCUTILS_RET_INVALID_ARGUMENT, ret) << rcutils_get_error_string().str; +} + +TEST_F(ArrayListTest, init_null_allocator_fails) { + rcutils_ret_t ret = rcutils_array_list_init(&list, 2, sizeof(uint32_t), NULL); + EXPECT_EQ(RCUTILS_RET_INVALID_ARGUMENT, ret) << rcutils_get_error_string().str; +} + +TEST_F(ArrayListTest, init_success) { + rcutils_ret_t ret = rcutils_array_list_init(&list, 2, sizeof(uint32_t), &allocator); + EXPECT_EQ(RCUTILS_RET_OK, ret) << rcutils_get_error_string().str; +} + +TEST_F(ArrayListTest, fini_list_null) { + rcutils_ret_t ret = rcutils_array_list_fini(NULL); + EXPECT_EQ(RCUTILS_RET_INVALID_ARGUMENT, ret) << rcutils_get_error_string().str; +} + +TEST_F(ArrayListTest, fini_list_not_initialized) { + rcutils_ret_t ret = rcutils_array_list_fini(&list); + EXPECT_EQ(RCUTILS_RET_NOT_INITIALIZED, ret) << rcutils_get_error_string().str; +} + +TEST_F(ArrayListTest, fini_success) { + rcutils_ret_t ret; + + ret = rcutils_array_list_init(&list, 2, sizeof(uint32_t), &allocator); + EXPECT_EQ(RCUTILS_RET_OK, ret) << rcutils_get_error_string().str; + + ret = rcutils_array_list_fini(&list); + EXPECT_EQ(RCUTILS_RET_OK, ret) << rcutils_get_error_string().str; +} + +TEST_F(ArrayListTest, add_list_null_fails) { + uint32_t data = 22; + rcutils_ret_t ret = rcutils_array_list_add(NULL, &data); + EXPECT_EQ(RCUTILS_RET_INVALID_ARGUMENT, ret) << rcutils_get_error_string().str; +} + +TEST_F(ArrayListTest, add_list_not_initialized_fails) { + uint32_t data = 22; + rcutils_ret_t ret = rcutils_array_list_add(&list, &data); + EXPECT_EQ(RCUTILS_RET_NOT_INITIALIZED, ret) << rcutils_get_error_string().str; +} + +TEST_F(ArrayListPreInitTest, add_data_null_fails) { + rcutils_ret_t ret = rcutils_array_list_add(&list, NULL); + EXPECT_EQ(RCUTILS_RET_INVALID_ARGUMENT, ret) << rcutils_get_error_string().str; +} + +TEST_F(ArrayListPreInitTest, add_success) { + uint32_t data = 22; + rcutils_ret_t ret = rcutils_array_list_add(&list, &data); + EXPECT_EQ(RCUTILS_RET_OK, ret) << rcutils_get_error_string().str; +} + +TEST_F(ArrayListTest, set_list_null_fails) { + uint32_t data = 22; + size_t index = 0; + rcutils_ret_t ret = rcutils_array_list_set(NULL, index, &data); + EXPECT_EQ(RCUTILS_RET_INVALID_ARGUMENT, ret) << rcutils_get_error_string().str; +} + +TEST_F(ArrayListTest, set_list_not_initialized_fails) { + uint32_t data = 22; + size_t index = 0; + rcutils_ret_t ret = rcutils_array_list_set(&list, index, &data); + EXPECT_EQ(RCUTILS_RET_NOT_INITIALIZED, ret) << rcutils_get_error_string().str; +} + +TEST_F(ArrayListPreInitTest, set_data_null_fails) { + uint32_t data = 22; + size_t index = 0; + + // Add something first so we know the index isn't out of bounds + rcutils_ret_t ret = rcutils_array_list_add(&list, &data); + EXPECT_EQ(RCUTILS_RET_OK, ret) << rcutils_get_error_string().str; + + ret = rcutils_array_list_set(&list, index, NULL); + EXPECT_EQ(RCUTILS_RET_INVALID_ARGUMENT, ret) << rcutils_get_error_string().str; +} + +TEST_F(ArrayListPreInitTest, set_data_index_oob_fails) { + uint32_t data = 22; + size_t index = 1; + + // Add something first so we know the index isn't out of bounds + rcutils_ret_t ret = rcutils_array_list_add(&list, &data); + EXPECT_EQ(RCUTILS_RET_OK, ret) << rcutils_get_error_string().str; + + ret = rcutils_array_list_set(&list, index, &data); + EXPECT_EQ(RCUTILS_RET_INVALID_ARGUMENT, ret) << rcutils_get_error_string().str; +} + +TEST_F(ArrayListPreInitTest, set_success_changes_data) { + uint32_t data = 22; + uint32_t ret_data = 0; + size_t index = 0; + + // Add something first so we know the index isn't out of bounds + rcutils_ret_t ret = rcutils_array_list_add(&list, &data); + EXPECT_EQ(RCUTILS_RET_OK, ret) << rcutils_get_error_string().str; + + data = 23; + ret = rcutils_array_list_set(&list, index, &data); + EXPECT_EQ(RCUTILS_RET_OK, ret) << rcutils_get_error_string().str; + + ret = rcutils_array_list_get(&list, index, &ret_data); + EXPECT_EQ(RCUTILS_RET_OK, ret) << rcutils_get_error_string().str; + EXPECT_EQ(data, ret_data); +} + +TEST_F(ArrayListTest, remove_list_null_fails) { + size_t index = 0; + rcutils_ret_t ret = rcutils_array_list_remove(NULL, index); + EXPECT_EQ(RCUTILS_RET_INVALID_ARGUMENT, ret) << rcutils_get_error_string().str; +} + +TEST_F(ArrayListTest, remove_list_not_initialized_fails) { + size_t index = 0; + rcutils_ret_t ret = rcutils_array_list_remove(&list, index); + EXPECT_EQ(RCUTILS_RET_NOT_INITIALIZED, ret) << rcutils_get_error_string().str; +} + +TEST_F(ArrayListPreInitTest, remove_data_index_oob_fails) { + uint32_t data = 22; + size_t index = 1; + + // Add something first so we know the index isn't out of bounds + rcutils_ret_t ret = rcutils_array_list_add(&list, &data); + EXPECT_EQ(RCUTILS_RET_OK, ret) << rcutils_get_error_string().str; + + ret = rcutils_array_list_remove(&list, index); + EXPECT_EQ(RCUTILS_RET_INVALID_ARGUMENT, ret) << rcutils_get_error_string().str; +} + +TEST_F(ArrayListPreInitTest, remove_success_removes_from_list) { + uint32_t data = 22; + size_t index = 0; + size_t size = 0; + + // Add something first so we know the index isn't out of bounds + rcutils_ret_t ret = rcutils_array_list_add(&list, &data); + EXPECT_EQ(RCUTILS_RET_OK, ret) << rcutils_get_error_string().str; + + ret = rcutils_array_list_get_size(&list, &size); + EXPECT_EQ(size, (size_t)1); + + ret = rcutils_array_list_remove(&list, index); + EXPECT_EQ(RCUTILS_RET_OK, ret) << rcutils_get_error_string().str; + + ret = rcutils_array_list_get_size(&list, &size); + EXPECT_EQ(size, (size_t)0); +} + +TEST_F(ArrayListTest, get_list_null_fails) { + size_t index = 0; + uint32_t data = 0; + rcutils_ret_t ret = rcutils_array_list_get(NULL, index, &data); + EXPECT_EQ(RCUTILS_RET_INVALID_ARGUMENT, ret) << rcutils_get_error_string().str; +} + +TEST_F(ArrayListTest, get_list_not_initialized_fails) { + size_t index = 0; + uint32_t data = 0; + rcutils_ret_t ret = rcutils_array_list_get(&list, index, &data); + EXPECT_EQ(RCUTILS_RET_NOT_INITIALIZED, ret) << rcutils_get_error_string().str; +} + +TEST_F(ArrayListPreInitTest, get_data_index_oob_fails) { + uint32_t data = 22; + uint32_t ret_data = 0; + size_t index = 1; + + // Add something first so we know the index of 0 isn't out of bounds + rcutils_ret_t ret = rcutils_array_list_add(&list, &data); + EXPECT_EQ(RCUTILS_RET_OK, ret) << rcutils_get_error_string().str; + + ret = rcutils_array_list_get(&list, index, &ret_data); + EXPECT_EQ(RCUTILS_RET_INVALID_ARGUMENT, ret) << rcutils_get_error_string().str; +} + +TEST_F(ArrayListPreInitTest, get_data_success_returns_data) { + uint32_t data = 22; + uint32_t ret_data = 0; + size_t index = 0; + + rcutils_ret_t ret = rcutils_array_list_add(&list, &data); + EXPECT_EQ(RCUTILS_RET_OK, ret) << rcutils_get_error_string().str; + + ret = rcutils_array_list_get(&list, index, &ret_data); + EXPECT_EQ(RCUTILS_RET_OK, ret) << rcutils_get_error_string().str; + EXPECT_EQ(data, ret_data); +} + +TEST_F(ArrayListTest, get_size_list_null_fails) { + size_t size = 0; + rcutils_ret_t ret = rcutils_array_list_get_size(NULL, &size); + EXPECT_EQ(RCUTILS_RET_INVALID_ARGUMENT, ret) << rcutils_get_error_string().str; +} + +TEST_F(ArrayListTest, get_size_list_not_initialized_fails) { + size_t size = 0; + rcutils_ret_t ret = rcutils_array_list_get_size(&list, &size); + EXPECT_EQ(RCUTILS_RET_NOT_INITIALIZED, ret) << rcutils_get_error_string().str; +} + +TEST_F(ArrayListPreInitTest, get_size_size_null_fails) { + rcutils_ret_t ret = rcutils_array_list_get_size(&list, NULL); + EXPECT_EQ(RCUTILS_RET_INVALID_ARGUMENT, ret) << rcutils_get_error_string().str; +} + +TEST_F(ArrayListPreInitTest, get_size_increases_with_add) { + size_t size = 0; + uint32_t data = 22; + rcutils_ret_t ret; + + ret = rcutils_array_list_get_size(&list, &size); + EXPECT_EQ(size, (size_t)0); + + ret = rcutils_array_list_add(&list, &data); + EXPECT_EQ(RCUTILS_RET_OK, ret) << rcutils_get_error_string().str; + + ret = rcutils_array_list_get_size(&list, &size); + EXPECT_EQ(size, (size_t)1); +} + +// Don't let this one extend from ArrayListPreInitTest so we can control the initial cap +TEST_F(ArrayListTest, add_grow_capacity) { + rcutils_ret_t ret = rcutils_array_list_init(&list, 2, sizeof(uint32_t), &allocator); + EXPECT_EQ(RCUTILS_RET_OK, ret) << rcutils_get_error_string().str; + + for (uint32_t i = 0; i < 17; ++i) { + uint32_t data = i * 2; + ret = rcutils_array_list_add(&list, &data); + EXPECT_EQ(RCUTILS_RET_OK, ret) << rcutils_get_error_string().str; + } + + // Verify all the data is still set after growing + for (uint32_t i = 0; i < 17; ++i) { + uint32_t ret_data = 0; + ret = rcutils_array_list_get(&list, i, &ret_data); + EXPECT_EQ((i * 2), ret_data) << rcutils_get_error_string().str; + } +} + +TEST_F(ArrayListPreInitTest, remove_preserves_data_around_it) { + rcutils_ret_t ret; + size_t size = 0; + uint32_t ret_data = 0; + for (uint32_t i = 0; i < 4; ++i) { + uint32_t data = i * 2; + ret = rcutils_array_list_add(&list, &data); + EXPECT_EQ(RCUTILS_RET_OK, ret) << rcutils_get_error_string().str; + } + + ret = rcutils_array_list_get_size(&list, &size); + EXPECT_EQ(RCUTILS_RET_OK, ret) << rcutils_get_error_string().str; + EXPECT_EQ(size, (size_t)4); + + ret = rcutils_array_list_remove(&list, 2); + EXPECT_EQ(RCUTILS_RET_OK, ret) << rcutils_get_error_string().str; + + ret = rcutils_array_list_get_size(&list, &size); + EXPECT_EQ(RCUTILS_RET_OK, ret) << rcutils_get_error_string().str; + EXPECT_EQ(size, (size_t)3); + + ret = rcutils_array_list_get(&list, 0, &ret_data); + EXPECT_EQ(RCUTILS_RET_OK, ret) << rcutils_get_error_string().str; + EXPECT_EQ((uint32_t)0, ret_data) << rcutils_get_error_string().str; + + ret = rcutils_array_list_get(&list, 1, &ret_data); + EXPECT_EQ(RCUTILS_RET_OK, ret) << rcutils_get_error_string().str; + EXPECT_EQ((uint32_t)2, ret_data) << rcutils_get_error_string().str; + + ret = rcutils_array_list_get(&list, 2, &ret_data); + EXPECT_EQ(RCUTILS_RET_OK, ret) << rcutils_get_error_string().str; + EXPECT_EQ((uint32_t)6, ret_data) << rcutils_get_error_string().str; +} diff --git a/test/test_hash_map.cpp b/test/test_hash_map.cpp new file mode 100644 index 00000000..83588af8 --- /dev/null +++ b/test/test_hash_map.cpp @@ -0,0 +1,452 @@ +// Copyright 2017 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 "./allocator_testing_utils.h" +#include "rcutils/allocator.h" +#include "rcutils/error_handling.h" +#include "rcutils/types/hash_map.h" + +size_t test_hash_map_uint32_hash_func(const void * key) +{ + const uint32_t * key_ptr = reinterpret_cast(key); + return *key_ptr * 73; +} + +int test_uint32_cmp(const void * val1, const void * val2) +{ + const uint32_t * cval1 = (const uint32_t *)val1; + const uint32_t * cval2 = (const uint32_t *)val2; + if (*cval1 < *cval2) { + return -1; + } else if (*cval2 > *cval1) { + return 1; + } else { + return 0; + } +} + +class HashMapBaseTest : public ::testing::Test +{ +protected: + void SetUp() override + { + allocator = rcutils_get_default_allocator(); + map = rcutils_get_zero_initialized_hash_map(); + rcutils_reset_error(); + } + + // void TearDown() override {} + + rcutils_allocator_t allocator; + rcutils_hash_map_t map; +}; + +// This fixture is used for test that want the map pre initialized +class HashMapPreInitTest : public ::testing::Test +{ +protected: + void SetUp() override + { + allocator = rcutils_get_default_allocator(); + map = rcutils_get_zero_initialized_hash_map(); + rcutils_ret_t ret = rcutils_hash_map_init(&map, 2, sizeof(uint32_t), sizeof(uint32_t), + test_hash_map_uint32_hash_func, test_uint32_cmp, &allocator); + EXPECT_EQ(RCUTILS_RET_OK, ret) << rcutils_get_error_string().str; + rcutils_reset_error(); + } + + void TearDown() override + { + rcutils_ret_t ret = rcutils_hash_map_fini(&map); + EXPECT_EQ(RCUTILS_RET_OK, ret) << rcutils_get_error_string().str; + } + + rcutils_allocator_t allocator; + rcutils_hash_map_t map; +}; + +TEST_F(HashMapBaseTest, init_map_NULL_fails) { + rcutils_ret_t ret = rcutils_hash_map_init(NULL, 2, sizeof(uint32_t), sizeof(uint32_t), + test_hash_map_uint32_hash_func, test_uint32_cmp, &allocator); + EXPECT_EQ(RCUTILS_RET_INVALID_ARGUMENT, ret) << rcutils_get_error_string().str; +} + +TEST_F(HashMapBaseTest, init_map_initial_capacity_zero_fails) { + rcutils_ret_t ret = rcutils_hash_map_init(&map, 0, sizeof(uint32_t), sizeof(uint32_t), + test_hash_map_uint32_hash_func, test_uint32_cmp, &allocator); + EXPECT_EQ(RCUTILS_RET_INVALID_ARGUMENT, ret) << rcutils_get_error_string().str; +} + +TEST_F(HashMapBaseTest, init_map_key_size_zero_fails) { + rcutils_ret_t ret = rcutils_hash_map_init(&map, 2, 0, sizeof(uint32_t), + test_hash_map_uint32_hash_func, test_uint32_cmp, &allocator); + EXPECT_EQ(RCUTILS_RET_INVALID_ARGUMENT, ret) << rcutils_get_error_string().str; +} + +TEST_F(HashMapBaseTest, init_map_data_size_zero_fails) { + rcutils_ret_t ret = rcutils_hash_map_init(&map, 2, sizeof(uint32_t), 0, + test_hash_map_uint32_hash_func, test_uint32_cmp, &allocator); + EXPECT_EQ(RCUTILS_RET_INVALID_ARGUMENT, ret) << rcutils_get_error_string().str; +} + +TEST_F(HashMapBaseTest, init_map_hash_func_NULL_fails) { + rcutils_ret_t ret = rcutils_hash_map_init(&map, 2, sizeof(uint32_t), sizeof(uint32_t), + NULL, test_uint32_cmp, &allocator); + EXPECT_EQ(RCUTILS_RET_INVALID_ARGUMENT, ret) << rcutils_get_error_string().str; +} + +TEST_F(HashMapBaseTest, init_map_cmp_func_NULL_fails) { + rcutils_ret_t ret = rcutils_hash_map_init(&map, 2, sizeof(uint32_t), sizeof(uint32_t), + test_hash_map_uint32_hash_func, NULL, &allocator); + EXPECT_EQ(RCUTILS_RET_INVALID_ARGUMENT, ret) << rcutils_get_error_string().str; +} + +TEST_F(HashMapBaseTest, init_map_allocator_NULL_fails) { + rcutils_ret_t ret = rcutils_hash_map_init(&map, 2, sizeof(uint32_t), sizeof(uint32_t), + test_hash_map_uint32_hash_func, test_uint32_cmp, NULL); + EXPECT_EQ(RCUTILS_RET_INVALID_ARGUMENT, ret) << rcutils_get_error_string().str; +} + +TEST_F(HashMapBaseTest, init_map_success) { + rcutils_ret_t ret = rcutils_hash_map_init(&map, 2, sizeof(uint32_t), sizeof(uint32_t), + test_hash_map_uint32_hash_func, test_uint32_cmp, &allocator); + EXPECT_EQ(RCUTILS_RET_OK, ret) << rcutils_get_error_string().str; + + ret = rcutils_hash_map_fini(&map); +} + +TEST_F(HashMapBaseTest, fini_map_success) { + rcutils_ret_t ret = rcutils_hash_map_init(&map, 2, sizeof(uint32_t), sizeof(uint32_t), + test_hash_map_uint32_hash_func, test_uint32_cmp, &allocator); + EXPECT_EQ(RCUTILS_RET_OK, ret) << rcutils_get_error_string().str; + + ret = rcutils_hash_map_fini(&map); + EXPECT_EQ(RCUTILS_RET_OK, ret) << rcutils_get_error_string().str; +} + +TEST_F(HashMapPreInitTest, get_capacity_map_null_fails) { + size_t cap = 0; + rcutils_ret_t ret = rcutils_hash_map_get_capacity(NULL, &cap); + EXPECT_EQ(RCUTILS_RET_INVALID_ARGUMENT, ret) << rcutils_get_error_string().str; +} + +TEST_F(HashMapBaseTest, get_capacity_map_not_init_fails) { + size_t cap = 0; + rcutils_ret_t ret = rcutils_hash_map_get_capacity(&map, &cap); + EXPECT_EQ(RCUTILS_RET_NOT_INITIALIZED, ret) << rcutils_get_error_string().str; +} + +TEST_F(HashMapPreInitTest, get_capacity_capacity_null_fails) { + rcutils_ret_t ret = rcutils_hash_map_get_capacity(&map, NULL); + EXPECT_EQ(RCUTILS_RET_INVALID_ARGUMENT, ret) << rcutils_get_error_string().str; +} + +TEST_F(HashMapPreInitTest, get_capacity_success_returns_ok) { + size_t cap = 0; + rcutils_ret_t ret = rcutils_hash_map_get_capacity(&map, &cap); + EXPECT_EQ(RCUTILS_RET_OK, ret) << rcutils_get_error_string().str; + EXPECT_EQ((size_t)2, cap); +} + +TEST_F(HashMapPreInitTest, get_size_map_null_fails) { + size_t size = 0; + rcutils_ret_t ret = rcutils_hash_map_get_size(NULL, &size); + EXPECT_EQ(RCUTILS_RET_INVALID_ARGUMENT, ret) << rcutils_get_error_string().str; +} + +TEST_F(HashMapBaseTest, get_size_map_not_init_fails) { + size_t size = 0; + rcutils_ret_t ret = rcutils_hash_map_get_size(&map, &size); + EXPECT_EQ(RCUTILS_RET_NOT_INITIALIZED, ret) << rcutils_get_error_string().str; +} + +TEST_F(HashMapPreInitTest, get_size_size_null_fails) { + rcutils_ret_t ret = rcutils_hash_map_get_size(&map, NULL); + EXPECT_EQ(RCUTILS_RET_INVALID_ARGUMENT, ret) << rcutils_get_error_string().str; +} + +TEST_F(HashMapPreInitTest, get_size_success_returns_ok) { + size_t size = 22; + rcutils_ret_t ret = rcutils_hash_map_get_size(&map, &size); + EXPECT_EQ(RCUTILS_RET_OK, ret) << rcutils_get_error_string().str; + EXPECT_EQ((size_t)0, size); +} + +TEST_F(HashMapPreInitTest, set_map_null_fails) { + uint32_t key = 2, data = 22; + rcutils_ret_t ret = rcutils_hash_map_set(NULL, &key, &data); + EXPECT_EQ(RCUTILS_RET_INVALID_ARGUMENT, ret) << rcutils_get_error_string().str; +} + +TEST_F(HashMapBaseTest, set_map_not_init_fails) { + uint32_t key = 2, data = 22; + rcutils_ret_t ret = rcutils_hash_map_set(&map, &key, &data); + EXPECT_EQ(RCUTILS_RET_NOT_INITIALIZED, ret) << rcutils_get_error_string().str; +} + +TEST_F(HashMapPreInitTest, set_key_null_fails) { + uint32_t data = 22; + rcutils_ret_t ret = rcutils_hash_map_set(&map, NULL, &data); + EXPECT_EQ(RCUTILS_RET_INVALID_ARGUMENT, ret) << rcutils_get_error_string().str; +} + +TEST_F(HashMapPreInitTest, set_data_null_fails) { + uint32_t key = 22; + rcutils_ret_t ret = rcutils_hash_map_set(&map, &key, NULL); + EXPECT_EQ(RCUTILS_RET_INVALID_ARGUMENT, ret) << rcutils_get_error_string().str; +} + +TEST_F(HashMapPreInitTest, set_data_success_returns_ok) { + uint32_t key = 2, data = 22; + rcutils_ret_t ret = rcutils_hash_map_set(&map, &key, &data); + EXPECT_EQ(RCUTILS_RET_OK, ret) << rcutils_get_error_string().str; +} + +TEST_F(HashMapPreInitTest, unset_map_null_fails) { + uint32_t key = 2; + rcutils_ret_t ret = rcutils_hash_map_unset(NULL, &key); + EXPECT_EQ(RCUTILS_RET_INVALID_ARGUMENT, ret) << rcutils_get_error_string().str; +} + +TEST_F(HashMapBaseTest, unset_map_not_init_fails) { + uint32_t key = 2; + rcutils_ret_t ret = rcutils_hash_map_unset(&map, &key); + EXPECT_EQ(RCUTILS_RET_NOT_INITIALIZED, ret) << rcutils_get_error_string().str; +} + +TEST_F(HashMapPreInitTest, unset_key_null_fails) { + rcutils_ret_t ret = rcutils_hash_map_unset(&map, NULL); + EXPECT_EQ(RCUTILS_RET_INVALID_ARGUMENT, ret) << rcutils_get_error_string().str; +} + +TEST_F(HashMapPreInitTest, unset_key_not_in_map_success) { + uint32_t key = 22; + rcutils_ret_t ret = rcutils_hash_map_unset(&map, &key); + EXPECT_EQ(RCUTILS_RET_OK, ret) << rcutils_get_error_string().str; +} + +TEST_F(HashMapPreInitTest, unset_key_in_map_success) { + uint32_t key = 2, data = 22; + + rcutils_ret_t ret = rcutils_hash_map_set(&map, &key, &data); + EXPECT_EQ(RCUTILS_RET_OK, ret) << rcutils_get_error_string().str; + + ret = rcutils_hash_map_unset(&map, &key); + EXPECT_EQ(RCUTILS_RET_OK, ret) << rcutils_get_error_string().str; +} + +TEST_F(HashMapPreInitTest, key_exists_map_null_returns_false) { + uint32_t key = 2; + bool ret = rcutils_hash_map_key_exists(NULL, &key); + EXPECT_FALSE(ret); +} + +TEST_F(HashMapBaseTest, key_exists_map_not_init_returns_false) { + uint32_t key = 2; + bool ret = rcutils_hash_map_key_exists(&map, &key); + EXPECT_FALSE(ret); +} + +TEST_F(HashMapPreInitTest, key_exists_key_null_returns_false) { + bool ret = rcutils_hash_map_key_exists(&map, NULL); + EXPECT_FALSE(ret); +} + +TEST_F(HashMapPreInitTest, key_exists_key_not_in_map_returns_false) { + uint32_t key = 22; + bool ret = rcutils_hash_map_key_exists(&map, &key); + EXPECT_FALSE(ret); +} + +TEST_F(HashMapPreInitTest, key_exists_key_in_map_returns_true) { + uint32_t key = 2, data = 22; + + rcutils_ret_t ret = rcutils_hash_map_set(&map, &key, &data); + EXPECT_EQ(RCUTILS_RET_OK, ret) << rcutils_get_error_string().str; + + bool ret_bool = rcutils_hash_map_key_exists(&map, &key); + EXPECT_TRUE(ret_bool); +} + +TEST_F(HashMapPreInitTest, get_map_null_fails) { + uint32_t key = 2, data = 0; + rcutils_ret_t ret = rcutils_hash_map_get(NULL, &key, &data); + EXPECT_EQ(RCUTILS_RET_INVALID_ARGUMENT, ret) << rcutils_get_error_string().str; +} + +TEST_F(HashMapBaseTest, get_map_not_init_fails) { + uint32_t key = 2, data = 0; + rcutils_ret_t ret = rcutils_hash_map_get(&map, &key, &data); + EXPECT_EQ(RCUTILS_RET_NOT_INITIALIZED, ret) << rcutils_get_error_string().str; +} + +TEST_F(HashMapPreInitTest, get_key_null_fails) { + uint32_t data = 0; + rcutils_ret_t ret = rcutils_hash_map_get(&map, NULL, &data); + EXPECT_EQ(RCUTILS_RET_INVALID_ARGUMENT, ret) << rcutils_get_error_string().str; +} + +TEST_F(HashMapPreInitTest, get_data_null_fails) { + uint32_t key = 22; + rcutils_ret_t ret = rcutils_hash_map_get(&map, &key, NULL); + EXPECT_EQ(RCUTILS_RET_INVALID_ARGUMENT, ret) << rcutils_get_error_string().str; +} + +TEST_F(HashMapPreInitTest, get_key_not_in_map_fails) { + uint32_t key = 22, data = 0; + rcutils_ret_t ret = rcutils_hash_map_get(&map, &key, &data); + EXPECT_EQ(RCUTILS_RET_NOT_FOUND, ret) << rcutils_get_error_string().str; +} + +TEST_F(HashMapPreInitTest, get_key_in_map_sets_data) { + uint32_t key = 2, data = 22, ret_data = 0; + + rcutils_ret_t ret = rcutils_hash_map_set(&map, &key, &data); + EXPECT_EQ(RCUTILS_RET_OK, ret) << rcutils_get_error_string().str; + + ret = rcutils_hash_map_get(&map, &key, &ret_data); + EXPECT_EQ(RCUTILS_RET_OK, ret) << rcutils_get_error_string().str; + EXPECT_EQ(data, ret_data); +} + +TEST_F(HashMapPreInitTest, get_next_key_and_data_map_null_fails) { + uint32_t previous_key = 1, key = 0, data = 0; + rcutils_ret_t ret = rcutils_hash_map_get_next_key_and_data(NULL, &previous_key, &key, &data); + EXPECT_EQ(RCUTILS_RET_INVALID_ARGUMENT, ret) << rcutils_get_error_string().str; +} + +TEST_F(HashMapBaseTest, get_next_key_and_data_map_not_init_fails) { + uint32_t previous_key = 1, key = 0, data = 0; + rcutils_ret_t ret = rcutils_hash_map_get_next_key_and_data(&map, &previous_key, &key, &data); + EXPECT_EQ(RCUTILS_RET_NOT_INITIALIZED, ret) << rcutils_get_error_string().str; +} + +TEST_F(HashMapPreInitTest, get_next_key_and_data_key_null_fails) { + uint32_t previous_key = 1, data = 0; + rcutils_ret_t ret = rcutils_hash_map_get_next_key_and_data(&map, &previous_key, NULL, &data); + EXPECT_EQ(RCUTILS_RET_INVALID_ARGUMENT, ret) << rcutils_get_error_string().str; +} + +TEST_F(HashMapPreInitTest, get_next_key_and_data_data_null_fails) { + uint32_t previous_key = 1, key = 0; + rcutils_ret_t ret = rcutils_hash_map_get_next_key_and_data(&map, &previous_key, &key, NULL); + EXPECT_EQ(RCUTILS_RET_INVALID_ARGUMENT, ret) << rcutils_get_error_string().str; +} + +TEST_F(HashMapPreInitTest, get_next_key_and_data_key_not_in_map_fails) { + uint32_t previous_key = 1, key = 0, data = 0; + rcutils_ret_t ret = rcutils_hash_map_get_next_key_and_data(&map, &previous_key, &key, &data); + EXPECT_EQ(RCUTILS_RET_NOT_FOUND, ret) << rcutils_get_error_string().str; +} + +TEST_F(HashMapPreInitTest, get_next_key_and_data_working) { + uint32_t key = 1, data = 3, ret_key = 0, ret_data = 0, last_key = 0; + + rcutils_ret_t ret = rcutils_hash_map_set(&map, &key, &data); + EXPECT_EQ(RCUTILS_RET_OK, ret) << rcutils_get_error_string().str; + + key++; + data++; + ret = rcutils_hash_map_set(&map, &key, &data); + EXPECT_EQ(RCUTILS_RET_OK, ret) << rcutils_get_error_string().str; + + // Get the first entry, but there's no guaranteed ordering + ret = rcutils_hash_map_get_next_key_and_data(&map, NULL, &ret_key, &ret_data); + EXPECT_EQ(RCUTILS_RET_OK, ret) << rcutils_get_error_string().str; + EXPECT_TRUE(1 == ret_key || 2 == ret_key); // we only put these two keys in the map + if (1 == ret_key) { + EXPECT_EQ((uint32_t)3, ret_data); + } else { + EXPECT_EQ((uint32_t)4, ret_data); + } + last_key = ret_key; + + // Get the next entry + ret = rcutils_hash_map_get_next_key_and_data(&map, &ret_key, &ret_key, &ret_data); + EXPECT_EQ(RCUTILS_RET_OK, ret) << rcutils_get_error_string().str; + EXPECT_TRUE(1 == ret_key || 2 == ret_key); // we only put these two keys in the map + EXPECT_NE(last_key, ret_key); + if (1 == ret_key) { + EXPECT_EQ((uint32_t)3, ret_data); + } else { + EXPECT_EQ((uint32_t)4, ret_data); + } + + // There should be no more keys beyond the last one + ret = rcutils_hash_map_get_next_key_and_data(&map, &ret_key, &ret_key, &ret_data); + EXPECT_EQ(RCUTILS_RET_HASH_MAP_NO_MORE_ENTRIES, ret) << rcutils_get_error_string().str; +} + +/* Use BaseTest as the fixture here so we can control the initial capacity independent of the + * other tests + */ +TEST_F(HashMapBaseTest, growing_the_map_beyond_initial_capacity) { + size_t capacity = 0; + uint32_t key = 22, data = 0; + rcutils_ret_t ret = rcutils_hash_map_init(&map, 2, sizeof(uint32_t), sizeof(uint32_t), + test_hash_map_uint32_hash_func, test_uint32_cmp, &allocator); + EXPECT_EQ(RCUTILS_RET_OK, ret) << rcutils_get_error_string().str; + + for (uint32_t i = 0; i < 50; ++i) { + ret = rcutils_hash_map_set(&map, &i, &i); + EXPECT_EQ(RCUTILS_RET_OK, ret) << rcutils_get_error_string().str; + } + + ret = rcutils_hash_map_get_capacity(&map, &capacity); + EXPECT_EQ(RCUTILS_RET_OK, ret) << rcutils_get_error_string().str; + // the reserved capacity should be greater than 50 due to the Load Factor + EXPECT_LT((size_t)50, capacity); + + // Retrieve some data to make sure it wasn't lost while growing the map + ret = rcutils_hash_map_get(&map, &key, &data); + EXPECT_EQ(RCUTILS_RET_OK, ret) << rcutils_get_error_string().str; + EXPECT_EQ(key, data); + + key++; + ret = rcutils_hash_map_get(&map, &key, &data); + EXPECT_EQ(RCUTILS_RET_OK, ret) << rcutils_get_error_string().str; + EXPECT_EQ(key, data); + + ret = rcutils_hash_map_fini(&map); + EXPECT_EQ(RCUTILS_RET_OK, ret) << rcutils_get_error_string().str; +} + +TEST_F(HashMapBaseTest, string_keys) { + uint32_t data = 1, ret_data = 0; + const char * key1 = "one"; + const char * key2 = "two"; + const char * lookup_key = "one"; + rcutils_ret_t ret = rcutils_hash_map_init(&map, 10, sizeof(char *), sizeof(uint32_t), + rcutils_hash_map_string_hash_func, rcutils_hash_map_string_cmp_func, + &allocator); + EXPECT_EQ(RCUTILS_RET_OK, ret) << rcutils_get_error_string().str; + + ret = rcutils_hash_map_set(&map, &key1, &data); + EXPECT_EQ(RCUTILS_RET_OK, ret) << rcutils_get_error_string().str; + + data++; + ret = rcutils_hash_map_set(&map, &key2, &data); + EXPECT_EQ(RCUTILS_RET_OK, ret) << rcutils_get_error_string().str; + + ret = rcutils_hash_map_get(&map, &lookup_key, &ret_data); + EXPECT_EQ(RCUTILS_RET_OK, ret) << rcutils_get_error_string().str; + EXPECT_EQ((uint32_t)1, ret_data); + + ret = rcutils_hash_map_fini(&map); + EXPECT_EQ(RCUTILS_RET_OK, ret) << rcutils_get_error_string().str; +}