Thu Apr 28 2011 17:13:36

Asterisk developer's documentation


test.c

Go to the documentation of this file.
00001 /*
00002  * Asterisk -- An open source telephony toolkit.
00003  *
00004  * Copyright (C) 2009-2010, Digium, Inc.
00005  *
00006  * David Vossel <dvossel@digium.com>
00007  * Russell Bryant <russell@digium.com>
00008  *
00009  * See http://www.asterisk.org for more information about
00010  * the Asterisk project. Please do not directly contact
00011  * any of the maintainers of this project for assistance;
00012  * the project provides a web site, mailing lists and IRC
00013  * channels for your use.
00014  *
00015  * This program is free software, distributed under the terms of
00016  * the GNU General Public License Version 2. See the LICENSE file
00017  * at the top of the source tree.
00018  */
00019 
00020 /*!
00021  * \file
00022  * \brief Unit Test Framework
00023  *
00024  * \author David Vossel <dvossel@digium.com>
00025  * \author Russell Bryant <russell@digium.com>
00026  */
00027 
00028 #include "asterisk.h"
00029 
00030 ASTERISK_FILE_VERSION(__FILE__, "$Revision: 286024 $");
00031 
00032 #include "asterisk/_private.h"
00033 
00034 #ifdef TEST_FRAMEWORK
00035 #include "asterisk/test.h"
00036 #include "asterisk/logger.h"
00037 #include "asterisk/linkedlists.h"
00038 #include "asterisk/utils.h"
00039 #include "asterisk/cli.h"
00040 #include "asterisk/term.h"
00041 #include "asterisk/version.h"
00042 #include "asterisk/paths.h"
00043 #include "asterisk/time.h"
00044 
00045 /*! This array corresponds to the values defined in the ast_test_state enum */
00046 static const char * const test_result2str[] = {
00047    [AST_TEST_NOT_RUN] = "NOT RUN",
00048    [AST_TEST_PASS]    = "PASS",
00049    [AST_TEST_FAIL]    = "FAIL",
00050 };
00051 
00052 /*! holds all the information pertaining to a single defined test */
00053 struct ast_test {
00054    struct ast_test_info info;        /*!< holds test callback information */
00055    /*!
00056     * \brief Test defined status output from last execution
00057     */
00058    struct ast_str *status_str;
00059    /*!
00060     * \brief CLI arguments, if tests being run from the CLI
00061     *
00062     * If this is set, status updates from the tests will be sent to the
00063     * CLI in addition to being saved off in status_str.
00064     */
00065    struct ast_cli_args *cli;
00066    enum ast_test_result_state state; /*!< current test state */
00067    unsigned int time;                /*!< time in ms test took */
00068    ast_test_cb_t *cb;                /*!< test callback function */
00069    AST_LIST_ENTRY(ast_test) entry;
00070 };
00071 
00072 /*! global structure containing both total and last test execution results */
00073 static struct ast_test_execute_results {
00074    unsigned int total_tests;  /*!< total number of tests, regardless if they have been executed or not */
00075    unsigned int total_passed; /*!< total number of executed tests passed */
00076    unsigned int total_failed; /*!< total number of executed tests failed */
00077    unsigned int total_time;   /*!< total time of all executed tests */
00078    unsigned int last_passed;  /*!< number of passed tests during last execution */
00079    unsigned int last_failed;  /*!< number of failed tests during last execution */
00080    unsigned int last_time;    /*!< total time of the last test execution */
00081 } last_results;
00082 
00083 enum test_mode {
00084    TEST_ALL = 0,
00085    TEST_CATEGORY = 1,
00086    TEST_NAME_CATEGORY = 2,
00087 };
00088 
00089 /*! List of registered test definitions */
00090 static AST_LIST_HEAD_STATIC(tests, ast_test);
00091 
00092 static struct ast_test *test_alloc(ast_test_cb_t *cb);
00093 static struct ast_test *test_free(struct ast_test *test);
00094 static int test_insert(struct ast_test *test);
00095 static struct ast_test *test_remove(ast_test_cb_t *cb);
00096 static int test_cat_cmp(const char *cat1, const char *cat2);
00097 
00098 int __ast_test_status_update(const char *file, const char *func, int line,
00099       struct ast_test *test, const char *fmt, ...)
00100 {
00101    struct ast_str *buf = NULL;
00102    va_list ap;
00103 
00104    if (!(buf = ast_str_create(128))) {
00105       return -1;
00106    }
00107 
00108    va_start(ap, fmt);
00109    ast_str_set_va(&buf, 0, fmt, ap);
00110    va_end(ap);
00111 
00112    if (test->cli) {
00113       ast_cli(test->cli->fd, "[%s:%s:%d]: %s",
00114             file, func, line, ast_str_buffer(buf));
00115    }
00116 
00117    ast_str_append(&test->status_str, 0, "[%s:%s:%d]: %s",
00118          file, func, line, ast_str_buffer(buf));
00119 
00120    ast_free(buf);
00121 
00122    return 0;
00123 }
00124 
00125 int ast_test_register(ast_test_cb_t *cb)
00126 {
00127    struct ast_test *test;
00128 
00129    if (!cb) {
00130       ast_log(LOG_WARNING, "Attempted to register test without all required information\n");
00131       return -1;
00132    }
00133 
00134    if (!(test = test_alloc(cb))) {
00135       return -1;
00136    }
00137 
00138    if (test_insert(test)) {
00139       test_free(test);
00140       return -1;
00141    }
00142 
00143    return 0;
00144 }
00145 
00146 int ast_test_unregister(ast_test_cb_t *cb)
00147 {
00148    struct ast_test *test;
00149 
00150    if (!(test = test_remove(cb))) {
00151       return -1; /* not found */
00152    }
00153 
00154    test_free(test);
00155 
00156    return 0;
00157 }
00158 
00159 /*!
00160  * \internal
00161  * \brief executes a single test, storing the results in the test->result structure.
00162  *
00163  * \note The last_results structure which contains global statistics about test execution
00164  * must be updated when using this function. See use in test_execute_multiple().
00165  */
00166 static void test_execute(struct ast_test *test)
00167 {
00168    struct timeval begin;
00169 
00170    ast_str_reset(test->status_str);
00171 
00172    begin = ast_tvnow();
00173    test->state = test->cb(&test->info, TEST_EXECUTE, test);
00174    test->time = ast_tvdiff_ms(ast_tvnow(), begin);
00175 }
00176 
00177 static void test_xml_entry(struct ast_test *test, FILE *f)
00178 {
00179    if (!f || !test || test->state == AST_TEST_NOT_RUN) {
00180       return;
00181    }
00182 
00183    fprintf(f, "\t<testcase time=\"%d.%d\" name=\"%s%s\"%s>\n",
00184          test->time / 1000, test->time % 1000,
00185          test->info.category, test->info.name,
00186          test->state == AST_TEST_PASS ? "/" : "");
00187 
00188    if (test->state == AST_TEST_FAIL) {
00189       fprintf(f, "\t\t<failure><![CDATA[\n%s\n\t\t]]></failure>\n",
00190             S_OR(ast_str_buffer(test->status_str), "NA"));
00191       fprintf(f, "\t</testcase>\n");
00192    }
00193 
00194 }
00195 
00196 static void test_txt_entry(struct ast_test *test, FILE *f)
00197 {
00198    if (!f || !test) {
00199       return;
00200    }
00201 
00202    fprintf(f, "\nName:              %s\n", test->info.name);
00203    fprintf(f,   "Category:          %s\n", test->info.category);
00204    fprintf(f,   "Summary:           %s\n", test->info.summary);
00205    fprintf(f,   "Description:       %s\n", test->info.description);
00206    fprintf(f,   "Result:            %s\n", test_result2str[test->state]);
00207    if (test->state != AST_TEST_NOT_RUN) {
00208       fprintf(f,   "Time:              %d\n", test->time);
00209    }
00210    if (test->state == AST_TEST_FAIL) {
00211       fprintf(f,   "Error Description: %s\n\n", S_OR(ast_str_buffer(test->status_str), "NA"));
00212    }
00213 }
00214 
00215 /*!
00216  * \internal
00217  * \brief Executes registered unit tests
00218  *
00219  * \param name of test to run (optional)
00220  * \param test category to run (optional)
00221  * \param cli args for cli test updates (optional)
00222  *
00223  * \return number of tests executed.
00224  *
00225  * \note This function has three modes of operation
00226  * -# When given a name and category, a matching individual test will execute if found.
00227  * -# When given only a category all matching tests within that category will execute.
00228  * -# If given no name or category all registered tests will execute.
00229  */
00230 static int test_execute_multiple(const char *name, const char *category, struct ast_cli_args *cli)
00231 {
00232    char result_buf[32] = { 0 };
00233    struct ast_test *test = NULL;
00234    enum test_mode mode = TEST_ALL; /* 3 modes, 0 = run all, 1 = only by category, 2 = only by name and category */
00235    int execute = 0;
00236    int res = 0;
00237 
00238    if (!ast_strlen_zero(category)) {
00239       if (!ast_strlen_zero(name)) {
00240          mode = TEST_NAME_CATEGORY;
00241       } else {
00242          mode = TEST_CATEGORY;
00243       }
00244    }
00245 
00246    AST_LIST_LOCK(&tests);
00247    /* clear previous execution results */
00248    memset(&last_results, 0, sizeof(last_results));
00249    AST_LIST_TRAVERSE(&tests, test, entry) {
00250 
00251       execute = 0;
00252       switch (mode) {
00253       case TEST_CATEGORY:
00254          if (!test_cat_cmp(test->info.category, category)) {
00255             execute = 1;
00256          }
00257          break;
00258       case TEST_NAME_CATEGORY:
00259          if (!(test_cat_cmp(test->info.category, category)) && !(strcmp(test->info.name, name))) {
00260             execute = 1;
00261          }
00262          break;
00263       case TEST_ALL:
00264          execute = 1;
00265       }
00266 
00267       if (execute) {
00268          if (cli) {
00269             ast_cli(cli->fd, "START  %s - %s \n", test->info.category, test->info.name);
00270          }
00271 
00272          /* set the test status update argument. it is ok if cli is NULL */
00273          test->cli = cli;
00274 
00275          /* execute the test and save results */
00276          test_execute(test);
00277 
00278          test->cli = NULL;
00279 
00280          /* update execution specific counts here */
00281          last_results.last_time += test->time;
00282          if (test->state == AST_TEST_PASS) {
00283             last_results.last_passed++;
00284          } else if (test->state == AST_TEST_FAIL) {
00285             last_results.last_failed++;
00286          }
00287 
00288          if (cli) {
00289             term_color(result_buf,
00290                test_result2str[test->state],
00291                (test->state == AST_TEST_FAIL) ? COLOR_RED : COLOR_GREEN,
00292                0,
00293                sizeof(result_buf));
00294             ast_cli(cli->fd, "END    %s - %s Time: %s%dms Result: %s\n",
00295                test->info.category,
00296                test->info.name,
00297                test->time ? "" : "<",
00298                test->time ? test->time : 1,
00299                result_buf);
00300          }
00301       }
00302 
00303       /* update total counts as well during this iteration
00304        * even if the current test did not execute this time */
00305       last_results.total_time += test->time;
00306       last_results.total_tests++;
00307       if (test->state != AST_TEST_NOT_RUN) {
00308          if (test->state == AST_TEST_PASS) {
00309             last_results.total_passed++;
00310          } else {
00311             last_results.total_failed++;
00312          }
00313       }
00314    }
00315    res = last_results.last_passed + last_results.last_failed;
00316    AST_LIST_UNLOCK(&tests);
00317 
00318    return res;
00319 }
00320 
00321 /*!
00322  * \internal
00323  * \brief Generate test results.
00324  *
00325  * \param name of test result to generate (optional)
00326  * \param test category to generate (optional)
00327  * \param path to xml file to generate. (optional)
00328  * \param path to txt file to generate, (optional)
00329  *
00330  * \retval 0 success
00331  * \retval -1 failure
00332  *
00333  * \note This function has three modes of operation.
00334  * -# When given both a name and category, results will be generated for that single test.
00335  * -# When given only a category, results for every test within the category will be generated.
00336  * -# When given no name or category, results for every registered test will be generated.
00337  *
00338  * In order for the results to be generated, an xml and or txt file path must be provided.
00339  */
00340 static int test_generate_results(const char *name, const char *category, const char *xml_path, const char *txt_path)
00341 {
00342    enum test_mode mode = TEST_ALL;  /* 0 generate all, 1 generate by category only, 2 generate by name and category */
00343    FILE *f_xml = NULL, *f_txt = NULL;
00344    int res = 0;
00345    struct ast_test *test = NULL;
00346 
00347    /* verify at least one output file was given */
00348    if (ast_strlen_zero(xml_path) && ast_strlen_zero(txt_path)) {
00349       return -1;
00350    }
00351 
00352    /* define what mode is to be used */
00353    if (!ast_strlen_zero(category)) {
00354       if (!ast_strlen_zero(name)) {
00355          mode = TEST_NAME_CATEGORY;
00356       } else {
00357          mode = TEST_CATEGORY;
00358       }
00359    }
00360    /* open files for writing */
00361    if (!ast_strlen_zero(xml_path)) {
00362       if (!(f_xml = fopen(xml_path, "w"))) {
00363          ast_log(LOG_WARNING, "Could not open file %s for xml test results\n", xml_path);
00364          res = -1;
00365          goto done;
00366       }
00367    }
00368    if (!ast_strlen_zero(txt_path)) {
00369       if (!(f_txt = fopen(txt_path, "w"))) {
00370          ast_log(LOG_WARNING, "Could not open file %s for text output of test results\n", txt_path);
00371          res = -1;
00372          goto done;
00373       }
00374    }
00375 
00376    AST_LIST_LOCK(&tests);
00377    /* xml header information */
00378    if (f_xml) {
00379       /*
00380        * http://confluence.atlassian.com/display/BAMBOO/JUnit+parsing+in+Bamboo
00381        */
00382       fprintf(f_xml, "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
00383       fprintf(f_xml, "<testsuite errors=\"0\" time=\"%d.%d\" tests=\"%d\" "
00384             "name=\"AsteriskUnitTests\">\n",
00385             last_results.total_time / 1000, last_results.total_time % 1000,
00386             last_results.total_tests);
00387       fprintf(f_xml, "\t<properties>\n");
00388       fprintf(f_xml, "\t\t<property name=\"version\" value=\"%s\"/>\n", ASTERISK_VERSION);
00389       fprintf(f_xml, "\t</properties>\n");
00390    }
00391 
00392    /* txt header information */
00393    if (f_txt) {
00394       fprintf(f_txt, "Asterisk Version:         %s\n", ASTERISK_VERSION);
00395       fprintf(f_txt, "Asterisk Version Number:  %d\n", ASTERISK_VERSION_NUM);
00396       fprintf(f_txt, "Number of Tests:          %d\n", last_results.total_tests);
00397       fprintf(f_txt, "Number of Tests Executed: %d\n", (last_results.total_passed + last_results.total_failed));
00398       fprintf(f_txt, "Passed Tests:             %d\n", last_results.total_passed);
00399       fprintf(f_txt, "Failed Tests:             %d\n", last_results.total_failed);
00400       fprintf(f_txt, "Total Execution Time:     %d\n", last_results.total_time);
00401    }
00402 
00403    /* export each individual test */
00404    AST_LIST_TRAVERSE(&tests, test, entry) {
00405       switch (mode) {
00406       case TEST_CATEGORY:
00407          if (!test_cat_cmp(test->info.category, category)) {
00408             test_xml_entry(test, f_xml);
00409             test_txt_entry(test, f_txt);
00410          }
00411          break;
00412       case TEST_NAME_CATEGORY:
00413          if (!(strcmp(test->info.category, category)) && !(strcmp(test->info.name, name))) {
00414             test_xml_entry(test, f_xml);
00415             test_txt_entry(test, f_txt);
00416          }
00417          break;
00418       case TEST_ALL:
00419          test_xml_entry(test, f_xml);
00420          test_txt_entry(test, f_txt);
00421       }
00422    }
00423    AST_LIST_UNLOCK(&tests);
00424 
00425 done:
00426    if (f_xml) {
00427       fprintf(f_xml, "</testsuite>\n");
00428       fclose(f_xml);
00429    }
00430    if (f_txt) {
00431       fclose(f_txt);
00432    }
00433 
00434    return res;
00435 }
00436 
00437 /*!
00438  * \internal
00439  * \brief adds test to container sorted first by category then by name
00440  *
00441  * \retval 0 success
00442  * \retval -1 failure
00443  */
00444 static int test_insert(struct ast_test *test)
00445 {
00446    /* This is a slow operation that may need to be optimized in the future
00447     * as the test framework expands.  At the moment we are doing string
00448     * comparisons on every item within the list to insert in sorted order. */
00449 
00450    AST_LIST_LOCK(&tests);
00451    AST_LIST_INSERT_SORTALPHA(&tests, test, entry, info.category);
00452    AST_LIST_UNLOCK(&tests);
00453 
00454    return 0;
00455 }
00456 
00457 /*!
00458  * \internal
00459  * \brief removes test from container
00460  *
00461  * \return ast_test removed from list on success, or NULL on failure
00462  */
00463 static struct ast_test *test_remove(ast_test_cb_t *cb)
00464 {
00465    struct ast_test *cur = NULL;
00466 
00467    AST_LIST_LOCK(&tests);
00468    AST_LIST_TRAVERSE_SAFE_BEGIN(&tests, cur, entry) {
00469       if (cur->cb == cb) {
00470          AST_LIST_REMOVE_CURRENT(entry);
00471          break;
00472       }
00473    }
00474    AST_LIST_TRAVERSE_SAFE_END;
00475    AST_LIST_UNLOCK(&tests);
00476 
00477    return cur;
00478 }
00479 
00480 /*!
00481  * \brief compares two test categories to determine if cat1 resides in cat2
00482  * \internal
00483  *
00484  * \retval 0 true
00485  * \retval non-zero false
00486  */
00487 
00488 static int test_cat_cmp(const char *cat1, const char *cat2)
00489 {
00490    int len1 = 0;
00491    int len2 = 0;
00492 
00493    if (!cat1 || !cat2) {
00494       return -1;
00495    }
00496 
00497    len1 = strlen(cat1);
00498    len2 = strlen(cat2);
00499 
00500    if (len2 > len1) {
00501       return -1;
00502    }
00503 
00504    return strncmp(cat1, cat2, len2) ? 1 : 0;
00505 }
00506 
00507 /*!
00508  * \internal
00509  * \brief free an ast_test object and all it's data members
00510  */
00511 static struct ast_test *test_free(struct ast_test *test)
00512 {
00513    if (!test) {
00514       return NULL;
00515    }
00516 
00517    ast_free(test->status_str);
00518    ast_free(test);
00519 
00520    return NULL;
00521 }
00522 
00523 /*!
00524  * \internal
00525  * \brief allocate an ast_test object.
00526  */
00527 static struct ast_test *test_alloc(ast_test_cb_t *cb)
00528 {
00529    struct ast_test *test;
00530 
00531    if (!cb || !(test = ast_calloc(1, sizeof(*test)))) {
00532       return NULL;
00533    }
00534 
00535    test->cb = cb;
00536 
00537    test->cb(&test->info, TEST_INIT, test);
00538 
00539    if (ast_strlen_zero(test->info.name)) {
00540       ast_log(LOG_WARNING, "Test has no name, test registration refused.\n");
00541       return test_free(test);
00542    }
00543 
00544    if (ast_strlen_zero(test->info.category)) {
00545       ast_log(LOG_WARNING, "Test %s has no category, test registration refused.\n",
00546             test->info.name);
00547       return test_free(test);
00548    }
00549 
00550    if (test->info.category[0] != '/' || test->info.category[strlen(test->info.category) - 1] != '/') {
00551       ast_log(LOG_WARNING, "Test category is missing a leading or trailing backslash for test %s%s\n",
00552             test->info.category, test->info.name);
00553    }
00554 
00555    if (ast_strlen_zero(test->info.summary)) {
00556       ast_log(LOG_WARNING, "Test %s/%s has no summary, test registration refused.\n",
00557             test->info.category, test->info.name);
00558       return test_free(test);
00559    }
00560 
00561    if (ast_strlen_zero(test->info.description)) {
00562       ast_log(LOG_WARNING, "Test %s/%s has no description, test registration refused.\n",
00563             test->info.category, test->info.name);
00564       return test_free(test);
00565    }
00566 
00567    if (!(test->status_str = ast_str_create(128))) {
00568       return test_free(test);
00569    }
00570 
00571    return test;
00572 }
00573 
00574 static char *complete_test_category(const char *line, const char *word, int pos, int state)
00575 {
00576    int which = 0;
00577    int wordlen = strlen(word);
00578    char *ret = NULL;
00579    struct ast_test *test;
00580 
00581    AST_LIST_LOCK(&tests);
00582    AST_LIST_TRAVERSE(&tests, test, entry) {
00583       if (!strncasecmp(word, test->info.category, wordlen) && ++which > state) {
00584          ret = ast_strdup(test->info.category);
00585          break;
00586       }
00587    }
00588    AST_LIST_UNLOCK(&tests);
00589    return ret;
00590 }
00591 
00592 static char *complete_test_name(const char *line, const char *word, int pos, int state, const char *category)
00593 {
00594    int which = 0;
00595    int wordlen = strlen(word);
00596    char *ret = NULL;
00597    struct ast_test *test;
00598 
00599    AST_LIST_LOCK(&tests);
00600    AST_LIST_TRAVERSE(&tests, test, entry) {
00601       if (!test_cat_cmp(test->info.category, category) && (!strncasecmp(word, test->info.name, wordlen) && ++which > state)) {
00602          ret = ast_strdup(test->info.name);
00603          break;
00604       }
00605    }
00606    AST_LIST_UNLOCK(&tests);
00607    return ret;
00608 }
00609 
00610 /* CLI commands */
00611 static char *test_cli_show_registered(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
00612 {
00613 #define FORMAT "%-25.25s %-30.30s %-40.40s %-13.13s\n"
00614    static char * const option1[] = { "all", "category", NULL };
00615    static char * const option2[] = { "name", NULL };
00616    struct ast_test *test = NULL;
00617    int count = 0;
00618    switch (cmd) {
00619    case CLI_INIT:
00620       e->command = "test show registered";
00621 
00622       e->usage =
00623          "Usage: 'test show registered' can be used in three ways.\n"
00624          "       1. 'test show registered all' shows all registered tests\n"
00625          "       2. 'test show registered category [test category]' shows all tests in the given\n"
00626          "          category.\n"
00627          "       3. 'test show registered category [test category] name [test name]' shows all\n"
00628          "           tests in a given category matching a given name\n";
00629       return NULL;
00630    case CLI_GENERATE:
00631       if (a->pos == 3) {
00632          return ast_cli_complete(a->word, option1, a->n);
00633       }
00634       if (a->pos == 4) {
00635          return complete_test_category(a->line, a->word, a->pos, a->n);
00636       }
00637       if (a->pos == 5) {
00638          return ast_cli_complete(a->word, option2, a->n);
00639       }
00640       if (a->pos == 6) {
00641          return complete_test_name(a->line, a->word, a->pos, a->n, a->argv[3]);
00642       }
00643       return NULL;
00644    case CLI_HANDLER:
00645       if ((a->argc < 4) || (a->argc == 6) || (a->argc > 7) ||
00646          ((a->argc == 4) && strcmp(a->argv[3], "all")) ||
00647          ((a->argc == 7) && strcmp(a->argv[5], "name"))) {
00648          return CLI_SHOWUSAGE;
00649       }
00650       ast_cli(a->fd, FORMAT, "Category", "Name", "Summary", "Test Result");
00651       ast_cli(a->fd, FORMAT, "--------", "----", "-------", "-----------");
00652       AST_LIST_LOCK(&tests);
00653       AST_LIST_TRAVERSE(&tests, test, entry) {
00654          if ((a->argc == 4) ||
00655              ((a->argc == 5) && !test_cat_cmp(test->info.category, a->argv[4])) ||
00656              ((a->argc == 7) && !strcmp(test->info.category, a->argv[4]) && !strcmp(test->info.name, a->argv[6]))) {
00657 
00658             ast_cli(a->fd, FORMAT, test->info.category, test->info.name,
00659                   test->info.summary, test_result2str[test->state]);
00660             count++;
00661          }
00662       }
00663       AST_LIST_UNLOCK(&tests);
00664       ast_cli(a->fd, FORMAT, "--------", "----", "-------", "-----------");
00665       ast_cli(a->fd, "\n%d Registered Tests Matched\n", count);
00666    default:
00667       return NULL;
00668    }
00669 
00670    return CLI_SUCCESS;
00671 }
00672 
00673 static char *test_cli_execute_registered(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
00674 {
00675    static char * const option1[] = { "all", "category", NULL };
00676    static char * const option2[] = { "name", NULL };
00677 
00678    switch (cmd) {
00679    case CLI_INIT:
00680       e->command = "test execute";
00681       e->usage =
00682          "Usage: test execute can be used in three ways.\n"
00683          "       1. 'test execute all' runs all registered tests\n"
00684          "       2. 'test execute category [test category]' runs all tests in the given\n"
00685          "          category.\n"
00686          "       3. 'test execute category [test category] name [test name]' runs all\n"
00687          "           tests in a given category matching a given name\n";
00688       return NULL;
00689    case CLI_GENERATE:
00690       if (a->pos == 2) {
00691          return ast_cli_complete(a->word, option1, a->n);
00692       }
00693       if (a->pos == 3) {
00694          return complete_test_category(a->line, a->word, a->pos, a->n);
00695       }
00696       if (a->pos == 4) {
00697          return ast_cli_complete(a->word, option2, a->n);
00698       }
00699       if (a->pos == 5) {
00700          return complete_test_name(a->line, a->word, a->pos, a->n, a->argv[3]);
00701       }
00702       return NULL;
00703    case CLI_HANDLER:
00704 
00705       if (a->argc < 3|| a->argc > 6) {
00706          return CLI_SHOWUSAGE;
00707       }
00708 
00709       if ((a->argc == 3) && !strcmp(a->argv[2], "all")) { /* run all registered tests */
00710          ast_cli(a->fd, "Running all available tests...\n\n");
00711          test_execute_multiple(NULL, NULL, a);
00712       } else if (a->argc == 4) { /* run only tests within a category */
00713          ast_cli(a->fd, "Running all available tests matching category %s\n\n", a->argv[3]);
00714          test_execute_multiple(NULL, a->argv[3], a);
00715       } else if (a->argc == 6) { /* run only a single test matching the category and name */
00716          ast_cli(a->fd, "Running all available tests matching category %s and name %s\n\n", a->argv[3], a->argv[5]);
00717          test_execute_multiple(a->argv[5], a->argv[3], a);
00718       } else {
00719          return CLI_SHOWUSAGE;
00720       }
00721 
00722       AST_LIST_LOCK(&tests);
00723       if (!(last_results.last_passed + last_results.last_failed)) {
00724          ast_cli(a->fd, "--- No Tests Found! ---\n");
00725       }
00726       ast_cli(a->fd, "\n%d Test(s) Executed  %d Passed  %d Failed\n",
00727          (last_results.last_passed + last_results.last_failed),
00728          last_results.last_passed,
00729          last_results.last_failed);
00730       AST_LIST_UNLOCK(&tests);
00731    default:
00732       return NULL;
00733    }
00734 
00735    return CLI_SUCCESS;
00736 }
00737 
00738 static char *test_cli_show_results(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
00739 {
00740 #define FORMAT_RES_ALL1 "%s%s %-30.30s %-25.25s %-10.10s\n"
00741 #define FORMAT_RES_ALL2 "%s%s %-30.30s %-25.25s %s%ums\n"
00742    static char * const option1[] = { "all", "failed", "passed", NULL };
00743    char result_buf[32] = { 0 };
00744    struct ast_test *test = NULL;
00745    int failed = 0;
00746    int passed = 0;
00747    int mode;  /* 0 for show all, 1 for show fail, 2 for show passed */
00748 
00749    switch (cmd) {
00750    case CLI_INIT:
00751       e->command = "test show results";
00752       e->usage =
00753          "Usage: test show results can be used in three ways\n"
00754          "       1. 'test show results all' Displays results for all executed tests.\n"
00755          "       2. 'test show results passed' Displays results for all passed tests.\n"
00756          "       3. 'test show results failed' Displays results for all failed tests.\n";
00757       return NULL;
00758    case CLI_GENERATE:
00759       if (a->pos == 3) {
00760          return ast_cli_complete(a->word, option1, a->n);
00761       }
00762       return NULL;
00763    case CLI_HANDLER:
00764 
00765       /* verify input */
00766       if (a->argc != 4) {
00767          return CLI_SHOWUSAGE;
00768       } else if (!strcmp(a->argv[3], "passed")) {
00769          mode = 2;
00770       } else if (!strcmp(a->argv[3], "failed")) {
00771          mode = 1;
00772       } else if (!strcmp(a->argv[3], "all")) {
00773          mode = 0;
00774       } else {
00775          return CLI_SHOWUSAGE;
00776       }
00777 
00778       ast_cli(a->fd, FORMAT_RES_ALL1, "Result", "", "Name", "Category", "Time");
00779       AST_LIST_LOCK(&tests);
00780       AST_LIST_TRAVERSE(&tests, test, entry) {
00781          if (test->state == AST_TEST_NOT_RUN) {
00782             continue;
00783          }
00784          test->state == AST_TEST_FAIL ? failed++ : passed++;
00785          if (!mode || ((mode == 1) && (test->state == AST_TEST_FAIL)) || ((mode == 2) && (test->state == AST_TEST_PASS))) {
00786             /* give our results pretty colors */
00787             term_color(result_buf, test_result2str[test->state],
00788                (test->state == AST_TEST_FAIL) ? COLOR_RED : COLOR_GREEN,
00789                0, sizeof(result_buf));
00790 
00791             ast_cli(a->fd, FORMAT_RES_ALL2,
00792                result_buf,
00793                "  ",
00794                test->info.name,
00795                test->info.category,
00796                test->time ? " " : "<",
00797                test->time ? test->time : 1);
00798          }
00799       }
00800       AST_LIST_UNLOCK(&tests);
00801 
00802       ast_cli(a->fd, "%d Test(s) Executed  %d Passed  %d Failed\n", (failed + passed), passed, failed);
00803    default:
00804       return NULL;
00805    }
00806    return CLI_SUCCESS;
00807 }
00808 
00809 static char *test_cli_generate_results(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
00810 {
00811    static char * const option[] = { "xml", "txt", NULL };
00812    const char *file = NULL;
00813    const char *type = "";
00814    int isxml = 0;
00815    int res = 0;
00816    struct ast_str *buf = NULL;
00817    struct timeval time = ast_tvnow();
00818 
00819    switch (cmd) {
00820    case CLI_INIT:
00821       e->command = "test generate results";
00822       e->usage =
00823          "Usage: 'test generate results'\n"
00824          "       Generates test results in either xml or txt format. An optional \n"
00825          "       file path may be provided to specify the location of the xml or\n"
00826          "       txt file\n"
00827          "       \nExample usage:\n"
00828          "       'test generate results xml' this writes to a default file\n"
00829          "       'test generate results xml /path/to/file.xml' writes to specified file\n";
00830       return NULL;
00831    case CLI_GENERATE:
00832       if (a->pos == 3) {
00833          return ast_cli_complete(a->word, option, a->n);
00834       }
00835       return NULL;
00836    case CLI_HANDLER:
00837 
00838       /* verify input */
00839       if (a->argc < 4 || a->argc > 5) {
00840          return CLI_SHOWUSAGE;
00841       } else if (!strcmp(a->argv[3], "xml")) {
00842          type = "xml";
00843          isxml = 1;
00844       } else if (!strcmp(a->argv[3], "txt")) {
00845          type = "txt";
00846       } else {
00847          return CLI_SHOWUSAGE;
00848       }
00849 
00850       if (a->argc == 5) {
00851          file = a->argv[4];
00852       } else {
00853          if (!(buf = ast_str_create(256))) {
00854             return NULL;
00855          }
00856          ast_str_set(&buf, 0, "%s/asterisk_test_results-%ld.%s", ast_config_AST_LOG_DIR, (long) time.tv_sec, type);
00857 
00858          file = ast_str_buffer(buf);
00859       }
00860 
00861       if (isxml) {
00862          res = test_generate_results(NULL, NULL, file, NULL);
00863       } else {
00864          res = test_generate_results(NULL, NULL, NULL, file);
00865       }
00866 
00867       if (!res) {
00868          ast_cli(a->fd, "Results Generated Successfully: %s\n", S_OR(file, ""));
00869       } else {
00870          ast_cli(a->fd, "Results Could Not Be Generated: %s\n", S_OR(file, ""));
00871       }
00872 
00873       ast_free(buf);
00874    default:
00875       return NULL;
00876    }
00877 
00878    return CLI_SUCCESS;
00879 }
00880 
00881 static struct ast_cli_entry test_cli[] = {
00882    AST_CLI_DEFINE(test_cli_show_registered,           "show registered tests"),
00883    AST_CLI_DEFINE(test_cli_execute_registered,        "execute registered tests"),
00884    AST_CLI_DEFINE(test_cli_show_results,              "show last test results"),
00885    AST_CLI_DEFINE(test_cli_generate_results,          "generate test results to file"),
00886 };
00887 #endif /* TEST_FRAMEWORK */
00888 
00889 int ast_test_init(void)
00890 {
00891 #ifdef TEST_FRAMEWORK
00892    /* Register cli commands */
00893    ast_cli_register_multiple(test_cli, ARRAY_LEN(test_cli));
00894 #endif
00895 
00896    return 0;
00897 }