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