diff --git a/include/asterisk/vector.h b/include/asterisk/vector.h index 0053d8a32523b851c688755ae52567477917ac07..23db8a502f3124ed17d4bfdb1b61a09e880da79a 100644 --- a/include/asterisk/vector.h +++ b/include/asterisk/vector.h @@ -19,6 +19,8 @@ #ifndef _ASTERISK_VECTOR_H #define _ASTERISK_VECTOR_H +#include "asterisk/lock.h" + /*! \file * * \brief Vector container support. @@ -46,6 +48,20 @@ size_t current; \ } +/*! + * \brief Define a vector structure with a read/write lock + * + * \param name Optional vector struct name. + * \param type Vector element type. + */ +#define AST_VECTOR_RW(name, type) \ + struct name { \ + type *elems; \ + size_t max; \ + size_t current; \ + ast_rwlock_t lock; \ + } + /*! * \brief Initialize a vector * @@ -71,6 +87,26 @@ (alloc_size == 0 || (vec)->elems != NULL) ? 0 : -1; \ }) +/*! + * \brief Initialize a vector with a read/write lock + * + * If \a size is 0, then no space will be allocated until the vector is + * appended to. + * + * \param vec Vector to initialize. + * \param size Initial size of the vector. + * + * \return 0 on success. + * \return Non-zero on failure. + */ +#define AST_VECTOR_RW_INIT(vec, size) ({ \ + int res = -1; \ + if (AST_VECTOR_INIT(vec, size) == 0) { \ + res = ast_rwlock_init(&(vec)->lock); \ + } \ + res; \ +}) + /*! * \brief Deallocates this vector. * @@ -86,6 +122,19 @@ (vec)->current = 0; \ } while (0) +/*! + * \brief Deallocates this locked vector + * + * If any code to free the elements of this vector need to be run, that should + * be done prior to this call. + * + * \param vec Vector to deallocate. + */ +#define AST_VECTOR_RW_FREE(vec) do { \ + AST_VECTOR_FREE(vec); \ + ast_rwlock_destroy(&(vec)->lock); \ +} while(0) + /*! * \brief Append an element to a vector, growing the vector if needed. * @@ -116,11 +165,11 @@ }) /*! - * \brief Insert an element at a specific position in a vector, growing the vector if needed. + * \brief Replace an element at a specific position in a vector, growing the vector if needed. * - * \param vec Vector to insert into. - * \param idx Position to insert at. - * \param elem Element to insert. + * \param vec Vector to replace into. + * \param idx Position to replace. + * \param elem Element to replace. * * \return 0 on success. * \return Non-zero on failure. @@ -131,7 +180,7 @@ * index means you can not use the UNORDERED assortment of macros. These macros alter the ordering * of the vector itself. */ -#define AST_VECTOR_INSERT(vec, idx, elem) ({ \ +#define AST_VECTOR_REPLACE(vec, idx, elem) ({ \ int res = 0; \ do { \ if (((idx) + 1) > (vec)->max) { \ @@ -157,6 +206,46 @@ res; \ }) +/*! + * \brief Insert an element at a specific position in a vector, growing the vector if needed. + * + * \param vec Vector to insert into. + * \param idx Position to insert at. + * \param elem Element to insert. + * + * \return 0 on success. + * \return Non-zero on failure. + * + * \warning This macro will shift existing elements right to make room for the new element. + * + * \warning Use of this macro with the expectation that the element will remain at the provided + * index means you can not use the UNORDERED assortment of macros. These macros alter the ordering + * of the vector itself. + */ +#define AST_VECTOR_INSERT_AT(vec, idx, elem) ({ \ + int res = 0; \ + size_t __move; \ + do { \ + if ((vec)->current + 1 > (vec)->max) { \ + size_t new_max = (vec)->max ? 2 * (vec)->max : 1; \ + typeof((vec)->elems) new_elems = ast_realloc( \ + (vec)->elems, new_max * sizeof(*new_elems)); \ + if (new_elems) { \ + (vec)->elems = new_elems; \ + (vec)->max = new_max; \ + } else { \ + res = -1; \ + break; \ + } \ + } \ + __move = ((vec)->current - 1) * sizeof(typeof((vec)->elems[0])); \ + memmove(&(vec)->elems[(idx) + 1], &(vec)->elems[(idx)], __move); \ + (vec)->elems[(idx)] = (elem); \ + (vec)->current++; \ + } while (0); \ + res; \ +}) + /*! * \brief Remove an element from a vector by index. * @@ -195,7 +284,6 @@ res; \ }) - /*! * \brief Remove an element from a vector that matches the given comparison * @@ -330,4 +418,118 @@ (vec)->elems[__idx]; \ }) +/*! + * \brief Get an element from a vector that matches the given comparison + * + * \param vec Vector to get from. + * \param value Value to pass into comparator. + * \param cmp Comparator function/macros (called as \c cmp(elem, value)) + * + * \return a pointer to the element that was found or NULL + */ +#define AST_VECTOR_GET_CMP(vec, value, cmp) ({ \ + void *res = NULL; \ + size_t idx; \ + typeof(value) __value = (value); \ + for (idx = 0; idx < (vec)->current; ++idx) { \ + if (cmp((vec)->elems[idx], __value)) { \ + res = &(vec)->elems[idx]; \ + break; \ + } \ + } \ + res; \ +}) + +/*! + * \brief Execute a callback on every element in a vector + * + * \param vec Vector to operate on. + * \param callback A callback that takes at least 1 argument (the element) + * plus number of optional arguments + * + * \return the number of elements visited before the end of the vector + * was reached or CMP_STOP was returned. + */ +#define AST_VECTOR_CALLBACK(vec, callback, ...) ({ \ + size_t idx; \ + for (idx = 0; idx < (vec)->current; idx++) { \ + int rc = callback((vec)->elems[idx], ##__VA_ARGS__); \ + if (rc == CMP_STOP) { \ + idx++; \ + break; \ + }\ + } \ + idx; \ +}) + +/*! + * \brief Obtain read lock on vector + * + * \param vec Vector to operate on. + * + * \return 0 if success + * \return Non-zero if error + */ +#define AST_VECTOR_RW_RDLOCK(vec) ast_rwlock_rdlock(&(vec)->lock) + +/*! + * \brief Obtain write lock on vector + * + * \param vec Vector to operate on. + * + * \return 0 if success + * \return Non-zero if error + */ +#define AST_VECTOR_RW_WRLOCK(vec) ast_rwlock_wrlock(&(vec)->lock) + +/*! + * \brief Unlock vector + * + * \param vec Vector to operate on. + * + * \return 0 if success + * \return Non-zero if error + */ +#define AST_VECTOR_RW_UNLOCK(vec) ast_rwlock_unlock(&(vec)->lock) + +/*! + * \brief Try to obtain read lock on vector failing immediately if unable + * + * \param vec Vector to operate on. + * + * \return 0 if success + * \return Non-zero if error + */ +#define AST_VECTOR_RW_RDLOCK_TRY(vec) ast_rwlock_tryrdlock(&(vec)->lock) + +/*! + * \brief Try to obtain write lock on vector failing immediately if unable + * + * \param vec Vector to operate on. + * + * \return 0 if success + * \return Non-zero if error + */ +#define AST_VECTOR_RW_WRLOCK_TRY(vec) ast_rwlock_trywrlock(&(vec)->lock) + +/*! + * \brief Try to obtain read lock on vector failing after timeout if unable + * + * \param vec Vector to operate on. + * + * \return 0 if success + * \return Non-zero if error + */ +#define AST_VECTOR_RW_RDLOCK_TIMED(vec, timespec) ast_rwlock_timedrdlock(&(vec)->lock, timespec) + +/*! + * \brief Try to obtain write lock on vector failing after timeout if unable + * + * \param vec Vector to operate on. + * + * \return 0 if success + * \return Non-zero if error + */ +#define AST_VECTOR_RW_WRLOCK_TIMED(vec, timespec) ast_rwlock_timedwrlock(&(vec)->lock, timespec) + #endif /* _ASTERISK_VECTOR_H */ diff --git a/main/format_cap.c b/main/format_cap.c index 7456a444b1629ffdfc73631cf5f8aad150aac9f6..52262579cf0fa98e3e194b56432dba7ec121a3ce 100644 --- a/main/format_cap.c +++ b/main/format_cap.c @@ -139,7 +139,7 @@ static inline int format_cap_framed_init(struct format_cap_framed *framed, struc framed->framing = framing; if (ast_format_get_codec_id(format) >= AST_VECTOR_SIZE(&cap->formats)) { - if (AST_VECTOR_INSERT(&cap->formats, ast_format_get_codec_id(format), format_cap_framed_list_empty)) { + if (AST_VECTOR_REPLACE(&cap->formats, ast_format_get_codec_id(format), format_cap_framed_list_empty)) { ao2_ref(framed, -1); return -1; } diff --git a/main/rtp_engine.c b/main/rtp_engine.c index aa91b7da42aba807afc6d02dd37f39470246db9f..88d2db3d0bd26e42040dd2385e2e552574ca15f2 100644 --- a/main/rtp_engine.c +++ b/main/rtp_engine.c @@ -625,7 +625,7 @@ void ast_rtp_codecs_payloads_copy(struct ast_rtp_codecs *src, struct ast_rtp_cod } ast_debug(2, "Copying payload %d (%p) from %p to %p\n", i, type, src, dest); ao2_bump(type); - AST_VECTOR_INSERT(&dest->payloads, i, type); + AST_VECTOR_REPLACE(&dest->payloads, i, type); if (instance && instance->engine && instance->engine->payload_set) { instance->engine->payload_set(instance, i, type->asterisk_format, type->format, type->rtp_code); @@ -662,7 +662,7 @@ void ast_rtp_codecs_payloads_set_m_type(struct ast_rtp_codecs *codecs, struct as new_type->format = ao2_bump(static_RTP_PT[payload].format); ast_debug(1, "Setting payload %d (%p) based on m type on %p\n", payload, new_type, codecs); - AST_VECTOR_INSERT(&codecs->payloads, payload, new_type); + AST_VECTOR_REPLACE(&codecs->payloads, payload, new_type); if (instance && instance->engine && instance->engine->payload_set) { instance->engine->payload_set(instance, payload, new_type->asterisk_format, new_type->format, new_type->rtp_code); @@ -727,7 +727,7 @@ int ast_rtp_codecs_payloads_set_rtpmap_type_rate(struct ast_rtp_codecs *codecs, } else { new_type->format = ao2_bump(t->payload_type.format); } - AST_VECTOR_INSERT(&codecs->payloads, pt, new_type); + AST_VECTOR_REPLACE(&codecs->payloads, pt, new_type); if (instance && instance->engine && instance->engine->payload_set) { instance->engine->payload_set(instance, pt, new_type->asterisk_format, new_type->format, new_type->rtp_code); @@ -760,7 +760,7 @@ void ast_rtp_codecs_payloads_unset(struct ast_rtp_codecs *codecs, struct ast_rtp if (payload < AST_VECTOR_SIZE(&codecs->payloads)) { type = AST_VECTOR_GET(&codecs->payloads, payload); ao2_cleanup(type); - AST_VECTOR_INSERT(&codecs->payloads, payload, NULL); + AST_VECTOR_REPLACE(&codecs->payloads, payload, NULL); } if (instance && instance->engine && instance->engine->payload_set) { diff --git a/tests/test_message.c b/tests/test_message.c index 7140b5f172ee191a8b087ed0a3466edfcdf27fb3..26cd90a4ddf4749d0d2f951483a9830b608a30f5 100644 --- a/tests/test_message.c +++ b/tests/test_message.c @@ -160,7 +160,7 @@ static int verify_user_event_fields(int user_event, const char *header, const ch bad_headers_head = AST_VECTOR_GET(&bad_headers, user_event); } ast_variable_list_append(&bad_headers_head, bad_header); - AST_VECTOR_INSERT(&bad_headers, user_event, bad_headers_head); + AST_VECTOR_REPLACE(&bad_headers, user_event, bad_headers_head); } regfree(®exbuf); return -1; @@ -492,28 +492,28 @@ AST_TEST_DEFINE(test_message_queue_dialplan_nominal) ast_variable_list_append(&expected_response, expected); expected = ast_variable_new("Value","^foo$", __FILE__); ast_variable_list_append(&expected_response, expected); - AST_VECTOR_INSERT(&expected_user_event_fields, 0, expected_response); + AST_VECTOR_REPLACE(&expected_user_event_fields, 0, expected_response); expected_response = NULL; expected = ast_variable_new("Verify", "^From$", __FILE__); ast_variable_list_append(&expected_response, expected); expected = ast_variable_new("Value","^bar$", __FILE__); ast_variable_list_append(&expected_response, expected); - AST_VECTOR_INSERT(&expected_user_event_fields, 1, expected_response); + AST_VECTOR_REPLACE(&expected_user_event_fields, 1, expected_response); expected_response = NULL; expected = ast_variable_new("Verify", "^Body$", __FILE__); ast_variable_list_append(&expected_response, expected); expected = ast_variable_new("Value", "^a body$", __FILE__); ast_variable_list_append(&expected_response, expected); - AST_VECTOR_INSERT(&expected_user_event_fields, 2, expected_response); + AST_VECTOR_REPLACE(&expected_user_event_fields, 2, expected_response); expected_response = NULL; expected = ast_variable_new("Verify", "^Custom$", __FILE__); ast_variable_list_append(&expected_response, expected); expected = ast_variable_new("Value", "^field$", __FILE__); ast_variable_list_append(&expected_response, expected); - AST_VECTOR_INSERT(&expected_user_event_fields, 3, expected_response); + AST_VECTOR_REPLACE(&expected_user_event_fields, 3, expected_response); ast_msg_set_to(msg, "foo"); ast_msg_set_from(msg, "bar"); @@ -609,21 +609,21 @@ AST_TEST_DEFINE(test_message_queue_both_nominal) ast_variable_list_append(&expected_response, expected); expected = ast_variable_new("Value","^foo$", __FILE__); ast_variable_list_append(&expected_response, expected); - AST_VECTOR_INSERT(&expected_user_event_fields, 0, expected_response); + AST_VECTOR_REPLACE(&expected_user_event_fields, 0, expected_response); expected_response = NULL; expected = ast_variable_new("Verify", "^From$", __FILE__); ast_variable_list_append(&expected_response, expected); expected = ast_variable_new("Value","^bar$", __FILE__); ast_variable_list_append(&expected_response, expected); - AST_VECTOR_INSERT(&expected_user_event_fields, 1, expected_response); + AST_VECTOR_REPLACE(&expected_user_event_fields, 1, expected_response); expected_response = NULL; expected = ast_variable_new("Verify", "^Body$", __FILE__); ast_variable_list_append(&expected_response, expected); expected = ast_variable_new("Value", "^a body$", __FILE__); ast_variable_list_append(&expected_response, expected); - AST_VECTOR_INSERT(&expected_user_event_fields, 2, expected_response); + AST_VECTOR_REPLACE(&expected_user_event_fields, 2, expected_response); ast_msg_set_to(msg, "foo"); ast_msg_set_from(msg, "bar"); diff --git a/tests/test_vector.c b/tests/test_vector.c new file mode 100644 index 0000000000000000000000000000000000000000..eae188143233b0fb05d2f6ad4771b32df8728960 --- /dev/null +++ b/tests/test_vector.c @@ -0,0 +1,428 @@ +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (C) 2015, Fairview 5 Engineering, LLC + * + * George Joseph <george.joseph@fairview5.com> + * + * See http://www.asterisk.org for more information about + * the Asterisk project. Please do not directly contact + * any of the maintainers of this project for assistance; + * the project provides a web site, mailing lists and IRC + * channels for your use. + * + * This program is free software, distributed under the terms of + * the GNU General Public License Version 2. See the LICENSE file + * at the top of the source tree. + */ + +/*! + * \file + * \brief Vector tests + * + * \author George Joseph <george.joseph@fairview5.com> + * + * This module will run some vector tests. + * + * \ingroup tests + */ + +/*** MODULEINFO + <depend>TEST_FRAMEWORK</depend> + <support_level>core</support_level> + ***/ + +#include "asterisk.h" + +ASTERISK_REGISTER_FILE() + +#include "asterisk/test.h" +#include "asterisk/utils.h" +#include "asterisk/strings.h" +#include "asterisk/module.h" +#include "asterisk/vector.h" + +#define test_validate_cleanup(condition) ({ \ + if (!(condition)) { \ + ast_test_status_update((test), "%s: %s\n", "Condition failed", #condition); \ + rc = AST_TEST_FAIL; \ + goto cleanup; \ + } \ +}) + +static int cleanup_count; + +static void cleanup(char *element) +{ + cleanup_count++; +} + +AST_TEST_DEFINE(basic_ops) +{ + AST_VECTOR(test_struct, char *) sv1; + int rc = AST_TEST_PASS; + + char *AAA = "AAA"; + char *BBB = "BBB"; + char *CCC = "CCC"; + char *ZZZ = "ZZZ"; + + switch (cmd) { + case TEST_INIT: + info->name = "basic"; + info->category = "/main/vector/"; + info->summary = "Test vector basic ops"; + info->description = "Test vector basic ops"; + return AST_TEST_NOT_RUN; + case TEST_EXECUTE: + break; + } + + ast_test_validate(test, AST_VECTOR_INIT(&sv1, 3) == 0); + test_validate_cleanup(sv1.max == 3); + test_validate_cleanup(AST_VECTOR_SIZE(&sv1) == 0); + + test_validate_cleanup(AST_VECTOR_APPEND(&sv1, AAA) == 0); + test_validate_cleanup(AST_VECTOR_APPEND(&sv1, BBB) == 0); + test_validate_cleanup(AST_VECTOR_APPEND(&sv1, CCC) == 0); + test_validate_cleanup(AST_VECTOR_SIZE(&sv1) == 3); + test_validate_cleanup(sv1.max == 3); + test_validate_cleanup(AST_VECTOR_GET(&sv1, 0) == AAA); + test_validate_cleanup(AST_VECTOR_GET(&sv1, 1) == BBB); + test_validate_cleanup(AST_VECTOR_GET(&sv1, 2) == CCC); + + test_validate_cleanup(AST_VECTOR_INSERT_AT(&sv1, 1, ZZZ) == 0); + test_validate_cleanup(sv1.max >= 4); + test_validate_cleanup(AST_VECTOR_SIZE(&sv1) == 4); + test_validate_cleanup(AST_VECTOR_GET(&sv1, 0) == AAA); + test_validate_cleanup(AST_VECTOR_GET(&sv1, 1) == ZZZ); + test_validate_cleanup(AST_VECTOR_GET(&sv1, 2) == BBB); + test_validate_cleanup(AST_VECTOR_GET(&sv1, 3) == CCC); + + test_validate_cleanup(*(char **)AST_VECTOR_GET_CMP(&sv1, "AAA", 0 == strcmp) == AAA); + test_validate_cleanup(*(char **)AST_VECTOR_GET_CMP(&sv1, "ZZZ", 0 == strcmp) == ZZZ); + + AST_VECTOR_FREE(&sv1); + ast_test_validate(test, sv1.elems == NULL); + ast_test_validate(test, sv1.current == 0); + ast_test_validate(test, sv1.max == 0); + + ast_test_validate(test, AST_VECTOR_INIT(&sv1, 0) == 0); + test_validate_cleanup(sv1.max == 0); + test_validate_cleanup(AST_VECTOR_SIZE(&sv1) == 0); + + test_validate_cleanup(AST_VECTOR_APPEND(&sv1, AAA) == 0); + test_validate_cleanup(AST_VECTOR_APPEND(&sv1, BBB) == 0); + test_validate_cleanup(AST_VECTOR_APPEND(&sv1, CCC) == 0); + test_validate_cleanup(sv1.max >= 3); + test_validate_cleanup(AST_VECTOR_SIZE(&sv1) == 3); + + test_validate_cleanup(AST_VECTOR_GET(&sv1, 0) == AAA); + test_validate_cleanup(AST_VECTOR_GET(&sv1, 1) == BBB); + test_validate_cleanup(AST_VECTOR_GET(&sv1, 2) == CCC); + + /* Overwrite index 1 */ + test_validate_cleanup(AST_VECTOR_REPLACE(&sv1, 1, ZZZ) == 0); + test_validate_cleanup(AST_VECTOR_SIZE(&sv1) == 3); + test_validate_cleanup(AST_VECTOR_GET(&sv1, 0) == AAA); + test_validate_cleanup(AST_VECTOR_GET(&sv1, 1) == ZZZ); + test_validate_cleanup(AST_VECTOR_GET(&sv1, 2) == CCC); + + /* Remove index 0 and bring the last entry into it's empty slot */ + test_validate_cleanup(AST_VECTOR_REMOVE_UNORDERED(&sv1, 0) == AAA); + test_validate_cleanup(AST_VECTOR_SIZE(&sv1) == 2); + test_validate_cleanup(AST_VECTOR_GET(&sv1, 0) == CCC); + test_validate_cleanup(AST_VECTOR_GET(&sv1, 1) == ZZZ); + + /* Replace 0 and 2 leaving 1 alone */ + test_validate_cleanup(AST_VECTOR_REPLACE(&sv1, 0, AAA) == 0); + test_validate_cleanup(AST_VECTOR_REPLACE(&sv1, 2, CCC) == 0); + test_validate_cleanup(AST_VECTOR_SIZE(&sv1) == 3); + test_validate_cleanup(AST_VECTOR_GET(&sv1, 0) == AAA); + test_validate_cleanup(AST_VECTOR_GET(&sv1, 1) == ZZZ); + test_validate_cleanup(AST_VECTOR_GET(&sv1, 2) == CCC); + + /* Remove 1 and compact preserving order */ + test_validate_cleanup(AST_VECTOR_REMOVE_ORDERED(&sv1, 1) == ZZZ); + test_validate_cleanup(AST_VECTOR_SIZE(&sv1) == 2); + test_validate_cleanup(AST_VECTOR_GET(&sv1, 0) == AAA); + test_validate_cleanup(AST_VECTOR_GET(&sv1, 1) == CCC); + + /* Equivalent of APPEND */ + test_validate_cleanup(AST_VECTOR_REPLACE(&sv1, 2, ZZZ) == 0); + test_validate_cleanup(AST_VECTOR_SIZE(&sv1) == 3); + + /* This should fail because comparison is by pointer */ + test_validate_cleanup(AST_VECTOR_REMOVE_ELEM_ORDERED(&sv1, "ZZZ", cleanup) != 0); + + /* This should work because we passing in the specific object to be removed */ + cleanup_count = 0; + test_validate_cleanup(AST_VECTOR_REMOVE_ELEM_ORDERED(&sv1, ZZZ, cleanup) == 0); + test_validate_cleanup(AST_VECTOR_SIZE(&sv1) == 2); + test_validate_cleanup(AST_VECTOR_GET(&sv1, 0) == AAA); + test_validate_cleanup(AST_VECTOR_GET(&sv1, 1) == CCC); + test_validate_cleanup(cleanup_count == 1); + + /* If we want a comparison by value, we need to pass in a comparison + * function. The comparison looks weird but that's what it takes. + */ + cleanup_count = 0; + test_validate_cleanup(AST_VECTOR_REMOVE_CMP_ORDERED(&sv1, "AAA", 0 == strcmp, cleanup) == 0); + test_validate_cleanup(AST_VECTOR_SIZE(&sv1) == 1); + test_validate_cleanup(AST_VECTOR_GET(&sv1, 0) == CCC); + test_validate_cleanup(cleanup_count == 1); + + /* This element is gone so we shouldn't be able to find it or delete it again. */ + test_validate_cleanup(AST_VECTOR_GET_CMP(&sv1, "AAA", 0 == strcmp) == NULL); + test_validate_cleanup(AST_VECTOR_REMOVE_CMP_ORDERED(&sv1, "AAA", 0 == strcmp, cleanup) != 0); + + /* CCC should still be there though */ + test_validate_cleanup(*(char **)AST_VECTOR_GET_CMP(&sv1, "CCC", 0 == strcmp) == CCC); + +cleanup: + AST_VECTOR_FREE(&sv1); + return rc; +} + +static void cleanup_int(int element) +{ + cleanup_count++; +} + +AST_TEST_DEFINE(basic_ops_integer) +{ + AST_VECTOR(test_struct, int) sv1; + int rc = AST_TEST_PASS; + + int AAA = 1; + int BBB = 2; + int CCC = 3; + int ZZZ = 26; + + switch (cmd) { + case TEST_INIT: + info->name = "basic integer"; + info->category = "/main/vector/"; + info->summary = "Test integer vector basic ops"; + info->description = "Test integer vector basic ops"; + return AST_TEST_NOT_RUN; + case TEST_EXECUTE: + break; + } + + ast_test_validate(test, AST_VECTOR_INIT(&sv1, 3) == 0); + test_validate_cleanup(sv1.max == 3); + test_validate_cleanup(AST_VECTOR_SIZE(&sv1) == 0); + + test_validate_cleanup(AST_VECTOR_APPEND(&sv1, AAA) == 0); + test_validate_cleanup(AST_VECTOR_APPEND(&sv1, BBB) == 0); + test_validate_cleanup(AST_VECTOR_APPEND(&sv1, CCC) == 0); + test_validate_cleanup(AST_VECTOR_SIZE(&sv1) == 3); + test_validate_cleanup(sv1.max == 3); + test_validate_cleanup(AST_VECTOR_GET(&sv1, 0) == AAA); + test_validate_cleanup(AST_VECTOR_GET(&sv1, 1) == BBB); + test_validate_cleanup(AST_VECTOR_GET(&sv1, 2) == CCC); + + test_validate_cleanup(AST_VECTOR_INSERT_AT(&sv1, 1, ZZZ) == 0); + test_validate_cleanup(sv1.max >= 4); + test_validate_cleanup(AST_VECTOR_SIZE(&sv1) == 4); + test_validate_cleanup(AST_VECTOR_GET(&sv1, 0) == AAA); + test_validate_cleanup(AST_VECTOR_GET(&sv1, 1) == ZZZ); + test_validate_cleanup(AST_VECTOR_GET(&sv1, 2) == BBB); + test_validate_cleanup(AST_VECTOR_GET(&sv1, 3) == CCC); + + test_validate_cleanup(*(int *)AST_VECTOR_GET_CMP(&sv1, AAA, AST_VECTOR_ELEM_DEFAULT_CMP) == AAA); + test_validate_cleanup(*(int *)AST_VECTOR_GET_CMP(&sv1, ZZZ, AST_VECTOR_ELEM_DEFAULT_CMP) == ZZZ); + + AST_VECTOR_FREE(&sv1); + ast_test_validate(test, sv1.elems == NULL); + ast_test_validate(test, sv1.current == 0); + ast_test_validate(test, sv1.max == 0); + + ast_test_validate(test, AST_VECTOR_INIT(&sv1, 0) == 0); + test_validate_cleanup(sv1.max == 0); + test_validate_cleanup(AST_VECTOR_SIZE(&sv1) == 0); + + test_validate_cleanup(AST_VECTOR_APPEND(&sv1, AAA) == 0); + test_validate_cleanup(AST_VECTOR_APPEND(&sv1, BBB) == 0); + test_validate_cleanup(AST_VECTOR_APPEND(&sv1, CCC) == 0); + test_validate_cleanup(sv1.max >= 3); + test_validate_cleanup(AST_VECTOR_SIZE(&sv1) == 3); + + test_validate_cleanup(AST_VECTOR_GET(&sv1, 0) == AAA); + test_validate_cleanup(AST_VECTOR_GET(&sv1, 1) == BBB); + test_validate_cleanup(AST_VECTOR_GET(&sv1, 2) == CCC); + + /* Overwrite index 1 */ + test_validate_cleanup(AST_VECTOR_REPLACE(&sv1, 1, ZZZ) == 0); + test_validate_cleanup(AST_VECTOR_SIZE(&sv1) == 3); + test_validate_cleanup(AST_VECTOR_GET(&sv1, 0) == AAA); + test_validate_cleanup(AST_VECTOR_GET(&sv1, 1) == ZZZ); + test_validate_cleanup(AST_VECTOR_GET(&sv1, 2) == CCC); + + /* Remove index 0 and bring the last entry into it's empty slot */ + test_validate_cleanup(AST_VECTOR_REMOVE_UNORDERED(&sv1, 0) == 1); + test_validate_cleanup(AST_VECTOR_SIZE(&sv1) == 2); + test_validate_cleanup(AST_VECTOR_GET(&sv1, 0) == CCC); + test_validate_cleanup(AST_VECTOR_GET(&sv1, 1) == ZZZ); + + /* Replace 0 and 2 leaving 1 alone */ + test_validate_cleanup(AST_VECTOR_REPLACE(&sv1, 0, AAA) == 0); + test_validate_cleanup(AST_VECTOR_REPLACE(&sv1, 2, CCC) == 0); + test_validate_cleanup(AST_VECTOR_SIZE(&sv1) == 3); + test_validate_cleanup(AST_VECTOR_GET(&sv1, 0) == AAA); + test_validate_cleanup(AST_VECTOR_GET(&sv1, 1) == ZZZ); + test_validate_cleanup(AST_VECTOR_GET(&sv1, 2) == CCC); + + /* Remove 1 and compact preserving order */ + test_validate_cleanup(AST_VECTOR_REMOVE_ORDERED(&sv1, 1) == ZZZ); + test_validate_cleanup(AST_VECTOR_SIZE(&sv1) == 2); + test_validate_cleanup(AST_VECTOR_GET(&sv1, 0) == AAA); + test_validate_cleanup(AST_VECTOR_GET(&sv1, 1) == CCC); + + /* Equivalent of APPEND */ + test_validate_cleanup(AST_VECTOR_REPLACE(&sv1, 2, ZZZ) == 0); + test_validate_cleanup(AST_VECTOR_SIZE(&sv1) == 3); + + /* This should work because we passing in the specific object to be removed */ + cleanup_count = 0; + test_validate_cleanup(AST_VECTOR_REMOVE_ELEM_ORDERED(&sv1, ZZZ, cleanup_int) == 0); + test_validate_cleanup(AST_VECTOR_SIZE(&sv1) == 2); + test_validate_cleanup(AST_VECTOR_GET(&sv1, 0) == AAA); + test_validate_cleanup(AST_VECTOR_GET(&sv1, 1) == CCC); + test_validate_cleanup(cleanup_count == 1); + + /* If we want a comparison by value, we need to pass in a comparison + * function. + */ + cleanup_count = 0; + test_validate_cleanup(AST_VECTOR_REMOVE_CMP_ORDERED(&sv1, AAA, AST_VECTOR_ELEM_DEFAULT_CMP, cleanup_int) == 0); + test_validate_cleanup(AST_VECTOR_SIZE(&sv1) == 1); + test_validate_cleanup(AST_VECTOR_GET(&sv1, 0) == CCC); + test_validate_cleanup(cleanup_count == 1); + + /* This element is gone so we shouldn't be able to find it or delete it again. */ + test_validate_cleanup(AST_VECTOR_GET_CMP(&sv1, AAA, AST_VECTOR_ELEM_DEFAULT_CMP) == NULL); + test_validate_cleanup(AST_VECTOR_REMOVE_CMP_ORDERED(&sv1, AAA, AST_VECTOR_ELEM_DEFAULT_CMP, cleanup_int) != 0); + + /* CCC should still be there though */ + test_validate_cleanup(*(int *)AST_VECTOR_GET_CMP(&sv1, CCC, AST_VECTOR_ELEM_DEFAULT_CMP) == CCC); + +cleanup: + AST_VECTOR_FREE(&sv1); + return rc; +} + + +static int cb(void *obj, void *arg, void *data) +{ + return strcmp(arg, "ARG") == 0 ? 0 : CMP_STOP; +} + +static int cb_first(void *obj, void *arg, void *data) +{ + return data == arg ? CMP_STOP : 0; +} + +AST_TEST_DEFINE(callbacks) +{ + AST_VECTOR(, char *) sv1; + int rc = AST_TEST_PASS; + + switch (cmd) { + case TEST_INIT: + info->name = "callbacks"; + info->category = "/main/vector/"; + info->summary = "Test vector callback ops"; + info->description = "Test vector callback ops"; + return AST_TEST_NOT_RUN; + case TEST_EXECUTE: + break; + } + + AST_VECTOR_INIT(&sv1, 32); + + AST_VECTOR_APPEND(&sv1, "AAA"); + AST_VECTOR_APPEND(&sv1, "BBB"); + AST_VECTOR_APPEND(&sv1, "CCC"); + + test_validate_cleanup(AST_VECTOR_CALLBACK(&sv1, cb, "ARG", test) == 3); + + test_validate_cleanup(AST_VECTOR_CALLBACK(&sv1, cb_first, test, test) == 1); + +cleanup: + AST_VECTOR_FREE(&sv1); + + return rc; +} + +AST_TEST_DEFINE(locks) +{ + AST_VECTOR_RW(, char *) sv1; + int rc = AST_TEST_PASS; + struct timespec ts; + + switch (cmd) { + case TEST_INIT: + info->name = "locks"; + info->category = "/main/vector/"; + info->summary = "Test vector locking ops"; + info->description = "Test vector locking ops"; + return AST_TEST_NOT_RUN; + case TEST_EXECUTE: + break; + } + + /* We're not actually checking that locking works, + * just that the macro expansions work + */ + + AST_VECTOR_RW_INIT(&sv1, 0); + + test_validate_cleanup(AST_VECTOR_RW_RDLOCK(&sv1) == 0); + test_validate_cleanup(AST_VECTOR_RW_UNLOCK(&sv1) == 0); + test_validate_cleanup(AST_VECTOR_RW_WRLOCK(&sv1) == 0); + test_validate_cleanup(AST_VECTOR_RW_UNLOCK(&sv1) == 0); + + test_validate_cleanup(AST_VECTOR_RW_RDLOCK_TRY(&sv1) == 0); + test_validate_cleanup(AST_VECTOR_RW_WRLOCK_TRY(&sv1) != 0); + test_validate_cleanup(AST_VECTOR_RW_UNLOCK(&sv1) == 0); + test_validate_cleanup(AST_VECTOR_RW_WRLOCK_TRY(&sv1) == 0); + test_validate_cleanup(AST_VECTOR_RW_UNLOCK(&sv1) == 0); + + ts.tv_nsec = 0; + ts.tv_sec = 2; + + test_validate_cleanup(AST_VECTOR_RW_RDLOCK_TIMED(&sv1, &ts) == 0); + test_validate_cleanup(AST_VECTOR_RW_WRLOCK_TIMED(&sv1, &ts) != 0); + test_validate_cleanup(AST_VECTOR_RW_UNLOCK(&sv1) == 0); + test_validate_cleanup(AST_VECTOR_RW_WRLOCK_TIMED(&sv1, &ts) == 0); + test_validate_cleanup(AST_VECTOR_RW_UNLOCK(&sv1) == 0); + +cleanup: + AST_VECTOR_RW_FREE(&sv1); + + return rc; +} + +static int unload_module(void) +{ + AST_TEST_UNREGISTER(locks); + AST_TEST_UNREGISTER(callbacks); + AST_TEST_UNREGISTER(basic_ops_integer); + AST_TEST_UNREGISTER(basic_ops); + + return 0; +} + +static int load_module(void) +{ + AST_TEST_REGISTER(locks); + AST_TEST_REGISTER(callbacks); + AST_TEST_REGISTER(basic_ops_integer); + AST_TEST_REGISTER(basic_ops); + + return AST_MODULE_LOAD_SUCCESS; +} + +AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Vector test module");