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