Mon Mar 19 11:30:30 2012

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

Generated on Mon Mar 19 11:30:30 2012 for Asterisk - The Open Source Telephony Project by  doxygen 1.4.7