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