ARI: Add ability to raise arbitrary User Events
[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         test->state = AST_TEST_NOT_RUN;
254         result = test->cb(&test->info, TEST_EXECUTE, test);
255         if (test->state != AST_TEST_FAIL) {
256                 test->state = result;
257         }
258         if (test->cleanup_cb && test->cleanup_cb(&test->info, test)) {
259                 test->state = AST_TEST_FAIL;
260         }
261 exit:
262         test->time = ast_tvdiff_ms(ast_tvnow(), begin);
263 }
264
265 void ast_test_set_result(struct ast_test *test, enum ast_test_result_state state)
266 {
267         if (test->state == AST_TEST_FAIL || state == AST_TEST_NOT_RUN) {
268                 return;
269         }
270         test->state = state;
271 }
272
273 static void test_xml_entry(struct ast_test *test, FILE *f)
274 {
275         if (!f || !test || test->state == AST_TEST_NOT_RUN) {
276                 return;
277         }
278
279         fprintf(f, "\t<testcase time=\"%d.%d\" name=\"%s%s\"%s>\n",
280                         test->time / 1000, test->time % 1000,
281                         test->info.category, test->info.name,
282                         test->state == AST_TEST_PASS ? "/" : "");
283
284         if (test->state == AST_TEST_FAIL) {
285                 fprintf(f, "\t\t<failure><![CDATA[\n%s\n\t\t]]></failure>\n",
286                                 S_OR(ast_str_buffer(test->status_str), "NA"));
287                 fprintf(f, "\t</testcase>\n");
288         }
289
290 }
291
292 static void test_txt_entry(struct ast_test *test, FILE *f)
293 {
294         if (!f || !test) {
295                 return;
296         }
297
298         fprintf(f, "\nName:              %s\n", test->info.name);
299         fprintf(f,   "Category:          %s\n", test->info.category);
300         fprintf(f,   "Summary:           %s\n", test->info.summary);
301         fprintf(f,   "Description:       %s\n", test->info.description);
302         fprintf(f,   "Result:            %s\n", test_result2str[test->state]);
303         if (test->state != AST_TEST_NOT_RUN) {
304                 fprintf(f,   "Time:              %d\n", test->time);
305         }
306         if (test->state == AST_TEST_FAIL) {
307                 fprintf(f,   "Error Description: %s\n\n", S_OR(ast_str_buffer(test->status_str), "NA"));
308         }
309 }
310
311 /*!
312  * \internal
313  * \brief Executes registered unit tests
314  *
315  * \param name of test to run (optional)
316  * \param test category to run (optional)
317  * \param cli args for cli test updates (optional)
318  *
319  * \return number of tests executed.
320  *
321  * \note This function has three modes of operation
322  * -# When given a name and category, a matching individual test will execute if found.
323  * -# When given only a category all matching tests within that category will execute.
324  * -# If given no name or category all registered tests will execute.
325  */
326 static int test_execute_multiple(const char *name, const char *category, struct ast_cli_args *cli)
327 {
328         char result_buf[32] = { 0 };
329         struct ast_test *test = NULL;
330         enum test_mode mode = TEST_ALL; /* 3 modes, 0 = run all, 1 = only by category, 2 = only by name and category */
331         int execute = 0;
332         int res = 0;
333
334         if (!ast_strlen_zero(category)) {
335                 if (!ast_strlen_zero(name)) {
336                         mode = TEST_NAME_CATEGORY;
337                 } else {
338                         mode = TEST_CATEGORY;
339                 }
340         }
341
342         AST_LIST_LOCK(&tests);
343         /* clear previous execution results */
344         memset(&last_results, 0, sizeof(last_results));
345         AST_LIST_TRAVERSE(&tests, test, entry) {
346
347                 execute = 0;
348                 switch (mode) {
349                 case TEST_CATEGORY:
350                         if (!test_cat_cmp(test->info.category, category)) {
351                                 execute = 1;
352                         }
353                         break;
354                 case TEST_NAME_CATEGORY:
355                         if (!(test_cat_cmp(test->info.category, category)) && !(strcmp(test->info.name, name))) {
356                                 execute = 1;
357                         }
358                         break;
359                 case TEST_ALL:
360                         execute = 1;
361                 }
362
363                 if (execute) {
364                         if (cli) {
365                                 ast_cli(cli->fd, "START  %s - %s \n", test->info.category, test->info.name);
366                         }
367
368                         /* set the test status update argument. it is ok if cli is NULL */
369                         test->cli = cli;
370
371                         /* execute the test and save results */
372                         test_execute(test);
373
374                         test->cli = NULL;
375
376                         /* update execution specific counts here */
377                         last_results.last_time += test->time;
378                         if (test->state == AST_TEST_PASS) {
379                                 last_results.last_passed++;
380                         } else if (test->state == AST_TEST_FAIL) {
381                                 last_results.last_failed++;
382                         }
383
384                         if (cli) {
385                                 term_color(result_buf,
386                                         test_result2str[test->state],
387                                         (test->state == AST_TEST_FAIL) ? COLOR_RED : COLOR_GREEN,
388                                         0,
389                                         sizeof(result_buf));
390                                 ast_cli(cli->fd, "END    %s - %s Time: %s%dms Result: %s\n",
391                                         test->info.category,
392                                         test->info.name,
393                                         test->time ? "" : "<",
394                                         test->time ? test->time : 1,
395                                         result_buf);
396                         }
397                 }
398
399                 /* update total counts as well during this iteration
400                  * even if the current test did not execute this time */
401                 last_results.total_time += test->time;
402                 last_results.total_tests++;
403                 if (test->state != AST_TEST_NOT_RUN) {
404                         if (test->state == AST_TEST_PASS) {
405                                 last_results.total_passed++;
406                         } else {
407                                 last_results.total_failed++;
408                         }
409                 }
410         }
411         res = last_results.last_passed + last_results.last_failed;
412         AST_LIST_UNLOCK(&tests);
413
414         return res;
415 }
416
417 /*!
418  * \internal
419  * \brief Generate test results.
420  *
421  * \param name of test result to generate (optional)
422  * \param test category to generate (optional)
423  * \param path to xml file to generate. (optional)
424  * \param path to txt file to generate, (optional)
425  *
426  * \retval 0 success
427  * \retval -1 failure
428  *
429  * \note This function has three modes of operation.
430  * -# When given both a name and category, results will be generated for that single test.
431  * -# When given only a category, results for every test within the category will be generated.
432  * -# When given no name or category, results for every registered test will be generated.
433  *
434  * In order for the results to be generated, an xml and or txt file path must be provided.
435  */
436 static int test_generate_results(const char *name, const char *category, const char *xml_path, const char *txt_path)
437 {
438         enum test_mode mode = TEST_ALL;  /* 0 generate all, 1 generate by category only, 2 generate by name and category */
439         FILE *f_xml = NULL, *f_txt = NULL;
440         int res = 0;
441         struct ast_test *test = NULL;
442
443         /* verify at least one output file was given */
444         if (ast_strlen_zero(xml_path) && ast_strlen_zero(txt_path)) {
445                 return -1;
446         }
447
448         /* define what mode is to be used */
449         if (!ast_strlen_zero(category)) {
450                 if (!ast_strlen_zero(name)) {
451                         mode = TEST_NAME_CATEGORY;
452                 } else {
453                         mode = TEST_CATEGORY;
454                 }
455         }
456         /* open files for writing */
457         if (!ast_strlen_zero(xml_path)) {
458                 if (!(f_xml = fopen(xml_path, "w"))) {
459                         ast_log(LOG_WARNING, "Could not open file %s for xml test results\n", xml_path);
460                         res = -1;
461                         goto done;
462                 }
463         }
464         if (!ast_strlen_zero(txt_path)) {
465                 if (!(f_txt = fopen(txt_path, "w"))) {
466                         ast_log(LOG_WARNING, "Could not open file %s for text output of test results\n", txt_path);
467                         res = -1;
468                         goto done;
469                 }
470         }
471
472         AST_LIST_LOCK(&tests);
473         /* xml header information */
474         if (f_xml) {
475                 /*
476                  * http://confluence.atlassian.com/display/BAMBOO/JUnit+parsing+in+Bamboo
477                  */
478                 fprintf(f_xml, "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
479                 fprintf(f_xml, "<testsuite errors=\"0\" time=\"%d.%d\" tests=\"%d\" "
480                                 "name=\"AsteriskUnitTests\">\n",
481                                 last_results.total_time / 1000, last_results.total_time % 1000,
482                                 last_results.total_tests);
483                 fprintf(f_xml, "\t<properties>\n");
484                 fprintf(f_xml, "\t\t<property name=\"version\" value=\"%s\"/>\n", ast_get_version());
485                 fprintf(f_xml, "\t</properties>\n");
486         }
487
488         /* txt header information */
489         if (f_txt) {
490                 fprintf(f_txt, "Asterisk Version:         %s\n", ast_get_version());
491                 fprintf(f_txt, "Asterisk Version Number:  %s\n", ast_get_version_num());
492                 fprintf(f_txt, "Number of Tests:          %d\n", last_results.total_tests);
493                 fprintf(f_txt, "Number of Tests Executed: %d\n", (last_results.total_passed + last_results.total_failed));
494                 fprintf(f_txt, "Passed Tests:             %d\n", last_results.total_passed);
495                 fprintf(f_txt, "Failed Tests:             %d\n", last_results.total_failed);
496                 fprintf(f_txt, "Total Execution Time:     %d\n", last_results.total_time);
497         }
498
499         /* export each individual test */
500         AST_LIST_TRAVERSE(&tests, test, entry) {
501                 switch (mode) {
502                 case TEST_CATEGORY:
503                         if (!test_cat_cmp(test->info.category, category)) {
504                                 test_xml_entry(test, f_xml);
505                                 test_txt_entry(test, f_txt);
506                         }
507                         break;
508                 case TEST_NAME_CATEGORY:
509                         if (!(strcmp(test->info.category, category)) && !(strcmp(test->info.name, name))) {
510                                 test_xml_entry(test, f_xml);
511                                 test_txt_entry(test, f_txt);
512                         }
513                         break;
514                 case TEST_ALL:
515                         test_xml_entry(test, f_xml);
516                         test_txt_entry(test, f_txt);
517                 }
518         }
519         AST_LIST_UNLOCK(&tests);
520
521 done:
522         if (f_xml) {
523                 fprintf(f_xml, "</testsuite>\n");
524                 fclose(f_xml);
525         }
526         if (f_txt) {
527                 fclose(f_txt);
528         }
529
530         return res;
531 }
532
533 /*!
534  * \internal
535  * \brief adds test to container sorted first by category then by name
536  *
537  * \retval 0 success
538  * \retval -1 failure
539  */
540 static int test_insert(struct ast_test *test)
541 {
542         /* This is a slow operation that may need to be optimized in the future
543          * as the test framework expands.  At the moment we are doing string
544          * comparisons on every item within the list to insert in sorted order. */
545
546         AST_LIST_LOCK(&tests);
547         AST_LIST_INSERT_SORTALPHA(&tests, test, entry, info.category);
548         AST_LIST_UNLOCK(&tests);
549
550         return 0;
551 }
552
553 /*!
554  * \internal
555  * \brief removes test from container
556  *
557  * \return ast_test removed from list on success, or NULL on failure
558  */
559 static struct ast_test *test_remove(ast_test_cb_t *cb)
560 {
561         struct ast_test *cur = NULL;
562
563         AST_LIST_LOCK(&tests);
564         AST_LIST_TRAVERSE_SAFE_BEGIN(&tests, cur, entry) {
565                 if (cur->cb == cb) {
566                         AST_LIST_REMOVE_CURRENT(entry);
567                         break;
568                 }
569         }
570         AST_LIST_TRAVERSE_SAFE_END;
571         AST_LIST_UNLOCK(&tests);
572
573         return cur;
574 }
575
576 /*!
577  * \brief compares two test categories to determine if cat1 resides in cat2
578  * \internal
579  *
580  * \retval 0 true
581  * \retval non-zero false
582  */
583
584 static int test_cat_cmp(const char *cat1, const char *cat2)
585 {
586         int len1 = 0;
587         int len2 = 0;
588
589         if (!cat1 || !cat2) {
590                 return -1;
591         }
592
593         len1 = strlen(cat1);
594         len2 = strlen(cat2);
595
596         if (len2 > len1) {
597                 return -1;
598         }
599
600         return strncmp(cat1, cat2, len2) ? 1 : 0;
601 }
602
603 /*!
604  * \internal
605  * \brief free an ast_test object and all it's data members
606  */
607 static struct ast_test *test_free(struct ast_test *test)
608 {
609         if (!test) {
610                 return NULL;
611         }
612
613         ast_free(test->status_str);
614         ast_free(test);
615
616         return NULL;
617 }
618
619 /*!
620  * \internal
621  * \brief allocate an ast_test object.
622  */
623 static struct ast_test *test_alloc(ast_test_cb_t *cb)
624 {
625         struct ast_test *test;
626
627         if (!cb || !(test = ast_calloc(1, sizeof(*test)))) {
628                 return NULL;
629         }
630
631         test->cb = cb;
632
633         test->cb(&test->info, TEST_INIT, test);
634
635         if (ast_strlen_zero(test->info.name)) {
636                 ast_log(LOG_WARNING, "Test has no name, test registration refused.\n");
637                 return test_free(test);
638         }
639
640         if (ast_strlen_zero(test->info.category)) {
641                 ast_log(LOG_WARNING, "Test %s has no category, test registration refused.\n",
642                                 test->info.name);
643                 return test_free(test);
644         }
645
646         if (test->info.category[0] != '/' || test->info.category[strlen(test->info.category) - 1] != '/') {
647                 ast_log(LOG_WARNING, "Test category '%s' for test '%s' is missing a leading or trailing slash.\n",
648                                 test->info.category, test->info.name);
649         }
650
651         if (ast_strlen_zero(test->info.summary)) {
652                 ast_log(LOG_WARNING, "Test %s%s has no summary, test registration refused.\n",
653                                 test->info.category, test->info.name);
654                 return test_free(test);
655         }
656
657         if (ast_strlen_zero(test->info.description)) {
658                 ast_log(LOG_WARNING, "Test %s%s has no description, test registration refused.\n",
659                                 test->info.category, test->info.name);
660                 return test_free(test);
661         }
662
663         if (!(test->status_str = ast_str_create(128))) {
664                 return test_free(test);
665         }
666
667         return test;
668 }
669
670 static char *complete_test_category(const char *line, const char *word, int pos, int state)
671 {
672         int which = 0;
673         int wordlen = strlen(word);
674         char *ret = NULL;
675         struct ast_test *test;
676
677         AST_LIST_LOCK(&tests);
678         AST_LIST_TRAVERSE(&tests, test, entry) {
679                 if (!strncasecmp(word, test->info.category, wordlen) && ++which > state) {
680                         ret = ast_strdup(test->info.category);
681                         break;
682                 }
683         }
684         AST_LIST_UNLOCK(&tests);
685         return ret;
686 }
687
688 static char *complete_test_name(const char *line, const char *word, int pos, int state, const char *category)
689 {
690         int which = 0;
691         int wordlen = strlen(word);
692         char *ret = NULL;
693         struct ast_test *test;
694
695         AST_LIST_LOCK(&tests);
696         AST_LIST_TRAVERSE(&tests, test, entry) {
697                 if (!test_cat_cmp(test->info.category, category) && (!strncasecmp(word, test->info.name, wordlen) && ++which > state)) {
698                         ret = ast_strdup(test->info.name);
699                         break;
700                 }
701         }
702         AST_LIST_UNLOCK(&tests);
703         return ret;
704 }
705
706 /* CLI commands */
707 static char *test_cli_show_registered(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
708 {
709 #define FORMAT "%-25.25s %-30.30s %-40.40s %-13.13s\n"
710         static const char * const option1[] = { "all", "category", NULL };
711         static const char * const option2[] = { "name", NULL };
712         struct ast_test *test = NULL;
713         int count = 0;
714         switch (cmd) {
715         case CLI_INIT:
716                 e->command = "test show registered";
717
718                 e->usage =
719                         "Usage: 'test show registered' can be used in three ways.\n"
720                         "       1. 'test show registered all' shows all registered tests\n"
721                         "       2. 'test show registered category [test category]' shows all tests in the given\n"
722                         "          category.\n"
723                         "       3. 'test show registered category [test category] name [test name]' shows all\n"
724                         "           tests in a given category matching a given name\n";
725                 return NULL;
726         case CLI_GENERATE:
727                 if (a->pos == 3) {
728                         return ast_cli_complete(a->word, option1, a->n);
729                 }
730                 if (a->pos == 4) {
731                         return complete_test_category(a->line, a->word, a->pos, a->n);
732                 }
733                 if (a->pos == 5) {
734                         return ast_cli_complete(a->word, option2, a->n);
735                 }
736                 if (a->pos == 6) {
737                         return complete_test_name(a->line, a->word, a->pos, a->n, a->argv[3]);
738                 }
739                 return NULL;
740         case CLI_HANDLER:
741                 if ((a->argc < 4) || (a->argc == 6) || (a->argc > 7) ||
742                         ((a->argc == 4) && strcmp(a->argv[3], "all")) ||
743                         ((a->argc == 7) && strcmp(a->argv[5], "name"))) {
744                         return CLI_SHOWUSAGE;
745                 }
746                 ast_cli(a->fd, FORMAT, "Category", "Name", "Summary", "Test Result");
747                 ast_cli(a->fd, FORMAT, "--------", "----", "-------", "-----------");
748                 AST_LIST_LOCK(&tests);
749                 AST_LIST_TRAVERSE(&tests, test, entry) {
750                         if ((a->argc == 4) ||
751                                  ((a->argc == 5) && !test_cat_cmp(test->info.category, a->argv[4])) ||
752                                  ((a->argc == 7) && !strcmp(test->info.category, a->argv[4]) && !strcmp(test->info.name, a->argv[6]))) {
753
754                                 ast_cli(a->fd, FORMAT, test->info.category, test->info.name,
755                                                 test->info.summary, test_result2str[test->state]);
756                                 count++;
757                         }
758                 }
759                 AST_LIST_UNLOCK(&tests);
760                 ast_cli(a->fd, FORMAT, "--------", "----", "-------", "-----------");
761                 ast_cli(a->fd, "\n%d Registered Tests Matched\n", count);
762         default:
763                 return NULL;
764         }
765
766         return CLI_SUCCESS;
767 }
768
769 static char *test_cli_execute_registered(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
770 {
771         static const char * const option1[] = { "all", "category", NULL };
772         static const char * const option2[] = { "name", NULL };
773
774         switch (cmd) {
775         case CLI_INIT:
776                 e->command = "test execute";
777                 e->usage =
778                         "Usage: test execute can be used in three ways.\n"
779                         "       1. 'test execute all' runs all registered tests\n"
780                         "       2. 'test execute category [test category]' runs all tests in the given\n"
781                         "          category.\n"
782                         "       3. 'test execute category [test category] name [test name]' runs all\n"
783                         "           tests in a given category matching a given name\n";
784                 return NULL;
785         case CLI_GENERATE:
786                 if (a->pos == 2) {
787                         return ast_cli_complete(a->word, option1, a->n);
788                 }
789                 if (a->pos == 3) {
790                         return complete_test_category(a->line, a->word, a->pos, a->n);
791                 }
792                 if (a->pos == 4) {
793                         return ast_cli_complete(a->word, option2, a->n);
794                 }
795                 if (a->pos == 5) {
796                         return complete_test_name(a->line, a->word, a->pos, a->n, a->argv[3]);
797                 }
798                 return NULL;
799         case CLI_HANDLER:
800
801                 if (a->argc < 3|| a->argc > 6) {
802                         return CLI_SHOWUSAGE;
803                 }
804
805                 if ((a->argc == 3) && !strcmp(a->argv[2], "all")) { /* run all registered tests */
806                         ast_cli(a->fd, "Running all available tests...\n\n");
807                         test_execute_multiple(NULL, NULL, a);
808                 } else if (a->argc == 4) { /* run only tests within a category */
809                         ast_cli(a->fd, "Running all available tests matching category %s\n\n", a->argv[3]);
810                         test_execute_multiple(NULL, a->argv[3], a);
811                 } else if (a->argc == 6) { /* run only a single test matching the category and name */
812                         ast_cli(a->fd, "Running all available tests matching category %s and name %s\n\n", a->argv[3], a->argv[5]);
813                         test_execute_multiple(a->argv[5], a->argv[3], a);
814                 } else {
815                         return CLI_SHOWUSAGE;
816                 }
817
818                 AST_LIST_LOCK(&tests);
819                 if (!(last_results.last_passed + last_results.last_failed)) {
820                         ast_cli(a->fd, "--- No Tests Found! ---\n");
821                 }
822                 ast_cli(a->fd, "\n%d Test(s) Executed  %d Passed  %d Failed\n",
823                         (last_results.last_passed + last_results.last_failed),
824                         last_results.last_passed,
825                         last_results.last_failed);
826                 AST_LIST_UNLOCK(&tests);
827         default:
828                 return NULL;
829         }
830
831         return CLI_SUCCESS;
832 }
833
834 static char *test_cli_show_results(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
835 {
836 #define FORMAT_RES_ALL1 "%s%s %-30.30s %-25.25s %-10.10s\n"
837 #define FORMAT_RES_ALL2 "%s%s %-30.30s %-25.25s %s%ums\n"
838         static const char * const option1[] = { "all", "failed", "passed", NULL };
839         char result_buf[32] = { 0 };
840         struct ast_test *test = NULL;
841         int failed = 0;
842         int passed = 0;
843         int mode;  /* 0 for show all, 1 for show fail, 2 for show passed */
844
845         switch (cmd) {
846         case CLI_INIT:
847                 e->command = "test show results";
848                 e->usage =
849                         "Usage: test show results can be used in three ways\n"
850                         "       1. 'test show results all' Displays results for all executed tests.\n"
851                         "       2. 'test show results passed' Displays results for all passed tests.\n"
852                         "       3. 'test show results failed' Displays results for all failed tests.\n";
853                 return NULL;
854         case CLI_GENERATE:
855                 if (a->pos == 3) {
856                         return ast_cli_complete(a->word, option1, a->n);
857                 }
858                 return NULL;
859         case CLI_HANDLER:
860
861                 /* verify input */
862                 if (a->argc != 4) {
863                         return CLI_SHOWUSAGE;
864                 } else if (!strcmp(a->argv[3], "passed")) {
865                         mode = 2;
866                 } else if (!strcmp(a->argv[3], "failed")) {
867                         mode = 1;
868                 } else if (!strcmp(a->argv[3], "all")) {
869                         mode = 0;
870                 } else {
871                         return CLI_SHOWUSAGE;
872                 }
873
874                 ast_cli(a->fd, FORMAT_RES_ALL1, "Result", "", "Name", "Category", "Time");
875                 AST_LIST_LOCK(&tests);
876                 AST_LIST_TRAVERSE(&tests, test, entry) {
877                         if (test->state == AST_TEST_NOT_RUN) {
878                                 continue;
879                         }
880                         test->state == AST_TEST_FAIL ? failed++ : passed++;
881                         if (!mode || ((mode == 1) && (test->state == AST_TEST_FAIL)) || ((mode == 2) && (test->state == AST_TEST_PASS))) {
882                                 /* give our results pretty colors */
883                                 term_color(result_buf, test_result2str[test->state],
884                                         (test->state == AST_TEST_FAIL) ? COLOR_RED : COLOR_GREEN,
885                                         0, sizeof(result_buf));
886
887                                 ast_cli(a->fd, FORMAT_RES_ALL2,
888                                         result_buf,
889                                         "  ",
890                                         test->info.name,
891                                         test->info.category,
892                                         test->time ? " " : "<",
893                                         test->time ? test->time : 1);
894                         }
895                 }
896                 AST_LIST_UNLOCK(&tests);
897
898                 ast_cli(a->fd, "%d Test(s) Executed  %d Passed  %d Failed\n", (failed + passed), passed, failed);
899         default:
900                 return NULL;
901         }
902         return CLI_SUCCESS;
903 }
904
905 static char *test_cli_generate_results(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
906 {
907         static const char * const option[] = { "xml", "txt", NULL };
908         const char *file = NULL;
909         const char *type = "";
910         int isxml = 0;
911         int res = 0;
912         struct ast_str *buf = NULL;
913         struct timeval time = ast_tvnow();
914
915         switch (cmd) {
916         case CLI_INIT:
917                 e->command = "test generate results";
918                 e->usage =
919                         "Usage: 'test generate results'\n"
920                         "       Generates test results in either xml or txt format. An optional \n"
921                         "       file path may be provided to specify the location of the xml or\n"
922                         "       txt file\n"
923                         "       \nExample usage:\n"
924                         "       'test generate results xml' this writes to a default file\n"
925                         "       'test generate results xml /path/to/file.xml' writes to specified file\n";
926                 return NULL;
927         case CLI_GENERATE:
928                 if (a->pos == 3) {
929                         return ast_cli_complete(a->word, option, a->n);
930                 }
931                 return NULL;
932         case CLI_HANDLER:
933
934                 /* verify input */
935                 if (a->argc < 4 || a->argc > 5) {
936                         return CLI_SHOWUSAGE;
937                 } else if (!strcmp(a->argv[3], "xml")) {
938                         type = "xml";
939                         isxml = 1;
940                 } else if (!strcmp(a->argv[3], "txt")) {
941                         type = "txt";
942                 } else {
943                         return CLI_SHOWUSAGE;
944                 }
945
946                 if (a->argc == 5) {
947                         file = a->argv[4];
948                 } else {
949                         if (!(buf = ast_str_create(256))) {
950                                 return NULL;
951                         }
952                         ast_str_set(&buf, 0, "%s/asterisk_test_results-%ld.%s", ast_config_AST_LOG_DIR, (long) time.tv_sec, type);
953
954                         file = ast_str_buffer(buf);
955                 }
956
957                 if (isxml) {
958                         res = test_generate_results(NULL, NULL, file, NULL);
959                 } else {
960                         res = test_generate_results(NULL, NULL, NULL, file);
961                 }
962
963                 if (!res) {
964                         ast_cli(a->fd, "Results Generated Successfully: %s\n", S_OR(file, ""));
965                 } else {
966                         ast_cli(a->fd, "Results Could Not Be Generated: %s\n", S_OR(file, ""));
967                 }
968
969                 ast_free(buf);
970         default:
971                 return NULL;
972         }
973
974         return CLI_SUCCESS;
975 }
976
977 static struct ast_cli_entry test_cli[] = {
978         AST_CLI_DEFINE(test_cli_show_registered,           "show registered tests"),
979         AST_CLI_DEFINE(test_cli_execute_registered,        "execute registered tests"),
980         AST_CLI_DEFINE(test_cli_show_results,              "show last test results"),
981         AST_CLI_DEFINE(test_cli_generate_results,          "generate test results to file"),
982 };
983
984 struct stasis_topic *ast_test_suite_topic(void)
985 {
986         return test_suite_topic;
987 }
988
989 /*!
990  * \since 12
991  * \brief A wrapper object that can be ao2 ref counted around an \ref ast_json blob
992  */
993 struct ast_test_suite_message_payload {
994         struct ast_json *blob; /*!< The actual blob that we want to deliver */
995 };
996
997 /*! \internal
998  * \since 12
999  * \brief Destructor for \ref ast_test_suite_message_payload
1000  */
1001 static void test_suite_message_payload_dtor(void *obj)
1002 {
1003         struct ast_test_suite_message_payload *payload = obj;
1004
1005         if (payload->blob) {
1006                 ast_json_unref(payload->blob);
1007         }
1008 }
1009
1010 struct ast_json *ast_test_suite_get_blob(struct ast_test_suite_message_payload *payload)
1011 {
1012         return payload->blob;
1013 }
1014
1015 void __ast_test_suite_event_notify(const char *file, const char *func, int line, const char *state, const char *fmt, ...)
1016 {
1017         RAII_VAR(struct ast_test_suite_message_payload *, payload,
1018                         ao2_alloc(sizeof(*payload), test_suite_message_payload_dtor),
1019                         ao2_cleanup);
1020         RAII_VAR(struct stasis_message *, msg, NULL, ao2_cleanup);
1021         RAII_VAR(struct ast_str *, buf, ast_str_create(128), ast_free);
1022         va_list ap;
1023
1024         if (!buf) {
1025                 return;
1026         }
1027         if (!payload) {
1028                 return;
1029         }
1030
1031         va_start(ap, fmt);
1032         ast_str_set_va(&buf, 0, fmt, ap);
1033         va_end(ap);
1034         payload->blob = ast_json_pack("{s: s, s: s, s: s, s: s, s: i, s: s}",
1035                              "type", "testevent",
1036                              "state", state,
1037                              "appfile", file,
1038                              "appfunction", func,
1039                              "line", line,
1040                              "data", ast_str_buffer(buf));
1041         if (!payload->blob) {
1042                 return;
1043         }
1044         msg = stasis_message_create(ast_test_suite_message_type(), payload);
1045         if (!msg) {
1046                 return;
1047         }
1048         stasis_publish(ast_test_suite_topic(), msg);
1049 }
1050
1051 #endif /* TEST_FRAMEWORK */
1052
1053 #ifdef TEST_FRAMEWORK
1054
1055 static void test_cleanup(void)
1056 {
1057         ast_cli_unregister_multiple(test_cli, ARRAY_LEN(test_cli));
1058         ao2_cleanup(test_suite_topic);
1059         test_suite_topic = NULL;
1060         STASIS_MESSAGE_TYPE_CLEANUP(ast_test_suite_message_type);
1061 }
1062
1063 #endif
1064
1065 int ast_test_init(void)
1066 {
1067 #ifdef TEST_FRAMEWORK
1068         ast_register_cleanup(test_cleanup);
1069
1070         /* Create stasis topic */
1071         test_suite_topic = stasis_topic_create("test_suite_topic");
1072         if (!test_suite_topic) {
1073                 return -1;
1074         }
1075
1076         if (STASIS_MESSAGE_TYPE_INIT(ast_test_suite_message_type) != 0) {
1077                 return -1;
1078         }
1079
1080         /* Register cli commands */
1081         ast_cli_register_multiple(test_cli, ARRAY_LEN(test_cli));
1082 #endif
1083
1084         return 0;
1085 }