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