]> git.ipfire.org Git - thirdparty/asterisk.git/commitdiff
Add unit tests for func_presencestate and config hooks.
authorMark Michelson <mmichelson@digium.com>
Thu, 5 Apr 2012 17:48:47 +0000 (17:48 +0000)
committerMark Michelson <mmichelson@digium.com>
Thu, 5 Apr 2012 17:48:47 +0000 (17:48 +0000)
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
tests/test_config.c

index a8c2b3b88b19ec28471e4ee3a2466752bb39deb0..da4af32d1225b762816ac32e01ad9716834ffd1d 100644 (file)
@@ -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 <semaphore.h>
+#endif
 
 /*** DOCUMENTATION
        <function name="PRESENCE_STATE" language="en_US">
@@ -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;
 }
index c4f9698c2948aaeb8cbb55de52f180ba9ac2e6b3..07bf95ffddd1db846f52d4061ed566c4da2db2a4 100644 (file)
@@ -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;
 }