123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245 |
- /*
- * Asterisk -- An open source telephony toolkit.
- *
- * Copyright (C) 2009-2010, Digium, Inc.
- *
- * David Vossel <dvossel@digium.com>
- * Russell Bryant <russell@digium.com>
- *
- * See http://www.asterisk.org for more information about
- * the Asterisk project. Please do not directly contact
- * any of the maintainers of this project for assistance;
- * the project provides a web site, mailing lists and IRC
- * channels for your use.
- *
- * This program is free software, distributed under the terms of
- * the GNU General Public License Version 2. See the LICENSE file
- * at the top of the source tree.
- */
- /*!
- * \file
- * \brief Unit Test Framework
- *
- * \author David Vossel <dvossel@digium.com>
- * \author Russell Bryant <russell@digium.com>
- */
- /*** MODULEINFO
- <support_level>core</support_level>
- ***/
- #include "asterisk.h"
- ASTERISK_FILE_VERSION(__FILE__, "$Revision$");
- #include "asterisk/_private.h"
- #ifdef TEST_FRAMEWORK
- #include "asterisk/test.h"
- #include "asterisk/logger.h"
- #include "asterisk/linkedlists.h"
- #include "asterisk/utils.h"
- #include "asterisk/cli.h"
- #include "asterisk/term.h"
- #include "asterisk/ast_version.h"
- #include "asterisk/paths.h"
- #include "asterisk/time.h"
- #include "asterisk/stasis.h"
- #include "asterisk/json.h"
- #include "asterisk/astobj2.h"
- #include "asterisk/stasis.h"
- #include "asterisk/json.h"
- /*! \since 12
- * \brief The topic for test suite messages
- */
- struct stasis_topic *test_suite_topic;
- /*! This array corresponds to the values defined in the ast_test_state enum */
- static const char * const test_result2str[] = {
- [AST_TEST_NOT_RUN] = "NOT RUN",
- [AST_TEST_PASS] = "PASS",
- [AST_TEST_FAIL] = "FAIL",
- };
- /*! holds all the information pertaining to a single defined test */
- struct ast_test {
- struct ast_test_info info; /*!< holds test callback information */
- /*!
- * \brief Test defined status output from last execution
- */
- struct ast_str *status_str;
- /*!
- * \brief CLI arguments, if tests being run from the CLI
- *
- * If this is set, status updates from the tests will be sent to the
- * CLI in addition to being saved off in status_str.
- */
- struct ast_cli_args *cli;
- enum ast_test_result_state state; /*!< current test state */
- unsigned int time; /*!< time in ms test took */
- ast_test_cb_t *cb; /*!< test callback function */
- ast_test_init_cb_t *init_cb; /*!< test init function */
- ast_test_cleanup_cb_t *cleanup_cb; /*!< test cleanup function */
- AST_LIST_ENTRY(ast_test) entry;
- };
- /*! global structure containing both total and last test execution results */
- static struct ast_test_execute_results {
- unsigned int total_tests; /*!< total number of tests, regardless if they have been executed or not */
- unsigned int total_passed; /*!< total number of executed tests passed */
- unsigned int total_failed; /*!< total number of executed tests failed */
- unsigned int total_time; /*!< total time of all executed tests */
- unsigned int last_passed; /*!< number of passed tests during last execution */
- unsigned int last_failed; /*!< number of failed tests during last execution */
- unsigned int last_time; /*!< total time of the last test execution */
- } last_results;
- enum test_mode {
- TEST_ALL = 0,
- TEST_CATEGORY = 1,
- TEST_NAME_CATEGORY = 2,
- };
- /*! List of registered test definitions */
- static AST_LIST_HEAD_STATIC(tests, ast_test);
- static struct ast_test *test_alloc(ast_test_cb_t *cb);
- static struct ast_test *test_free(struct ast_test *test);
- static int test_insert(struct ast_test *test);
- static struct ast_test *test_remove(ast_test_cb_t *cb);
- static int test_cat_cmp(const char *cat1, const char *cat2);
- static int registration_errors = 0;
- void ast_test_debug(struct ast_test *test, const char *fmt, ...)
- {
- struct ast_str *buf = NULL;
- va_list ap;
- buf = ast_str_create(128);
- if (!buf) {
- return;
- }
- va_start(ap, fmt);
- ast_str_set_va(&buf, 0, fmt, ap);
- va_end(ap);
- if (test->cli) {
- ast_cli(test->cli->fd, "%s", ast_str_buffer(buf));
- }
- ast_free(buf);
- }
- int __ast_test_status_update(const char *file, const char *func, int line, struct ast_test *test, const char *fmt, ...)
- {
- struct ast_str *buf = NULL;
- va_list ap;
- if (!(buf = ast_str_create(128))) {
- return -1;
- }
- va_start(ap, fmt);
- ast_str_set_va(&buf, 0, fmt, ap);
- va_end(ap);
- if (test->cli) {
- ast_cli(test->cli->fd, "[%s:%s:%d]: %s",
- file, func, line, ast_str_buffer(buf));
- }
- ast_str_append(&test->status_str, 0, "[%s:%s:%d]: %s",
- file, func, line, ast_str_buffer(buf));
- ast_free(buf);
- return 0;
- }
- int ast_test_register_init(const char *category, ast_test_init_cb_t *cb)
- {
- struct ast_test *test;
- int registered = 1;
- AST_LIST_LOCK(&tests);
- AST_LIST_TRAVERSE(&tests, test, entry) {
- if (!(test_cat_cmp(test->info.category, category))) {
- test->init_cb = cb;
- registered = 0;
- }
- }
- AST_LIST_UNLOCK(&tests);
- return registered;
- }
- int ast_test_register_cleanup(const char *category, ast_test_cleanup_cb_t *cb)
- {
- struct ast_test *test;
- int registered = 1;
- AST_LIST_LOCK(&tests);
- AST_LIST_TRAVERSE(&tests, test, entry) {
- if (!(test_cat_cmp(test->info.category, category))) {
- test->cleanup_cb = cb;
- registered = 0;
- }
- }
- AST_LIST_UNLOCK(&tests);
- return registered;
- }
- int ast_test_register(ast_test_cb_t *cb)
- {
- struct ast_test *test;
- if (!cb) {
- ast_log(LOG_ERROR, "Attempted to register test without all required information\n");
- registration_errors++;
- return -1;
- }
- if (!(test = test_alloc(cb))) {
- registration_errors++;
- return -1;
- }
- if (test_insert(test)) {
- test_free(test);
- registration_errors++;
- return -1;
- }
- return 0;
- }
- int ast_test_unregister(ast_test_cb_t *cb)
- {
- struct ast_test *test;
- if (!(test = test_remove(cb))) {
- return -1; /* not found */
- }
- test_free(test);
- return 0;
- }
- /*!
- * \internal
- * \brief executes a single test, storing the results in the test->result structure.
- *
- * \note The last_results structure which contains global statistics about test execution
- * must be updated when using this function. See use in test_execute_multiple().
- */
- static void test_execute(struct ast_test *test)
- {
- struct timeval begin;
- enum ast_test_result_state result;
- ast_str_reset(test->status_str);
- begin = ast_tvnow();
- if (test->init_cb && test->init_cb(&test->info, test)) {
- test->state = AST_TEST_FAIL;
- goto exit;
- }
- test->state = AST_TEST_NOT_RUN;
- result = test->cb(&test->info, TEST_EXECUTE, test);
- if (test->state != AST_TEST_FAIL) {
- test->state = result;
- }
- if (test->cleanup_cb && test->cleanup_cb(&test->info, test)) {
- test->state = AST_TEST_FAIL;
- }
- exit:
- test->time = ast_tvdiff_ms(ast_tvnow(), begin);
- }
- void ast_test_set_result(struct ast_test *test, enum ast_test_result_state state)
- {
- if (test->state == AST_TEST_FAIL || state == AST_TEST_NOT_RUN) {
- return;
- }
- test->state = state;
- }
- /*
- * These are the Java reserved words we need to munge so Jenkins
- * doesn't barf on them.
- */
- static char *reserved_words[] = {
- "abstract", "arguments", "as", "assert", "await",
- "boolean", "break", "byte", "case", "catch", "char", "class",
- "const", "continue", "debugger", "def", "default", "delete", "do",
- "double", "else", "enum", "eval", "export", "extends", "false",
- "final", "finally", "float", "for", "function", "goto", "if",
- "implements", "import", "in", "instanceof", "int", "interface",
- "let", "long", "native", "new", "null", "package", "private",
- "protected", "public", "return", "short", "static", "strictfp",
- "string", "super", "switch", "synchronized", "this", "throw", "throws",
- "trait", "transient", "true", "try", "typeof", "var", "void",
- "volatile", "while", "with", "yield" };
- static int is_reserved_word(const char *word)
- {
- int i;
- for (i = 0; i < ARRAY_LEN(reserved_words); i++) {
- if (strcmp(word, reserved_words[i]) == 0) {
- return 1;
- }
- }
- return 0;
- }
- static void test_xml_entry(struct ast_test *test, FILE *f)
- {
- /* We need a copy of the category skipping past the initial '/' */
- char *test_cat = ast_strdupa(test->info.category + 1);
- char *next_cat;
- char *test_name = (char *)test->info.name;
- struct ast_str *category = ast_str_create(strlen(test->info.category) + 32);
- if (!category || test->state == AST_TEST_NOT_RUN) {
- ast_free(category);
- return;
- }
- while ((next_cat = ast_strsep(&test_cat, '/', AST_STRSEP_TRIM))) {
- char *prefix = "";
- if (is_reserved_word(next_cat)) {
- prefix = "_";
- }
- ast_str_append(&category, 0, ".%s%s", prefix, next_cat);
- }
- test_cat = ast_str_buffer(category);
- /* Skip past the initial '.' */
- test_cat++;
- if (is_reserved_word(test->info.name)) {
- size_t name_length = strlen(test->info.name) + 2;
- test_name = ast_alloca(name_length);
- snprintf(test_name, name_length, "_%s", test->info.name);
- }
- fprintf(f, "\t\t<testcase time=\"%u.%u\" classname=\"%s\" name=\"%s\"%s>\n",
- test->time / 1000, test->time % 1000,
- test_cat, test_name,
- test->state == AST_TEST_PASS ? "/" : "");
- ast_free(category);
- if (test->state == AST_TEST_FAIL) {
- fprintf(f, "\t\t\t<failure><![CDATA[\n%s\n\t\t]]></failure>\n",
- S_OR(ast_str_buffer(test->status_str), "NA"));
- fprintf(f, "\t\t</testcase>\n");
- }
- }
- static void test_txt_entry(struct ast_test *test, FILE *f)
- {
- if (!f || !test) {
- return;
- }
- fprintf(f, "\nName: %s\n", test->info.name);
- fprintf(f, "Category: %s\n", test->info.category);
- fprintf(f, "Summary: %s\n", test->info.summary);
- fprintf(f, "Description: %s\n", test->info.description);
- fprintf(f, "Result: %s\n", test_result2str[test->state]);
- if (test->state != AST_TEST_NOT_RUN) {
- fprintf(f, "Time: %u\n", test->time);
- }
- if (test->state == AST_TEST_FAIL) {
- fprintf(f, "Error Description: %s\n\n", S_OR(ast_str_buffer(test->status_str), "NA"));
- }
- }
- /*!
- * \internal
- * \brief Executes registered unit tests
- *
- * \param name of test to run (optional)
- * \param test category to run (optional)
- * \param cli args for cli test updates (optional)
- *
- * \return number of tests executed.
- *
- * \note This function has three modes of operation
- * -# When given a name and category, a matching individual test will execute if found.
- * -# When given only a category all matching tests within that category will execute.
- * -# If given no name or category all registered tests will execute.
- */
- static int test_execute_multiple(const char *name, const char *category, struct ast_cli_args *cli)
- {
- char result_buf[32] = { 0 };
- struct ast_test *test = NULL;
- enum test_mode mode = TEST_ALL; /* 3 modes, 0 = run all, 1 = only by category, 2 = only by name and category */
- int execute = 0;
- int res = 0;
- if (!ast_strlen_zero(category)) {
- if (!ast_strlen_zero(name)) {
- mode = TEST_NAME_CATEGORY;
- } else {
- mode = TEST_CATEGORY;
- }
- }
- AST_LIST_LOCK(&tests);
- /* clear previous execution results */
- memset(&last_results, 0, sizeof(last_results));
- AST_LIST_TRAVERSE(&tests, test, entry) {
- execute = 0;
- switch (mode) {
- case TEST_CATEGORY:
- if (!test_cat_cmp(test->info.category, category) && !test->info.explicit_only) {
- execute = 1;
- }
- break;
- case TEST_NAME_CATEGORY:
- if (!(test_cat_cmp(test->info.category, category)) && !(strcmp(test->info.name, name))) {
- execute = 1;
- }
- break;
- case TEST_ALL:
- execute = !test->info.explicit_only;
- }
- if (execute) {
- if (cli) {
- ast_cli(cli->fd, "START %s - %s \n", test->info.category, test->info.name);
- }
- /* set the test status update argument. it is ok if cli is NULL */
- test->cli = cli;
- /* execute the test and save results */
- test_execute(test);
- test->cli = NULL;
- /* update execution specific counts here */
- last_results.last_time += test->time;
- if (test->state == AST_TEST_PASS) {
- last_results.last_passed++;
- } else if (test->state == AST_TEST_FAIL) {
- last_results.last_failed++;
- }
- if (cli) {
- term_color(result_buf,
- test_result2str[test->state],
- (test->state == AST_TEST_FAIL) ? COLOR_RED : COLOR_GREEN,
- 0,
- sizeof(result_buf));
- ast_cli(cli->fd, "END %s - %s Time: %s%ums Result: %s\n",
- test->info.category,
- test->info.name,
- test->time ? "" : "<",
- test->time ? test->time : 1,
- result_buf);
- }
- }
- /* update total counts as well during this iteration
- * even if the current test did not execute this time */
- last_results.total_time += test->time;
- last_results.total_tests++;
- if (test->state != AST_TEST_NOT_RUN) {
- if (test->state == AST_TEST_PASS) {
- last_results.total_passed++;
- } else {
- last_results.total_failed++;
- }
- }
- }
- res = last_results.last_passed + last_results.last_failed;
- AST_LIST_UNLOCK(&tests);
- return res;
- }
- /*!
- * \internal
- * \brief Generate test results.
- *
- * \param name of test result to generate (optional)
- * \param test category to generate (optional)
- * \param path to xml file to generate. (optional)
- * \param path to txt file to generate, (optional)
- *
- * \retval 0 success
- * \retval -1 failure
- *
- * \note This function has three modes of operation.
- * -# When given both a name and category, results will be generated for that single test.
- * -# When given only a category, results for every test within the category will be generated.
- * -# When given no name or category, results for every registered test will be generated.
- *
- * In order for the results to be generated, an xml and or txt file path must be provided.
- */
- static int test_generate_results(const char *name, const char *category, const char *xml_path, const char *txt_path)
- {
- enum test_mode mode = TEST_ALL; /* 0 generate all, 1 generate by category only, 2 generate by name and category */
- FILE *f_xml = NULL, *f_txt = NULL;
- int res = 0;
- struct ast_test *test = NULL;
- /* verify at least one output file was given */
- if (ast_strlen_zero(xml_path) && ast_strlen_zero(txt_path)) {
- return -1;
- }
- /* define what mode is to be used */
- if (!ast_strlen_zero(category)) {
- if (!ast_strlen_zero(name)) {
- mode = TEST_NAME_CATEGORY;
- } else {
- mode = TEST_CATEGORY;
- }
- }
- /* open files for writing */
- if (!ast_strlen_zero(xml_path)) {
- if (!(f_xml = fopen(xml_path, "w"))) {
- ast_log(LOG_WARNING, "Could not open file %s for xml test results\n", xml_path);
- res = -1;
- goto done;
- }
- }
- if (!ast_strlen_zero(txt_path)) {
- if (!(f_txt = fopen(txt_path, "w"))) {
- ast_log(LOG_WARNING, "Could not open file %s for text output of test results\n", txt_path);
- res = -1;
- goto done;
- }
- }
- AST_LIST_LOCK(&tests);
- /* xml header information */
- if (f_xml) {
- /*
- * http://confluence.atlassian.com/display/BAMBOO/JUnit+parsing+in+Bamboo
- */
- fprintf(f_xml, "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
- fprintf(f_xml, "<testsuites>\n");
- fprintf(f_xml, "\t<testsuite errors=\"0\" time=\"%u.%u\" tests=\"%u\" "
- "name=\"AsteriskUnitTests\">\n",
- last_results.total_time / 1000, last_results.total_time % 1000,
- last_results.total_tests);
- fprintf(f_xml, "\t\t<properties>\n");
- fprintf(f_xml, "\t\t\t<property name=\"version\" value=\"%s\"/>\n", ast_get_version());
- fprintf(f_xml, "\t\t</properties>\n");
- }
- /* txt header information */
- if (f_txt) {
- fprintf(f_txt, "Asterisk Version: %s\n", ast_get_version());
- fprintf(f_txt, "Asterisk Version Number: %s\n", ast_get_version_num());
- fprintf(f_txt, "Number of Tests: %u\n", last_results.total_tests);
- fprintf(f_txt, "Number of Tests Executed: %u\n", (last_results.total_passed + last_results.total_failed));
- fprintf(f_txt, "Passed Tests: %u\n", last_results.total_passed);
- fprintf(f_txt, "Failed Tests: %u\n", last_results.total_failed);
- fprintf(f_txt, "Total Execution Time: %u\n", last_results.total_time);
- }
- /* export each individual test */
- AST_LIST_TRAVERSE(&tests, test, entry) {
- switch (mode) {
- case TEST_CATEGORY:
- if (!test_cat_cmp(test->info.category, category)) {
- test_xml_entry(test, f_xml);
- test_txt_entry(test, f_txt);
- }
- break;
- case TEST_NAME_CATEGORY:
- if (!(strcmp(test->info.category, category)) && !(strcmp(test->info.name, name))) {
- test_xml_entry(test, f_xml);
- test_txt_entry(test, f_txt);
- }
- break;
- case TEST_ALL:
- test_xml_entry(test, f_xml);
- test_txt_entry(test, f_txt);
- }
- }
- AST_LIST_UNLOCK(&tests);
- done:
- if (f_xml) {
- fprintf(f_xml, "\t</testsuite>\n");
- fprintf(f_xml, "</testsuites>\n");
- fclose(f_xml);
- }
- if (f_txt) {
- fclose(f_txt);
- }
- return res;
- }
- /*!
- * \internal
- * \brief adds test to container sorted first by category then by name
- *
- * \retval 0 success
- * \retval -1 failure
- */
- static int test_insert(struct ast_test *test)
- {
- /* This is a slow operation that may need to be optimized in the future
- * as the test framework expands. At the moment we are doing string
- * comparisons on every item within the list to insert in sorted order. */
- AST_LIST_LOCK(&tests);
- AST_LIST_INSERT_SORTALPHA(&tests, test, entry, info.category);
- AST_LIST_UNLOCK(&tests);
- return 0;
- }
- /*!
- * \internal
- * \brief removes test from container
- *
- * \return ast_test removed from list on success, or NULL on failure
- */
- static struct ast_test *test_remove(ast_test_cb_t *cb)
- {
- struct ast_test *cur = NULL;
- AST_LIST_LOCK(&tests);
- AST_LIST_TRAVERSE_SAFE_BEGIN(&tests, cur, entry) {
- if (cur->cb == cb) {
- AST_LIST_REMOVE_CURRENT(entry);
- break;
- }
- }
- AST_LIST_TRAVERSE_SAFE_END;
- AST_LIST_UNLOCK(&tests);
- return cur;
- }
- /*!
- * \brief compares two test categories to determine if cat1 resides in cat2
- * \internal
- *
- * \retval 0 true
- * \retval non-zero false
- */
- static int test_cat_cmp(const char *cat1, const char *cat2)
- {
- int len1 = 0;
- int len2 = 0;
- if (!cat1 || !cat2) {
- return -1;
- }
- len1 = strlen(cat1);
- len2 = strlen(cat2);
- if (len2 > len1) {
- return -1;
- }
- return strncmp(cat1, cat2, len2) ? 1 : 0;
- }
- /*!
- * \internal
- * \brief free an ast_test object and all it's data members
- */
- static struct ast_test *test_free(struct ast_test *test)
- {
- if (!test) {
- return NULL;
- }
- ast_free(test->status_str);
- ast_free(test);
- return NULL;
- }
- /*!
- * \internal
- * \brief allocate an ast_test object.
- */
- static struct ast_test *test_alloc(ast_test_cb_t *cb)
- {
- struct ast_test *test;
- test = ast_calloc(1, sizeof(*test));
- if (!test) {
- ast_log(LOG_ERROR, "Failed to allocate test, registration failed.\n");
- return NULL;
- }
- test->cb = cb;
- test->cb(&test->info, TEST_INIT, test);
- if (ast_strlen_zero(test->info.name)) {
- ast_log(LOG_ERROR, "Test has no name, test registration refused.\n");
- return test_free(test);
- }
- if (ast_strlen_zero(test->info.category)) {
- ast_log(LOG_ERROR, "Test %s has no category, test registration refused.\n",
- test->info.name);
- return test_free(test);
- }
- if (test->info.category[0] != '/' || test->info.category[strlen(test->info.category) - 1] != '/') {
- ast_log(LOG_WARNING, "Test category '%s' for test '%s' is missing a leading or trailing slash.\n",
- test->info.category, test->info.name);
- /*
- * Flag an error anyways so test_registrations fails but allow the
- * test to be registered.
- */
- ++registration_errors;
- }
- if (ast_strlen_zero(test->info.summary)) {
- ast_log(LOG_ERROR, "Test %s%s has no summary, test registration refused.\n",
- test->info.category, test->info.name);
- return test_free(test);
- }
- if (test->info.summary[strlen(test->info.summary) - 1] == '\n') {
- ast_log(LOG_WARNING, "Test %s%s summary has a trailing newline.\n",
- test->info.category, test->info.name);
- /*
- * Flag an error anyways so test_registrations fails but allow the
- * test to be registered.
- */
- ++registration_errors;
- }
- if (ast_strlen_zero(test->info.description)) {
- ast_log(LOG_ERROR, "Test %s%s has no description, test registration refused.\n",
- test->info.category, test->info.name);
- return test_free(test);
- }
- if (test->info.description[strlen(test->info.description) - 1] == '\n') {
- ast_log(LOG_WARNING, "Test %s%s description has a trailing newline.\n",
- test->info.category, test->info.name);
- /*
- * Flag an error anyways so test_registrations fails but allow the
- * test to be registered.
- */
- ++registration_errors;
- }
- if (!(test->status_str = ast_str_create(128))) {
- ast_log(LOG_ERROR, "Failed to allocate status_str for %s%s, test registration failed.\n",
- test->info.category, test->info.name);
- return test_free(test);
- }
- return test;
- }
- static char *complete_test_category(const char *word)
- {
- int wordlen = strlen(word);
- struct ast_test *test;
- AST_LIST_LOCK(&tests);
- AST_LIST_TRAVERSE(&tests, test, entry) {
- if (!strncasecmp(word, test->info.category, wordlen)) {
- if (ast_cli_completion_add(ast_strdup(test->info.category))) {
- break;
- }
- }
- }
- AST_LIST_UNLOCK(&tests);
- return NULL;
- }
- static char *complete_test_name(const char *word, const char *category)
- {
- int wordlen = strlen(word);
- struct ast_test *test;
- AST_LIST_LOCK(&tests);
- AST_LIST_TRAVERSE(&tests, test, entry) {
- if (!test_cat_cmp(test->info.category, category) && !strncasecmp(word, test->info.name, wordlen)) {
- if (ast_cli_completion_add(ast_strdup(test->info.name))) {
- break;
- }
- }
- }
- AST_LIST_UNLOCK(&tests);
- return NULL;
- }
- /* CLI commands */
- static char *test_cli_show_registered(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
- {
- #define FORMAT "%-25.25s %-30.30s %-40.40s %-13.13s\n"
- static const char * const option1[] = { "all", "category", NULL };
- static const char * const option2[] = { "name", NULL };
- struct ast_test *test = NULL;
- int count = 0;
- switch (cmd) {
- case CLI_INIT:
- e->command = "test show registered";
- e->usage =
- "Usage: 'test show registered' can be used in three ways.\n"
- " 1. 'test show registered all' shows all registered tests\n"
- " 2. 'test show registered category [test category]' shows all tests in the given\n"
- " category.\n"
- " 3. 'test show registered category [test category] name [test name]' shows all\n"
- " tests in a given category matching a given name\n";
- return NULL;
- case CLI_GENERATE:
- if (a->pos == 3) {
- return ast_cli_complete(a->word, option1, -1);
- }
- if (a->pos == 4 && !strcasecmp(a->argv[3], "category")) {
- return complete_test_category(a->word);
- }
- if (a->pos == 5) {
- return ast_cli_complete(a->word, option2, -1);
- }
- if (a->pos == 6) {
- return complete_test_name(a->word, a->argv[4]);
- }
- return NULL;
- case CLI_HANDLER:
- if ((a->argc < 4) || (a->argc == 6) || (a->argc > 7) ||
- ((a->argc == 4) && strcasecmp(a->argv[3], "all")) ||
- ((a->argc == 7) && strcasecmp(a->argv[5], "name"))) {
- return CLI_SHOWUSAGE;
- }
- ast_cli(a->fd, FORMAT, "Category", "Name", "Summary", "Test Result");
- ast_cli(a->fd, FORMAT, "--------", "----", "-------", "-----------");
- AST_LIST_LOCK(&tests);
- AST_LIST_TRAVERSE(&tests, test, entry) {
- if ((a->argc == 4) ||
- ((a->argc == 5) && !test_cat_cmp(test->info.category, a->argv[4])) ||
- ((a->argc == 7) && !strcmp(test->info.category, a->argv[4]) && !strcmp(test->info.name, a->argv[6]))) {
- ast_cli(a->fd, FORMAT, test->info.category, test->info.name,
- test->info.summary, test_result2str[test->state]);
- count++;
- }
- }
- AST_LIST_UNLOCK(&tests);
- ast_cli(a->fd, FORMAT, "--------", "----", "-------", "-----------");
- ast_cli(a->fd, "\n%d Registered Tests Matched\n", count);
- default:
- return NULL;
- }
- return CLI_SUCCESS;
- }
- static char *test_cli_execute_registered(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
- {
- static const char * const option1[] = { "all", "category", NULL };
- static const char * const option2[] = { "name", NULL };
- switch (cmd) {
- case CLI_INIT:
- e->command = "test execute";
- e->usage =
- "Usage: test execute can be used in three ways.\n"
- " 1. 'test execute all' runs all registered tests\n"
- " 2. 'test execute category [test category]' runs all tests in the given\n"
- " category.\n"
- " 3. 'test execute category [test category] name [test name]' runs all\n"
- " tests in a given category matching a given name\n";
- return NULL;
- case CLI_GENERATE:
- if (a->pos == 2) {
- return ast_cli_complete(a->word, option1, -1);
- }
- if (a->pos == 3 && !strcasecmp(a->argv[2], "category")) {
- return complete_test_category(a->word);
- }
- if (a->pos == 4) {
- return ast_cli_complete(a->word, option2, -1);
- }
- if (a->pos == 5) {
- return complete_test_name(a->word, a->argv[3]);
- }
- return NULL;
- case CLI_HANDLER:
- if (a->argc < 3|| a->argc > 6) {
- return CLI_SHOWUSAGE;
- }
- if ((a->argc == 3) && !strcasecmp(a->argv[2], "all")) { /* run all registered tests */
- ast_cli(a->fd, "Running all available tests...\n\n");
- test_execute_multiple(NULL, NULL, a);
- } else if (a->argc == 4) { /* run only tests within a category */
- ast_cli(a->fd, "Running all available tests matching category %s\n\n", a->argv[3]);
- test_execute_multiple(NULL, a->argv[3], a);
- } else if (a->argc == 6) { /* run only a single test matching the category and name */
- ast_cli(a->fd, "Running all available tests matching category %s and name %s\n\n", a->argv[3], a->argv[5]);
- test_execute_multiple(a->argv[5], a->argv[3], a);
- } else {
- return CLI_SHOWUSAGE;
- }
- AST_LIST_LOCK(&tests);
- if (!(last_results.last_passed + last_results.last_failed)) {
- ast_cli(a->fd, "--- No Tests Found! ---\n");
- }
- ast_cli(a->fd, "\n%u Test(s) Executed %u Passed %u Failed\n",
- (last_results.last_passed + last_results.last_failed),
- last_results.last_passed,
- last_results.last_failed);
- AST_LIST_UNLOCK(&tests);
- default:
- return NULL;
- }
- return CLI_SUCCESS;
- }
- static char *test_cli_show_results(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
- {
- #define FORMAT_RES_ALL1 "%s%s %-30.30s %-25.25s %-10.10s\n"
- #define FORMAT_RES_ALL2 "%s%s %-30.30s %-25.25s %s%ums\n"
- static const char * const option1[] = { "all", "failed", "passed", NULL };
- char result_buf[32] = { 0 };
- struct ast_test *test = NULL;
- int failed = 0;
- int passed = 0;
- int mode; /* 0 for show all, 1 for show fail, 2 for show passed */
- switch (cmd) {
- case CLI_INIT:
- e->command = "test show results";
- e->usage =
- "Usage: test show results can be used in three ways\n"
- " 1. 'test show results all' Displays results for all executed tests.\n"
- " 2. 'test show results passed' Displays results for all passed tests.\n"
- " 3. 'test show results failed' Displays results for all failed tests.\n";
- return NULL;
- case CLI_GENERATE:
- if (a->pos == 3) {
- return ast_cli_complete(a->word, option1, -1);
- }
- return NULL;
- case CLI_HANDLER:
- /* verify input */
- if (a->argc != 4) {
- return CLI_SHOWUSAGE;
- } else if (!strcasecmp(a->argv[3], "passed")) {
- mode = 2;
- } else if (!strcasecmp(a->argv[3], "failed")) {
- mode = 1;
- } else if (!strcasecmp(a->argv[3], "all")) {
- mode = 0;
- } else {
- return CLI_SHOWUSAGE;
- }
- ast_cli(a->fd, FORMAT_RES_ALL1, "Result", "", "Name", "Category", "Time");
- AST_LIST_LOCK(&tests);
- AST_LIST_TRAVERSE(&tests, test, entry) {
- if (test->state == AST_TEST_NOT_RUN) {
- continue;
- }
- test->state == AST_TEST_FAIL ? failed++ : passed++;
- if (!mode || ((mode == 1) && (test->state == AST_TEST_FAIL)) || ((mode == 2) && (test->state == AST_TEST_PASS))) {
- /* give our results pretty colors */
- term_color(result_buf, test_result2str[test->state],
- (test->state == AST_TEST_FAIL) ? COLOR_RED : COLOR_GREEN,
- 0, sizeof(result_buf));
- ast_cli(a->fd, FORMAT_RES_ALL2,
- result_buf,
- " ",
- test->info.name,
- test->info.category,
- test->time ? " " : "<",
- test->time ? test->time : 1);
- }
- }
- AST_LIST_UNLOCK(&tests);
- ast_cli(a->fd, "%d Test(s) Executed %d Passed %d Failed\n", (failed + passed), passed, failed);
- default:
- return NULL;
- }
- return CLI_SUCCESS;
- }
- static char *test_cli_generate_results(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
- {
- static const char * const option[] = { "xml", "txt", NULL };
- const char *file = NULL;
- const char *type = "";
- int isxml = 0;
- int res = 0;
- struct ast_str *buf = NULL;
- struct timeval time = ast_tvnow();
- switch (cmd) {
- case CLI_INIT:
- e->command = "test generate results";
- e->usage =
- "Usage: 'test generate results'\n"
- " Generates test results in either xml or txt format. An optional \n"
- " file path may be provided to specify the location of the xml or\n"
- " txt file\n"
- " \nExample usage:\n"
- " 'test generate results xml' this writes to a default file\n"
- " 'test generate results xml /path/to/file.xml' writes to specified file\n";
- return NULL;
- case CLI_GENERATE:
- if (a->pos == 3) {
- return ast_cli_complete(a->word, option, -1);
- }
- return NULL;
- case CLI_HANDLER:
- /* verify input */
- if (a->argc < 4 || a->argc > 5) {
- return CLI_SHOWUSAGE;
- } else if (!strcasecmp(a->argv[3], "xml")) {
- type = "xml";
- isxml = 1;
- } else if (!strcasecmp(a->argv[3], "txt")) {
- type = "txt";
- } else {
- return CLI_SHOWUSAGE;
- }
- if (a->argc == 5) {
- file = a->argv[4];
- } else {
- if (!(buf = ast_str_create(256))) {
- return NULL;
- }
- ast_str_set(&buf, 0, "%s/asterisk_test_results-%ld.%s", ast_config_AST_LOG_DIR, (long) time.tv_sec, type);
- file = ast_str_buffer(buf);
- }
- if (isxml) {
- res = test_generate_results(NULL, NULL, file, NULL);
- } else {
- res = test_generate_results(NULL, NULL, NULL, file);
- }
- if (!res) {
- ast_cli(a->fd, "Results Generated Successfully: %s\n", S_OR(file, ""));
- } else {
- ast_cli(a->fd, "Results Could Not Be Generated: %s\n", S_OR(file, ""));
- }
- ast_free(buf);
- default:
- return NULL;
- }
- return CLI_SUCCESS;
- }
- static struct ast_cli_entry test_cli[] = {
- AST_CLI_DEFINE(test_cli_show_registered, "show registered tests"),
- AST_CLI_DEFINE(test_cli_execute_registered, "execute registered tests"),
- AST_CLI_DEFINE(test_cli_show_results, "show last test results"),
- AST_CLI_DEFINE(test_cli_generate_results, "generate test results to file"),
- };
- struct stasis_topic *ast_test_suite_topic(void)
- {
- return test_suite_topic;
- }
- /*!
- * \since 12
- * \brief A wrapper object that can be ao2 ref counted around an \ref ast_json blob
- */
- struct ast_test_suite_message_payload {
- struct ast_json *blob; /*!< The actual blob that we want to deliver */
- };
- /*! \internal
- * \since 12
- * \brief Destructor for \ref ast_test_suite_message_payload
- */
- static void test_suite_message_payload_dtor(void *obj)
- {
- struct ast_test_suite_message_payload *payload = obj;
- if (payload->blob) {
- ast_json_unref(payload->blob);
- }
- }
- struct ast_json *ast_test_suite_get_blob(struct ast_test_suite_message_payload *payload)
- {
- return payload->blob;
- }
- static struct ast_manager_event_blob *test_suite_event_to_ami(struct stasis_message *msg)
- {
- RAII_VAR(struct ast_str *, packet_string, ast_str_create(128), ast_free);
- struct ast_test_suite_message_payload *payload;
- struct ast_json *blob;
- const char *type;
- payload = stasis_message_data(msg);
- if (!payload) {
- return NULL;
- }
- blob = ast_test_suite_get_blob(payload);
- if (!blob) {
- return NULL;
- }
- type = ast_json_string_get(ast_json_object_get(blob, "type"));
- if (ast_strlen_zero(type) || strcmp("testevent", type)) {
- return NULL;
- }
- ast_str_append(&packet_string, 0, "Type: StateChange\r\n");
- ast_str_append(&packet_string, 0, "State: %s\r\n",
- ast_json_string_get(ast_json_object_get(blob, "state")));
- ast_str_append(&packet_string, 0, "AppFile: %s\r\n",
- ast_json_string_get(ast_json_object_get(blob, "appfile")));
- ast_str_append(&packet_string, 0, "AppFunction: %s\r\n",
- ast_json_string_get(ast_json_object_get(blob, "appfunction")));
- ast_str_append(&packet_string, 0, "AppLine: %jd\r\n",
- ast_json_integer_get(ast_json_object_get(blob, "line")));
- ast_str_append(&packet_string, 0, "%s\r\n",
- ast_json_string_get(ast_json_object_get(blob, "data")));
- return ast_manager_event_blob_create(EVENT_FLAG_REPORTING,
- "TestEvent",
- "%s",
- ast_str_buffer(packet_string));
- }
- /*! \since 12
- * \brief The message type for test suite messages
- */
- STASIS_MESSAGE_TYPE_DEFN(ast_test_suite_message_type,
- .to_ami = test_suite_event_to_ami);
- void __ast_test_suite_event_notify(const char *file, const char *func, int line, const char *state, const char *fmt, ...)
- {
- RAII_VAR(struct ast_test_suite_message_payload *, payload,
- NULL,
- ao2_cleanup);
- RAII_VAR(struct stasis_message *, msg, NULL, ao2_cleanup);
- RAII_VAR(struct ast_str *, buf, NULL, ast_free);
- va_list ap;
- if (!ast_test_suite_message_type()) {
- return;
- }
- buf = ast_str_create(128);
- if (!buf) {
- return;
- }
- payload = ao2_alloc(sizeof(*payload), test_suite_message_payload_dtor);
- if (!payload) {
- return;
- }
- va_start(ap, fmt);
- ast_str_set_va(&buf, 0, fmt, ap);
- va_end(ap);
- payload->blob = ast_json_pack("{s: s, s: s, s: s, s: s, s: i, s: s}",
- "type", "testevent",
- "state", state,
- "appfile", file,
- "appfunction", func,
- "line", line,
- "data", ast_str_buffer(buf));
- if (!payload->blob) {
- return;
- }
- msg = stasis_message_create(ast_test_suite_message_type(), payload);
- if (!msg) {
- return;
- }
- stasis_publish(ast_test_suite_topic(), msg);
- }
- AST_TEST_DEFINE(test_registrations)
- {
- switch (cmd) {
- case TEST_INIT:
- info->name = "registrations";
- info->category = "/main/test/";
- info->summary = "Validate Test Registration Data.";
- info->description = "Validate Test Registration Data.";
- return AST_TEST_NOT_RUN;
- case TEST_EXECUTE:
- break;
- }
- if (registration_errors) {
- ast_test_status_update(test,
- "%d test registration error%s occurred. See startup logs for details.\n",
- registration_errors, registration_errors > 1 ? "s" : "");
- return AST_TEST_FAIL;
- }
- return AST_TEST_PASS;
- }
- static void test_cleanup(void)
- {
- AST_TEST_UNREGISTER(test_registrations);
- ast_cli_unregister_multiple(test_cli, ARRAY_LEN(test_cli));
- ao2_cleanup(test_suite_topic);
- test_suite_topic = NULL;
- STASIS_MESSAGE_TYPE_CLEANUP(ast_test_suite_message_type);
- }
- #endif /* TEST_FRAMEWORK */
- int ast_test_init(void)
- {
- #ifdef TEST_FRAMEWORK
- ast_register_cleanup(test_cleanup);
- /* Create stasis topic */
- test_suite_topic = stasis_topic_create("test_suite_topic");
- if (!test_suite_topic) {
- return -1;
- }
- if (STASIS_MESSAGE_TYPE_INIT(ast_test_suite_message_type) != 0) {
- return -1;
- }
- AST_TEST_REGISTER(test_registrations);
- /* Register cli commands */
- ast_cli_register_multiple(test_cli, ARRAY_LEN(test_cli));
- #endif
- return 0;
- }
|