Fix spelling of 'category.'
[asterisk/asterisk.git] / main / test.c
1 /*
2  * Asterisk -- An open source telephony toolkit.
3  *
4  * Copyright (C) 2009, Digium, Inc.
5  *
6  * David Vossel <dvossel@digium.com>
7  *
8  * See http://www.asterisk.org for more information about
9  * the Asterisk project. Please do not directly contact
10  * any of the maintainers of this project for assistance;
11  * the project provides a web site, mailing lists and IRC
12  * channels for your use.
13  *
14  * This program is free software, distributed under the terms of
15  * the GNU General Public License Version 2. See the LICENSE file
16  * at the top of the source tree.
17  */
18
19 /*! \file
20  *
21  * \brief Unit Test Framework
22  *
23  * \author David Vossel <dvossel@digium.com>
24  */
25
26 #include "asterisk.h"
27
28 ASTERISK_FILE_VERSION(__FILE__, "$Revision$");
29
30 #include "asterisk/_private.h"
31
32 #ifdef TEST_FRAMEWORK
33 #include "asterisk/test.h"
34 #include "asterisk/logger.h"
35 #include "asterisk/linkedlists.h"
36 #include "asterisk/utils.h"
37 #include "asterisk/cli.h"
38 #include "asterisk/term.h"
39 #include "asterisk/version.h"
40 #include "asterisk/paths.h"
41 #include "asterisk/time.h"
42
43 /*! This array corrisponds to the values defined in the ast_test_state enum */
44 static const char * const test_result2str[] = {
45         [AST_TEST_NOT_RUN] = "NOT RUN",
46         [AST_TEST_PASS] = "PASS",
47         [AST_TEST_FAIL] = "FAIL",
48 };
49
50 /*! holds all the information pertaining to a single defined test */
51 struct ast_test {
52         struct ast_test_info info;        /*! holds test callback information */
53         struct ast_test_args args;        /*! function callback arguments */
54         enum ast_test_result_state state; /*! current test state */
55         unsigned int time;                /*! time in ms test took */
56         ast_test_cb_t *cb;                /*! test callback function */
57         AST_LIST_ENTRY(ast_test) entry;
58 };
59
60 /*! global structure containing both total and last test execution results */
61 static struct ast_test_execute_results {
62         unsigned int total_tests;  /* total number of tests, reguardless if they have been executed or not */
63         unsigned int total_passed; /* total number of executed tests passed */
64         unsigned int total_failed; /* total number of executed tests failed */
65         unsigned int total_time;   /* total time of all executed tests */
66         unsigned int last_passed;  /* number of passed tests during last execution */
67         unsigned int last_failed;  /* number of failed tests during last execution */
68         unsigned int last_time;    /* total time of the last test execution */
69 } last_results;
70
71 enum test_mode {
72         TEST_ALL = 0,
73         TEST_CATEGORY = 1,
74         TEST_NAME_CATEGORY = 2,
75 };
76
77 /*! List of registered test definitions */
78 static AST_LIST_HEAD_STATIC(tests, ast_test);
79
80 /*! static function prototypes */
81 static struct ast_test *test_alloc(ast_test_cb_t *cb);
82 static struct ast_test *test_free(struct ast_test *test);
83 static int test_insert(struct ast_test *test);
84 static struct ast_test *test_remove(ast_test_cb_t *cb);
85 static int test_cat_cmp(const char *cat1, const char *cat2);
86
87 int ast_test_status_update(struct ast_test_status_args *args, const char *fmt, ...)
88 {
89         struct ast_str *buf = NULL;
90         va_list ap;
91
92         /* it is not an error if no cli args exist. */
93         if (!args->cli) {
94                 return 0;
95         }
96
97         if (!(buf = ast_str_create(128))) {
98                 return -1;
99         }
100
101         va_start(ap, fmt);
102         ast_str_set_va(&buf, 0, fmt, ap);
103         va_end(ap);
104
105         ast_cli(args->cli->fd, "%s", ast_str_buffer(buf));
106
107         ast_free(buf);
108         return 0;
109 }
110
111 int ast_test_register(ast_test_cb_t *cb)
112 {
113         struct ast_test *test;
114
115         /* verify data.*/
116         if (!cb) {
117                 ast_log(LOG_WARNING, "Attempted to register test without all required information\n");
118                 return -1;
119         }
120
121         /* create test object */
122         if (!(test = test_alloc(cb))) {
123                 return -1;
124         }
125
126         /* insert into list */
127         if (test_insert(test)) {
128                 test_free(test);
129                 return -1;
130         }
131
132         return 0;
133 }
134
135 int ast_test_unregister(ast_test_cb_t *cb)
136 {
137         struct ast_test *test;
138
139         /* find test and remove */
140         if (!(test = test_remove(cb))) {
141                 return -1; /* not found */
142         }
143
144         /* free test object */
145         test_free(test);
146
147         return 0;
148 }
149
150 /*!
151  * \internal
152  * \brief executes a single test, storing the results in the test->result structure.
153  *
154  * \note The last_results structure which contains global statistics about test execution
155  * must be updated when using this function. See use in test_execute_multiple().
156  */
157 static void test_execute(struct ast_test *test)
158 {
159         struct timeval begin;
160
161         /* clear any previous error results before starting */
162         ast_str_reset(test->args.ast_test_error_str);
163         /* get start time */
164         begin = ast_tvnow();
165         /* the callback gets the pointer to the pointer of the error buf */
166         test->state = test->cb(&test->info, TEST_EXECUTE, &test->args);
167         /* record the total time the test took */
168         test->time = ast_tvdiff_ms(ast_tvnow(), begin);
169         /* clear any status update args that may have been set */
170         memset(&test->args.status_update, 0, sizeof(struct ast_test_status_args));
171 }
172
173 static void test_xml_entry(struct ast_test *test, FILE *f)
174 {
175         if (!f || !test) {
176                 return;
177         }
178
179         fprintf(f, "\n<test>\n");
180         fprintf(f, "<name>%s</name>\n", test->info.name);
181         fprintf(f, "<category>%s</category>\n", test->info.category);
182         fprintf(f, "<summary>%s</summary>\n", test->info.summary);
183         fprintf(f, "<description>\n%s\n</description>\n", test->info.description);
184
185         fprintf(f, "<result>\n\t%s\n", test_result2str[test->state]);
186         if (test->state == AST_TEST_FAIL) {
187                 fprintf(f, "\t<error>\n\t\t%s\n\t</error>\n", S_OR(ast_str_buffer(test->args.ast_test_error_str), "NA"));
188         }
189         if (test->state != AST_TEST_NOT_RUN) {
190                 fprintf(f, "\t<time>\n\t\t%d\n\t</time>\n", test->time);
191         }
192         fprintf(f, "</result>\n");
193
194         fprintf(f, "</test>\n");
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_FAIL) {
209                 fprintf(f,   "Error Description: %s\n", S_OR(ast_str_buffer(test->args.ast_test_error_str), "NA"));
210         }
211         if (test->state != AST_TEST_NOT_RUN) {
212                 fprintf(f,   "Time:              %d\n", test->time);
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 (!(strcmp(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->args.status_update.cli = cli;
275
276                         /* execute the test and save results */
277                         test_execute(test);
278
279                         /* update execution specific counts here */
280                         last_results.last_time += test->time;
281                         if (test->state == AST_TEST_PASS) {
282                                 last_results.last_passed++;
283                         } else {
284                                 last_results.last_failed++;
285                         }
286
287                         if (cli) {
288                                 term_color(result_buf,
289                                         test_result2str[test->state],
290                                         (test->state == AST_TEST_FAIL) ? COLOR_RED : COLOR_GREEN,
291                                         0,
292                                         sizeof(result_buf));
293                                 ast_cli(cli->fd, "END    %s - %s Time: %dms Result: %s %s\n",
294                                         test->info.category,
295                                         test->info.name,
296                                         test->time,
297                                         result_buf,
298                                         ast_str_buffer(test->args.ast_test_error_str));
299                         }
300                 }
301
302                 /* update total counts as well during this iteration
303                  * even if the current test did not execute this time */
304                 last_results.total_time += test->time;
305                 last_results.total_tests++;
306                 if (test->state != AST_TEST_NOT_RUN) {
307                         if (test->state == AST_TEST_PASS) {
308                                 last_results.total_passed++;
309                         } else {
310                                 last_results.total_failed++;
311                         }
312                 }
313         }
314         res = last_results.last_passed + last_results.last_failed;
315         AST_LIST_UNLOCK(&tests);
316
317         return res;
318 }
319
320 /*!
321  * \internal
322  * \brief Generate test results.
323  *
324  * \param name of test result to generate (optional)
325  * \param test category to generate (optional)
326  * \param path to xml file to generate. (optional)
327  * \param path to txt file to generate, (optional)
328  *
329  * \retval 0 success
330  * \retval -1 failure
331  *
332  * \note This function has three modes of operation.
333  * -# When given both a name and category, results will be generated for that single test.
334  * -# When given only a category, results for every test within the category will be generated.
335  * -# When given no name or category, results for every registered test will be generated.
336  *
337  * In order for the results to be generated, an xml and or txt file path must be provided.
338  */
339 static int test_generate_results(const char *name, const char *category, const char *xml_path, const char *txt_path)
340 {
341         enum test_mode mode = TEST_ALL;  /* 0 generate all, 1 generate by category only, 2 generate by name and category */
342         FILE *f_xml = NULL, *f_txt = NULL;
343         int res = 0;
344         struct ast_test *test = NULL;
345
346         /* verify at least one output file was given */
347         if (ast_strlen_zero(xml_path) && ast_strlen_zero(txt_path)) {
348                 return -1;
349         }
350
351         /* define what mode is to be used */
352         if (!ast_strlen_zero(category)) {
353                 if (!ast_strlen_zero(name)) {
354                         mode = TEST_NAME_CATEGORY;
355                 } else {
356                         mode = TEST_CATEGORY;
357                 }
358         }
359         /* open files for writing */
360         if (!ast_strlen_zero(xml_path)) {
361                 if (!(f_xml = fopen(xml_path, "w"))) {
362                         ast_log(LOG_WARNING, "Could not open file %s for xml test results\n", xml_path);
363                         res = -1;
364                         goto done;
365                 }
366         }
367         if (!ast_strlen_zero(txt_path)) {
368                 if (!(f_txt = fopen(txt_path, "w"))) {
369                         ast_log(LOG_WARNING, "Could not open file %s for text output of test results\n", txt_path);
370                         res = -1;
371                         goto done;
372                 }
373         }
374
375         AST_LIST_LOCK(&tests);
376         /* xml header information */
377         if (f_xml) {
378                 fprintf(f_xml, "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n");
379                 fprintf(f_xml, "\n<results>\n");
380                 fprintf(f_xml, "<version>%s</version>\n", ASTERISK_VERSION);
381                 fprintf(f_xml, "<versionnum>%d</versionnum>\n", ASTERISK_VERSION_NUM);
382                 fprintf(f_xml, "<numtests>%d</numtests>\n", (last_results.total_tests));
383                 fprintf(f_xml, "<executedtests>%d</executedtests>\n", (last_results.total_passed + last_results.total_failed));
384                 fprintf(f_xml, "<passedtests>%d</passedtests>\n", last_results.total_passed);
385                 fprintf(f_xml, "<failedtests>%d</failedtests>\n", last_results.total_failed);
386                 fprintf(f_xml, "<totaltime>%d</totaltime>\n", last_results.total_time);
387                 fprintf(f_xml, "</results>\n");
388         }
389
390         /* txt header information */
391         if (f_txt) {
392                 fprintf(f_txt, "Asterisk Version:         %s\n", ASTERISK_VERSION);
393                 fprintf(f_txt, "Asterisk Version Number:  %d\n", ASTERISK_VERSION_NUM);
394                 fprintf(f_txt, "Number of Tests:          %d\n", last_results.total_tests);
395                 fprintf(f_txt, "Number of Tests Executed: %d\n", (last_results.total_passed + last_results.total_failed));
396                 fprintf(f_txt, "Passed Tests:             %d\n", last_results.total_passed);
397                 fprintf(f_txt, "Failed Tests:             %d\n", last_results.total_failed);
398                 fprintf(f_txt, "Total Execution Time:     %d\n", last_results.total_time);
399         }
400
401         /* export each individual test */
402         AST_LIST_TRAVERSE(&tests, test, entry) {
403                 switch (mode) {
404                 case TEST_CATEGORY:
405                         if (!test_cat_cmp(test->info.category, category)) {
406                                 test_xml_entry(test, f_xml);
407                                 test_txt_entry(test, f_txt);
408                         }
409                         break;
410                 case TEST_NAME_CATEGORY:
411                         if (!(strcmp(test->info.category, category)) && !(strcmp(test->info.name, name))) {
412                                 test_xml_entry(test, f_xml);
413                                 test_txt_entry(test, f_txt);
414                         }
415                         break;
416                 case TEST_ALL:
417                         test_xml_entry(test, f_xml);
418                         test_txt_entry(test, f_txt);
419                 }
420         }
421         AST_LIST_UNLOCK(&tests);
422
423 done:
424         if (f_xml) {
425                 fclose(f_xml);
426         }
427         if (f_txt) {
428                 fclose(f_txt);
429         }
430
431         return res;
432 }
433
434 /*!
435  * \internal
436  * \brief adds test to container sorted first by category then by name
437  *
438  * \return 0 on success, -1 on failure
439  */
440 static int test_insert(struct ast_test *test)
441 {
442         struct ast_test *cur = NULL;
443         int res = 0;
444         int i = 0;
445         int inserted = 0;
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         AST_LIST_LOCK(&tests);
451         AST_LIST_TRAVERSE_SAFE_BEGIN(&tests, cur, entry) {
452                 if ((i = strcmp(test->info.category, cur->info.category)) < 0) {
453                         AST_LIST_INSERT_BEFORE_CURRENT(test, entry);
454                         inserted = 1;
455                         break;
456                 } else if (!i) {  /* same category, now insert by name within that category*/
457                         if ((i = strcmp(test->info.name, cur->info.name)) < 0) {
458                                 AST_LIST_INSERT_BEFORE_CURRENT(test, entry);
459                                 inserted = 1;
460                                 break;
461                         } else if (!i) {
462                                 /* Error, duplicate found */
463                                 res = -1;
464                                 break;
465                         }
466                 }
467         }
468         AST_LIST_TRAVERSE_SAFE_END;
469
470         if (!inserted && !res) {
471                 AST_LIST_INSERT_TAIL(&tests, test, entry);
472                 inserted = 1;
473         }
474
475         AST_LIST_UNLOCK(&tests);
476
477         return res;
478 }
479
480 /*!
481  * \internal
482  * \brief removes test from container
483  *
484  * \return ast_test removed from list on success, or NULL on failure
485  */
486 static struct ast_test *test_remove(ast_test_cb_t *cb)
487 {
488         struct ast_test *cur = NULL;
489
490         AST_LIST_LOCK(&tests);
491         AST_LIST_TRAVERSE_SAFE_BEGIN(&tests, cur, entry) {
492                 if (cur->cb == cb) {
493                         AST_LIST_REMOVE_CURRENT(entry);
494                         break;
495                 }
496         }
497         AST_LIST_TRAVERSE_SAFE_END;
498         AST_LIST_UNLOCK(&tests);
499
500         return cur;
501 }
502
503 /*!
504  * \brief compares two test categories to determine if cat1 resides in cat2
505  * \internal
506  *
507  * \return 0 if true
508  */
509
510 static int test_cat_cmp(const char *cat1, const char *cat2)
511 {
512         int len1 = 0;
513         int len2 = 0;
514
515         if (!cat1 || !cat2) {
516                 return -1;
517         }
518
519         len1 = strlen(cat1);
520         len2 = strlen(cat2);
521
522         if (len2 > len1) {
523                 return -1;
524         }
525
526         return strncmp(cat1, cat2, len2) ? 1 : 0;
527 }
528
529 /*!
530  * \brief frees a ast_test object and all it's data members
531  * \internal
532  */
533 static struct ast_test *test_free(struct ast_test *test)
534 {
535         if (!test) {
536                 return NULL;
537         }
538
539         ast_free(test->args.ast_test_error_str);
540         ast_free(test);
541
542         return NULL;
543 }
544
545 /*!
546  * \internal
547  * \brief allocates an ast_test object.
548  */
549 static struct ast_test *test_alloc(ast_test_cb_t *cb)
550 {
551         struct ast_test *test;
552
553         if (!cb || !(test = ast_calloc(1, sizeof(*test)))) {
554                 return NULL;
555         }
556
557         test->cb = cb;
558
559         test->cb(&test->info, TEST_INIT, &test->args);
560
561         if (ast_strlen_zero(test->info.name) ||
562                 ast_strlen_zero(test->info.category) ||
563                 ast_strlen_zero(test->info.summary) ||
564                 ast_strlen_zero(test->info.description) ||
565                 !(test->args.ast_test_error_str = ast_str_create(128))) {
566
567                 return test_free(test);
568         }
569
570         return test;
571 }
572
573 /* CLI commands */
574 static char *test_cli_show_registered(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
575 {
576 #define FORMAT "%-15s %-20s %-30s %-10s\n"
577         static const char * const option1[] = { "all", "category", NULL };
578         static const char * const option2[] = { "name", NULL };
579         struct ast_test *test = NULL;
580         int count = 0;
581         switch (cmd) {
582         case CLI_INIT:
583                 e->command = "test show registered";
584
585                 e->usage =
586                         "Usage: 'test show registered' can be used in three ways.\n"
587                         "       1. 'test show registered all' shows all registered tests\n"
588                         "       2. 'test show registered category [test category]' shows all tests in the given\n"
589                         "          category.\n"
590                         "       3. 'test show registered category [test category] name [test name]' shows all\n"
591                         "           tests in a given category matching a given name\n";
592                 return NULL;
593         case CLI_GENERATE:
594                 if (a->pos == 3) {
595                         return ast_cli_complete(a->word, option1, a->n);
596                 }
597                 if (a->pos == 5) {
598                         return ast_cli_complete(a->word, option2, a->n);
599                 }
600                 return NULL;
601         case CLI_HANDLER:
602                 if ((a->argc < 4) || (a->argc == 6) || (a->argc > 7) ||
603                         ((a->argc == 4) && strcmp(a->argv[3], "all")) ||
604                         ((a->argc == 7) && strcmp(a->argv[5], "name"))) {
605                         return CLI_SHOWUSAGE;
606                 }
607                 ast_cli(a->fd, FORMAT, "Name", "Category", "Summary", "Test Result");
608                 AST_LIST_LOCK(&tests);
609                 AST_LIST_TRAVERSE(&tests, test, entry) {
610                         if ((a->argc == 4) ||
611                                  ((a->argc == 5) && !test_cat_cmp(test->info.category, a->argv[4])) ||
612                                  ((a->argc == 7) && !strcmp(test->info.category, a->argv[4]) && !strcmp(test->info.name, a->argv[6]))) {
613
614                                 ast_cli(a->fd, FORMAT, test->info.name, test->info.category, test->info.summary, test_result2str[test->state]);
615                                 count ++;
616                         }
617                 }
618                 AST_LIST_UNLOCK(&tests);
619                 ast_cli(a->fd, "%d Registered Tests Matched\n", count);
620         default:
621                 return NULL;
622         }
623
624         return CLI_SUCCESS;
625 }
626
627 static char *test_cli_execute_registered(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
628 {
629         static const char * const option1[] = { "all", "category", NULL };
630         static const char * const option2[] = { "name", NULL };
631         switch (cmd) {
632         case CLI_INIT:
633                 e->command = "test execute";
634                 e->usage =
635                         "Usage: test execute can be used in three ways.\n"
636                         "       1. 'test execute all' runs all registered tests\n"
637                         "       2. 'test execute category [test category]' runs all tests in the given\n"
638                         "          category.\n"
639                         "       3. 'test execute category [test category] name [test name]' runs all\n"
640                         "           tests in a given category matching a given name\n";
641                 return NULL;
642         case CLI_GENERATE:
643                 if (a->pos == 2) {
644                         return ast_cli_complete(a->word, option1, a->n);
645                 }
646                 if (a->pos == 4) {
647                         return ast_cli_complete(a->word, option2, a->n);
648                 }
649                 return NULL;
650         case CLI_HANDLER:
651
652                 if (a->argc < 3|| a->argc > 6) {
653                         return CLI_SHOWUSAGE;
654                 }
655
656                 if ((a->argc == 3) && !strcmp(a->argv[2], "all")) { /* run all registered tests */
657                         ast_cli(a->fd, "Running all available tests...\n\n");
658                         test_execute_multiple(NULL, NULL, a);
659                 } else if (a->argc == 4) { /* run only tests within a category */
660                         ast_cli(a->fd, "Running all available tests matching category %s\n\n", a->argv[3]);
661                         test_execute_multiple(NULL, a->argv[3], a);
662                 } else if (a->argc == 6) { /* run only a single test matching the category and name */
663                         ast_cli(a->fd, "Running all available tests matching category %s and name %s\n\n", a->argv[5], a->argv[3]);
664                         test_execute_multiple(a->argv[5], a->argv[3], a);
665                 } else {
666                         return CLI_SHOWUSAGE;
667                 }
668
669                 AST_LIST_LOCK(&tests);
670                 if (!(last_results.last_passed + last_results.last_failed)) {
671                         ast_cli(a->fd, "--- No Tests Found! ---\n");
672                 }
673                 ast_cli(a->fd, "\n%d Test(s) Executed  %d Passed  %d Failed\n",
674                         (last_results.last_passed + last_results.last_failed),
675                         last_results.last_passed,
676                         last_results.last_failed);
677                 AST_LIST_UNLOCK(&tests);
678         default:
679                 return NULL;
680         }
681
682         return CLI_SUCCESS;
683 }
684
685 static char *test_cli_show_results(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
686 {
687 #define FORMAT_RES_ALL "%s%s %-15s %-20s %-30s\n"
688         static const char * const option1[] = { "all", "failed", "passed", NULL };
689         char result_buf[32] = { 0 };
690         struct ast_test *test = NULL;
691         int failed = 0;
692         int passed = 0;
693         int mode;  /* 0 for show all, 1 for show fail, 2 for show passed */
694
695         switch (cmd) {
696         case CLI_INIT:
697                 e->command = "test show results";
698                 e->usage =
699                         "Usage: test show results can be used in three ways\n"
700                         "       1. 'test show results all' Displays results for all executed tests.\n"
701                         "       2. 'test show results passed' Displays results for all passed tests.\n"
702                         "       3. 'test show results failed' Displays results for all failed tests.\n";
703                 return NULL;
704         case CLI_GENERATE:
705                 if (a->pos == 3) {
706                         return ast_cli_complete(a->word, option1, a->n);
707                 }
708                 return NULL;
709         case CLI_HANDLER:
710
711                 /* verify input */
712                 if (a->argc != 4) {
713                         return CLI_SHOWUSAGE;
714                 } else if (!strcmp(a->argv[3], "passed")) {
715                         mode = 2;
716                 } else if (!strcmp(a->argv[3], "failed")) {
717                         mode = 1;
718                 } else if (!strcmp(a->argv[3], "all")) {
719                         mode = 0;
720                 } else {
721                         return CLI_SHOWUSAGE;
722                 }
723
724                 ast_cli(a->fd, FORMAT_RES_ALL, "Result", "", "Name", "Category", "Error Description");
725                 AST_LIST_LOCK(&tests);
726                 AST_LIST_TRAVERSE(&tests, test, entry) {
727                         if (test->state == AST_TEST_NOT_RUN) {
728                                 continue;
729                         }
730                         test->state == AST_TEST_FAIL ? failed++ : passed++;
731                         if (!mode || ((mode == 1) && (test->state == AST_TEST_FAIL)) || ((mode == 2) && (test->state == AST_TEST_PASS))) {
732                                 /* give our results pretty colors */
733                                 term_color(result_buf, test_result2str[test->state],
734                                         (test->state == AST_TEST_FAIL) ? COLOR_RED : COLOR_GREEN,
735                                         0, sizeof(result_buf));
736
737                                 ast_cli(a->fd, FORMAT_RES_ALL,
738                                         result_buf,
739                                         "  ",
740                                         test->info.name,
741                                         test->info.category,
742                                         (test->state == AST_TEST_FAIL) ? S_OR(ast_str_buffer(test->args.ast_test_error_str), "Not Avaliable") : "");
743                         }
744                 }
745                 AST_LIST_UNLOCK(&tests);
746
747                 ast_cli(a->fd, "%d Test(s) Executed  %d Passed  %d Failed\n", (failed + passed), passed, failed);
748         default:
749                 return NULL;
750         }
751         return CLI_SUCCESS;
752 }
753
754 static char *test_cli_generate_results(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
755 {
756         static const char * const option[] = { "xml", "txt", NULL };
757         const char *file = NULL;
758         const char *type = "";
759         int isxml = 0;
760         int res = 0;
761         struct ast_str *buf = NULL;
762         struct timeval time = ast_tvnow();
763
764         switch (cmd) {
765         case CLI_INIT:
766                 e->command = "test generate results";
767                 e->usage =
768                         "Usage: 'test generate results'\n"
769                         "       Generates test results in either xml or txt format. An optional \n"
770                         "       file path may be provided to specify the location of the xml or\n"
771                         "       txt file\n"
772                         "       \nExample usage:\n"
773                         "       'test generate results xml' this writes to a default file\n"
774                         "       'test generate results xml /path/to/file.xml' writes to specified file\n";
775                 return NULL;
776         case CLI_GENERATE:
777                 if (a->pos == 3) {
778                         return ast_cli_complete(a->word, option, a->n);
779                 }
780                 return NULL;
781         case CLI_HANDLER:
782
783                 /* verify input */
784                 if (a->argc < 4 || a->argc > 5) {
785                         return CLI_SHOWUSAGE;
786                 } else if (!strcmp(a->argv[3], "xml")) {
787                         type = "xml";
788                         isxml = 1;
789                 } else if (!strcmp(a->argv[3], "txt")) {
790                         type = "txt";
791                 } else {
792                         return CLI_SHOWUSAGE;
793                 }
794
795                 if (a->argc == 5) {
796                         file = a->argv[4];
797                 } else {
798                         if (!(buf = ast_str_create(256))) {
799                                 return NULL;
800                         }
801                         ast_str_set(&buf, 0, "%s/asterisk_test_results-%ld.%s", ast_config_AST_LOG_DIR, time.tv_sec, type);
802
803                         file = ast_str_buffer(buf);
804                 }
805
806                 if (isxml) {
807                         res = test_generate_results(NULL, NULL, file, NULL);
808                 } else {
809                         res = test_generate_results(NULL, NULL, NULL, file);
810                 }
811
812                 if (!res) {
813                         ast_cli(a->fd, "Results Generated Successfully: %s\n", S_OR(file, ""));
814                 } else {
815                         ast_cli(a->fd, "Results Could Not Be Generated: %s\n", S_OR(file, ""));
816                 }
817
818                 ast_free(buf);
819         default:
820                 return NULL;
821         }
822
823         return CLI_SUCCESS;
824 }
825
826 static struct ast_cli_entry test_cli[] = {
827         AST_CLI_DEFINE(test_cli_show_registered,           "show registered tests"),
828         AST_CLI_DEFINE(test_cli_execute_registered,        "execute registered tests"),
829         AST_CLI_DEFINE(test_cli_show_results,              "show last test results"),
830         AST_CLI_DEFINE(test_cli_generate_results,          "generate test results to file"),
831 };
832 #endif /* TEST_FRAMEWORK */
833
834 int ast_test_init()
835 {
836 #ifdef TEST_FRAMEWORK
837         /* Register cli commands */
838         ast_cli_register_multiple(test_cli, ARRAY_LEN(test_cli));
839
840         /* in the future this function could be used to register functions not
841          * defined within a module */
842 #endif
843
844         return 0;
845 }