[asterisk-commits] mmichelson: branch 10-digiumphones r361293 - in /branches/10-digiumphones: fu...

SVN commits to the Asterisk project asterisk-commits at lists.digium.com
Thu Apr 5 12:48:50 CDT 2012


Author: mmichelson
Date: Thu Apr  5 12:48:47 2012
New Revision: 361293

URL: http://svnview.digium.com/svn/asterisk?view=rev&rev=361293
Log:
Add unit tests for func_presencestate and config hooks.

These tests were originally written for the trunk merge of Digium
phone support to Asterisk, but this branch can benefit from these
tests too.


Modified:
    branches/10-digiumphones/funcs/func_presencestate.c
    branches/10-digiumphones/tests/test_config.c

Modified: branches/10-digiumphones/funcs/func_presencestate.c
URL: http://svnview.digium.com/svn/asterisk/branches/10-digiumphones/funcs/func_presencestate.c?view=diff&rev=361293&r1=361292&r2=361293
==============================================================================
--- branches/10-digiumphones/funcs/func_presencestate.c (original)
+++ branches/10-digiumphones/funcs/func_presencestate.c Thu Apr  5 12:48:47 2012
@@ -35,6 +35,11 @@
 #include "asterisk/cli.h"
 #include "asterisk/astdb.h"
 #include "asterisk/app.h"
