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