Wed Jan 8 2020 09:49:51

Asterisk developer's documentation


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