diff --git a/tests/test_scoped_lock.c b/tests/test_scoped_lock.c new file mode 100644 index 0000000000..2fcaae2d8e --- /dev/null +++ b/tests/test_scoped_lock.c @@ -0,0 +1,280 @@ +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (C) 2012, Digium, Inc. + * + * Mark Michelson + * + * 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 SCOPED_LOCK unit tests + * + * \author Mark Michelson + * + */ + +/*** MODULEINFO + TEST_FRAMEWORK + core + ***/ + +#include "asterisk.h" + +#include "asterisk/test.h" +#include "asterisk/utils.h" +#include "asterisk/module.h" +#include "asterisk/astobj2.h" + +static int indicator; +static struct ast_test *current_test; +AST_MUTEX_DEFINE_STATIC(the_lock); + +static void lock_it(ast_mutex_t *lock) +{ + indicator = 1; + ast_mutex_lock(lock); +} + +static void unlock_it(ast_mutex_t *lock) +{ + indicator = 0; + ast_mutex_unlock(lock); +} + +AST_TEST_DEFINE(lock_test) +{ + enum ast_test_result_state res = AST_TEST_PASS; + int i; + + switch(cmd) { + case TEST_INIT: + info->name = "lock_test"; + info->category = "/main/lock/"; + info->summary = "SCOPED_LOCK test"; + info->description = + "Tests that scoped locks are scoped as they are expected to be"; + return AST_TEST_NOT_RUN; + case TEST_EXECUTE: + break; + } + + current_test = test; + indicator = 0; + { + SCOPED_LOCK(lock, &the_lock, lock_it, unlock_it); + if (indicator != 1) { + ast_log(LOG_ERROR, "The lock was not acquired via RAII"); + res = AST_TEST_FAIL; + } + } + if (indicator != 0) { + ast_log(LOG_ERROR, "The lock was not released when the variable went out of scope"); + res = AST_TEST_FAIL; + } + + for (i = 0; i < 10; ++i) { + SCOPED_LOCK(lock, &the_lock, lock_it, unlock_it); + if (indicator != 1) { + ast_log(LOG_ERROR, "The lock was not acquired via RAII"); + res = AST_TEST_FAIL; + } + } + + if (indicator != 0) { + ast_log(LOG_ERROR, "The lock was not released when the variable went out of scope"); + res = AST_TEST_FAIL; + } + + return res; +} + +struct test_struct +{ + int locked; + int reffed; +}; + +/*! + * \brief lock callback function + * + * Locks the object passed in. Only sets the locked + * flag if the object is reffed. This allows us to check + * that locking is always occurring after reffing. + */ +static void test_lock(struct test_struct *test) +{ + ast_test_status_update(current_test, "Lock is occurring\n"); + ao2_lock(test); + if (test->reffed) { + test->locked = 1; + } +} + +/*! + * \brief unlock callback function + * + * Unlocks the object passed in. Only clears the locked + * flag if the object is still reffed. This allows us to + * ensure that unlocking is always occurring before unreffing. + */ +static void test_unlock(struct test_struct *test) +{ + ast_test_status_update(current_test, "Unlock is occurring\n"); + ao2_unlock(test); + if (test->reffed) { + test->locked = 0; + } +} + +/*! + * \brief ref callback function + * + * Refs the object passed in. Only sets the reffed flag if + * the object is not locked. This allows us to ensure that + * reffing always occurs before locking. + */ +static struct test_struct *test_ref(struct test_struct *test) +{ + ast_test_status_update(current_test, "Ref is occurring\n"); + ao2_ref(test, +1); + if (!test->locked) { + test->reffed = 1; + } + return test; +} + +/*! + * \brief unref callback function + * + * Unrefs the object passed in. Only sets the unreffed flag if + * the object is not locked. This allows us to ensure that + * unreffing always occurs after unlocking. + */ +static void test_unref(struct test_struct *test) +{ + ast_test_status_update(current_test, "Unref is occurring\n"); + ao2_ref(test, -1); + if (!test->locked) { + test->reffed = 0; + } +} + +/*! + * \brief wrapper for ao2_iterator_next + * + * Grabs the next item in the container and replaces the ref acquired + * from ao2_iterator_next() with a call to test_ref(). + */ +static struct test_struct *test_iterator_next(struct ao2_iterator *iter) +{ + struct test_struct *test = ao2_iterator_next(iter); + + if (!test) { + return NULL; + } + + /* Remove ref from ao2_iterator_next() and replace it with + * a test_ref() call. The order here is safe since we can guarantee + * the container still has a ref to the test structure. + */ + ao2_ref(test, -1); + test_ref(test); + + return test; +} + +AST_TEST_DEFINE(cleanup_order) +{ + enum ast_test_result_state res = AST_TEST_PASS; + struct ao2_iterator iter; + struct test_struct *object_iter; + RAII_VAR(struct ao2_container*, container, ao2_container_alloc(13, NULL, NULL), ao2_cleanup); + RAII_VAR(struct test_struct *, object, ao2_alloc(sizeof(*object), NULL), ao2_cleanup); + + switch(cmd) { + case TEST_INIT: + info->name = "cleanup_order_test"; + info->category = "/main/lock/"; + info->summary = "cleanup order test"; + info->description = + "Tests that variables with cleanup attributes are cleaned up\n" + "in the reverse order they are declared."; + return AST_TEST_NOT_RUN; + case TEST_EXECUTE: + break; + } + current_test = test; + + if (!object || !container) { + /* Allocation failure. We can't even pretend to do this test properly */ + return AST_TEST_FAIL; + } + + { + /* Purpose of this block is to make sure that the cleanup operations + * run in the reverse order that they were created here. + */ + RAII_VAR(struct test_struct *, object2, test_ref(object), test_unref); + SCOPED_LOCK(lock, object, test_lock, test_unlock); + if (!object->reffed || !object->locked) { + ast_log(LOG_ERROR, "Test failed due to out of order initializations"); + res = AST_TEST_FAIL; + } + } + + if (object->reffed || object->locked) { + ast_log(LOG_ERROR, "Test failed due to out of order cleanups\n"); + res = AST_TEST_FAIL; + } + + /* Now link the object into the container for a little experiment ... */ + ao2_link(container, object); + + /* This loop is to ensure that unrefs in a for loop occur after the cleanup + * operations of items inside the loop. If we hope to be able to mix scoped locks + * and ao2 refs, this is the way to go about it. + */ + for (iter = ao2_iterator_init(container, 0); + (object_iter = test_iterator_next(&iter)); + test_unref(object_iter)) { + SCOPED_LOCK(lock, object_iter, test_lock, test_unlock); + if (!object->reffed || !object->locked) { + ast_log(LOG_ERROR, "Test failed due to out of order initializations"); + res = AST_TEST_FAIL; + } + } + + if (object->reffed || object->locked) { + ast_log(LOG_ERROR, "Test failed due to out of order cleanups\n"); + res = AST_TEST_FAIL; + } + + return res; +} + +static int unload_module(void) +{ + AST_TEST_UNREGISTER(lock_test); + AST_TEST_UNREGISTER(cleanup_order); + return 0; +} + +static int load_module(void) +{ + AST_TEST_REGISTER(lock_test); + AST_TEST_REGISTER(cleanup_order); + return AST_MODULE_LOAD_SUCCESS; +} + +AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "SCOPED_LOCK test module");