Skip to content
Snippets Groups Projects
test_config.c 70.1 KiB
Newer Older
  • Learn to ignore specific revisions
  • /*
     * Asterisk -- An open source telephony toolkit.
     *
    
     * Copyright (C) 2010, Digium, Inc.
    
     * Mark Michelson <mmichelson@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 Configuration unit tests
    
     * \author Mark Michelson <mmichelson@digium.com>
    
     *
     */
    
    /*** MODULEINFO
    	<depend>TEST_FRAMEWORK</depend>
    	<support_level>core</support_level>
     ***/
    
    #include "asterisk.h"
    
    #include <math.h> /* HUGE_VAL */
    
    
    #include "asterisk/config.h"
    
    #include "asterisk/module.h"
    #include "asterisk/test.h"
    #include "asterisk/paths.h"
    
    #include "asterisk/config_options.h"
    #include "asterisk/netsock2.h"
    #include "asterisk/acl.h"
    
    #include "asterisk/app.h"
    
    #include "asterisk/frame.h"
    #include "asterisk/utils.h"
    
    #include "asterisk/logger.h"
    
    #define CONFIG_FILE "test_config.conf"
    
    #define CONFIG_INCLUDE_FILE "test_config_include.conf"
    
    
    /*
     * This builds the folowing config:
     * [Capitals]
     * Germany = Berlin
     * China = Beijing
     * Canada = Ottawa
     *
     * [Protagonists]
     * 1984 = Winston Smith
     * Green Eggs And Ham = Sam I Am
     * The Kalevala = Vainamoinen
     *
     * This config is used for all tests below.
     */
    const char cat1[] = "Capitals";
    const char cat1varname1[] = "Germany";
    const char cat1varvalue1[] = "Berlin";
    const char cat1varname2[] = "China";
    const char cat1varvalue2[] = "Beijing";
    const char cat1varname3[] = "Canada";
    const char cat1varvalue3[] = "Ottawa";
    
    const char cat2[] = "Protagonists";
    const char cat2varname1[] = "1984";
    const char cat2varvalue1[] = "Winston Smith";
    const char cat2varname2[] = "Green Eggs And Ham";
    const char cat2varvalue2[] = "Sam I Am";
    const char cat2varname3[] = "The Kalevala";
    const char cat2varvalue3[] = "Vainamoinen";
    
    struct pair {
    	const char *name;
    	const char *val;
    };
    
    struct association {
    	const char *category;
    	struct pair vars[3];
    } categories [] = {
    	{ cat1,
    		{
    			{ cat1varname1, cat1varvalue1 },
    			{ cat1varname2, cat1varvalue2 },
    			{ cat1varname3, cat1varvalue3 },
    		}
    	},
    	{ cat2,
    		{
    			{ cat2varname1, cat2varvalue1 },
    			{ cat2varname2, cat2varvalue2 },
    			{ cat2varname3, cat2varvalue3 },
    		}
    	},
    };
    
    /*!
     * \brief Build ast_config struct from above definitions
     *
     * \retval NULL Failed to build the config
     * \retval non-NULL An ast_config struct populated with data
     */
    static struct ast_config *build_cfg(void)
    {
    	struct ast_config *cfg;
    	struct association *cat_iter;
    	struct pair *var_iter;
    	size_t i;
    	size_t j;
    
    	cfg = ast_config_new();
    	if (!cfg) {
    		goto fail;
    	}
    
    	for (i = 0; i < ARRAY_LEN(categories); ++i) {
    		struct ast_category *cat;
    		cat_iter = &categories[i];
    
    		cat = ast_category_new(cat_iter->category, "", 999999);
    		if (!cat) {
    			goto fail;
    		}
    		ast_category_append(cfg, cat);
    
    		for (j = 0; j < ARRAY_LEN(cat_iter->vars); ++j) {
    			struct ast_variable *var;
    			var_iter = &cat_iter->vars[j];
    
    			var = ast_variable_new(var_iter->name, var_iter->val, "");
    			if (!var) {
    				goto fail;
    			}
    			ast_variable_append(cat, var);
    		}
    	}
    
    	return cfg;
    
    fail:
    	ast_config_destroy(cfg);
    	return NULL;
    }
    
    /*!
     * \brief Tests that the contents of an ast_config is what is expected
     *
     * \param cfg Config to test
     * \retval -1 Failed to pass a test
     * \retval 0 Config passes checks
     */
    static int test_config_validity(struct ast_config *cfg)
    {
    	int i;
    	const char *cat_iter = NULL;
    	/* Okay, let's see if the correct content is there */
    	for (i = 0; i < ARRAY_LEN(categories); ++i) {
    		struct ast_variable *var = NULL;
    		size_t j;
    		cat_iter = ast_category_browse(cfg, cat_iter);
    		if (strcmp(cat_iter, categories[i].category)) {
    			ast_log(LOG_ERROR, "Category name mismatch, %s does not match %s\n", cat_iter, categories[i].category);
    			return -1;
    		}
    		for (j = 0; j < ARRAY_LEN(categories[i].vars); ++j) {
    			var = var ? var->next : ast_variable_browse(cfg, cat_iter);
    			if (strcmp(var->name, categories[i].vars[j].name)) {
    				ast_log(LOG_ERROR, "Variable name mismatch, %s does not match %s\n", var->name, categories[i].vars[j].name);
    				return -1;
    			}
    			if (strcmp(var->value, categories[i].vars[j].val)) {
    				ast_log(LOG_ERROR, "Variable value mismatch, %s does not match %s\n", var->value, categories[i].vars[j].val);
    				return -1;
    			}
    		}
    	}
    	return 0;
    }
    
    AST_TEST_DEFINE(copy_config)
    {
    	enum ast_test_result_state res = AST_TEST_FAIL;
    	struct ast_config *cfg = NULL;
    	struct ast_config *copy = NULL;
    
    	switch (cmd) {
    	case TEST_INIT:
    		info->name = "copy_config";
    		info->category = "/main/config/";
    		info->summary = "Test copying configuration";
    		info->description =
    			"Ensure that variables and categories are copied correctly";
    		return AST_TEST_NOT_RUN;
    	case TEST_EXECUTE:
    		break;
    	}
    
    	cfg = build_cfg();
    	if (!cfg) {
    		goto out;
    	}
    
    	copy = ast_config_copy(cfg);
    	if (!copy) {
    		goto out;
    	}
    
    	if (test_config_validity(copy) != 0) {
    		goto out;
    	}
    
    	res = AST_TEST_PASS;
    
    out:
    	ast_config_destroy(cfg);
    	ast_config_destroy(copy);
    	return res;
    }
    
    
    AST_TEST_DEFINE(config_basic_ops)
    {
    	enum ast_test_result_state res = AST_TEST_FAIL;
    	struct ast_config *cfg = NULL;
    	struct ast_category *cat = NULL;
    	struct ast_variable *var;
    
    	struct ast_variable *varlist;
    
    	char temp[32];
    	const char *cat_name;
    	const char *var_value;
    	int i;
    
    	switch (cmd) {
    	case TEST_INIT:
    		info->name = "config_basic_ops";
    		info->category = "/main/config/";
    		info->summary = "Test basic config ops";
    		info->description =	"Test basic config ops";
    		return AST_TEST_NOT_RUN;
    	case TEST_EXECUTE:
    		break;
    	}
    
    	cfg = ast_config_new();
    	if (!cfg) {
    		return res;
    	}
    
    	/* load the config */
    	for(i = 0; i < 5; i++) {
    		snprintf(temp, sizeof(temp), "test%d", i);
    		ast_category_append(cfg, ast_category_new(temp, "dummy", -1));
    	}
    
    	/* test0 test1 test2 test3 test4 */
    	/* check the config has 5 elements */
    	i = 0;
    	cat = NULL;
    	while ((cat = ast_category_browse_filtered(cfg, NULL, cat, NULL))) {
    		snprintf(temp, sizeof(temp), "test%d", i);
    		if (strcmp(ast_category_get_name(cat), temp)) {
    			ast_test_status_update(test, "%s != %s\n", ast_category_get_name(cat), temp);
    			goto out;
    		}
    		i++;
    	}
    	if (i != 5) {
    		ast_test_status_update(test, "There were %d matches instead of 5.\n", i);
    		goto out;
    	}
    
    	/* search for test2 */
    	cat = ast_category_get(cfg, "test2", NULL);
    	if (!cat || strcmp(ast_category_get_name(cat), "test2")) {
    		ast_test_status_update(test, "Get failed %s != %s\n", ast_category_get_name(cat), "test2");
    		goto out;
    	}
    
    	/* delete test2 */
    	cat = ast_category_delete(cfg, cat);
    
    	/* Now: test0 test1 test3 test4 */
    	/* make sure the curr category is test1 */
    	if (!cat || strcmp(ast_category_get_name(cat), "test1")) {
    		ast_test_status_update(test, "Delete failed %s != %s\n", ast_category_get_name(cat), "test1");
    		goto out;
    	}
    
    	/* Now: test0 test1 test3 test4 */
    	/* make sure the test2 is not found */
    	cat = ast_category_get(cfg, "test2", NULL);
    	if (cat) {
    		ast_test_status_update(test, "Should not have found test2\n");
    		goto out;
    	}
    
    	/* Now: test0 test1 test3 test4 */
    	/* make sure the sequence is correctly missing test2 */
    	i = 0;
    	cat = NULL;
    	while ((cat = ast_category_browse_filtered(cfg, NULL, cat, NULL))) {
    		snprintf(temp, sizeof(temp), "test%d", i);
    		if (strcmp(ast_category_get_name(cat), temp)) {
    			ast_test_status_update(test, "%s != %s\n", ast_category_get_name(cat), temp);
    			goto out;
    		}
    		i++;
    		if (i == 2) {
    			i++;
    		}
    	}
    	if (i != 5) {
    		ast_test_status_update(test, "There were %d matches instead of 5.\n", i);
    		goto out;
    	}
    
    	/* insert test2 back in before test3 */
    	ast_category_insert(cfg, ast_category_new("test2", "dummy", -1), "test3");
    
    	/* Now: test0 test1 test2 test3 test4 */
    	/* make sure the sequence is correct again */
    	i = 0;
    	cat = NULL;
    	while ((cat = ast_category_browse_filtered(cfg, NULL, cat, NULL))) {
    		snprintf(temp, sizeof(temp), "test%d", i);
    		if (strcmp(ast_category_get_name(cat), temp)) {
    			ast_test_status_update(test, "%s != %s\n", ast_category_get_name(cat), temp);
    			goto out;
    		}
    		i++;
    	}
    	if (i != 5) {
    		ast_test_status_update(test, "There were %d matches instead of 5.\n", i);
    		goto out;
    	}
    
    	/* Now: test0 test1 test2 test3 test4 */
    	/* make sure non filtered browse still works */
    	i = 0;
    	cat_name = NULL;
    	while ((cat_name = ast_category_browse(cfg, cat_name))) {
    		snprintf(temp, sizeof(temp), "test%d", i);
    		if (strcmp(cat_name, temp)) {
    			ast_test_status_update(test, "%s != %s\n", cat_name, temp);
    			goto out;
    		}
    		i++;
    	}
    	if (i != 5) {
    		ast_test_status_update(test, "There were %d matches instead of 5.\n", i);
    		goto out;
    	}
    
    	/* append another test2 */
    	ast_category_append(cfg, ast_category_new("test2", "dummy", -1));
    	/* Now: test0 test1 test2 test3 test4 test2*/
    	/* make sure only test2's are returned */
    	i = 0;
    	cat = NULL;
    	while ((cat = ast_category_browse_filtered(cfg, "test2", cat, NULL))) {
    		if (strcmp(ast_category_get_name(cat), "test2")) {
    			ast_test_status_update(test, "Should have returned test2 instead of %s\n", ast_category_get_name(cat));
    			goto out;
    		}
    		i++;
    	}
    	/* make sure 2 test2's were found */
    	if (i != 2) {
    		ast_test_status_update(test, "Should have found 2 test2's %d\n", i);
    		goto out;
    	}
    
    	/* Test in-flight deletion using ast_category_browse_filtered */
    	/* Now: test0 test1 test2 test3 test4 test2 */
    	/* Delete the middle test2 and continue */
    	cat = NULL;
    	for(i = 0; i < 5; i++) {
    		snprintf(temp, sizeof(temp), "test%d", i);
    		cat = ast_category_browse_filtered(cfg, NULL, cat, NULL);
    		cat_name = ast_category_get_name(cat);
    		if (strcmp(cat_name, temp)) {
    			ast_test_status_update(test, "Should have returned %s instead of %s: %d\n", temp, cat_name, i);
    			goto out;
    		}
    		if (i == 2) {
    			cat = ast_category_delete(cfg, cat);
    		}
    	}
    
    	/* Now: test0 test3 test4 test2 */
    	/* delete the head item */
    	cat = ast_category_browse_filtered(cfg, NULL, NULL, NULL);
    	cat_name = ast_category_get_name(cat);
    	if (strcmp(cat_name, "test0")) {
    		ast_test_status_update(test, "Should have returned test0 instead of %s\n", cat_name);
    		goto out;
    	}
    	ast_category_delete(cfg, cat);
    	/* Now: test3 test4 test2 */
    
    	/* make sure head got updated to the new first element */
    	cat = ast_category_browse_filtered(cfg, NULL, NULL, NULL);
    	cat_name = ast_category_get_name(cat);
    
    	if (strcmp(cat_name, "test1")) {
    
    		ast_test_status_update(test, "Should have returned test3 instead of %s\n", cat_name);
    		goto out;
    	}
    
    	/* delete the tail item */
    	cat = ast_category_get(cfg, "test2", NULL);
    	cat_name = ast_category_get_name(cat);
    	if (strcmp(cat_name, "test2")) {
    		ast_test_status_update(test, "Should have returned test2 instead of %s\n", cat_name);
    		goto out;
    	}
    	ast_category_delete(cfg, cat);
    	/* Now: test3 test4 */
    
    	/* There should now only be 2 elements in the list */
    	cat = NULL;
    
    	cat = ast_category_browse_filtered(cfg, NULL, cat, NULL);
    	cat_name = ast_category_get_name(cat);
    	if (strcmp(cat_name, "test1")) {
    		ast_test_status_update(test, "Should have returned test1 instead of %s\n", cat_name);
    		goto out;
    	}
    
    
    	cat = ast_category_browse_filtered(cfg, NULL, cat, NULL);
    	cat_name = ast_category_get_name(cat);
    	if (strcmp(cat_name, "test3")) {
    		ast_test_status_update(test, "Should have returned test3 instead of %s\n", cat_name);
    		goto out;
    	}
    
    	cat = ast_category_browse_filtered(cfg, NULL, cat, NULL);
    	cat_name = ast_category_get_name(cat);
    	if (strcmp(cat_name, "test4")) {
    		ast_test_status_update(test, "Should have returned test4 instead of %s\n", cat_name);
    		goto out;
    	}
    
    	/* There should be nothing more */
    	cat = ast_category_browse_filtered(cfg, NULL, cat, NULL);
    	if (cat) {
    		ast_test_status_update(test, "Should not have returned anything\n");
    		goto out;
    	}
    
    	/* Test ast_variable retrieve.
    	 * Get the second category.
    	 */
    	cat = ast_category_browse_filtered(cfg, NULL, NULL, NULL);
    	cat = ast_category_browse_filtered(cfg, NULL, cat, NULL);
    	cat_name = ast_category_get_name(cat);
    
    	var = ast_variable_new("aaa", "bbb0", "dummy");
    
    	if (!var) {
    		ast_test_status_update(test, "Couldn't allocate variable.\n");
    		goto out;
    	}
    	ast_variable_append(cat, var);
    
    	/* Make sure we can retrieve with specific category name */
    	var_value = ast_variable_retrieve(cfg, cat_name, "aaa");
    
    	if (!var_value || strcmp(var_value, "bbb0")) {
    
    		ast_test_status_update(test, "Variable not found or wrong value.\n");
    		goto out;
    	}
    
    	/* Make sure we can retrieve with NULL category name */
    	var_value = ast_variable_retrieve(cfg, NULL, "aaa");
    
    	if (!var_value || strcmp(var_value, "bbb0")) {
    
    		ast_test_status_update(test, "Variable not found or wrong value.\n");
    		goto out;
    	}
    
    
    	/* Now test variable retrieve inside a browse loop
    	 * with multiple categories of the same name
    	 */
    	cat = ast_category_new("test3", "dummy", -1);
    	if (!cat) {
    		ast_test_status_update(test, "Couldn't allocate category.\n");
    		goto out;
    	}
    	var = ast_variable_new("aaa", "bbb1", "dummy");
    	if (!var) {
    		ast_test_status_update(test, "Couldn't allocate variable.\n");
    		goto out;
    	}
    	ast_variable_append(cat, var);
    	ast_category_append(cfg, cat);
    
    	cat = ast_category_new("test3", "dummy", -1);
    	if (!cat) {
    		ast_test_status_update(test, "Couldn't allocate category.\n");
    		goto out;
    	}
    	var = ast_variable_new("aaa", "bbb2", "dummy");
    	if (!var) {
    		ast_test_status_update(test, "Couldn't allocate variable.\n");
    		goto out;
    	}
    	ast_variable_append(cat, var);
    	ast_category_append(cfg, cat);
    
    	cat_name = NULL;
    	i = 0;
    	while ((cat_name = ast_category_browse(cfg, cat_name))) {
    		if (!strcmp(cat_name, "test3")) {
    			snprintf(temp, sizeof(temp), "bbb%d", i);
    
    			var_value = ast_variable_retrieve(cfg, cat_name, "aaa");
    			if (!var_value || strcmp(var_value, temp)) {
    				ast_test_status_update(test, "Variable not found or wrong value %s.\n", var_value);
    				goto out;
    			}
    
    			var = ast_variable_browse(cfg, cat_name);
    			if (!var->value || strcmp(var->value, temp)) {
    				ast_test_status_update(test, "Variable not found or wrong value %s.\n", var->value);
    				goto out;
    			}
    
    			i++;
    		}
    	}
    	if (i != 3) {
    		ast_test_status_update(test, "There should have been 3 matches instead of %d.\n", i);
    		goto out;
    	}
    
    
    	varlist = ast_variable_new("name1", "value1", "");
    	ast_variable_list_append_hint(&varlist, NULL, ast_variable_new("name1", "value2", ""));
    	ast_variable_list_append_hint(&varlist, NULL, ast_variable_new("name1", "value3", ""));
    
    	var_value = ast_variable_find_in_list(varlist, "name1");
    	if (strcmp(var_value, "value1") != 0) {
    		ast_test_status_update(test, "Wrong variable retrieved %s.\n", var_value);
    		goto out;
    	}
    
    	var_value = ast_variable_find_last_in_list(varlist, "name1");
    	if (strcmp(var_value, "value3") != 0) {
    		ast_test_status_update(test, "Wrong variable retrieved %s.\n", var_value);
    		goto out;
    	}
    
    
    	res = AST_TEST_PASS;
    
    out:
    	ast_config_destroy(cfg);
    	return res;
    }
    
    AST_TEST_DEFINE(config_filtered_ops)
    {
    	enum ast_test_result_state res = AST_TEST_FAIL;
    	struct ast_config *cfg = NULL;
    	struct ast_category *cat = NULL;
    	char temp[32];
    	const char *value;
    	int i;
    
    	switch (cmd) {
    	case TEST_INIT:
    		info->name = "config_filtered_ops";
    		info->category = "/main/config/";
    		info->summary = "Test filtered config ops";
    		info->description =	"Test filtered config ops";
    		return AST_TEST_NOT_RUN;
    	case TEST_EXECUTE:
    		break;
    	}
    
    	cfg = ast_config_new();
    	if (!cfg) {
    		return res;
    	}
    
    	/* load the config */
    	for(i = 0; i < 5; i++) {
    		snprintf(temp, sizeof(temp), "test%d", i);
    		cat = ast_category_new(temp, "dummy", -1);
    		ast_variable_insert(cat, ast_variable_new("type", "a", "dummy"), "0");
    		ast_category_append(cfg, cat);
    	}
    
    	for(i = 0; i < 5; i++) {
    		snprintf(temp, sizeof(temp), "test%d", i);
    		cat = ast_category_new(temp, "dummy", -1);
    		ast_variable_insert(cat, ast_variable_new("type", "b", "dummy"), "0");
    		ast_category_append(cfg, cat);
    	}
    
    	/* check the config has 5 elements for each type*/
    	i = 0;
    	cat = NULL;
    	while ((cat = ast_category_browse_filtered(cfg, NULL, cat, "type=a"))) {
    		snprintf(temp, sizeof(temp), "test%d", i);
    		if (strcmp(ast_category_get_name(cat), temp)) {
    			ast_test_status_update(test, "%s != %s\n", ast_category_get_name(cat), temp);
    			goto out;
    		}
    		value = ast_variable_find(cat, "type");
    		if (!value || strcmp(value, "a")) {
    			ast_test_status_update(test, "Type %s != %s\n", "a", value);
    			goto out;
    		}
    		i++;
    	}
    	if (i != 5) {
    		ast_test_status_update(test, "There were %d matches instead of 5.\n", i);
    		goto out;
    	}
    
    	i = 0;
    	cat = NULL;
    	while ((cat = ast_category_browse_filtered(cfg, NULL, cat, "type=b"))) {
    		snprintf(temp, sizeof(temp), "test%d", i);
    		if (!cat || strcmp(ast_category_get_name(cat), temp)) {
    			ast_test_status_update(test, "%s != %s\n", ast_category_get_name(cat), temp);
    			goto out;
    		}
    		value = ast_variable_find(cat, "type");
    		if (!value || strcmp(value, "b")) {
    			ast_test_status_update(test, "Type %s != %s\n", "b", value);
    			goto out;
    		}
    		i++;
    	}
    	if (i != 5) {
    		ast_test_status_update(test, "There were %d matches instead of 5.\n", i);
    		goto out;
    	}
    
    	/* Delete b3 and make sure it's gone and a3 is still there.
    	 * Really this is a test of get since delete takes a specific category structure.
    	 */
    	cat = ast_category_get(cfg, "test3", "type=b");
    	value = ast_variable_find(cat, "type");
    	if (strcmp(value, "b")) {
    		ast_test_status_update(test, "Type %s != %s\n", "b", value);
    		goto out;
    	}
    	ast_category_delete(cfg, cat);
    
    	cat = ast_category_get(cfg, "test3", "type=b");
    	if (cat) {
    		ast_test_status_update(test, "Category b was not deleted.\n");
    		goto out;
    	}
    
    	cat = ast_category_get(cfg, "test3", "type=a");
    	if (!cat) {
    		ast_test_status_update(test, "Category a was deleted.\n");
    		goto out;
    	}
    
    	value = ast_variable_find(cat, "type");
    	if (strcmp(value, "a")) {
    		ast_test_status_update(test, "Type %s != %s\n", value, "a");
    		goto out;
    	}
    
    	/* Basic regex stuff is handled by regcomp/regexec so not testing here.
    	 * Still need to test multiple name/value pairs though.
    	 */
    	ast_category_empty(cat);
    	ast_variable_insert(cat, ast_variable_new("type", "bx", "dummy"), "0");
    	ast_variable_insert(cat, ast_variable_new("e", "z", "dummy"), "0");
    
    	cat = ast_category_get(cfg, "test3", "type=.,e=z");
    	if (!cat) {
    		ast_test_status_update(test, "Category not found.\n");
    		goto out;
    	}
    
    	cat = ast_category_get(cfg, "test3", "type=.,e=zX");
    	if (cat) {
    		ast_test_status_update(test, "Category found.\n");
    		goto out;
    	}
    
    	cat = ast_category_get(cfg, "test3", "TEMPLATE=restrict,type=.,e=z");
    	if (cat) {
    		ast_test_status_update(test, "Category found.\n");
    		goto out;
    	}
    
    	res = AST_TEST_PASS;
    
    out:
    	ast_config_destroy(cfg);
    	return res;
    }
    
    AST_TEST_DEFINE(config_template_ops)
    {
    	enum ast_test_result_state res = AST_TEST_FAIL;
    	struct ast_config *cfg = NULL;
    	struct ast_category *cat = NULL;
    	char temp[32];
    	const char *value;
    	int i;
    
    	switch (cmd) {
    	case TEST_INIT:
    		info->name = "config_template_ops";
    		info->category = "/main/config/";
    		info->summary = "Test template config ops";
    		info->description =	"Test template config ops";
    		return AST_TEST_NOT_RUN;
    	case TEST_EXECUTE:
    		break;
    	}
    
    	cfg = ast_config_new();
    	if (!cfg) {
    		return res;
    	}
    
    	/* load the config with 5 templates and 5 regular */
    	for(i = 0; i < 5; i++) {
    		snprintf(temp, sizeof(temp), "test%d", i);
    		cat = ast_category_new_template(temp, "dummy", -1);
    		ast_variable_insert(cat, ast_variable_new("type", "a", "dummy"), "0");
    		ast_category_append(cfg, cat);
    	}
    
    	for(i = 0; i < 5; i++) {
    		snprintf(temp, sizeof(temp), "test%d", i);
    		cat = ast_category_new(temp, "dummy", -1);
    		ast_variable_insert(cat, ast_variable_new("type", "b", "dummy"), "0");
    		ast_category_append(cfg, cat);
    	}
    
    	/* check the config has 5 template elements of type a */
    	i = 0;
    	cat = NULL;
    	while ((cat = ast_category_browse_filtered(cfg, NULL, cat, "TEMPLATES=restrict,type=a"))) {
    		snprintf(temp, sizeof(temp), "test%d", i);
    		if (strcmp(ast_category_get_name(cat), temp)) {
    			ast_test_status_update(test, "%s != %s\n", ast_category_get_name(cat), temp);
    			goto out;
    		}
    		value = ast_variable_find(cat, "type");
    		if (!value || strcmp(value, "a")) {
    			ast_test_status_update(test, "Type %s != %s\n", value, "a");
    			goto out;
    		}
    		i++;
    	}
    	if (i != 5) {
    		ast_test_status_update(test, "There were %d matches instead of 5.\n", i);
    		goto out;
    	}
    
    	/* Test again with 'include'.  There should still only be 5 (type a) */
    	i = 0;
    	cat = NULL;
    	while ((cat = ast_category_browse_filtered(cfg, NULL, cat, "TEMPLATES=include,type=a"))) {
    		snprintf(temp, sizeof(temp), "test%d", i);
    		if (strcmp(ast_category_get_name(cat), temp)) {
    			ast_test_status_update(test, "%s != %s\n", ast_category_get_name(cat), temp);
    			goto out;
    		}
    		value = ast_variable_find(cat, "type");
    		if (!value || strcmp(value, "a")) {
    			ast_test_status_update(test, "Type %s != %s\n", value, "a");
    			goto out;
    		}
    		i++;
    	}
    	if (i != 5) {
    		ast_test_status_update(test, "There were %d matches instead of 5.\n", i);
    		goto out;
    	}
    
    	/* Test again with 'include' but no type.  There should now be 10 (type a and type b) */
    	i = 0;
    	cat = NULL;
    	while ((cat = ast_category_browse_filtered(cfg, NULL, cat, "TEMPLATES=include"))) {
    		i++;
    	}
    	if (i != 10) {
    		ast_test_status_update(test, "There were %d matches instead of 10.\n", i);
    		goto out;
    	}
    
    	/* Test again with 'restrict' and type b.  There should 0 */
    	i = 0;
    	cat = NULL;
    	while ((cat = ast_category_browse_filtered(cfg, NULL, cat, "TEMPLATES=restrict,type=b"))) {
    		i++;
    	}
    	if (i != 0) {
    		ast_test_status_update(test, "There were %d matches instead of 0.\n", i);
    		goto out;
    	}
    
    	res = AST_TEST_PASS;
    
    out:
    	ast_config_destroy(cfg);
    	return res;
    }
    
    
    /*!
     * \brief Write the config file to disk
     *
     * This is necessary for testing config hooks since
     * they are only triggered when a config is read from
     * its intended storage medium
     */
    static int write_config_file(void)
    {
    	int i;
    	FILE *config_file;
    	char filename[PATH_MAX];
    
    	snprintf(filename, sizeof(filename), "%s/%s",
    			ast_config_AST_CONFIG_DIR, CONFIG_FILE);
    	config_file = fopen(filename, "w");
    
    	if (!config_file) {
    		return -1;
    	}
    
    	for (i = 0; i < ARRAY_LEN(categories); ++i) {
    		int j;
    		fprintf(config_file, "[%s]\n", categories[i].category);
    		for (j = 0; j < ARRAY_LEN(categories[i].vars); ++j) {
    			fprintf(config_file, "%s = %s\n",
    					categories[i].vars[j].name,
    					categories[i].vars[j].val);
    		}
    	}
    
    	fclose(config_file);
    	return 0;
    }
    
    /*!
     * \brief Delete config file created by write_config_file
     */
    static void delete_config_file(void)
    {
    	char filename[PATH_MAX];
    	snprintf(filename, sizeof(filename), "%s/%s",
    			ast_config_AST_CONFIG_DIR, CONFIG_FILE);
    	unlink(filename);
    }
    
    /*
     * Boolean to indicate if the config hook has run
     */
    static int hook_run;
    
    /*
     * Boolean to indicate if, when the hook runs, the
     * data passed to it is what is expected
     */
    static int hook_config_sane;
    
    static int hook_cb(struct ast_config *cfg)
    {
    	hook_run = 1;
    	if (test_config_validity(cfg) == 0) {
    		hook_config_sane = 1;
    	}
    	ast_config_destroy(cfg);
    	return 0;
    }
    
    
    AST_TEST_DEFINE(config_save)
    {
    	enum ast_test_result_state res = AST_TEST_FAIL;
    	struct ast_flags config_flags = { 0 };
    	struct ast_config *cfg;
    	char config_filename[PATH_MAX];
    	char include_filename[PATH_MAX];
    	struct stat config_stat;
    	off_t before_save;
    
    	switch (cmd) {
    	case TEST_INIT:
    		info->name = "config_save";
    		info->category = "/main/config/";
    		info->summary = "Test config save";
    		info->description =
    			"Test configuration save.";
    		return AST_TEST_NOT_RUN;
    	case TEST_EXECUTE:
    		break;
    	}
    
    	if (write_config_file()) {
    		ast_test_status_update(test, "Could not write initial config files\n");
    		return res;
    	}
    
    	snprintf(config_filename, PATH_MAX, "%s/%s", ast_config_AST_CONFIG_DIR, CONFIG_FILE);
    	snprintf(include_filename, PATH_MAX, "%s/%s", ast_config_AST_CONFIG_DIR, CONFIG_INCLUDE_FILE);
    
    	cfg = ast_config_load(CONFIG_FILE, config_flags);
    	if (!cfg) {
    		ast_test_status_update(test, "Could not load config\n");
    		goto out;
    	}
    
    	/* We need to re-save to get the generator header */
    	if (ast_config_text_file_save(CONFIG_FILE, cfg, "TEST")) {
    		ast_test_status_update(test, "Unable to write files\n");
    		goto out;
    	}
    
    	stat(config_filename, &config_stat);
    	before_save = config_stat.st_size;
    
    	if (!ast_include_new(cfg, CONFIG_FILE, CONFIG_INCLUDE_FILE, 0, NULL, 4, include_filename, PATH_MAX)) {
    		ast_test_status_update(test, "Could not create include\n");
    		goto out;
    	}
    
    	if (ast_config_text_file_save(CONFIG_FILE, cfg, "TEST")) {
    		ast_test_status_update(test, "Unable to write files\n");
    		goto out;
    	}
    
    	stat(config_filename, &config_stat);
    	if (config_stat.st_size <= before_save) {
    		ast_test_status_update(test, "Did not save config file with #include\n");
    		goto out;
    	}
    
    	res = AST_TEST_PASS;
    
    out:
    	ast_config_destroy(cfg);
    	unlink(config_filename);
    	unlink(include_filename);
    
    	return res;
    }
    
    
    AST_TEST_DEFINE(config_hook)
    {
    	enum ast_test_result_state res = AST_TEST_FAIL;
    	enum config_hook_flags hook_flags = { 0, };
    
    	struct ast_flags config_flags = { 0 };
    	struct ast_flags reload_flags = { CONFIG_FLAG_FILEUNCHANGED };
    
    	struct ast_config *cfg;
    
    	switch (cmd) {
    	case TEST_INIT:
    		info->name = "config_hook";
    		info->category = "/main/config/";
    		info->summary = "Test config hooks";
    		info->description =
    			"Ensure that config hooks are called at approriate times,"
    			"not called at inappropriate times, and that all information"
    			"that should be present is present.";
    		return AST_TEST_NOT_RUN;
    	case TEST_EXECUTE:
    		break;
    	}
    
    	write_config_file();
    
    	/*
    	 * Register a config hook to run when CONFIG_FILE is loaded by this module
    	 */
    	ast_config_hook_register("test_hook",
    			CONFIG_FILE,
    			AST_MODULE,
    			hook_flags,
    			hook_cb);
    
    	/*
    	 * Try loading the config file. This should result in the hook
    	 * being called
    	 */
    	cfg = ast_config_load(CONFIG_FILE, config_flags);
    	ast_config_destroy(cfg);
    	if (!hook_run || !hook_config_sane) {
    		ast_test_status_update(test, "Config hook either did not run or was given bad data!\n");
    		goto out;
    	}
    
    	/*