+#ifdef TEST_FRAMEWORK
+#include "asterisk/test.h"
+#include "asterisk/event.h"
+#include <semaphore.h>
+#endif
 
 /*** DOCUMENTATION
 	<function name="PRESENCE_STATE" language="en_US">
@@ -267,12 +272,304 @@
 	.write = presence_write,
 };
 
+#ifdef TEST_FRAMEWORK
+
+struct test_string {
+	char *parse_string;
+	struct {
+		int value;
+		const char *subtype;
+		const char *message;
+		const char *options; 
+	} outputs;
+};
+
+AST_TEST_DEFINE(test_valid_parse_data)
+{
+	int i;
+	int state;
+	char *subtype;
+	char *message;
+	char *options;
+	enum ast_test_result_state res = AST_TEST_PASS;
+	
+	struct test_string tests [] = {
+		{ "away",
+			{ AST_PRESENCE_AWAY,
+				"",
+				"",
+				""
+			}
+		},
+		{ "not_set",
+			{ AST_PRESENCE_NOT_SET,
+				"",
+				"",
+				""
+			}
+		},
+		{ "unavailable",
+			{ AST_PRESENCE_UNAVAILABLE,
+				"",
+				"",
+				""
+			}
+		},
+		{ "available",
+			{ AST_PRESENCE_AVAILABLE,
+				"",
+				"",
+				""
+			}
+		},
+		{ "xa",
+			{ AST_PRESENCE_XA,
+				"",
+				"",
+				""
+			}
+		},
+		{ "chat",
+			{ AST_PRESENCE_CHAT,
+				"",
+				"",
+				""
+			}
+		},
+		{ "dnd",
+			{ AST_PRESENCE_DND,
+				"",
+				"",
+				""
+			}
+		},
+		{ "away,down the hall",
+			{ AST_PRESENCE_AWAY,
+				"down the hall",
+				"",
+				""
+			}
+		},
+		{ "away,down the hall,Quarterly financial meeting",
+			{ AST_PRESENCE_AWAY,
+				"down the hall",
+				"Quarterly financial meeting",
+				""
+			}
+		},
+		{ "away,,Quarterly financial meeting",
+			{ AST_PRESENCE_AWAY,
+				"",
+				"Quarterly financial meeting",
+				""
+			}
+		},
+		{ "away,,,e",
+			{ AST_PRESENCE_AWAY,
+				"",
+				"",
+				"e",
+			}
+		},
+		{ "away,down the hall,,e",
+			{ AST_PRESENCE_AWAY,
+				"down the hall",
+				"",
+				"e"
+			}
+		},
+		{ "away,down the hall,Quarterly financial meeting,e",
+			{ AST_PRESENCE_AWAY,
+				"down the hall",
+				"Quarterly financial meeting",
+				"e"
+			}
+		},
+		{ "away,,Quarterly financial meeting,e",
+			{ AST_PRESENCE_AWAY,
+				"",
+				"Quarterly financial meeting",
+				"e"
+			}
+		}
+	};
+
+	switch (cmd) {
+	case TEST_INIT:
+		info->name = "parse_valid_presence_data";
+		info->category = "/funcs/func_presence";
+		info->summary = "PRESENCESTATE parsing test";
+		info->description =
+			"Ensure that parsing function accepts proper values, and gives proper outputs";
+		return AST_TEST_NOT_RUN;
+	case TEST_EXECUTE:
+		break;
+	}
+
+	for (i = 0; i < ARRAY_LEN(tests); ++i) {
+		int parse_result;
+		char *parse_string = ast_strdup(tests[i].parse_string);
+		if (!parse_string) {
+			res = AST_TEST_FAIL;
+			break;
+		}
+		parse_result = parse_data(parse_string, &state, &subtype, &message, &options);
+		if (parse_result == -1) {
+			res = AST_TEST_FAIL;
+			ast_free(parse_string);
+			break;
+		}
+		if (tests[i].outputs.value != state ||
+				strcmp(tests[i].outputs.subtype, subtype) ||
+				strcmp(tests[i].outputs.message, message) ||
+				strcmp(tests[i].outputs.options, options)) {
+			res = AST_TEST_FAIL;
+			ast_free(parse_string);
+			break;
+		}
+		ast_free(parse_string);
+	}
+
+	return res;
+}
+
+AST_TEST_DEFINE(test_invalid_parse_data)
+{
+	int i;
+	int state;
+	char *subtype;
+	char *message;
+	char *options;
+	enum ast_test_result_state res = AST_TEST_PASS;
+
+	char *tests[] = {
+		"",
+		"bored",
+		"away,,,i",
+		/* XXX The following actually is parsed correctly. Should that
+		 * be changed?
+		 * "away,,,,e",
+		 */
+	};
+
+	switch (cmd) {
+	case TEST_INIT:
+		info->name = "parse_invalid_presence_data";
+		info->category = "/funcs/func_presence";
+		info->summary = "PRESENCESTATE parsing test";
+		info->description =
+			"Ensure that parsing function rejects improper values";
+		return AST_TEST_NOT_RUN;
+	case TEST_EXECUTE:
+		break;
+	}
+
+	for (i = 0; i < ARRAY_LEN(tests); ++i) {
+		int parse_result;
+		char *parse_string = ast_strdup(tests[i]);
+		if (!parse_string) {
+			res = AST_TEST_FAIL;
+			break;
+		}
+		printf("parse string is %s\n", parse_string);
+		parse_result = parse_data(parse_string, &state, &subtype, &message, &options);
+		if (parse_result == 0) {
+			res = AST_TEST_FAIL;
+			ast_free(parse_string);
+			break;
+		}
+		ast_free(parse_string);
+	}
+
+	return res;
+}
+
+struct test_cb_data {
+	enum ast_presence_state presence;
+	const char *provider;
+	const char *subtype;
+	const char *message;
+	/* That's right. I'm using a semaphore */
+	sem_t sem;
+};
+
+static void test_cb(const struct ast_event *event, void *userdata)
+{
+	struct test_cb_data *cb_data = userdata;
+	cb_data->presence = ast_event_get_ie_uint(event, AST_EVENT_IE_PRESENCE_STATE);
+	cb_data->provider = ast_strdup(ast_event_get_ie_str(event, AST_EVENT_IE_PRESENCE_PROVIDER));
+	cb_data->subtype = ast_strdup(ast_event_get_ie_str(event, AST_EVENT_IE_PRESENCE_SUBTYPE));
+	cb_data->message = ast_strdup(ast_event_get_ie_str(event, AST_EVENT_IE_PRESENCE_MESSAGE));
+	sem_post(&cb_data->sem);
+	ast_log(LOG_NOTICE, "Callback called\n");
+}
+
+/* XXX This test could probably stand to be moved since
+ * it does not test func_presencestate but rather code in
+ * presencestate.h and presencestate.c. However, the convenience
+ * of presence_write() makes this a nice location for this test.
+ */
+AST_TEST_DEFINE(test_presence_state_change)
+{
+	struct ast_event_sub *test_sub;
+	struct test_cb_data *cb_data;
+
+	switch (cmd) {
+	case TEST_INIT:
+		info->name = "test_presence_state_change";
+		info->category = "/funcs/func_presence";
+		info->summary = "presence state change subscription";
+		info->description =
+			"Ensure that presence state changes are communicated to subscribers";
+		return AST_TEST_NOT_RUN;
+	case TEST_EXECUTE:
+		break;
+	}
+
+	cb_data = ast_calloc(1, sizeof(*cb_data));
+	if (!cb_data) {
+		return AST_TEST_FAIL;
+	}
+
+	if (!(test_sub = ast_event_subscribe(AST_EVENT_PRESENCE_STATE,
+			test_cb, "Test presence state callbacks", cb_data, AST_EVENT_IE_END))) {
+		return AST_TEST_FAIL;
+	}
+
+	if (sem_init(&cb_data->sem, 0, 0)) {
+		return AST_TEST_FAIL;
+	}
+
+	presence_write(NULL, "PRESENCESTATE", "CustomPresence:Bob", "away,down the hall,Quarterly financial meeting");
+	sem_wait(&cb_data->sem);
+	if (cb_data->presence != AST_PRESENCE_AWAY ||
+			strcmp(cb_data->provider, "CustomPresence:Bob") ||
+			strcmp(cb_data->subtype, "down the hall") ||
+			strcmp(cb_data->message, "Quarterly financial meeting")) {
+		return AST_TEST_FAIL;
+	}
+
+	ast_free((char *)cb_data->provider);
+	ast_free((char *)cb_data->subtype);
+	ast_free((char *)cb_data->message);
+	ast_free((char *)cb_data);
+
+	return AST_TEST_PASS;
+}
+
+#endif
+
 static int unload_module(void)
 {
 	int res = 0;
 
 	res |= ast_custom_function_unregister(&presence_function);
 	res |= ast_presence_state_prov_del("CustomPresence");
+#ifdef TEST_FRAMEWORK
+	AST_TEST_UNREGISTER(test_valid_parse_data);
+	AST_TEST_UNREGISTER(test_invalid_parse_data);
+	AST_TEST_UNREGISTER(test_presence_state_change);
+#endif
 	return res;
 }
 
