From ec341a2b6c47398c943c84c520eecea3ce9a3e18 Mon Sep 17 00:00:00 2001 From: Mark Michelson Date: Thu, 5 Apr 2012 17:48:47 +0000 Subject: [PATCH] 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. git-svn-id: https://origsvn.digium.com/svn/asterisk/branches/10-digiumphones@361293 65c4cc65-6c06-0410-ace0-fbb531ad65f3 --- funcs/func_presencestate.c | 302 +++++++++++++++++++++++++++++++++++++ tests/test_config.c | 238 ++++++++++++++++++++++++++--- 2 files changed, 519 insertions(+), 21 deletions(-) diff --git a/funcs/func_presencestate.c b/funcs/func_presencestate.c index a8c2b3b88b..da4af32d12 100644 --- a/funcs/func_presencestate.c +++ b/funcs/func_presencestate.c @@ -35,6 +35,11 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$") #include "asterisk/cli.h" #include "asterisk/astdb.h" #include "asterisk/app.h" +#ifdef TEST_FRAMEWORK +#include "asterisk/test.h" +#include "asterisk/event.h" +#include +#endif /*** DOCUMENTATION @@ -267,12 +272,304 @@ static struct ast_custom_function presence_function = { .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 @@ static int load_module(void) 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; } diff --git a/tests/test_config.c b/tests/test_config.c index c4f9698c29..07bf95ffdd 100644 --- a/tests/test_config.c +++ b/tests/test_config.c @@ -36,7 +36,24 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$"); #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 @@ struct association { }, }; +/*! + * \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 @@ fail: 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,45 +206,185 @@ AST_TEST_DEFINE(copy_config) goto out; } - /* Okay, let's see if the correct content is there */ + if (test_config_validity(copy) != 0) { + goto out; + } + + res = AST_TEST_PASS; + +out: + ast_config_destroy(cfg); + ast_config_destroy(copy); + 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) { - 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; - } + int j; + fprintf(config_file, "[%s]\n", categories[i].category); 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; - } + 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: - ast_config_destroy(cfg); - ast_config_destroy(copy); + 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; } -- 2.47.2