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