@@ -282,6 +579,11 @@
 
 	res |= ast_custom_function_register(&presence_function);
 	res |= ast_presence_state_prov_add("CustomPresence", custom_presence_callback);
+#ifdef TEST_FRAMEWORK
+	AST_TEST_REGISTER(test_valid_parse_data);
+	AST_TEST_REGISTER(test_invalid_parse_data);
+	AST_TEST_REGISTER(test_presence_state_change);
+#endif
 
 	return res;
 }

Modified: branches/10-digiumphones/tests/test_config.c
URL: http://svnview.digium.com/svn/asterisk/branches/10-digiumphones/tests/test_config.c?view=diff&rev=361293&r1=361292&r2=361293
==============================================================================
--- branches/10-digiumphones/tests/test_config.c (original)
+++ branches/10-digiumphones/tests/test_config.c Thu Apr  5 12:48:47 2012
@@ -36,7 +36,24 @@
 #include "asterisk/config.h"
 #include "asterisk/module.h"
 #include "asterisk/test.h"
-
+#include "asterisk/paths.h"
+
+#define CONFIG_FILE "test_config.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";
@@ -78,6 +95,12 @@
 	},
 };
 
+/*!
+ * \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;
@@ -120,13 +143,46 @@
 	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;
-	const char *cat_iter = NULL;
-	size_t i;
 
 	switch (cmd) {
 	case TEST_INIT:
@@ -150,26 +206,8 @@
 		goto out;
 	}
 
-	/* 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(copy, 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);
-			goto out;
-		}
-		for (j = 0; j < ARRAY_LEN(categories[i].vars); ++j) {
-			var = var ? var->next : ast_variable_browse(copy, 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);
-				goto out;
-			}
-			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);
-				goto out;
-			}
-		}
+	if (test_config_validity(copy) != 0) {
+		goto out;
 	}
 
 	res = AST_TEST_PASS;
@@ -180,15 +218,173 @@
 	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_hook)
+{
+	enum ast_test_result_state res = AST_TEST_FAIL;
+	enum config_hook_flags hook_flags = { 0, };
+	struct ast_flags config_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;
+	}
+
+	/*
+	 * Now try loading the wrong config file but from the right module.
+	 * Hook should not run
+	 */
+	hook_run = 0;
+	cfg = ast_config_load("asterisk.conf", config_flags);
+	ast_config_destroy(cfg);
+	if (hook_run) {
+		ast_test_status_update(test, "Config hook ran even though an incorrect file was specified.\n");
+		goto out;
+	}
+
+	/*
+	 * Now try loading the correct config file but from the wrong module.
+	 * Hook should not run
+	 */
+	hook_run = 0;
+	cfg = ast_config_load2(CONFIG_FILE, "fake_module.so", config_flags);
+	ast_config_destroy(cfg);
+	if (hook_run) {
+		ast_test_status_update(test, "Config hook ran even though an incorrect module was specified.\n");
+		goto out;
+	}
+
+	/*
+	 * Now try loading the file correctly, but without any changes to the file.
+	 * Hook should not run
+	 */
+	hook_run = 0;
+	cfg = ast_config_load(CONFIG_FILE, config_flags);
+	/* Only destroy this cfg conditionally. Otherwise a crash happens. */
+	if (cfg != CONFIG_STATUS_FILEUNCHANGED) {
+		ast_config_destroy(cfg);
+	}
+	if (hook_run) {
+		ast_test_status_update(test, "Config hook ran even though file contents had not changed\n");
+		goto out;
+	}
+
+	res = AST_TEST_PASS;
+
+out:
+	delete_config_file();
+	return res;
+}
+
 static int unload_module(void)
 {
 	AST_TEST_UNREGISTER(copy_config);
+	AST_TEST_UNREGISTER(config_hook);
 	return 0;
 }
 
 static int load_module(void)
 {
 	AST_TEST_REGISTER(copy_config);
+	AST_TEST_REGISTER(config_hook);
 	return AST_MODULE_LOAD_SUCCESS;
 }
 




More information about the asterisk-commits mailing list