summaryrefslogtreecommitdiff
path: root/tests/test_sorcery.c
diff options
context:
space:
mode:
authorJoshua Colp <jcolp@digium.com>2013-05-06 13:04:08 +0000
committerJoshua Colp <jcolp@digium.com>2013-05-06 13:04:08 +0000
commit40074542bf517a751061d7cf6f9ff83431e35ff8 (patch)
tree8e7d52a4c65363e0d4cbd8effd83995ac3bfa892 /tests/test_sorcery.c
parent6e2fe0c9ab1ec9cdd0d45e48fd03217d06cc17ae (diff)
Add support for observers and JSON objectset creation to sorcery.
This change adds the ability for modules to add themselves as observers to sorcery object types. Observers can be notified when objects are created, updated, or deleted as well as when the object type is loaded or reloaded. Observer notifications are done using a thread pool in a serialized fashion so the caller of the sorcery API calls is minimally impacted. This also adds the ability to create JSON changesets of a sorcery object. Tests are also present to confirm all of the above functionality. Review: https://reviewboard.asterisk.org/r/2477/ git-svn-id: https://origsvn.digium.com/svn/asterisk/trunk@387662 65c4cc65-6c06-0410-ace0-fbb531ad65f3
Diffstat (limited to 'tests/test_sorcery.c')
-rw-r--r--tests/test_sorcery.c267
1 files changed, 267 insertions, 0 deletions
diff --git a/tests/test_sorcery.c b/tests/test_sorcery.c
index 6cfd2de4f..e1d4b7fce 100644
--- a/tests/test_sorcery.c
+++ b/tests/test_sorcery.c
@@ -37,6 +37,7 @@ ASTERISK_FILE_VERSION(__FILE__, "")
#include "asterisk/module.h"
#include "asterisk/sorcery.h"
#include "asterisk/logger.h"
+#include "asterisk/json.h"
/*! \brief Dummy sorcery object */
struct test_sorcery_object {
@@ -126,6 +127,27 @@ struct sorcery_test_caching {
struct test_sorcery_object object;
};
+/*! \brief Test structure for observer */
+struct sorcery_test_observer {
+ /*! \brief Lock for notification */
+ ast_mutex_t lock;
+
+ /*! \brief Condition for notification */
+ ast_cond_t cond;
+
+ /*! \brief Pointer to the created object */
+ const void *created;
+
+ /*! \brief Pointer to the update object */
+ const void *updated;
+
+ /*! \brief Pointer to the deleted object */
+ const void *deleted;
+
+ /*! \brief Whether the type has been loaded */
+ unsigned int loaded:1;
+};
+
/*! \brief Global scope apply handler integer to make sure it executed */
static int apply_handler_called;
@@ -139,6 +161,9 @@ static int test_apply_handler(const struct ast_sorcery *sorcery, void *obj)
/*! \brief Global scope caching structure for testing */
static struct sorcery_test_caching cache = { 0, };
+/*! \brief Global scope observer structure for testing */
+static struct sorcery_test_observer observer;
+
static int sorcery_test_create(const struct ast_sorcery *sorcery, void *data, void *object)
{
cache.created = 1;
@@ -173,6 +198,42 @@ static struct ast_sorcery_wizard test_wizard = {
.delete = sorcery_test_delete,
};
+static void sorcery_observer_created(const void *object)
+{
+ SCOPED_MUTEX(lock, &observer.lock);
+ observer.created = object;
+ ast_cond_signal(&observer.cond);
+}
+
+static void sorcery_observer_updated(const void *object)
+{
+ SCOPED_MUTEX(lock, &observer.lock);
+ observer.updated = object;
+ ast_cond_signal(&observer.cond);
+}
+
+static void sorcery_observer_deleted(const void *object)
+{
+ SCOPED_MUTEX(lock, &observer.lock);
+ observer.deleted = object;
+ ast_cond_signal(&observer.cond);
+}
+
+static void sorcery_observer_loaded(const char *object_type)
+{
+ SCOPED_MUTEX(lock, &observer.lock);
+ observer.loaded = 1;
+ ast_cond_signal(&observer.cond);
+}
+
+/*! \brief Test sorcery observer implementation */
+static struct ast_sorcery_observer test_observer = {
+ .created = sorcery_observer_created,
+ .updated = sorcery_observer_updated,
+ .deleted = sorcery_observer_deleted,
+ .loaded = sorcery_observer_loaded,
+};
+
static struct ast_sorcery *alloc_and_initialize_sorcery(void)
{
struct ast_sorcery *sorcery;
@@ -869,6 +930,63 @@ AST_TEST_DEFINE(objectset_create)
return res;
}
+AST_TEST_DEFINE(objectset_json_create)
+{
+ int res = AST_TEST_PASS;
+ RAII_VAR(struct ast_sorcery *, sorcery, NULL, ast_sorcery_unref);
+ RAII_VAR(struct test_sorcery_object *, obj, NULL, ao2_cleanup);
+ RAII_VAR(struct ast_json *, objset, NULL, ast_json_unref);
+ struct ast_json_iter *field;
+
+ switch (cmd) {
+ case TEST_INIT:
+ info->name = "objectset_json_create";
+ info->category = "/main/sorcery/";
+ info->summary = "sorcery json object set creation unit test";
+ info->description =
+ "Test object set creation (for JSON format) in sorcery";
+ return AST_TEST_NOT_RUN;
+ case TEST_EXECUTE:
+ break;
+ }
+
+ if (!(sorcery = alloc_and_initialize_sorcery())) {
+ ast_test_status_update(test, "Failed to open sorcery structure\n");
+ return AST_TEST_FAIL;
+ }
+
+ if (!(obj = ast_sorcery_alloc(sorcery, "test", "blah"))) {
+ ast_test_status_update(test, "Failed to allocate a known object type\n");
+ return AST_TEST_FAIL;
+ }
+
+ if (!(objset = ast_sorcery_objectset_json_create(sorcery, obj))) {
+ ast_test_status_update(test, "Failed to create an object set for a known sane object\n");
+ return AST_TEST_FAIL;
+ }
+
+ for (field = ast_json_object_iter(objset); field; field = ast_json_object_iter_next(objset, field)) {
+ struct ast_json *value = ast_json_object_iter_value(field);
+
+ if (!strcmp(ast_json_object_iter_key(field), "bob")) {
+ if (strcmp(ast_json_string_get(value), "5")) {
+ ast_test_status_update(test, "Object set failed to create proper value for 'bob'\n");
+ res = AST_TEST_FAIL;
+ }
+ } else if (!strcmp(ast_json_object_iter_key(field), "joe")) {
+ if (strcmp(ast_json_string_get(value), "10")) {
+ ast_test_status_update(test, "Object set failed to create proper value for 'joe'\n");
+ res = AST_TEST_FAIL;
+ }
+ } else {
+ ast_test_status_update(test, "Object set created field '%s' which is unknown\n", ast_json_object_iter_key(field));
+ res = AST_TEST_FAIL;
+ }
+ }
+
+ return res;
+}
+
AST_TEST_DEFINE(objectset_create_regex)
{
int res = AST_TEST_PASS;
@@ -1950,6 +2068,151 @@ end:
return res;
}
+AST_TEST_DEFINE(object_type_observer)
+{
+ RAII_VAR(struct ast_sorcery *, sorcery, NULL, ast_sorcery_unref);
+ RAII_VAR(struct test_sorcery_object *, obj, NULL, ao2_cleanup);
+ int res = AST_TEST_FAIL;
+
+ switch (cmd) {
+ case TEST_INIT:
+ info->name = "object_type_observer";
+ info->category = "/main/sorcery/";
+ info->summary = "sorcery object type observer unit test";
+ info->description =
+ "Test that object type observers get called when they should";
+ return AST_TEST_NOT_RUN;
+ case TEST_EXECUTE:
+ break;
+ }
+
+ if (!(sorcery = alloc_and_initialize_sorcery())) {
+ ast_test_status_update(test, "Failed to open sorcery structure\n");
+ return AST_TEST_FAIL;
+ }
+
+ if (!ast_sorcery_observer_add(sorcery, "test", NULL)) {
+ ast_test_status_update(test, "Successfully added a NULL observer when it should not be possible\n");
+ return AST_TEST_FAIL;
+ }
+
+ if (ast_sorcery_observer_add(sorcery, "test", &test_observer)) {
+ ast_test_status_update(test, "Failed to add a proper observer\n");
+ return AST_TEST_FAIL;
+ }
+
+ if (!(obj = ast_sorcery_alloc(sorcery, "test", "blah"))) {
+ ast_test_status_update(test, "Failed to allocate a known object type\n");
+ goto end;
+ }
+
+ ast_mutex_init(&observer.lock);
+ ast_cond_init(&observer.cond, NULL);
+ observer.created = NULL;
+ observer.updated = NULL;
+ observer.deleted = NULL;
+
+ if (ast_sorcery_create(sorcery, obj)) {
+ ast_test_status_update(test, "Failed to create object using in-memory wizard\n");
+ goto end;
+ }
+
+ ast_mutex_lock(&observer.lock);
+ while (!observer.created) {
+ struct timeval start = ast_tvnow();
+ struct timespec end = {
+ .tv_sec = start.tv_sec + 10,
+ .tv_nsec = start.tv_usec * 1000,
+ };
+ if (ast_cond_timedwait(&observer.cond, &observer.lock, &end) == ETIMEDOUT) {
+ break;
+ }
+ }
+ ast_mutex_unlock(&observer.lock);
+
+ if (!observer.created) {
+ ast_test_status_update(test, "Failed to receive observer notification for object creation within suitable timeframe\n");
+ goto end;
+ }
+
+ if (ast_sorcery_update(sorcery, obj)) {
+ ast_test_status_update(test, "Failed to update object using in-memory wizard\n");
+ goto end;
+ }
+
+ ast_mutex_lock(&observer.lock);
+ while (!observer.updated) {
+ struct timeval start = ast_tvnow();
+ struct timespec end = {
+ .tv_sec = start.tv_sec + 10,
+ .tv_nsec = start.tv_usec * 1000,
+ };
+ if (ast_cond_timedwait(&observer.cond, &observer.lock, &end) == ETIMEDOUT) {
+ break;
+ }
+ }
+ ast_mutex_unlock(&observer.lock);
+
+ if (!observer.updated) {
+ ast_test_status_update(test, "Failed to receive observer notification for object updating within suitable timeframe\n");
+ goto end;
+ }
+
+ if (ast_sorcery_delete(sorcery, obj)) {
+ ast_test_status_update(test, "Failed to delete object using in-memory wizard\n");
+ goto end;
+ }
+
+ ast_mutex_lock(&observer.lock);
+ while (!observer.deleted) {
+ struct timeval start = ast_tvnow();
+ struct timespec end = {
+ .tv_sec = start.tv_sec + 10,
+ .tv_nsec = start.tv_usec * 1000,
+ };
+ if (ast_cond_timedwait(&observer.cond, &observer.lock, &end) == ETIMEDOUT) {
+ break;
+ }
+ }
+ ast_mutex_unlock(&observer.lock);
+
+ if (!observer.deleted) {
+ ast_test_status_update(test, "Failed to receive observer notification for object deletion within suitable timeframe\n");
+ goto end;
+ }
+
+ ast_sorcery_reload(sorcery);
+
+ ast_mutex_lock(&observer.lock);
+ while (!observer.loaded) {
+ struct timeval start = ast_tvnow();
+ struct timespec end = {
+ .tv_sec = start.tv_sec + 10,
+ .tv_nsec = start.tv_usec * 1000,
+ };
+ if (ast_cond_timedwait(&observer.cond, &observer.lock, &end) == ETIMEDOUT) {
+ break;
+ }
+ }
+ ast_mutex_unlock(&observer.lock);
+
+ if (!observer.loaded) {
+ ast_test_status_update(test, "Failed to receive observer notification for object type load within suitable timeframe\n");
+ goto end;
+ }
+
+ res = AST_TEST_PASS;
+
+end:
+ observer.created = NULL;
+ observer.updated = NULL;
+ observer.deleted = NULL;
+ ast_mutex_destroy(&observer.lock);
+ ast_cond_destroy(&observer.cond);
+
+ return res;
+}
+
AST_TEST_DEFINE(configuration_file_wizard)
{
struct ast_flags flags = { CONFIG_FLAG_NOCACHE };
@@ -2334,6 +2597,7 @@ static int unload_module(void)
AST_TEST_UNREGISTER(object_diff);
AST_TEST_UNREGISTER(object_diff_native);
AST_TEST_UNREGISTER(objectset_create);
+ AST_TEST_UNREGISTER(objectset_json_create);
AST_TEST_UNREGISTER(objectset_create_regex);
AST_TEST_UNREGISTER(objectset_apply);
AST_TEST_UNREGISTER(objectset_apply_handler);
@@ -2353,6 +2617,7 @@ static int unload_module(void)
AST_TEST_UNREGISTER(object_delete);
AST_TEST_UNREGISTER(object_delete_uncreated);
AST_TEST_UNREGISTER(caching_wizard_behavior);
+ AST_TEST_UNREGISTER(object_type_observer);
AST_TEST_UNREGISTER(configuration_file_wizard);
AST_TEST_UNREGISTER(configuration_file_wizard_with_file_integrity);
AST_TEST_UNREGISTER(configuration_file_wizard_with_criteria);
@@ -2379,6 +2644,7 @@ static int load_module(void)
AST_TEST_REGISTER(object_diff);
AST_TEST_REGISTER(object_diff_native);
AST_TEST_REGISTER(objectset_create);
+ AST_TEST_REGISTER(objectset_json_create);
AST_TEST_REGISTER(objectset_create_regex);
AST_TEST_REGISTER(objectset_apply);
AST_TEST_REGISTER(objectset_apply_handler);
@@ -2398,6 +2664,7 @@ static int load_module(void)
AST_TEST_REGISTER(object_delete);
AST_TEST_REGISTER(object_delete_uncreated);
AST_TEST_REGISTER(caching_wizard_behavior);
+ AST_TEST_REGISTER(object_type_observer);
AST_TEST_REGISTER(configuration_file_wizard);
AST_TEST_REGISTER(configuration_file_wizard_with_file_integrity);
AST_TEST_REGISTER(configuration_file_wizard_with_criteria);