Skip to content
Snippets Groups Projects
test_sorcery.c 120 KiB
Newer Older
  • Learn to ignore specific revisions
  • /*
     * Asterisk -- An open source telephony toolkit.
     *
     * Copyright (C) 2012 - 2013, Digium, Inc.
     *
     * Joshua Colp <jcolp@digium.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 Sorcery Unit Tests
     *
     * \author Joshua Colp <jcolp@digium.com>
     *
     */
    
    /*** MODULEINFO
    	<depend>TEST_FRAMEWORK</depend>
    
    	<depend>func_sorcery</depend>
    
    	<support_level>core</support_level>
     ***/
    
    #include "asterisk.h"
    
    #include "asterisk/test.h"
    #include "asterisk/module.h"
    
    #include "asterisk/astobj2.h"
    
    #include "asterisk/pbx.h"
    
    #include "asterisk/sorcery.h"
    #include "asterisk/logger.h"
    
    
    /*! \brief Dummy sorcery object */
    struct test_sorcery_object {
    	SORCERY_OBJECT(details);
    	unsigned int bob;
    	unsigned int joe;
    
    	struct ast_variable *jim;
    	struct ast_variable *jack;
    
    /*! \brief Internal function to destroy a test object */
    static void test_sorcery_object_destroy(void *obj)
    {
    	struct test_sorcery_object *tobj = obj;
    	ast_variables_destroy(tobj->jim);
    	ast_variables_destroy(tobj->jack);
    }
    
    
    /*! \brief Internal function to allocate a test object */
    static void *test_sorcery_object_alloc(const char *id)
    {
    
    	return ast_sorcery_generic_alloc(sizeof(struct test_sorcery_object), test_sorcery_object_destroy);
    
    }
    
    /*! \brief Internal function for object set transformation */
    static struct ast_variable *test_sorcery_transform(struct ast_variable *set)
    {
    	struct ast_variable *field, *transformed = NULL;
    
    	for (field = set; field; field = field->next) {
    		struct ast_variable *transformed_field;
    
    		if (!strcmp(field->name, "joe")) {
    			transformed_field = ast_variable_new(field->name, "5000", "");
    		} else {
    			transformed_field = ast_variable_new(field->name, field->value, "");
    		}
    
    		if (!transformed_field) {
    			ast_variables_destroy(transformed);
    			return NULL;
    		}
    
    		transformed_field->next = transformed;
    		transformed = transformed_field;
    	}
    
    	return transformed;
    }
    
    
    /*! \brief Internal function which copies pre-defined data into an object, natively */
    static int test_sorcery_copy(const void *src, void *dst)
    {
    	struct test_sorcery_object *obj = dst;
    	obj->bob = 10;
    	obj->joe = 20;
    
    	obj->jim = ast_variable_new("jim", "444", "");
    	obj->jack = ast_variable_new("jack", "999,000", "");
    
    	return 0;
    }
    
    /*! \brief Internal function which creates a pre-defined diff natively */
    static int test_sorcery_diff(const void *original, const void *modified, struct ast_variable **changes)
    {
    	*changes = ast_variable_new("yes", "itworks", "");
    	return 0;
    }
    
    
    /*! \brief Internal function which sets some values */
    static int test_sorcery_regex_handler(const struct aco_option *opt, struct ast_variable *var, void *obj)
    {
    	struct test_sorcery_object *test = obj;
    
    	test->bob = 256;
    
    	return 0;
    }
    
    /*! \brief Internal function which creates some ast_variable structures */
    static int test_sorcery_regex_fields(const void *obj, struct ast_variable **fields)
    {
    	*fields = ast_variable_new("toast-bob", "10", "");
    
    	return 0;
    }
    
    
    /*! \brief Test structure for caching */
    struct sorcery_test_caching {
    	/*! \brief Whether the object has been created in the cache or not */
    	unsigned int created:1;
    
    	/*! \brief Whether the object has been updated in the cache or not */
    	unsigned int updated:1;
    
    	/*! \brief Whether the object has been deleted from the cache or not */
    	unsigned int deleted:1;
    
    
    	/*! \brief Whether the object is stale or not */
    	unsigned int is_stale:1;
    
    
    	/*! \brief Object to return when asked */
    	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;
    
    /*! \brief Simple apply handler which sets global scope integer to 1 if called */
    
    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 void *wizard2_data;
    
    static void *sorcery_test_open(const char *data)
    {
    	wizard2_data = (void *)data;
    	return wizard2_data;
    }
    
    static void sorcery_test_close(void *data)
    {
    
    }
    
    
    static int sorcery_test_create(const struct ast_sorcery *sorcery, void *data, void *object)
    
    {
    	cache.created = 1;
    	cache.updated = 0;
    	cache.deleted = 0;
    	return 0;
    }
    
    static void *sorcery_test_retrieve_id(const struct ast_sorcery *sorcery, void *data, const char *type, const char *id)
    {
    	return (cache.created && !cache.deleted) ? ast_sorcery_alloc(sorcery, type, id) : NULL;
    }
    
    
    static int sorcery_test_update(const struct ast_sorcery *sorcery, void *data, void *object)
    
    {
    	cache.updated = 1;
    	return 0;
    }
    
    
    static int sorcery_test_delete(const struct ast_sorcery *sorcery, void *data, void *object)
    
    {
    	cache.deleted = 1;
    	return 0;
    }
    
    
    static int sorcery_test_is_stale(const struct ast_sorcery *sorcery, void *data, void *object)
    {
    	cache.is_stale = 1;
    	return 1;
    }
    
    
    /*! \brief Dummy sorcery wizards, not actually used so we only populate the name and nothing else */
    
    static struct ast_sorcery_wizard test_wizard = {
    	.name = "test",
    	.create = sorcery_test_create,
    	.retrieve_id = sorcery_test_retrieve_id,
    	.update = sorcery_test_update,
    	.delete = sorcery_test_delete,
    };
    
    
    static struct ast_sorcery_wizard test_wizard2 = {
    	.name = "test2",
    	.open = sorcery_test_open,
    	.close = sorcery_test_close,
    	.create = sorcery_test_create,
    	.retrieve_id = sorcery_test_retrieve_id,
    	.update = sorcery_test_update,
    	.delete = sorcery_test_delete,
    
    	.is_stale = sorcery_test_is_stale,
    
    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 const struct ast_sorcery_observer test_observer = {
    
    	.created = sorcery_observer_created,
    	.updated = sorcery_observer_updated,
    	.deleted = sorcery_observer_deleted,
    	.loaded = sorcery_observer_loaded,
    };
    
    
    /*  This handler takes a simple value and creates new list entry for it*/
    static int jim_handler(const struct aco_option *opt, struct ast_variable *var, void *obj)
    {
    	struct test_sorcery_object *tobj = obj;
    
    	ast_variable_list_append(&tobj->jim, ast_variables_dup(var));
    
    	return 0;
    }
    
    /*  This handler takes a CSV string and creates new a new list entry for each value */
    static int jack_handler(const struct aco_option *opt, struct ast_variable *var, void *obj)
    {
    	struct test_sorcery_object *tobj = obj;
    
    	char *jacks = ast_strdupa(var->value);
    	char *val;
    
    	while ((val = strsep(&jacks, ","))) {
    		ast_variable_list_append(&tobj->jack, ast_variable_new("jack", val, ""));
    	}
    	return 0;
    }
    
    static int jim_vl(const void *obj, struct ast_variable **fields)
    {
    	const struct test_sorcery_object *tobj = obj;
    	if (tobj->jim) {
    		*fields = ast_variables_dup(tobj->jim);
    	}
    	return 0;
    }
    
    static int jack_str(const void *obj, const intptr_t *args, char **buf)
    {
    	const struct test_sorcery_object *tobj = obj;
    	struct ast_variable *curr = tobj->jack;
    	RAII_VAR(struct ast_str *, str,	ast_str_create(128), ast_free);
    
    	while(curr) {
    		ast_str_append(&str, 0, "%s,", curr->value);
    		curr = curr->next;
    	}
    	ast_str_truncate(str, -1);
    	*buf = ast_strdup(ast_str_buffer(str));
    	str = NULL;
    	return 0;
    }
    
    
    static struct ast_sorcery *alloc_and_initialize_sorcery(void)
    {
    	struct ast_sorcery *sorcery;
    
    
    	if (!(sorcery = ast_sorcery_open())) {
    
    		return NULL;
    
    	if ((ast_sorcery_apply_default(sorcery, "test", "memory", NULL) != AST_SORCERY_APPLY_SUCCESS) ||
    
    Matthew Jordan's avatar
    Matthew Jordan committed
    		ast_sorcery_internal_object_register(sorcery, "test", test_sorcery_object_alloc, NULL, NULL)) {
    
    		ast_sorcery_unref(sorcery);
    		return NULL;
    
    Matthew Jordan's avatar
    Matthew Jordan committed
    	ast_sorcery_object_field_register_nodoc(sorcery, "test", "bob", "5", OPT_UINT_T, 0, FLDSET(struct test_sorcery_object, bob));
    	ast_sorcery_object_field_register_nodoc(sorcery, "test", "joe", "10", OPT_UINT_T, 0, FLDSET(struct test_sorcery_object, joe));
    
    	ast_sorcery_object_field_register_custom_nodoc(sorcery, "test", "jim", "444", jim_handler, NULL, jim_vl, 0, 0);
    	ast_sorcery_object_field_register_custom_nodoc(sorcery, "test", "jack", "888,999", jack_handler, jack_str, NULL, 0, 0);
    
    
    	return sorcery;
    }
    
    AST_TEST_DEFINE(wizard_registration)
    {
    	switch (cmd) {
    	case TEST_INIT:
    		info->name = "wizard_registration";
    		info->category = "/main/sorcery/";
    		info->summary = "sorcery wizard registration and unregistration unit test";
    		info->description =
    			"Test registration and unregistration of a sorcery wizard";
    		return AST_TEST_NOT_RUN;
    	case TEST_EXECUTE:
    		break;
    	}
    
    	if (ast_sorcery_wizard_register(&test_wizard)) {
    		ast_test_status_update(test, "Failed to register a perfectly valid sorcery wizard\n");
    		return AST_TEST_FAIL;
    	}
    
    	if (!ast_sorcery_wizard_register(&test_wizard)) {
    		ast_test_status_update(test, "Successfully registered a sorcery wizard twice, which is bad\n");
    		return AST_TEST_FAIL;
    	}
    
    	if (ast_sorcery_wizard_unregister(&test_wizard)) {
    		ast_test_status_update(test, "Failed to unregister a perfectly valid sorcery wizard\n");
    		return AST_TEST_FAIL;
    	}
    
    	if (!ast_sorcery_wizard_unregister(&test_wizard)) {
    		ast_test_status_update(test, "Successfully unregistered a sorcery wizard twice, which is bad\n");
    		return AST_TEST_FAIL;
    	}
    
    	return AST_TEST_PASS;
    }
    
    
    {
    	RAII_VAR(struct ast_sorcery *, sorcery, NULL, ast_sorcery_unref);
    
    	RAII_VAR(struct ast_sorcery *, sorcery2, NULL, ast_sorcery_unref);
    	int refcount;
    
    
    	switch (cmd) {
    	case TEST_INIT:
    		info->name = "open";
    		info->category = "/main/sorcery/";
    
    		info->summary = "sorcery open/close unit test";
    
    		info->description =
    
    			"Test opening of sorcery and registry operations";
    
    		return AST_TEST_NOT_RUN;
    	case TEST_EXECUTE:
    		break;
    	}
    
    
    	if ((sorcery = ast_sorcery_retrieve_by_module_name(AST_MODULE))) {
    		ast_test_status_update(test, "There should NOT have been an existing sorcery instance\n");
    		return AST_TEST_FAIL;
    	}
    
    
    	if (!(sorcery = ast_sorcery_open())) {
    		ast_test_status_update(test, "Failed to open new sorcery structure\n");
    		return AST_TEST_FAIL;
    	}
    
    
    	if (!(sorcery2 = ast_sorcery_retrieve_by_module_name(AST_MODULE))) {
    		ast_test_status_update(test, "Failed to find sorcery structure in registry\n");
    		return AST_TEST_FAIL;
    	}
    
    	if (sorcery2 != sorcery) {
    		ast_test_status_update(test, "Should have gotten same sorcery on retrieve\n");
    		return AST_TEST_FAIL;
    	}
    	ast_sorcery_unref(sorcery2);
    
    	if ((refcount = ao2_ref(sorcery, 0)) != 2) {
    		ast_test_status_update(test, "Should have been 2 references to sorcery instead of %d\n", refcount);
    		return AST_TEST_FAIL;
    	}
    
    	if (!(sorcery2 = ast_sorcery_open())) {
    		ast_test_status_update(test, "Failed to open second sorcery structure\n");
    		return AST_TEST_FAIL;
    	}
    
    	if (sorcery2 != sorcery) {
    		ast_test_status_update(test, "Should have gotten same sorcery on 2nd open\n");
    		return AST_TEST_FAIL;
    	}
    
    	if ((refcount = ao2_ref(sorcery, 0)) != 3) {
    		ast_test_status_update(test, "Should have been 3 references to sorcery instead of %d\n", refcount);
    		return AST_TEST_FAIL;
    	}
    
    	ast_sorcery_unref(sorcery);
    	ast_sorcery_unref(sorcery2);
    
    	sorcery2 = NULL;
    
    	if ((sorcery = ast_sorcery_retrieve_by_module_name(AST_MODULE))) {
    		ast_sorcery_unref(sorcery);
    		sorcery = NULL;
    		ast_test_status_update(test, "Should NOT have found sorcery structure in registry\n");
    		return AST_TEST_FAIL;
    	}
    
    
    	return AST_TEST_PASS;
    }
    
    AST_TEST_DEFINE(apply_default)
    {
    	RAII_VAR(struct ast_sorcery *, sorcery, NULL, ast_sorcery_unref);
    
    	switch (cmd) {
    	case TEST_INIT:
    		info->name = "apply_default";
    		info->category = "/main/sorcery/";
    		info->summary = "sorcery default wizard unit test";
    		info->description =
    			"Test setting default type wizard in sorcery";
    		return AST_TEST_NOT_RUN;
    	case TEST_EXECUTE:
    		break;
    	}
    
    	if (!(sorcery = ast_sorcery_open())) {
    		ast_test_status_update(test, "Failed to open sorcery structure\n");
    		return AST_TEST_FAIL;
    	}
    
    
    	if (ast_sorcery_apply_default(sorcery, "test", "dummy", NULL) != AST_SORCERY_APPLY_FAIL) {
    
    		ast_test_status_update(test, "Successfully set a default wizard that doesn't exist\n");
    		return AST_TEST_FAIL;
    	}
    
    
    	if (ast_sorcery_apply_default(sorcery, "test", "memory", NULL) != AST_SORCERY_APPLY_SUCCESS) {
    
    		ast_test_status_update(test, "Failed to set a known wizard as a default\n");
    		return AST_TEST_FAIL;
    	}
    
    
    	if (ast_sorcery_apply_default(sorcery, "test", "memory", NULL) != AST_SORCERY_APPLY_DEFAULT_UNNECESSARY) {
    
    		ast_test_status_update(test, "Successfully set a default wizard on a type twice\n");
    		return AST_TEST_FAIL;
    	}
    
    	return AST_TEST_PASS;
    }
    
    AST_TEST_DEFINE(apply_config)
    {
    	struct ast_flags flags = { CONFIG_FLAG_NOCACHE };
    	struct ast_config *config;
    	RAII_VAR(struct ast_sorcery *, sorcery, NULL, ast_sorcery_unref);
    
    	switch (cmd) {
    	case TEST_INIT:
    		info->name = "apply_config";
    		info->category = "/main/sorcery/";
    		info->summary = "sorcery object mapping configuration unit test";
    		info->description =
    			"Test configured object mapping in sorcery";
    		return AST_TEST_NOT_RUN;
    	case TEST_EXECUTE:
    		break;
    	}
    
    	if (!(config = ast_config_load2("sorcery.conf", "test_sorcery", flags))) {
    		ast_test_status_update(test, "Sorcery configuration file not present - skipping apply_config test\n");
    		return AST_TEST_NOT_RUN;
    	}
    
    
    	if (!ast_category_get(config, "test_sorcery_section", NULL)) {
    
    		ast_test_status_update(test, "Sorcery configuration file does not have test_sorcery section\n");
    		ast_config_destroy(config);
    		return AST_TEST_NOT_RUN;
    	}
    
    	ast_config_destroy(config);
    
    	if (!(sorcery = ast_sorcery_open())) {
    		ast_test_status_update(test, "Failed to open sorcery structure\n");
    		return AST_TEST_FAIL;
    	}
    
    
    	if (ast_sorcery_apply_config(sorcery, "test_sorcery_section") != AST_SORCERY_APPLY_SUCCESS) {
    
    		ast_test_status_update(test, "Failed to apply configured object mappings\n");
    		return AST_TEST_FAIL;
    	}
    
    	return AST_TEST_PASS;
    }
    
    AST_TEST_DEFINE(object_register)
    {
    	RAII_VAR(struct ast_sorcery *, sorcery, NULL, ast_sorcery_unref);
    
    	switch (cmd) {
    	case TEST_INIT:
    		info->name = "object_register";
    		info->category = "/main/sorcery/";
    		info->summary = "sorcery object type registration unit test";
    		info->description =
    			"Test object type registration in sorcery";
    		return AST_TEST_NOT_RUN;
    	case TEST_EXECUTE:
    		break;
    	}
    
    	if (!(sorcery = ast_sorcery_open())) {
    		ast_test_status_update(test, "Failed to open structure\n");
    		return AST_TEST_FAIL;
    	}
    
    
    	if (ast_sorcery_apply_default(sorcery, "test", "memory", NULL) != AST_SORCERY_APPLY_SUCCESS) {
    
    		ast_test_status_update(test, "Failed to set a known wizard as a default\n");
    		return AST_TEST_FAIL;
    	}
    
    
    Matthew Jordan's avatar
    Matthew Jordan committed
    	if (ast_sorcery_internal_object_register(sorcery, "test", test_sorcery_object_alloc, NULL, NULL)) {
    
    		ast_test_status_update(test, "Failed to register object type\n");
    		return AST_TEST_FAIL;
    	}
    
    
    Matthew Jordan's avatar
    Matthew Jordan committed
    	if (!ast_sorcery_internal_object_register(sorcery, "test", test_sorcery_object_alloc, NULL, NULL)) {
    
    		ast_test_status_update(test, "Registered object type a second time, despite it being registered already\n");
    		return AST_TEST_FAIL;
    	}
    
    
    	return AST_TEST_PASS;
    }
    
    AST_TEST_DEFINE(object_register_without_mapping)
    {
    	RAII_VAR(struct ast_sorcery *, sorcery, NULL, ast_sorcery_unref);
    
    	switch (cmd) {
    	case TEST_INIT:
    		info->name = "object_register_without_mapping";
    		info->category = "/main/sorcery/";
    		info->summary = "sorcery object type registration (without mapping) unit test";
    		info->description =
    			"Test object type registration when no mapping exists in sorcery";
    		return AST_TEST_NOT_RUN;
    	case TEST_EXECUTE:
    		break;
    	}
    
    	if (!(sorcery = ast_sorcery_open())) {
    		ast_test_status_update(test, "Failed to open sorcery structure\n");
    		return AST_TEST_FAIL;
    	}
    
    
    Matthew Jordan's avatar
    Matthew Jordan committed
    	if (!ast_sorcery_internal_object_register(sorcery, "test", test_sorcery_object_alloc, NULL, NULL)) {
    
    		ast_test_status_update(test, "Registered object type when no object mapping exists\n");
    		return AST_TEST_FAIL;
    	}
    
    	return AST_TEST_PASS;
    }
    
    AST_TEST_DEFINE(object_field_register)
    {
    	RAII_VAR(struct ast_sorcery *, sorcery, NULL, ast_sorcery_unref);
    
    	switch (cmd) {
    	case TEST_INIT:
    		info->name = "object_field_register";
    		info->category = "/main/sorcery/";
    		info->summary = "sorcery object field registration unit test";
    		info->description =
    			"Test object field registration in sorcery with a provided id";
    		return AST_TEST_NOT_RUN;
    	case TEST_EXECUTE:
    		break;
    	}
    
    	if (!(sorcery = ast_sorcery_open())) {
    		ast_test_status_update(test, "Failed to open sorcery structure\n");
    		return AST_TEST_FAIL;
    	}
    
    
    Matthew Jordan's avatar
    Matthew Jordan committed
    	if (!ast_sorcery_object_field_register_nodoc(sorcery, "test", "bob", "5", OPT_UINT_T, 0, FLDSET(struct test_sorcery_object, bob))) {
    
    		ast_test_status_update(test, "Registered an object field successfully when no mappings or object types exist\n");
    		return AST_TEST_FAIL;
    	}
    
    
    	if (ast_sorcery_apply_default(sorcery, "test", "memory", NULL) != AST_SORCERY_APPLY_SUCCESS) {
    
    		ast_test_status_update(test, "Failed to set a known wizard as a default\n");
    		return AST_TEST_FAIL;
    	}
    
    
    Matthew Jordan's avatar
    Matthew Jordan committed
    	if (!ast_sorcery_object_field_register_nodoc(sorcery, "test", "bob", "5", OPT_UINT_T, 0, FLDSET(struct test_sorcery_object, bob))) {
    
    		ast_test_status_update(test, "Registered an object field successfully when object type does not exist\n");
    		return AST_TEST_FAIL;
    	}
    
    
    Matthew Jordan's avatar
    Matthew Jordan committed
    	if (ast_sorcery_internal_object_register(sorcery, "test", test_sorcery_object_alloc, NULL, NULL)) {
    
    		ast_test_status_update(test, "Failed to register object type\n");
    		return AST_TEST_FAIL;
    	}
    
    
    Matthew Jordan's avatar
    Matthew Jordan committed
    	if (ast_sorcery_object_field_register_nodoc(sorcery, "test", "bob", "5", OPT_UINT_T, 0, FLDSET(struct test_sorcery_object, bob))) {
    
    		ast_test_status_update(test, "Could not successfully register object field when mapping and object type exists\n");
    		return AST_TEST_FAIL;
    	}
    
    	return AST_TEST_PASS;
    }
    
    
    AST_TEST_DEFINE(object_fields_register)
    {
    	RAII_VAR(struct ast_sorcery *, sorcery, NULL, ast_sorcery_unref);
    
    	switch (cmd) {
    	case TEST_INIT:
    		info->name = "object_fields_register";
    		info->category = "/main/sorcery/";
    		info->summary = "sorcery object regex fields registration unit test";
    		info->description =
    			"Test object regex fields registration in sorcery with a provided id";
    		return AST_TEST_NOT_RUN;
    	case TEST_EXECUTE:
    		break;
    	}
    
    	if (!(sorcery = ast_sorcery_open())) {
    		ast_test_status_update(test, "Failed to open sorcery structure\n");
    		return AST_TEST_FAIL;
    	}
    
    	if (!ast_sorcery_object_fields_register(sorcery, "test", "^toast-", test_sorcery_regex_handler, test_sorcery_regex_fields)) {
    		ast_test_status_update(test, "Registered a regex object field successfully when no mappings or object types exist\n");
    		return AST_TEST_FAIL;
    	}
    
    
    	if (ast_sorcery_apply_default(sorcery, "test", "memory", NULL) != AST_SORCERY_APPLY_SUCCESS) {
    
    		ast_test_status_update(test, "Failed to set a known wizard as a default\n");
    		return AST_TEST_FAIL;
    	}
    
    	if (!ast_sorcery_object_fields_register(sorcery, "test", "^toast-", test_sorcery_regex_handler, test_sorcery_regex_fields)) {
    		ast_test_status_update(test, "Registered a regex object field successfully when object type does not exist\n");
    		return AST_TEST_FAIL;
    	}
    
    
    Matthew Jordan's avatar
    Matthew Jordan committed
    	if (ast_sorcery_internal_object_register(sorcery, "test", test_sorcery_object_alloc, NULL, NULL)) {
    
    		ast_test_status_update(test, "Failed to register object type\n");
    		return AST_TEST_FAIL;
    	}
    
    	if (ast_sorcery_object_fields_register(sorcery, "test", "^toast-", test_sorcery_regex_handler, test_sorcery_regex_fields)) {
    		ast_test_status_update(test, "Registered a regex object field successfully when no mappings or object types exist\n");
    		return AST_TEST_FAIL;
    	}
    
    	return AST_TEST_PASS;
    }
    
    
    AST_TEST_DEFINE(object_alloc_with_id)
    {
    	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);
    
    	switch (cmd) {
    	case TEST_INIT:
    		info->name = "object_alloc_with_id";
    		info->category = "/main/sorcery/";
    		info->summary = "sorcery object allocation (with id) unit test";
    		info->description =
    			"Test object allocation in sorcery with a provided id";
    		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");
    		res = AST_TEST_FAIL;
    	} else if (ast_strlen_zero(ast_sorcery_object_get_id(obj))) {
    		ast_test_status_update(test, "Allocated object has empty id when it should not\n");
    		res = AST_TEST_FAIL;
    	} else if (strcmp(ast_sorcery_object_get_id(obj), "blah")) {
    		ast_test_status_update(test, "Allocated object does not have correct id\n");
    		res = AST_TEST_FAIL;
    	} else if (ast_strlen_zero(ast_sorcery_object_get_type(obj))) {
    		ast_test_status_update(test, "Allocated object has empty type when it should not\n");
    		res = AST_TEST_FAIL;
    	} else if (strcmp(ast_sorcery_object_get_type(obj), "test")) {
    		ast_test_status_update(test, "Allocated object does not have correct type\n");
    		res = AST_TEST_FAIL;
    	} else if ((obj->bob != 5) || (obj->joe != 10)) {
    		ast_test_status_update(test, "Allocated object does not have defaults set as it should\n");
    		res = AST_TEST_FAIL;
    	}
    
    	return res;
    }
    
    AST_TEST_DEFINE(object_alloc_without_id)
    {
    	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);
    
    	switch (cmd) {
    	case TEST_INIT:
    		info->name = "object_alloc_without_id";
    		info->category = "/main/sorcery/";
    		info->summary = "sorcery object allocation (without id) unit test";
    		info->description =
    			"Test object allocation in sorcery with no provided id";
    		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", NULL))) {
    		ast_test_status_update(test, "Failed to allocate a known object type\n");
    		res = AST_TEST_FAIL;
    	} else if (ast_strlen_zero(ast_sorcery_object_get_id(obj))) {
    		ast_test_status_update(test, "Allocated object has empty id when it should not\n");
    		res = AST_TEST_FAIL;
    	}
    
    	return res;
    }
    
    
    AST_TEST_DEFINE(object_copy)
    {
    	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 test_sorcery_object *, copy, NULL, ao2_cleanup);
    
    	switch (cmd) {
    	case TEST_INIT:
    		info->name = "object_copy";
    		info->category = "/main/sorcery/";
    		info->summary = "sorcery object copy unit test";
    		info->description =
    			"Test object copy 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;
    	}
    
    	obj->bob = 50;
    	obj->joe = 100;
    
    	jim_handler(NULL, ast_variable_new("jim", "444", ""), obj);
    	jim_handler(NULL, ast_variable_new("jim", "555", ""), obj);
    
    
    	if (!(copy = ast_sorcery_copy(sorcery, obj))) {
    		ast_test_status_update(test, "Failed to create a copy of a known valid object\n");
    		res = AST_TEST_FAIL;
    	} else if (copy == obj) {
    		ast_test_status_update(test, "Created copy is actually the original object\n");
    		res = AST_TEST_FAIL;
    	} else if (copy->bob != obj->bob) {
    		ast_test_status_update(test, "Value of 'bob' on newly created copy is not the same as original\n");
    		res = AST_TEST_FAIL;
    	} else if (copy->joe != obj->joe) {
    		ast_test_status_update(test, "Value of 'joe' on newly created copy is not the same as original\n");
    		res = AST_TEST_FAIL;
    
    	} else if (!copy->jim) {
    		ast_test_status_update(test, "A new ast_variable was not created for 'jim'\n");
    		res = AST_TEST_FAIL;
    	} else if (copy->jim == obj->jim) {
    		ast_test_status_update(test, "Created copy of 'jim' is actually the ogirinal 'jim'\n");
    		res = AST_TEST_FAIL;
    	} else if (strcmp(copy->jim->value, obj->jim->value)) {
    		ast_test_status_update(test, "Value of 1st 'jim' on newly created copy is not the same as original\n");
    		res = AST_TEST_FAIL;
    	} else if (!copy->jim->next) {
    		ast_test_status_update(test, "A new ast_variable was not created for 2nd 'jim'\n");
    		res = AST_TEST_FAIL;
    	} else if (strcmp(copy->jim->next->value, obj->jim->next->value)) {
    		ast_test_status_update(test, "Value of 2nd 'jim' (%s %s) on newly created copy is not the same as original (%s %s)\n",
    			copy->jim->value, copy->jim->next->value, obj->jim->value, obj->jim->next->value);
    		res = AST_TEST_FAIL;
    
    AST_TEST_DEFINE(object_copy_native)
    {
    	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 test_sorcery_object *, copy, NULL, ao2_cleanup);
    
    	switch (cmd) {
    	case TEST_INIT:
    		info->name = "object_copy_native";
    		info->category = "/main/sorcery/";
    		info->summary = "sorcery object native copy unit test";
    		info->description =
    			"Test object native copy 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;
    	}
    
    	ast_sorcery_object_set_copy_handler(sorcery, "test", test_sorcery_copy);
    
    	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;
    	}
    
    	obj->bob = 50;
    	obj->joe = 100;
    
    	if (!(copy = ast_sorcery_copy(sorcery, obj))) {
    		ast_test_status_update(test, "Failed to create a copy of a known valid object\n");
    		res = AST_TEST_FAIL;
    	} else if (copy == obj) {
    		ast_test_status_update(test, "Created copy is actually the original object\n");
    		res = AST_TEST_FAIL;
    	} else if (copy->bob != 10) {
    		ast_test_status_update(test, "Value of 'bob' on newly created copy is not the predefined native copy value\n");
    		res = AST_TEST_FAIL;
    	} else if (copy->joe != 20) {
    		ast_test_status_update(test, "Value of 'joe' on newly created copy is not the predefined native copy value\n");
    		res = AST_TEST_FAIL;
    
    	} else if (!copy->jim) {
    		ast_test_status_update(test, "A new ast_variable was not created for 'jim'\n");
    		res = AST_TEST_FAIL;
    	} else if (strcmp(copy->jim->value, "444")) {
    		ast_test_status_update(test, "Value of 'jim' on newly created copy is not the predefined native copy value\n");
    		res = AST_TEST_FAIL;
    
    AST_TEST_DEFINE(object_diff)
    {
           RAII_VAR(struct ast_sorcery *, sorcery, NULL, ast_sorcery_unref);
           RAII_VAR(struct test_sorcery_object *, obj1, NULL, ao2_cleanup);
           RAII_VAR(struct test_sorcery_object *, obj2, NULL, ao2_cleanup);
           RAII_VAR(struct ast_variable *, changes, NULL, ast_variables_destroy);
           struct ast_variable *field;
           int res = AST_TEST_PASS;
    
    
           switch (cmd) {
           case TEST_INIT:
    	       info->name = "object_diff";
    	       info->category = "/main/sorcery/";
    	       info->summary = "sorcery object diff unit test";
    	       info->description =
    		       "Test object diffing 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 (!(obj1 = ast_sorcery_alloc(sorcery, "test", "blah"))) {
    	       ast_test_status_update(test, "Failed to allocate a known object type\n");
    	       return AST_TEST_FAIL;
           }
    
           obj1->bob = 99;
           obj1->joe = 55;
    
           jim_handler(NULL, ast_variable_new("jim", "444", ""), obj1);
           jim_handler(NULL, ast_variable_new("jim", "555", ""), obj1);
    
    
           if (!(obj2 = ast_sorcery_alloc(sorcery, "test", "blah2"))) {
    	       ast_test_status_update(test, "Failed to allocate a second known object type\n");
    	       return AST_TEST_FAIL;
           }
    
           obj2->bob = 99;
           obj2->joe = 42;
    
           jim_handler(NULL, ast_variable_new("jim", "444", ""), obj2);
           jim_handler(NULL, ast_variable_new("jim", "666", ""), obj2);
           jim_handler(NULL, ast_variable_new("jim", "777", ""), obj2);
    
    
           if (ast_sorcery_diff(sorcery, obj1, obj2, &changes)) {
    	       ast_test_status_update(test, "Failed to diff obj1 and obj2\n");
           } else if (!changes) {
    	       ast_test_status_update(test, "Failed to produce a diff of two objects, despite there being differences\n");
    	       return AST_TEST_FAIL;
           }
    
    
    	for (field = changes; field; field = field->next) {
    		if (!strcmp(field->name, "joe")) {
    			if (strcmp(field->value, "42")) {
    				ast_test_status_update(test,
    					"Object diff produced unexpected value '%s' for joe\n", field->value);
    				res = AST_TEST_FAIL;
    			}
    		} else if (!strcmp(field->name, "jim")) {
    			jims++;
    			if (!strcmp(field->value, "555")) {
    				ast_test_status_update(test,
    					"Object diff produced unexpected value '%s' for jim\n", field->value);
    				res = AST_TEST_FAIL;
    			}
    		} else {
    			ast_test_status_update(test, "Object diff produced unexpected field '%s'\n",
    				field->name);
    			res = AST_TEST_FAIL;
    		}
    	}
    
           if (jims != 2) {
    	       ast_test_status_update(test, "Object diff didn't produce 2 jims\n");
    	       res = AST_TEST_FAIL;