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