Merge "asterisk.c: When astcanary dies on linux, reset priority on all threads."
[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_REGISTER_FILE();
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 /*! This array corresponds to the values defined in the ast_test_state enum */
60 static const char * const test_result2str[] = {
61         [AST_TEST_NOT_RUN] = "NOT RUN",
62         [AST_TEST_PASS]    = "PASS",
63         [AST_TEST_FAIL]    = "FAIL",
64 };
65
66 /*! holds all the information pertaining to a single defined test */
67 struct ast_test {
68         struct ast_test_info info;        /*!< holds test callback information */
69         /*!
70          * \brief Test defined status output from last execution
71          */
72         struct ast_str *status_str;
73         /*!
74          * \brief CLI arguments, if tests being run from the CLI
75          *
76          * If this is set, status updates from the tests will be sent to the
77          * CLI in addition to being saved off in status_str.
78          */
79         struct ast_cli_args *cli;
80         enum ast_test_result_state state;   /*!< current test state */
81         unsigned int time;                  /*!< time in ms test took */
82         ast_test_cb_t *cb;                  /*!< test callback function */
83         ast_test_init_cb_t *init_cb;        /*!< test init function */
84         ast_test_cleanup_cb_t *cleanup_cb;  /*!< test cleanup function */
85         AST_LIST_ENTRY(ast_test) entry;
86 };
87
88 /*! global structure containing both total and last test execution results */
89 static struct ast_test_execute_results {
90         unsigned int total_tests;  /*!< total number of tests, regardless if they have been executed or not */
91         unsigned int total_passed; /*!< total number of executed tests passed */
92         unsigned int total_failed; /*!< total number of executed tests failed */
93         unsigned int total_time;   /*!< total time of all executed tests */
94         unsigned int last_passed;  /*!< number of passed tests during last execution */
95         unsigned int last_failed;  /*!< number of failed tests during last execution */
96         unsigned int last_time;    /*!< total time of the last test execution */
97 } last_results;
98
99 enum test_mode {
100         TEST_ALL = 0,
101         TEST_CATEGORY = 1,
102         TEST_NAME_CATEGORY = 2,
103 };
104
105 /*! List of registered test definitions */
106 static AST_LIST_HEAD_STATIC(tests, ast_test);
107
108 static struct ast_test *test_alloc(ast_test_cb_t *cb);
109 static struct ast_test *test_free(struct ast_test *test);
110 static int test_insert(struct ast_test *test);
111 static struct ast_test *test_remove(ast_test_cb_t *cb);
112 static int test_cat_cmp(const char *cat1, const char *cat2);
113 static int registration_errors = 0;
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_init(const char *category, ast_test_init_cb_t *cb)
163 {
164         struct ast_test *test;
165         int registered = 1;
166
167         AST_LIST_LOCK(&tests);
168         AST_LIST_TRAVERSE(&tests, test, entry) {
169                 if (!(test_cat_cmp(test->info.category, category))) {
170                         test->init_cb = cb;
171                         registered = 0;
172                 }
173         }
174         AST_LIST_UNLOCK(&tests);
175
176         return registered;
177 }
178
179 int ast_test_register_cleanup(const char *category, ast_test_cleanup_cb_t *cb)
180 {
181         struct ast_test *test;
182         int registered = 1;
183
184         AST_LIST_LOCK(&tests);
185         AST_LIST_TRAVERSE(&tests, test, entry) {
186                 if (!(test_cat_cmp(test->info.category, category))) {
187                         test->cleanup_cb = cb;
188                         registered = 0;
189                 }
190         }
191         AST_LIST_UNLOCK(&tests);
192
193         return registered;
194 }
195
196 int ast_test_register(ast_test_cb_t *cb)
197 {
198         struct ast_test *test;
199
200         if (!cb) {
201                 ast_log(LOG_ERROR, "Attempted to register test without all required information\n");
202                 registration_errors++;
203                 return -1;
204         }
205
206         if (!(test = test_alloc(cb))) {
207                 registration_errors++;
208                 return -1;
209         }
210
211         if (test_insert(test)) {
212                 test_free(test);
213                 registration_errors++;
214                 return -1;
215         }
216
217         return 0;
218 }
219
220 int ast_test_unregister(ast_test_cb_t *cb)
221 {
222         struct ast_test *test;
223
224         if (!(test = test_remove(cb))) {
225                 return -1; /* not found */
226         }
227
228         test_free(test);
229
230         return 0;
231 }
232
233 /*!
234  * \internal
235  * \brief executes a single test, storing the results in the test->result structure.
236  *
237  * \note The last_results structure which contains global statistics about test execution
238  * must be updated when using this function. See use in test_execute_multiple().
239  */
240 static void test_execute(struct ast_test *test)
241 {
242         struct timeval begin;
243         enum ast_test_result_state result;
244
245         ast_str_reset(test->status_str);
246
247         begin = ast_tvnow();
248         if (test->init_cb && test->init_cb(&test->info, test)) {
249                 test->state = AST_TEST_FAIL;
250                 goto exit;
251         }
252         test->state = AST_TEST_NOT_RUN;
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=\"%u.%u\" 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:              %u\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%ums 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=\"%u.%u\" tests=\"%u\" "
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:          %u\n", last_results.total_tests);
492                 fprintf(f_txt, "Number of Tests Executed: %u\n", (last_results.total_passed + last_results.total_failed));
493                 fprintf(f_txt, "Passed Tests:             %u\n", last_results.total_passed);
494                 fprintf(f_txt, "Failed Tests:             %u\n", last_results.total_failed);
495                 fprintf(f_txt, "Total Execution Time:     %u\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         test = ast_calloc(1, sizeof(*test));
627         if (!test) {
628                 ast_log(LOG_ERROR, "Failed to allocate test, registration failed.\n");
629                 return NULL;
630         }
631
632         test->cb = cb;
633
634         test->cb(&test->info, TEST_INIT, test);
635
636         if (ast_strlen_zero(test->info.name)) {
637                 ast_log(LOG_ERROR, "Test has no name, test registration refused.\n");
638                 return test_free(test);
639         }
640
641         if (ast_strlen_zero(test->info.category)) {
642                 ast_log(LOG_ERROR, "Test %s has no category, test registration refused.\n",
643                         test->info.name);
644                 return test_free(test);
645         }
646
647         if (test->info.category[0] != '/' || test->info.category[strlen(test->info.category) - 1] != '/') {
648                 ast_log(LOG_WARNING, "Test category '%s' for test '%s' is missing a leading or trailing slash.\n",
649                         test->info.category, test->info.name);
650                 /*
651                  * Flag an error anyways so test_registrations fails but allow the
652                  * test to be registered.
653                  */
654                 ++registration_errors;
655         }
656
657         if (ast_strlen_zero(test->info.summary)) {
658                 ast_log(LOG_ERROR, "Test %s%s has no summary, test registration refused.\n",
659                         test->info.category, test->info.name);
660                 return test_free(test);
661         }
662         if (test->info.summary[strlen(test->info.summary) - 1] == '\n') {
663                 ast_log(LOG_WARNING, "Test %s%s summary has a trailing newline.\n",
664                         test->info.category, test->info.name);
665                 /*
666                  * Flag an error anyways so test_registrations fails but allow the
667                  * test to be registered.
668                  */
669                 ++registration_errors;
670         }
671
672         if (ast_strlen_zero(test->info.description)) {
673                 ast_log(LOG_ERROR, "Test %s%s has no description, test registration refused.\n",
674                         test->info.category, test->info.name);
675                 return test_free(test);
676         }
677         if (test->info.description[strlen(test->info.description) - 1] == '\n') {
678                 ast_log(LOG_WARNING, "Test %s%s description has a trailing newline.\n",
679                         test->info.category, test->info.name);
680                 /*
681                  * Flag an error anyways so test_registrations fails but allow the
682                  * test to be registered.
683                  */
684                 ++registration_errors;
685         }
686
687         if (!(test->status_str = ast_str_create(128))) {
688                 ast_log(LOG_ERROR, "Failed to allocate status_str for %s%s, test registration failed.\n",
689                         test->info.category, test->info.name);
690                 return test_free(test);
691         }
692
693         return test;
694 }
695
696 static char *complete_test_category(const char *line, const char *word, int pos, int state)
697 {
698         int which = 0;
699         int wordlen = strlen(word);
700         char *ret = NULL;
701         struct ast_test *test;
702
703         AST_LIST_LOCK(&tests);
704         AST_LIST_TRAVERSE(&tests, test, entry) {
705                 if (!strncasecmp(word, test->info.category, wordlen) && ++which > state) {
706                         ret = ast_strdup(test->info.category);
707                         break;
708                 }
709         }
710         AST_LIST_UNLOCK(&tests);
711         return ret;
712 }
713
714 static char *complete_test_name(const char *line, const char *word, int pos, int state, const char *category)
715 {
716         int which = 0;
717         int wordlen = strlen(word);
718         char *ret = NULL;
719         struct ast_test *test;
720
721         AST_LIST_LOCK(&tests);
722         AST_LIST_TRAVERSE(&tests, test, entry) {
723                 if (!test_cat_cmp(test->info.category, category) && (!strncasecmp(word, test->info.name, wordlen) && ++which > state)) {
724                         ret = ast_strdup(test->info.name);
725                         break;
726                 }
727         }
728         AST_LIST_UNLOCK(&tests);
729         return ret;
730 }
731
732 /* CLI commands */
733 static char *test_cli_show_registered(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
734 {
735 #define FORMAT "%-25.25s %-30.30s %-40.40s %-13.13s\n"
736         static const char * const option1[] = { "all", "category", NULL };
737         static const char * const option2[] = { "name", NULL };
738         struct ast_test *test = NULL;
739         int count = 0;
740         switch (cmd) {
741         case CLI_INIT:
742                 e->command = "test show registered";
743
744                 e->usage =
745                         "Usage: 'test show registered' can be used in three ways.\n"
746                         "       1. 'test show registered all' shows all registered tests\n"
747                         "       2. 'test show registered category [test category]' shows all tests in the given\n"
748                         "          category.\n"
749                         "       3. 'test show registered category [test category] name [test name]' shows all\n"
750                         "           tests in a given category matching a given name\n";
751                 return NULL;
752         case CLI_GENERATE:
753                 if (a->pos == 3) {
754                         return ast_cli_complete(a->word, option1, a->n);
755                 }
756                 if (a->pos == 4) {
757                         return complete_test_category(a->line, a->word, a->pos, a->n);
758                 }
759                 if (a->pos == 5) {
760                         return ast_cli_complete(a->word, option2, a->n);
761                 }
762                 if (a->pos == 6) {
763                         return complete_test_name(a->line, a->word, a->pos, a->n, a->argv[3]);
764                 }
765                 return NULL;
766         case CLI_HANDLER:
767                 if ((a->argc < 4) || (a->argc == 6) || (a->argc > 7) ||
768                         ((a->argc == 4) && strcmp(a->argv[3], "all")) ||
769                         ((a->argc == 7) && strcmp(a->argv[5], "name"))) {
770                         return CLI_SHOWUSAGE;
771                 }
772                 ast_cli(a->fd, FORMAT, "Category", "Name", "Summary", "Test Result");
773                 ast_cli(a->fd, FORMAT, "--------", "----", "-------", "-----------");
774                 AST_LIST_LOCK(&tests);
775                 AST_LIST_TRAVERSE(&tests, test, entry) {
776                         if ((a->argc == 4) ||
777                                  ((a->argc == 5) && !test_cat_cmp(test->info.category, a->argv[4])) ||
778                                  ((a->argc == 7) && !strcmp(test->info.category, a->argv[4]) && !strcmp(test->info.name, a->argv[6]))) {
779
780                                 ast_cli(a->fd, FORMAT, test->info.category, test->info.name,
781                                                 test->info.summary, test_result2str[test->state]);
782                                 count++;
783                         }
784                 }
785                 AST_LIST_UNLOCK(&tests);
786                 ast_cli(a->fd, FORMAT, "--------", "----", "-------", "-----------");
787                 ast_cli(a->fd, "\n%d Registered Tests Matched\n", count);
788         default:
789                 return NULL;
790         }
791
792         return CLI_SUCCESS;
793 }
794
795 static char *test_cli_execute_registered(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
796 {
797         static const char * const option1[] = { "all", "category", NULL };
798         static const char * const option2[] = { "name", NULL };
799
800         switch (cmd) {
801         case CLI_INIT:
802                 e->command = "test execute";
803                 e->usage =
804                         "Usage: test execute can be used in three ways.\n"
805                         "       1. 'test execute all' runs all registered tests\n"
806                         "       2. 'test execute category [test category]' runs all tests in the given\n"
807                         "          category.\n"
808                         "       3. 'test execute category [test category] name [test name]' runs all\n"
809                         "           tests in a given category matching a given name\n";
810                 return NULL;
811         case CLI_GENERATE:
812                 if (a->pos == 2) {
813                         return ast_cli_complete(a->word, option1, a->n);
814                 }
815                 if (a->pos == 3) {
816                         return complete_test_category(a->line, a->word, a->pos, a->n);
817                 }
818                 if (a->pos == 4) {
819                         return ast_cli_complete(a->word, option2, a->n);
820                 }
821                 if (a->pos == 5) {
822                         return complete_test_name(a->line, a->word, a->pos, a->n, a->argv[3]);
823                 }
824                 return NULL;
825         case CLI_HANDLER:
826
827                 if (a->argc < 3|| a->argc > 6) {
828                         return CLI_SHOWUSAGE;
829                 }
830
831                 if ((a->argc == 3) && !strcmp(a->argv[2], "all")) { /* run all registered tests */
832                         ast_cli(a->fd, "Running all available tests...\n\n");
833                         test_execute_multiple(NULL, NULL, a);
834                 } else if (a->argc == 4) { /* run only tests within a category */
835                         ast_cli(a->fd, "Running all available tests matching category %s\n\n", a->argv[3]);
836                         test_execute_multiple(NULL, a->argv[3], a);
837                 } else if (a->argc == 6) { /* run only a single test matching the category and name */
838                         ast_cli(a->fd, "Running all available tests matching category %s and name %s\n\n", a->argv[3], a->argv[5]);
839                         test_execute_multiple(a->argv[5], a->argv[3], a);
840                 } else {
841                         return CLI_SHOWUSAGE;
842                 }
843
844                 AST_LIST_LOCK(&tests);
845                 if (!(last_results.last_passed + last_results.last_failed)) {
846                         ast_cli(a->fd, "--- No Tests Found! ---\n");
847                 }
848                 ast_cli(a->fd, "\n%u Test(s) Executed  %u Passed  %u Failed\n",
849                         (last_results.last_passed + last_results.last_failed),
850                         last_results.last_passed,
851                         last_results.last_failed);
852                 AST_LIST_UNLOCK(&tests);
853         default:
854                 return NULL;
855         }
856
857         return CLI_SUCCESS;
858 }
859
860 static char *test_cli_show_results(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
861 {
862 #define FORMAT_RES_ALL1 "%s%s %-30.30s %-25.25s %-10.10s\n"
863 #define FORMAT_RES_ALL2 "%s%s %-30.30s %-25.25s %s%ums\n"
864         static const char * const option1[] = { "all", "failed", "passed", NULL };
865         char result_buf[32] = { 0 };
866         struct ast_test *test = NULL;
867         int failed = 0;
868         int passed = 0;
869         int mode;  /* 0 for show all, 1 for show fail, 2 for show passed */
870
871         switch (cmd) {
872         case CLI_INIT:
873                 e->command = "test show results";
874                 e->usage =
875                         "Usage: test show results can be used in three ways\n"
876                         "       1. 'test show results all' Displays results for all executed tests.\n"
877                         "       2. 'test show results passed' Displays results for all passed tests.\n"
878                         "       3. 'test show results failed' Displays results for all failed tests.\n";
879                 return NULL;
880         case CLI_GENERATE:
881                 if (a->pos == 3) {
882                         return ast_cli_complete(a->word, option1, a->n);
883                 }
884                 return NULL;
885         case CLI_HANDLER:
886
887                 /* verify input */
888                 if (a->argc != 4) {
889                         return CLI_SHOWUSAGE;
890                 } else if (!strcmp(a->argv[3], "passed")) {
891                         mode = 2;
892                 } else if (!strcmp(a->argv[3], "failed")) {
893                         mode = 1;
894                 } else if (!strcmp(a->argv[3], "all")) {
895                         mode = 0;
896                 } else {
897                         return CLI_SHOWUSAGE;
898                 }
899
900                 ast_cli(a->fd, FORMAT_RES_ALL1, "Result", "", "Name", "Category", "Time");
901                 AST_LIST_LOCK(&tests);
902                 AST_LIST_TRAVERSE(&tests, test, entry) {
903                         if (test->state == AST_TEST_NOT_RUN) {
904                                 continue;
905                         }
906                         test->state == AST_TEST_FAIL ? failed++ : passed++;
907                         if (!mode || ((mode == 1) && (test->state == AST_TEST_FAIL)) || ((mode == 2) && (test->state == AST_TEST_PASS))) {
908                                 /* give our results pretty colors */
909                                 term_color(result_buf, test_result2str[test->state],
910                                         (test->state == AST_TEST_FAIL) ? COLOR_RED : COLOR_GREEN,
911                                         0, sizeof(result_buf));
912
913                                 ast_cli(a->fd, FORMAT_RES_ALL2,
914                                         result_buf,
915                                         "  ",
916                                         test->info.name,
917                                         test->info.category,
918                                         test->time ? " " : "<",
919                                         test->time ? test->time : 1);
920                         }
921                 }
922                 AST_LIST_UNLOCK(&tests);
923
924                 ast_cli(a->fd, "%d Test(s) Executed  %d Passed  %d Failed\n", (failed + passed), passed, failed);
925         default:
926                 return NULL;
927         }
928         return CLI_SUCCESS;
929 }
930
931 static char *test_cli_generate_results(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
932 {
933         static const char * const option[] = { "xml", "txt", NULL };
934         const char *file = NULL;
935         const char *type = "";
936         int isxml = 0;
937         int res = 0;
938         struct ast_str *buf = NULL;
939         struct timeval time = ast_tvnow();
940
941         switch (cmd) {
942         case CLI_INIT:
943                 e->command = "test generate results";
944                 e->usage =
945                         "Usage: 'test generate results'\n"
946                         "       Generates test results in either xml or txt format. An optional \n"
947                         "       file path may be provided to specify the location of the xml or\n"
948                         "       txt file\n"
949                         "       \nExample usage:\n"
950                         "       'test generate results xml' this writes to a default file\n"
951                         "       'test generate results xml /path/to/file.xml' writes to specified file\n";
952                 return NULL;
953         case CLI_GENERATE:
954                 if (a->pos == 3) {
955                         return ast_cli_complete(a->word, option, a->n);
956                 }
957                 return NULL;
958         case CLI_HANDLER:
959
960                 /* verify input */
961                 if (a->argc < 4 || a->argc > 5) {
962                         return CLI_SHOWUSAGE;
963                 } else if (!strcmp(a->argv[3], "xml")) {
964                         type = "xml";
965                         isxml = 1;
966                 } else if (!strcmp(a->argv[3], "txt")) {
967                         type = "txt";
968                 } else {
969                         return CLI_SHOWUSAGE;
970                 }
971
972                 if (a->argc == 5) {
973                         file = a->argv[4];
974                 } else {
975                         if (!(buf = ast_str_create(256))) {
976                                 return NULL;
977                         }
978                         ast_str_set(&buf, 0, "%s/asterisk_test_results-%ld.%s", ast_config_AST_LOG_DIR, (long) time.tv_sec, type);
979
980                         file = ast_str_buffer(buf);
981                 }
982
983                 if (isxml) {
984                         res = test_generate_results(NULL, NULL, file, NULL);
985                 } else {
986                         res = test_generate_results(NULL, NULL, NULL, file);
987                 }
988
989                 if (!res) {
990                         ast_cli(a->fd, "Results Generated Successfully: %s\n", S_OR(file, ""));
991                 } else {
992                         ast_cli(a->fd, "Results Could Not Be Generated: %s\n", S_OR(file, ""));
993                 }
994
995                 ast_free(buf);
996         default:
997                 return NULL;
998         }
999
1000         return CLI_SUCCESS;
1001 }
1002
1003 static struct ast_cli_entry test_cli[] = {
1004         AST_CLI_DEFINE(test_cli_show_registered,           "show registered tests"),
1005         AST_CLI_DEFINE(test_cli_execute_registered,        "execute registered tests"),
1006         AST_CLI_DEFINE(test_cli_show_results,              "show last test results"),
1007         AST_CLI_DEFINE(test_cli_generate_results,          "generate test results to file"),
1008 };
1009
1010 struct stasis_topic *ast_test_suite_topic(void)
1011 {
1012         return test_suite_topic;
1013 }
1014
1015 /*!
1016  * \since 12
1017  * \brief A wrapper object that can be ao2 ref counted around an \ref ast_json blob
1018  */
1019 struct ast_test_suite_message_payload {
1020         struct ast_json *blob; /*!< The actual blob that we want to deliver */
1021 };
1022
1023 /*! \internal
1024  * \since 12
1025  * \brief Destructor for \ref ast_test_suite_message_payload
1026  */
1027 static void test_suite_message_payload_dtor(void *obj)
1028 {
1029         struct ast_test_suite_message_payload *payload = obj;
1030
1031         if (payload->blob) {
1032                 ast_json_unref(payload->blob);
1033         }
1034 }
1035
1036 struct ast_json *ast_test_suite_get_blob(struct ast_test_suite_message_payload *payload)
1037 {
1038         return payload->blob;
1039 }
1040
1041 static struct ast_manager_event_blob *test_suite_event_to_ami(struct stasis_message *msg)
1042 {
1043         RAII_VAR(struct ast_str *, packet_string, ast_str_create(128), ast_free);
1044         struct ast_test_suite_message_payload *payload;
1045         struct ast_json *blob;
1046         const char *type;
1047
1048         payload = stasis_message_data(msg);
1049         if (!payload) {
1050                 return NULL;
1051         }
1052         blob = ast_test_suite_get_blob(payload);
1053         if (!blob) {
1054                 return NULL;
1055         }
1056
1057         type = ast_json_string_get(ast_json_object_get(blob, "type"));
1058         if (ast_strlen_zero(type) || strcmp("testevent", type)) {
1059                 return NULL;
1060         }
1061
1062         ast_str_append(&packet_string, 0, "Type: StateChange\r\n");
1063         ast_str_append(&packet_string, 0, "State: %s\r\n",
1064                 ast_json_string_get(ast_json_object_get(blob, "state")));
1065         ast_str_append(&packet_string, 0, "AppFile: %s\r\n",
1066                 ast_json_string_get(ast_json_object_get(blob, "appfile")));
1067         ast_str_append(&packet_string, 0, "AppFunction: %s\r\n",
1068                 ast_json_string_get(ast_json_object_get(blob, "appfunction")));
1069         ast_str_append(&packet_string, 0, "AppLine: %jd\r\n",
1070                 ast_json_integer_get(ast_json_object_get(blob, "line")));
1071         ast_str_append(&packet_string, 0, "%s\r\n",
1072                 ast_json_string_get(ast_json_object_get(blob, "data")));
1073
1074         return ast_manager_event_blob_create(EVENT_FLAG_REPORTING,
1075                 "TestEvent",
1076                 "%s",
1077                 ast_str_buffer(packet_string));
1078 }
1079
1080 /*! \since 12
1081  * \brief The message type for test suite messages
1082  */
1083 STASIS_MESSAGE_TYPE_DEFN(ast_test_suite_message_type,
1084         .to_ami = test_suite_event_to_ami);
1085
1086 void __ast_test_suite_event_notify(const char *file, const char *func, int line, const char *state, const char *fmt, ...)
1087 {
1088         RAII_VAR(struct ast_test_suite_message_payload *, payload,
1089                         NULL,
1090                         ao2_cleanup);
1091         RAII_VAR(struct stasis_message *, msg, NULL, ao2_cleanup);
1092         RAII_VAR(struct ast_str *, buf, NULL, ast_free);
1093         va_list ap;
1094
1095         if (!ast_test_suite_message_type()) {
1096                 return;
1097         }
1098
1099         buf = ast_str_create(128);
1100         if (!buf) {
1101                 return;
1102         }
1103
1104         payload = ao2_alloc(sizeof(*payload), test_suite_message_payload_dtor);
1105         if (!payload) {
1106                 return;
1107         }
1108
1109         va_start(ap, fmt);
1110         ast_str_set_va(&buf, 0, fmt, ap);
1111         va_end(ap);
1112         payload->blob = ast_json_pack("{s: s, s: s, s: s, s: s, s: i, s: s}",
1113                              "type", "testevent",
1114                              "state", state,
1115                              "appfile", file,
1116                              "appfunction", func,
1117                              "line", line,
1118                              "data", ast_str_buffer(buf));
1119         if (!payload->blob) {
1120                 return;
1121         }
1122         msg = stasis_message_create(ast_test_suite_message_type(), payload);
1123         if (!msg) {
1124                 return;
1125         }
1126         stasis_publish(ast_test_suite_topic(), msg);
1127 }
1128
1129 AST_TEST_DEFINE(test_registrations)
1130 {
1131         switch (cmd) {
1132         case TEST_INIT:
1133                 info->name = "registrations";
1134                 info->category = "/main/test/";
1135                 info->summary = "Validate Test Registration Data.";
1136                 info->description = "Validate Test Registration Data.";
1137                 return AST_TEST_NOT_RUN;
1138         case TEST_EXECUTE:
1139                 break;
1140         }
1141
1142         if (registration_errors) {
1143                 ast_test_status_update(test,
1144                         "%d test registration error%s occurred.  See startup logs for details.\n",
1145                         registration_errors, registration_errors > 1 ? "s" : "");
1146                 return AST_TEST_FAIL;
1147         }
1148
1149         return AST_TEST_PASS;
1150 }
1151
1152 static void test_cleanup(void)
1153 {
1154         AST_TEST_UNREGISTER(test_registrations);
1155         ast_cli_unregister_multiple(test_cli, ARRAY_LEN(test_cli));
1156         ao2_cleanup(test_suite_topic);
1157         test_suite_topic = NULL;
1158         STASIS_MESSAGE_TYPE_CLEANUP(ast_test_suite_message_type);
1159 }
1160 #endif /* TEST_FRAMEWORK */
1161
1162 int ast_test_init(void)
1163 {
1164 #ifdef TEST_FRAMEWORK
1165         ast_register_cleanup(test_cleanup);
1166
1167         /* Create stasis topic */
1168         test_suite_topic = stasis_topic_create("test_suite_topic");
1169         if (!test_suite_topic) {
1170                 return -1;
1171         }
1172
1173         if (STASIS_MESSAGE_TYPE_INIT(ast_test_suite_message_type) != 0) {
1174                 return -1;
1175         }
1176
1177         AST_TEST_REGISTER(test_registrations);
1178
1179         /* Register cli commands */
1180         ast_cli_register_multiple(test_cli, ARRAY_LEN(test_cli));
1181 #endif
1182
1183         return 0;
1184 }