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