test.c 30 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133
  1. /*
  2. * Asterisk -- An open source telephony toolkit.
  3. *
  4. * Copyright (C) 2009-2010, Digium, Inc.
  5. *
  6. * David Vossel <dvossel@digium.com>
  7. * Russell Bryant <russell@digium.com>
  8. *
  9. * See http://www.asterisk.org for more information about
  10. * the Asterisk project. Please do not directly contact
  11. * any of the maintainers of this project for assistance;
  12. * the project provides a web site, mailing lists and IRC
  13. * channels for your use.
  14. *
  15. * This program is free software, distributed under the terms of
  16. * the GNU General Public License Version 2. See the LICENSE file
  17. * at the top of the source tree.
  18. */
  19. /*!
  20. * \file
  21. * \brief Unit Test Framework
  22. *
  23. * \author David Vossel <dvossel@digium.com>
  24. * \author Russell Bryant <russell@digium.com>
  25. */
  26. /*** MODULEINFO
  27. <support_level>core</support_level>
  28. ***/
  29. #include "asterisk.h"
  30. ASTERISK_FILE_VERSION(__FILE__, "$Revision$");
  31. #include "asterisk/_private.h"
  32. #ifdef TEST_FRAMEWORK
  33. #include "asterisk/test.h"
  34. #include "asterisk/logger.h"
  35. #include "asterisk/linkedlists.h"
  36. #include "asterisk/utils.h"
  37. #include "asterisk/cli.h"
  38. #include "asterisk/term.h"
  39. #include "asterisk/ast_version.h"
  40. #include "asterisk/paths.h"
  41. #include "asterisk/time.h"
  42. #include "asterisk/stasis.h"
  43. #include "asterisk/json.h"
  44. #include "asterisk/astobj2.h"
  45. #include "asterisk/stasis.h"
  46. #include "asterisk/json.h"
  47. /*! \since 12
  48. * \brief The topic for test suite messages
  49. */
  50. struct stasis_topic *test_suite_topic;
  51. /*! This array corresponds to the values defined in the ast_test_state enum */
  52. static const char * const test_result2str[] = {
  53. [AST_TEST_NOT_RUN] = "NOT RUN",
  54. [AST_TEST_PASS] = "PASS",
  55. [AST_TEST_FAIL] = "FAIL",
  56. };
  57. /*! holds all the information pertaining to a single defined test */
  58. struct ast_test {
  59. struct ast_test_info info; /*!< holds test callback information */
  60. /*!
  61. * \brief Test defined status output from last execution
  62. */
  63. struct ast_str *status_str;
  64. /*!
  65. * \brief CLI arguments, if tests being run from the CLI
  66. *
  67. * If this is set, status updates from the tests will be sent to the
  68. * CLI in addition to being saved off in status_str.
  69. */
  70. struct ast_cli_args *cli;
  71. enum ast_test_result_state state; /*!< current test state */
  72. unsigned int time; /*!< time in ms test took */
  73. ast_test_cb_t *cb; /*!< test callback function */
  74. ast_test_init_cb_t *init_cb; /*!< test init function */
  75. ast_test_cleanup_cb_t *cleanup_cb; /*!< test cleanup function */
  76. AST_LIST_ENTRY(ast_test) entry;
  77. };
  78. /*! global structure containing both total and last test execution results */
  79. static struct ast_test_execute_results {
  80. unsigned int total_tests; /*!< total number of tests, regardless if they have been executed or not */
  81. unsigned int total_passed; /*!< total number of executed tests passed */
  82. unsigned int total_failed; /*!< total number of executed tests failed */
  83. unsigned int total_time; /*!< total time of all executed tests */
  84. unsigned int last_passed; /*!< number of passed tests during last execution */
  85. unsigned int last_failed; /*!< number of failed tests during last execution */
  86. unsigned int last_time; /*!< total time of the last test execution */
  87. } last_results;
  88. enum test_mode {
  89. TEST_ALL = 0,
  90. TEST_CATEGORY = 1,
  91. TEST_NAME_CATEGORY = 2,
  92. };
  93. /*! List of registered test definitions */
  94. static AST_LIST_HEAD_STATIC(tests, ast_test);
  95. static struct ast_test *test_alloc(ast_test_cb_t *cb);
  96. static struct ast_test *test_free(struct ast_test *test);
  97. static int test_insert(struct ast_test *test);
  98. static struct ast_test *test_remove(ast_test_cb_t *cb);
  99. static int test_cat_cmp(const char *cat1, const char *cat2);
  100. void ast_test_debug(struct ast_test *test, const char *fmt, ...)
  101. {
  102. struct ast_str *buf = NULL;
  103. va_list ap;
  104. buf = ast_str_create(128);
  105. if (!buf) {
  106. return;
  107. }
  108. va_start(ap, fmt);
  109. ast_str_set_va(&buf, 0, fmt, ap);
  110. va_end(ap);
  111. if (test->cli) {
  112. ast_cli(test->cli->fd, "%s", ast_str_buffer(buf));
  113. }
  114. ast_free(buf);
  115. }
  116. int __ast_test_status_update(const char *file, const char *func, int line, struct ast_test *test, const char *fmt, ...)
  117. {
  118. struct ast_str *buf = NULL;
  119. va_list ap;
  120. if (!(buf = ast_str_create(128))) {
  121. return -1;
  122. }
  123. va_start(ap, fmt);
  124. ast_str_set_va(&buf, 0, fmt, ap);
  125. va_end(ap);
  126. if (test->cli) {
  127. ast_cli(test->cli->fd, "[%s:%s:%d]: %s",
  128. file, func, line, ast_str_buffer(buf));
  129. }
  130. ast_str_append(&test->status_str, 0, "[%s:%s:%d]: %s",
  131. file, func, line, ast_str_buffer(buf));
  132. ast_free(buf);
  133. return 0;
  134. }
  135. int ast_test_register_init(const char *category, ast_test_init_cb_t *cb)
  136. {
  137. struct ast_test *test;
  138. int registered = 1;
  139. AST_LIST_LOCK(&tests);
  140. AST_LIST_TRAVERSE(&tests, test, entry) {
  141. if (!(test_cat_cmp(test->info.category, category))) {
  142. test->init_cb = cb;
  143. registered = 0;
  144. }
  145. }
  146. AST_LIST_UNLOCK(&tests);
  147. return registered;
  148. }
  149. int ast_test_register_cleanup(const char *category, ast_test_cleanup_cb_t *cb)
  150. {
  151. struct ast_test *test;
  152. int registered = 1;
  153. AST_LIST_LOCK(&tests);
  154. AST_LIST_TRAVERSE(&tests, test, entry) {
  155. if (!(test_cat_cmp(test->info.category, category))) {
  156. test->cleanup_cb = cb;
  157. registered = 0;
  158. }
  159. }
  160. AST_LIST_UNLOCK(&tests);
  161. return registered;
  162. }
  163. int ast_test_register(ast_test_cb_t *cb)
  164. {
  165. struct ast_test *test;
  166. if (!cb) {
  167. ast_log(LOG_WARNING, "Attempted to register test without all required information\n");
  168. return -1;
  169. }
  170. if (!(test = test_alloc(cb))) {
  171. return -1;
  172. }
  173. if (test_insert(test)) {
  174. test_free(test);
  175. return -1;
  176. }
  177. return 0;
  178. }
  179. int ast_test_unregister(ast_test_cb_t *cb)
  180. {
  181. struct ast_test *test;
  182. if (!(test = test_remove(cb))) {
  183. return -1; /* not found */
  184. }
  185. test_free(test);
  186. return 0;
  187. }
  188. /*!
  189. * \internal
  190. * \brief executes a single test, storing the results in the test->result structure.
  191. *
  192. * \note The last_results structure which contains global statistics about test execution
  193. * must be updated when using this function. See use in test_execute_multiple().
  194. */
  195. static void test_execute(struct ast_test *test)
  196. {
  197. struct timeval begin;
  198. enum ast_test_result_state result;
  199. ast_str_reset(test->status_str);
  200. begin = ast_tvnow();
  201. if (test->init_cb && test->init_cb(&test->info, test)) {
  202. test->state = AST_TEST_FAIL;
  203. goto exit;
  204. }
  205. test->state = AST_TEST_NOT_RUN;
  206. result = test->cb(&test->info, TEST_EXECUTE, test);
  207. if (test->state != AST_TEST_FAIL) {
  208. test->state = result;
  209. }
  210. if (test->cleanup_cb && test->cleanup_cb(&test->info, test)) {
  211. test->state = AST_TEST_FAIL;
  212. }
  213. exit:
  214. test->time = ast_tvdiff_ms(ast_tvnow(), begin);
  215. }
  216. void ast_test_set_result(struct ast_test *test, enum ast_test_result_state state)
  217. {
  218. if (test->state == AST_TEST_FAIL || state == AST_TEST_NOT_RUN) {
  219. return;
  220. }
  221. test->state = state;
  222. }
  223. static void test_xml_entry(struct ast_test *test, FILE *f)
  224. {
  225. if (!f || !test || test->state == AST_TEST_NOT_RUN) {
  226. return;
  227. }
  228. fprintf(f, "\t<testcase time=\"%u.%u\" name=\"%s%s\"%s>\n",
  229. test->time / 1000, test->time % 1000,
  230. test->info.category, test->info.name,
  231. test->state == AST_TEST_PASS ? "/" : "");
  232. if (test->state == AST_TEST_FAIL) {
  233. fprintf(f, "\t\t<failure><![CDATA[\n%s\n\t\t]]></failure>\n",
  234. S_OR(ast_str_buffer(test->status_str), "NA"));
  235. fprintf(f, "\t</testcase>\n");
  236. }
  237. }
  238. static void test_txt_entry(struct ast_test *test, FILE *f)
  239. {
  240. if (!f || !test) {
  241. return;
  242. }
  243. fprintf(f, "\nName: %s\n", test->info.name);
  244. fprintf(f, "Category: %s\n", test->info.category);
  245. fprintf(f, "Summary: %s\n", test->info.summary);
  246. fprintf(f, "Description: %s\n", test->info.description);
  247. fprintf(f, "Result: %s\n", test_result2str[test->state]);
  248. if (test->state != AST_TEST_NOT_RUN) {
  249. fprintf(f, "Time: %u\n", test->time);
  250. }
  251. if (test->state == AST_TEST_FAIL) {
  252. fprintf(f, "Error Description: %s\n\n", S_OR(ast_str_buffer(test->status_str), "NA"));
  253. }
  254. }
  255. /*!
  256. * \internal
  257. * \brief Executes registered unit tests
  258. *
  259. * \param name of test to run (optional)
  260. * \param test category to run (optional)
  261. * \param cli args for cli test updates (optional)
  262. *
  263. * \return number of tests executed.
  264. *
  265. * \note This function has three modes of operation
  266. * -# When given a name and category, a matching individual test will execute if found.
  267. * -# When given only a category all matching tests within that category will execute.
  268. * -# If given no name or category all registered tests will execute.
  269. */
  270. static int test_execute_multiple(const char *name, const char *category, struct ast_cli_args *cli)
  271. {
  272. char result_buf[32] = { 0 };
  273. struct ast_test *test = NULL;
  274. enum test_mode mode = TEST_ALL; /* 3 modes, 0 = run all, 1 = only by category, 2 = only by name and category */
  275. int execute = 0;
  276. int res = 0;
  277. if (!ast_strlen_zero(category)) {
  278. if (!ast_strlen_zero(name)) {
  279. mode = TEST_NAME_CATEGORY;
  280. } else {
  281. mode = TEST_CATEGORY;
  282. }
  283. }
  284. AST_LIST_LOCK(&tests);
  285. /* clear previous execution results */
  286. memset(&last_results, 0, sizeof(last_results));
  287. AST_LIST_TRAVERSE(&tests, test, entry) {
  288. execute = 0;
  289. switch (mode) {
  290. case TEST_CATEGORY:
  291. if (!test_cat_cmp(test->info.category, category)) {
  292. execute = 1;
  293. }
  294. break;
  295. case TEST_NAME_CATEGORY:
  296. if (!(test_cat_cmp(test->info.category, category)) && !(strcmp(test->info.name, name))) {
  297. execute = 1;
  298. }
  299. break;
  300. case TEST_ALL:
  301. execute = 1;
  302. }
  303. if (execute) {
  304. if (cli) {
  305. ast_cli(cli->fd, "START %s - %s \n", test->info.category, test->info.name);
  306. }
  307. /* set the test status update argument. it is ok if cli is NULL */
  308. test->cli = cli;
  309. /* execute the test and save results */
  310. test_execute(test);
  311. test->cli = NULL;
  312. /* update execution specific counts here */
  313. last_results.last_time += test->time;
  314. if (test->state == AST_TEST_PASS) {
  315. last_results.last_passed++;
  316. } else if (test->state == AST_TEST_FAIL) {
  317. last_results.last_failed++;
  318. }
  319. if (cli) {
  320. term_color(result_buf,
  321. test_result2str[test->state],
  322. (test->state == AST_TEST_FAIL) ? COLOR_RED : COLOR_GREEN,
  323. 0,
  324. sizeof(result_buf));
  325. ast_cli(cli->fd, "END %s - %s Time: %s%ums Result: %s\n",
  326. test->info.category,
  327. test->info.name,
  328. test->time ? "" : "<",
  329. test->time ? test->time : 1,
  330. result_buf);
  331. }
  332. }
  333. /* update total counts as well during this iteration
  334. * even if the current test did not execute this time */
  335. last_results.total_time += test->time;
  336. last_results.total_tests++;
  337. if (test->state != AST_TEST_NOT_RUN) {
  338. if (test->state == AST_TEST_PASS) {
  339. last_results.total_passed++;
  340. } else {
  341. last_results.total_failed++;
  342. }
  343. }
  344. }
  345. res = last_results.last_passed + last_results.last_failed;
  346. AST_LIST_UNLOCK(&tests);
  347. return res;
  348. }
  349. /*!
  350. * \internal
  351. * \brief Generate test results.
  352. *
  353. * \param name of test result to generate (optional)
  354. * \param test category to generate (optional)
  355. * \param path to xml file to generate. (optional)
  356. * \param path to txt file to generate, (optional)
  357. *
  358. * \retval 0 success
  359. * \retval -1 failure
  360. *
  361. * \note This function has three modes of operation.
  362. * -# When given both a name and category, results will be generated for that single test.
  363. * -# When given only a category, results for every test within the category will be generated.
  364. * -# When given no name or category, results for every registered test will be generated.
  365. *
  366. * In order for the results to be generated, an xml and or txt file path must be provided.
  367. */
  368. static int test_generate_results(const char *name, const char *category, const char *xml_path, const char *txt_path)
  369. {
  370. enum test_mode mode = TEST_ALL; /* 0 generate all, 1 generate by category only, 2 generate by name and category */
  371. FILE *f_xml = NULL, *f_txt = NULL;
  372. int res = 0;
  373. struct ast_test *test = NULL;
  374. /* verify at least one output file was given */
  375. if (ast_strlen_zero(xml_path) && ast_strlen_zero(txt_path)) {
  376. return -1;
  377. }
  378. /* define what mode is to be used */
  379. if (!ast_strlen_zero(category)) {
  380. if (!ast_strlen_zero(name)) {
  381. mode = TEST_NAME_CATEGORY;
  382. } else {
  383. mode = TEST_CATEGORY;
  384. }
  385. }
  386. /* open files for writing */
  387. if (!ast_strlen_zero(xml_path)) {
  388. if (!(f_xml = fopen(xml_path, "w"))) {
  389. ast_log(LOG_WARNING, "Could not open file %s for xml test results\n", xml_path);
  390. res = -1;
  391. goto done;
  392. }
  393. }
  394. if (!ast_strlen_zero(txt_path)) {
  395. if (!(f_txt = fopen(txt_path, "w"))) {
  396. ast_log(LOG_WARNING, "Could not open file %s for text output of test results\n", txt_path);
  397. res = -1;
  398. goto done;
  399. }
  400. }
  401. AST_LIST_LOCK(&tests);
  402. /* xml header information */
  403. if (f_xml) {
  404. /*
  405. * http://confluence.atlassian.com/display/BAMBOO/JUnit+parsing+in+Bamboo
  406. */
  407. fprintf(f_xml, "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
  408. fprintf(f_xml, "<testsuite errors=\"0\" time=\"%u.%u\" tests=\"%u\" "
  409. "name=\"AsteriskUnitTests\">\n",
  410. last_results.total_time / 1000, last_results.total_time % 1000,
  411. last_results.total_tests);
  412. fprintf(f_xml, "\t<properties>\n");
  413. fprintf(f_xml, "\t\t<property name=\"version\" value=\"%s\"/>\n", ast_get_version());
  414. fprintf(f_xml, "\t</properties>\n");
  415. }
  416. /* txt header information */
  417. if (f_txt) {
  418. fprintf(f_txt, "Asterisk Version: %s\n", ast_get_version());
  419. fprintf(f_txt, "Asterisk Version Number: %s\n", ast_get_version_num());
  420. fprintf(f_txt, "Number of Tests: %u\n", last_results.total_tests);
  421. fprintf(f_txt, "Number of Tests Executed: %u\n", (last_results.total_passed + last_results.total_failed));
  422. fprintf(f_txt, "Passed Tests: %u\n", last_results.total_passed);
  423. fprintf(f_txt, "Failed Tests: %u\n", last_results.total_failed);
  424. fprintf(f_txt, "Total Execution Time: %u\n", last_results.total_time);
  425. }
  426. /* export each individual test */
  427. AST_LIST_TRAVERSE(&tests, test, entry) {
  428. switch (mode) {
  429. case TEST_CATEGORY:
  430. if (!test_cat_cmp(test->info.category, category)) {
  431. test_xml_entry(test, f_xml);
  432. test_txt_entry(test, f_txt);
  433. }
  434. break;
  435. case TEST_NAME_CATEGORY:
  436. if (!(strcmp(test->info.category, category)) && !(strcmp(test->info.name, name))) {
  437. test_xml_entry(test, f_xml);
  438. test_txt_entry(test, f_txt);
  439. }
  440. break;
  441. case TEST_ALL:
  442. test_xml_entry(test, f_xml);
  443. test_txt_entry(test, f_txt);
  444. }
  445. }
  446. AST_LIST_UNLOCK(&tests);
  447. done:
  448. if (f_xml) {
  449. fprintf(f_xml, "</testsuite>\n");
  450. fclose(f_xml);
  451. }
  452. if (f_txt) {
  453. fclose(f_txt);
  454. }
  455. return res;
  456. }
  457. /*!
  458. * \internal
  459. * \brief adds test to container sorted first by category then by name
  460. *
  461. * \retval 0 success
  462. * \retval -1 failure
  463. */
  464. static int test_insert(struct ast_test *test)
  465. {
  466. /* This is a slow operation that may need to be optimized in the future
  467. * as the test framework expands. At the moment we are doing string
  468. * comparisons on every item within the list to insert in sorted order. */
  469. AST_LIST_LOCK(&tests);
  470. AST_LIST_INSERT_SORTALPHA(&tests, test, entry, info.category);
  471. AST_LIST_UNLOCK(&tests);
  472. return 0;
  473. }
  474. /*!
  475. * \internal
  476. * \brief removes test from container
  477. *
  478. * \return ast_test removed from list on success, or NULL on failure
  479. */
  480. static struct ast_test *test_remove(ast_test_cb_t *cb)
  481. {
  482. struct ast_test *cur = NULL;
  483. AST_LIST_LOCK(&tests);
  484. AST_LIST_TRAVERSE_SAFE_BEGIN(&tests, cur, entry) {
  485. if (cur->cb == cb) {
  486. AST_LIST_REMOVE_CURRENT(entry);
  487. break;
  488. }
  489. }
  490. AST_LIST_TRAVERSE_SAFE_END;
  491. AST_LIST_UNLOCK(&tests);
  492. return cur;
  493. }
  494. /*!
  495. * \brief compares two test categories to determine if cat1 resides in cat2
  496. * \internal
  497. *
  498. * \retval 0 true
  499. * \retval non-zero false
  500. */
  501. static int test_cat_cmp(const char *cat1, const char *cat2)
  502. {
  503. int len1 = 0;
  504. int len2 = 0;
  505. if (!cat1 || !cat2) {
  506. return -1;
  507. }
  508. len1 = strlen(cat1);
  509. len2 = strlen(cat2);
  510. if (len2 > len1) {
  511. return -1;
  512. }
  513. return strncmp(cat1, cat2, len2) ? 1 : 0;
  514. }
  515. /*!
  516. * \internal
  517. * \brief free an ast_test object and all it's data members
  518. */
  519. static struct ast_test *test_free(struct ast_test *test)
  520. {
  521. if (!test) {
  522. return NULL;
  523. }
  524. ast_free(test->status_str);
  525. ast_free(test);
  526. return NULL;
  527. }
  528. /*!
  529. * \internal
  530. * \brief allocate an ast_test object.
  531. */
  532. static struct ast_test *test_alloc(ast_test_cb_t *cb)
  533. {
  534. struct ast_test *test;
  535. if (!cb || !(test = ast_calloc(1, sizeof(*test)))) {
  536. return NULL;
  537. }
  538. test->cb = cb;
  539. test->cb(&test->info, TEST_INIT, test);
  540. if (ast_strlen_zero(test->info.name)) {
  541. ast_log(LOG_WARNING, "Test has no name, test registration refused.\n");
  542. return test_free(test);
  543. }
  544. if (ast_strlen_zero(test->info.category)) {
  545. ast_log(LOG_WARNING, "Test %s has no category, test registration refused.\n",
  546. test->info.name);
  547. return test_free(test);
  548. }
  549. if (test->info.category[0] != '/' || test->info.category[strlen(test->info.category) - 1] != '/') {
  550. ast_log(LOG_WARNING, "Test category '%s' for test '%s' is missing a leading or trailing slash.\n",
  551. test->info.category, test->info.name);
  552. }
  553. if (ast_strlen_zero(test->info.summary)) {
  554. ast_log(LOG_WARNING, "Test %s%s has no summary, test registration refused.\n",
  555. test->info.category, test->info.name);
  556. return test_free(test);
  557. }
  558. if (ast_strlen_zero(test->info.description)) {
  559. ast_log(LOG_WARNING, "Test %s%s has no description, test registration refused.\n",
  560. test->info.category, test->info.name);
  561. return test_free(test);
  562. }
  563. if (!(test->status_str = ast_str_create(128))) {
  564. return test_free(test);
  565. }
  566. return test;
  567. }
  568. static char *complete_test_category(const char *line, const char *word, int pos, int state)
  569. {
  570. int which = 0;
  571. int wordlen = strlen(word);
  572. char *ret = NULL;
  573. struct ast_test *test;
  574. AST_LIST_LOCK(&tests);
  575. AST_LIST_TRAVERSE(&tests, test, entry) {
  576. if (!strncasecmp(word, test->info.category, wordlen) && ++which > state) {
  577. ret = ast_strdup(test->info.category);
  578. break;
  579. }
  580. }
  581. AST_LIST_UNLOCK(&tests);
  582. return ret;
  583. }
  584. static char *complete_test_name(const char *line, const char *word, int pos, int state, const char *category)
  585. {
  586. int which = 0;
  587. int wordlen = strlen(word);
  588. char *ret = NULL;
  589. struct ast_test *test;
  590. AST_LIST_LOCK(&tests);
  591. AST_LIST_TRAVERSE(&tests, test, entry) {
  592. if (!test_cat_cmp(test->info.category, category) && (!strncasecmp(word, test->info.name, wordlen) && ++which > state)) {
  593. ret = ast_strdup(test->info.name);
  594. break;
  595. }
  596. }
  597. AST_LIST_UNLOCK(&tests);
  598. return ret;
  599. }
  600. /* CLI commands */
  601. static char *test_cli_show_registered(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
  602. {
  603. #define FORMAT "%-25.25s %-30.30s %-40.40s %-13.13s\n"
  604. static const char * const option1[] = { "all", "category", NULL };
  605. static const char * const option2[] = { "name", NULL };
  606. struct ast_test *test = NULL;
  607. int count = 0;
  608. switch (cmd) {
  609. case CLI_INIT:
  610. e->command = "test show registered";
  611. e->usage =
  612. "Usage: 'test show registered' can be used in three ways.\n"
  613. " 1. 'test show registered all' shows all registered tests\n"
  614. " 2. 'test show registered category [test category]' shows all tests in the given\n"
  615. " category.\n"
  616. " 3. 'test show registered category [test category] name [test name]' shows all\n"
  617. " tests in a given category matching a given name\n";
  618. return NULL;
  619. case CLI_GENERATE:
  620. if (a->pos == 3) {
  621. return ast_cli_complete(a->word, option1, a->n);
  622. }
  623. if (a->pos == 4) {
  624. return complete_test_category(a->line, a->word, a->pos, a->n);
  625. }
  626. if (a->pos == 5) {
  627. return ast_cli_complete(a->word, option2, a->n);
  628. }
  629. if (a->pos == 6) {
  630. return complete_test_name(a->line, a->word, a->pos, a->n, a->argv[3]);
  631. }
  632. return NULL;
  633. case CLI_HANDLER:
  634. if ((a->argc < 4) || (a->argc == 6) || (a->argc > 7) ||
  635. ((a->argc == 4) && strcmp(a->argv[3], "all")) ||
  636. ((a->argc == 7) && strcmp(a->argv[5], "name"))) {
  637. return CLI_SHOWUSAGE;
  638. }
  639. ast_cli(a->fd, FORMAT, "Category", "Name", "Summary", "Test Result");
  640. ast_cli(a->fd, FORMAT, "--------", "----", "-------", "-----------");
  641. AST_LIST_LOCK(&tests);
  642. AST_LIST_TRAVERSE(&tests, test, entry) {
  643. if ((a->argc == 4) ||
  644. ((a->argc == 5) && !test_cat_cmp(test->info.category, a->argv[4])) ||
  645. ((a->argc == 7) && !strcmp(test->info.category, a->argv[4]) && !strcmp(test->info.name, a->argv[6]))) {
  646. ast_cli(a->fd, FORMAT, test->info.category, test->info.name,
  647. test->info.summary, test_result2str[test->state]);
  648. count++;
  649. }
  650. }
  651. AST_LIST_UNLOCK(&tests);
  652. ast_cli(a->fd, FORMAT, "--------", "----", "-------", "-----------");
  653. ast_cli(a->fd, "\n%d Registered Tests Matched\n", count);
  654. default:
  655. return NULL;
  656. }
  657. return CLI_SUCCESS;
  658. }
  659. static char *test_cli_execute_registered(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
  660. {
  661. static const char * const option1[] = { "all", "category", NULL };
  662. static const char * const option2[] = { "name", NULL };
  663. switch (cmd) {
  664. case CLI_INIT:
  665. e->command = "test execute";
  666. e->usage =
  667. "Usage: test execute can be used in three ways.\n"
  668. " 1. 'test execute all' runs all registered tests\n"
  669. " 2. 'test execute category [test category]' runs all tests in the given\n"
  670. " category.\n"
  671. " 3. 'test execute category [test category] name [test name]' runs all\n"
  672. " tests in a given category matching a given name\n";
  673. return NULL;
  674. case CLI_GENERATE:
  675. if (a->pos == 2) {
  676. return ast_cli_complete(a->word, option1, a->n);
  677. }
  678. if (a->pos == 3) {
  679. return complete_test_category(a->line, a->word, a->pos, a->n);
  680. }
  681. if (a->pos == 4) {
  682. return ast_cli_complete(a->word, option2, a->n);
  683. }
  684. if (a->pos == 5) {
  685. return complete_test_name(a->line, a->word, a->pos, a->n, a->argv[3]);
  686. }
  687. return NULL;
  688. case CLI_HANDLER:
  689. if (a->argc < 3|| a->argc > 6) {
  690. return CLI_SHOWUSAGE;
  691. }
  692. if ((a->argc == 3) && !strcmp(a->argv[2], "all")) { /* run all registered tests */
  693. ast_cli(a->fd, "Running all available tests...\n\n");
  694. test_execute_multiple(NULL, NULL, a);
  695. } else if (a->argc == 4) { /* run only tests within a category */
  696. ast_cli(a->fd, "Running all available tests matching category %s\n\n", a->argv[3]);
  697. test_execute_multiple(NULL, a->argv[3], a);
  698. } else if (a->argc == 6) { /* run only a single test matching the category and name */
  699. ast_cli(a->fd, "Running all available tests matching category %s and name %s\n\n", a->argv[3], a->argv[5]);
  700. test_execute_multiple(a->argv[5], a->argv[3], a);
  701. } else {
  702. return CLI_SHOWUSAGE;
  703. }
  704. AST_LIST_LOCK(&tests);
  705. if (!(last_results.last_passed + last_results.last_failed)) {
  706. ast_cli(a->fd, "--- No Tests Found! ---\n");
  707. }
  708. ast_cli(a->fd, "\n%u Test(s) Executed %u Passed %u Failed\n",
  709. (last_results.last_passed + last_results.last_failed),
  710. last_results.last_passed,
  711. last_results.last_failed);
  712. AST_LIST_UNLOCK(&tests);
  713. default:
  714. return NULL;
  715. }
  716. return CLI_SUCCESS;
  717. }
  718. static char *test_cli_show_results(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
  719. {
  720. #define FORMAT_RES_ALL1 "%s%s %-30.30s %-25.25s %-10.10s\n"
  721. #define FORMAT_RES_ALL2 "%s%s %-30.30s %-25.25s %s%ums\n"
  722. static const char * const option1[] = { "all", "failed", "passed", NULL };
  723. char result_buf[32] = { 0 };
  724. struct ast_test *test = NULL;
  725. int failed = 0;
  726. int passed = 0;
  727. int mode; /* 0 for show all, 1 for show fail, 2 for show passed */
  728. switch (cmd) {
  729. case CLI_INIT:
  730. e->command = "test show results";
  731. e->usage =
  732. "Usage: test show results can be used in three ways\n"
  733. " 1. 'test show results all' Displays results for all executed tests.\n"
  734. " 2. 'test show results passed' Displays results for all passed tests.\n"
  735. " 3. 'test show results failed' Displays results for all failed tests.\n";
  736. return NULL;
  737. case CLI_GENERATE:
  738. if (a->pos == 3) {
  739. return ast_cli_complete(a->word, option1, a->n);
  740. }
  741. return NULL;
  742. case CLI_HANDLER:
  743. /* verify input */
  744. if (a->argc != 4) {
  745. return CLI_SHOWUSAGE;
  746. } else if (!strcmp(a->argv[3], "passed")) {
  747. mode = 2;
  748. } else if (!strcmp(a->argv[3], "failed")) {
  749. mode = 1;
  750. } else if (!strcmp(a->argv[3], "all")) {
  751. mode = 0;
  752. } else {
  753. return CLI_SHOWUSAGE;
  754. }
  755. ast_cli(a->fd, FORMAT_RES_ALL1, "Result", "", "Name", "Category", "Time");
  756. AST_LIST_LOCK(&tests);
  757. AST_LIST_TRAVERSE(&tests, test, entry) {
  758. if (test->state == AST_TEST_NOT_RUN) {
  759. continue;
  760. }
  761. test->state == AST_TEST_FAIL ? failed++ : passed++;
  762. if (!mode || ((mode == 1) && (test->state == AST_TEST_FAIL)) || ((mode == 2) && (test->state == AST_TEST_PASS))) {
  763. /* give our results pretty colors */
  764. term_color(result_buf, test_result2str[test->state],
  765. (test->state == AST_TEST_FAIL) ? COLOR_RED : COLOR_GREEN,
  766. 0, sizeof(result_buf));
  767. ast_cli(a->fd, FORMAT_RES_ALL2,
  768. result_buf,
  769. " ",
  770. test->info.name,
  771. test->info.category,
  772. test->time ? " " : "<",
  773. test->time ? test->time : 1);
  774. }
  775. }
  776. AST_LIST_UNLOCK(&tests);
  777. ast_cli(a->fd, "%d Test(s) Executed %d Passed %d Failed\n", (failed + passed), passed, failed);
  778. default:
  779. return NULL;
  780. }
  781. return CLI_SUCCESS;
  782. }
  783. static char *test_cli_generate_results(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
  784. {
  785. static const char * const option[] = { "xml", "txt", NULL };
  786. const char *file = NULL;
  787. const char *type = "";
  788. int isxml = 0;
  789. int res = 0;
  790. struct ast_str *buf = NULL;
  791. struct timeval time = ast_tvnow();
  792. switch (cmd) {
  793. case CLI_INIT:
  794. e->command = "test generate results";
  795. e->usage =
  796. "Usage: 'test generate results'\n"
  797. " Generates test results in either xml or txt format. An optional \n"
  798. " file path may be provided to specify the location of the xml or\n"
  799. " txt file\n"
  800. " \nExample usage:\n"
  801. " 'test generate results xml' this writes to a default file\n"
  802. " 'test generate results xml /path/to/file.xml' writes to specified file\n";
  803. return NULL;
  804. case CLI_GENERATE:
  805. if (a->pos == 3) {
  806. return ast_cli_complete(a->word, option, a->n);
  807. }
  808. return NULL;
  809. case CLI_HANDLER:
  810. /* verify input */
  811. if (a->argc < 4 || a->argc > 5) {
  812. return CLI_SHOWUSAGE;
  813. } else if (!strcmp(a->argv[3], "xml")) {
  814. type = "xml";
  815. isxml = 1;
  816. } else if (!strcmp(a->argv[3], "txt")) {
  817. type = "txt";
  818. } else {
  819. return CLI_SHOWUSAGE;
  820. }
  821. if (a->argc == 5) {
  822. file = a->argv[4];
  823. } else {
  824. if (!(buf = ast_str_create(256))) {
  825. return NULL;
  826. }
  827. ast_str_set(&buf, 0, "%s/asterisk_test_results-%ld.%s", ast_config_AST_LOG_DIR, (long) time.tv_sec, type);
  828. file = ast_str_buffer(buf);
  829. }
  830. if (isxml) {
  831. res = test_generate_results(NULL, NULL, file, NULL);
  832. } else {
  833. res = test_generate_results(NULL, NULL, NULL, file);
  834. }
  835. if (!res) {
  836. ast_cli(a->fd, "Results Generated Successfully: %s\n", S_OR(file, ""));
  837. } else {
  838. ast_cli(a->fd, "Results Could Not Be Generated: %s\n", S_OR(file, ""));
  839. }
  840. ast_free(buf);
  841. default:
  842. return NULL;
  843. }
  844. return CLI_SUCCESS;
  845. }
  846. static struct ast_cli_entry test_cli[] = {
  847. AST_CLI_DEFINE(test_cli_show_registered, "show registered tests"),
  848. AST_CLI_DEFINE(test_cli_execute_registered, "execute registered tests"),
  849. AST_CLI_DEFINE(test_cli_show_results, "show last test results"),
  850. AST_CLI_DEFINE(test_cli_generate_results, "generate test results to file"),
  851. };
  852. struct stasis_topic *ast_test_suite_topic(void)
  853. {
  854. return test_suite_topic;
  855. }
  856. /*!
  857. * \since 12
  858. * \brief A wrapper object that can be ao2 ref counted around an \ref ast_json blob
  859. */
  860. struct ast_test_suite_message_payload {
  861. struct ast_json *blob; /*!< The actual blob that we want to deliver */
  862. };
  863. /*! \internal
  864. * \since 12
  865. * \brief Destructor for \ref ast_test_suite_message_payload
  866. */
  867. static void test_suite_message_payload_dtor(void *obj)
  868. {
  869. struct ast_test_suite_message_payload *payload = obj;
  870. if (payload->blob) {
  871. ast_json_unref(payload->blob);
  872. }
  873. }
  874. struct ast_json *ast_test_suite_get_blob(struct ast_test_suite_message_payload *payload)
  875. {
  876. return payload->blob;
  877. }
  878. static struct ast_manager_event_blob *test_suite_event_to_ami(struct stasis_message *msg)
  879. {
  880. RAII_VAR(struct ast_str *, packet_string, ast_str_create(128), ast_free);
  881. struct ast_test_suite_message_payload *payload;
  882. struct ast_json *blob;
  883. const char *type;
  884. payload = stasis_message_data(msg);
  885. if (!payload) {
  886. return NULL;
  887. }
  888. blob = ast_test_suite_get_blob(payload);
  889. if (!blob) {
  890. return NULL;
  891. }
  892. type = ast_json_string_get(ast_json_object_get(blob, "type"));
  893. if (ast_strlen_zero(type) || strcmp("testevent", type)) {
  894. return NULL;
  895. }
  896. ast_str_append(&packet_string, 0, "Type: StateChange\r\n");
  897. ast_str_append(&packet_string, 0, "State: %s\r\n",
  898. ast_json_string_get(ast_json_object_get(blob, "state")));
  899. ast_str_append(&packet_string, 0, "AppFile: %s\r\n",
  900. ast_json_string_get(ast_json_object_get(blob, "appfile")));
  901. ast_str_append(&packet_string, 0, "AppFunction: %s\r\n",
  902. ast_json_string_get(ast_json_object_get(blob, "appfunction")));
  903. ast_str_append(&packet_string, 0, "AppLine: %jd\r\n",
  904. ast_json_integer_get(ast_json_object_get(blob, "line")));
  905. ast_str_append(&packet_string, 0, "%s\r\n",
  906. ast_json_string_get(ast_json_object_get(blob, "data")));
  907. return ast_manager_event_blob_create(EVENT_FLAG_REPORTING,
  908. "TestEvent",
  909. "%s",
  910. ast_str_buffer(packet_string));
  911. }
  912. /*! \since 12
  913. * \brief The message type for test suite messages
  914. */
  915. STASIS_MESSAGE_TYPE_DEFN(ast_test_suite_message_type,
  916. .to_ami = test_suite_event_to_ami);
  917. void __ast_test_suite_event_notify(const char *file, const char *func, int line, const char *state, const char *fmt, ...)
  918. {
  919. RAII_VAR(struct ast_test_suite_message_payload *, payload,
  920. NULL,
  921. ao2_cleanup);
  922. RAII_VAR(struct stasis_message *, msg, NULL, ao2_cleanup);
  923. RAII_VAR(struct ast_str *, buf, NULL, ast_free);
  924. va_list ap;
  925. if (!ast_test_suite_message_type()) {
  926. return;
  927. }
  928. buf = ast_str_create(128);
  929. if (!buf) {
  930. return;
  931. }
  932. payload = ao2_alloc(sizeof(*payload), test_suite_message_payload_dtor);
  933. if (!payload) {
  934. return;
  935. }
  936. va_start(ap, fmt);
  937. ast_str_set_va(&buf, 0, fmt, ap);
  938. va_end(ap);
  939. payload->blob = ast_json_pack("{s: s, s: s, s: s, s: s, s: i, s: s}",
  940. "type", "testevent",
  941. "state", state,
  942. "appfile", file,
  943. "appfunction", func,
  944. "line", line,
  945. "data", ast_str_buffer(buf));
  946. if (!payload->blob) {
  947. return;
  948. }
  949. msg = stasis_message_create(ast_test_suite_message_type(), payload);
  950. if (!msg) {
  951. return;
  952. }
  953. stasis_publish(ast_test_suite_topic(), msg);
  954. }
  955. #endif /* TEST_FRAMEWORK */
  956. #ifdef TEST_FRAMEWORK
  957. static void test_cleanup(void)
  958. {
  959. ast_cli_unregister_multiple(test_cli, ARRAY_LEN(test_cli));
  960. ao2_cleanup(test_suite_topic);
  961. test_suite_topic = NULL;
  962. STASIS_MESSAGE_TYPE_CLEANUP(ast_test_suite_message_type);
  963. }
  964. #endif
  965. int ast_test_init(void)
  966. {
  967. #ifdef TEST_FRAMEWORK
  968. ast_register_cleanup(test_cleanup);
  969. /* Create stasis topic */
  970. test_suite_topic = stasis_topic_create("test_suite_topic");
  971. if (!test_suite_topic) {
  972. return -1;
  973. }
  974. if (STASIS_MESSAGE_TYPE_INIT(ast_test_suite_message_type) != 0) {
  975. return -1;
  976. }
  977. /* Register cli commands */
  978. ast_cli_register_multiple(test_cli, ARRAY_LEN(test_cli));
  979. #endif
  980. return 0;
  981. }