Asterisk - The Open Source Telephony Project  21.4.1
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 #include "asterisk/_private.h"
35 
36 #ifdef TEST_FRAMEWORK
37 #include "asterisk/test.h"
38 #include "asterisk/logger.h"
39 #include "asterisk/linkedlists.h"
40 #include "asterisk/utils.h"
41 #include "asterisk/cli.h"
42 #include "asterisk/term.h"
43 #include "asterisk/ast_version.h"
44 #include "asterisk/paths.h"
45 #include "asterisk/time.h"
46 #include "asterisk/stasis.h"
47 #include "asterisk/json.h"
48 #include "asterisk/astobj2.h"
49 #include "asterisk/stasis.h"
50 #include "asterisk/json.h"
51 #include "asterisk/app.h" /* for ast_replace_sigchld(), etc. */
52 
53 #include <stdio.h>
54 #include <errno.h>
55 #include <fcntl.h>
56 #include <string.h>
57 #include <sys/types.h>
58 #include <sys/stat.h>
59 #include <sys/wait.h>
60 #include <signal.h>
61 
62 /*! \since 12
63  * \brief The topic for test suite messages
64  */
65 struct stasis_topic *test_suite_topic;
66 
67 /*! This array corresponds to the values defined in the ast_test_state enum */
68 static const char * const test_result2str[] = {
69  [AST_TEST_NOT_RUN] = "NOT RUN",
70  [AST_TEST_PASS] = "PASS",
71  [AST_TEST_FAIL] = "FAIL",
72 };
73 
74 /*! holds all the information pertaining to a single defined test */
75 struct ast_test {
76  struct ast_test_info info; /*!< holds test callback information */
77  /*!
78  * \brief Test defined status output from last execution
79  */
80  struct ast_str *status_str;
81  /*!
82  * \brief CLI arguments, if tests being run from the CLI
83  *
84  * If this is set, status updates from the tests will be sent to the
85  * CLI in addition to being saved off in status_str.
86  */
87  struct ast_cli_args *cli;
88  enum ast_test_result_state state; /*!< current test state */
89  unsigned int time; /*!< time in ms test took */
90  ast_test_cb_t *cb; /*!< test callback function */
91  ast_test_init_cb_t *init_cb; /*!< test init function */
92  ast_test_cleanup_cb_t *cleanup_cb; /*!< test cleanup function */
93  AST_LIST_ENTRY(ast_test) entry;
94 };
95 
96 /*! global structure containing both total and last test execution results */
97 static struct ast_test_execute_results {
98  unsigned int total_tests; /*!< total number of tests, regardless if they have been executed or not */
99  unsigned int total_passed; /*!< total number of executed tests passed */
100  unsigned int total_failed; /*!< total number of executed tests failed */
101  unsigned int total_time; /*!< total time of all executed tests */
102  unsigned int last_passed; /*!< number of passed tests during last execution */
103  unsigned int last_failed; /*!< number of failed tests during last execution */
104  unsigned int last_time; /*!< total time of the last test execution */
105 } last_results;
106 
107 enum test_mode {
108  TEST_ALL = 0,
109  TEST_CATEGORY = 1,
110  TEST_NAME_CATEGORY = 2,
111 };
112 
113 #define zfclose(fp) \
114  ({ if (fp != NULL) { \
115  fclose(fp); \
116  fp = NULL; \
117  } \
118  (void)0; \
119  })
120 
121 #define zclose(fd) \
122  ({ if (fd != -1) { \
123  close(fd); \
124  fd = -1; \
125  } \
126  (void)0; \
127  })
128 
129 #define movefd(oldfd, newfd) \
130  ({ if (oldfd != newfd) { \
131  dup2(oldfd, newfd); \
132  close(oldfd); \
133  oldfd = -1; \
134  } \
135  (void)0; \
136  })
137 
138 #define lowerfd(oldfd) \
139  ({ int newfd = dup(oldfd); \
140  if (newfd > oldfd) \
141  close(newfd); \
142  else { \
143  close(oldfd); \
144  oldfd = newfd; \
145  } \
146  (void)0; \
147  })
148 
149 /*! List of registered test definitions */
150 static AST_LIST_HEAD_STATIC(tests, ast_test);
151 
152 static struct ast_test *test_alloc(ast_test_cb_t *cb);
153 static struct ast_test *test_free(struct ast_test *test);
154 static int test_insert(struct ast_test *test);
155 static struct ast_test *test_remove(ast_test_cb_t *cb);
156 static int test_cat_cmp(const char *cat1, const char *cat2);
157 static int registration_errors = 0;
158 
159 void ast_test_debug(struct ast_test *test, const char *fmt, ...)
160 {
161  struct ast_str *buf = NULL;
162  va_list ap;
163 
164  buf = ast_str_create(128);
165  if (!buf) {
166  return;
167  }
168 
169  va_start(ap, fmt);
170  ast_str_set_va(&buf, 0, fmt, ap);
171  va_end(ap);
172 
173  if (test->cli) {
174  ast_cli(test->cli->fd, "%s", ast_str_buffer(buf));
175  }
176 
177  ast_free(buf);
178 }
179 
180 int __ast_test_status_update(const char *file, const char *func, int line, struct ast_test *test, const char *fmt, ...)
181 {
182  struct ast_str *buf = NULL;
183  va_list ap;
184 
185  if (!(buf = ast_str_create(128))) {
186  return -1;
187  }
188 
189  va_start(ap, fmt);
190  ast_str_set_va(&buf, 0, fmt, ap);
191  va_end(ap);
192 
193  if (test->cli) {
194  ast_cli(test->cli->fd, "[%s:%s:%d]: %s",
195  file, func, line, ast_str_buffer(buf));
196  }
197 
198  ast_str_append(&test->status_str, 0, "[%s:%s:%d]: %s",
199  file, func, line, ast_str_buffer(buf));
200 
201  ast_free(buf);
202 
203  return 0;
204 }
205 
206 int ast_test_register_init(const char *category, ast_test_init_cb_t *cb)
207 {
208  struct ast_test *test;
209  int registered = 1;
210 
211  AST_LIST_LOCK(&tests);
212  AST_LIST_TRAVERSE(&tests, test, entry) {
213  if (!(test_cat_cmp(test->info.category, category))) {
214  test->init_cb = cb;
215  registered = 0;
216  }
217  }
218  AST_LIST_UNLOCK(&tests);
219 
220  return registered;
221 }
222 
223 int ast_test_register_cleanup(const char *category, ast_test_cleanup_cb_t *cb)
224 {
225  struct ast_test *test;
226  int registered = 1;
227 
228  AST_LIST_LOCK(&tests);
229  AST_LIST_TRAVERSE(&tests, test, entry) {
230  if (!(test_cat_cmp(test->info.category, category))) {
231  test->cleanup_cb = cb;
232  registered = 0;
233  }
234  }
235  AST_LIST_UNLOCK(&tests);
236 
237  return registered;
238 }
239 
240 int ast_test_register(ast_test_cb_t *cb)
241 {
242  struct ast_test *test;
243 
244  if (!cb) {
245  ast_log(LOG_ERROR, "Attempted to register test without all required information\n");
246  registration_errors++;
247  return -1;
248  }
249 
250  if (!(test = test_alloc(cb))) {
251  registration_errors++;
252  return -1;
253  }
254 
255  if (test_insert(test)) {
256  test_free(test);
257  registration_errors++;
258  return -1;
259  }
260 
261  return 0;
262 }
263 
264 int ast_test_unregister(ast_test_cb_t *cb)
265 {
266  struct ast_test *test;
267 
268  if (!(test = test_remove(cb))) {
269  return -1; /* not found */
270  }
271 
272  test_free(test);
273 
274  return 0;
275 }
276 
277 /*!
278  * \internal
279  * \brief executes a single test, storing the results in the test->result structure.
280  *
281  * \note The last_results structure which contains global statistics about test execution
282  * must be updated when using this function. See use in test_execute_multiple().
283  */
284 static void test_execute(struct ast_test *test)
285 {
286  struct timeval begin;
287  enum ast_test_result_state result;
288 
289  ast_str_reset(test->status_str);
290 
291  begin = ast_tvnow();
292  if (test->init_cb && test->init_cb(&test->info, test)) {
293  test->state = AST_TEST_FAIL;
294  goto exit;
295  }
296  test->state = AST_TEST_NOT_RUN;
297  result = test->cb(&test->info, TEST_EXECUTE, test);
298  if (test->state != AST_TEST_FAIL) {
299  test->state = result;
300  }
301  if (test->cleanup_cb && test->cleanup_cb(&test->info, test)) {
302  test->state = AST_TEST_FAIL;
303  }
304 exit:
305  test->time = ast_tvdiff_ms(ast_tvnow(), begin);
306 }
307 
308 void ast_test_set_result(struct ast_test *test, enum ast_test_result_state state)
309 {
310  if (test->state == AST_TEST_FAIL || state == AST_TEST_NOT_RUN) {
311  return;
312  }
313  test->state = state;
314 }
315 
316 void ast_test_capture_init(struct ast_test_capture *capture)
317 {
318  capture->outbuf = capture->errbuf = NULL;
319  capture->pid = capture->exitcode = -1;
320 }
321 
322 void ast_test_capture_free(struct ast_test_capture *capture)
323 {
324  if (capture) {
325  /*
326  * Need to use ast_std_free because this memory wasn't
327  * allocated by the astmm functions.
328  */
329  ast_std_free(capture->outbuf);
330  capture->outbuf = NULL;
331  ast_std_free(capture->errbuf);
332  capture->errbuf = NULL;
333  }
334  capture->pid = -1;
335  capture->exitcode = -1;
336 }
337 
338 int ast_test_capture_command(struct ast_test_capture *capture, const char *file, char *const argv[], const char *data, unsigned datalen)
339 {
340  int fd0[2] = { -1, -1 }, fd1[2] = { -1, -1 }, fd2[2] = { -1, -1 };
341  pid_t pid = -1;
342  int status = 0;
343  FILE *cmd = NULL, *out = NULL, *err = NULL;
344 
345  ast_test_capture_init(capture);
346 
347  if (data != NULL && datalen > 0) {
348  if (pipe(fd0) == -1) {
349  ast_log(LOG_ERROR, "Couldn't open stdin pipe: %s\n", strerror(errno));
350  goto cleanup;
351  }
352  fcntl(fd0[1], F_SETFL, fcntl(fd0[1], F_GETFL, 0) | O_NONBLOCK);
353  } else {
354  if ((fd0[0] = open("/dev/null", O_RDONLY)) == -1) {
355  ast_log(LOG_ERROR, "Couldn't open /dev/null: %s\n", strerror(errno));
356  goto cleanup;
357  }
358  }
359 
360  if (pipe(fd1) == -1) {
361  ast_log(LOG_ERROR, "Couldn't open stdout pipe: %s\n", strerror(errno));
362  goto cleanup;
363  }
364 
365  if (pipe(fd2) == -1) {
366  ast_log(LOG_ERROR, "Couldn't open stderr pipe: %s\n", strerror(errno));
367  goto cleanup;
368  }
369 
370  /* we don't want anyone else reaping our children */
372 
373  if ((pid = fork()) == -1) {
374  ast_log(LOG_ERROR, "Failed to fork(): %s\n", strerror(errno));
375  goto cleanup;
376 
377  } else if (pid == 0) {
378  fclose(stdin);
379  zclose(fd0[1]);
380  zclose(fd1[0]);
381  zclose(fd2[0]);
382 
383  movefd(fd0[0], 0);
384  movefd(fd1[1], 1);
385  movefd(fd2[1], 2);
386 
387  execvp(file, argv);
388  ast_log(LOG_ERROR, "Failed to execv(): %s\n", strerror(errno));
389  exit(1);
390 
391  } else {
392  char buf[BUFSIZ];
393  int wstatus, n, nfds;
394  fd_set readfds, writefds;
395  unsigned i;
396 
397  zclose(fd0[0]);
398  zclose(fd1[1]);
399  zclose(fd2[1]);
400 
401  lowerfd(fd0[1]);
402  lowerfd(fd1[0]);
403  lowerfd(fd2[0]);
404 
405  if ((cmd = fmemopen(buf, sizeof(buf), "w")) == NULL) {
406  ast_log(LOG_ERROR, "Failed to open memory buffer: %s\n", strerror(errno));
407  kill(pid, SIGKILL);
408  goto cleanup;
409  }
410  for (i = 0; argv[i] != NULL; ++i) {
411  if (i > 0) {
412  fputc(' ', cmd);
413  }
414  fputs(argv[i], cmd);
415  }
416  zfclose(cmd);
417 
418  ast_log(LOG_TRACE, "run: %.*s\n", (int)sizeof(buf), buf);
419 
420  if ((out = open_memstream(&capture->outbuf, &capture->outlen)) == NULL) {
421  ast_log(LOG_ERROR, "Failed to open output buffer: %s\n", strerror(errno));
422  kill(pid, SIGKILL);
423  goto cleanup;
424  }
425 
426  if ((err = open_memstream(&capture->errbuf, &capture->errlen)) == NULL) {
427  ast_log(LOG_ERROR, "Failed to open error buffer: %s\n", strerror(errno));
428  kill(pid, SIGKILL);
429  goto cleanup;
430  }
431 
432  while (1) {
433  n = waitpid(pid, &wstatus, WNOHANG);
434 
435  if (n == pid && WIFEXITED(wstatus)) {
436  zclose(fd0[1]);
437  zclose(fd1[0]);
438  zclose(fd2[0]);
439  zfclose(out);
440  zfclose(err);
441 
442  capture->pid = pid;
443  capture->exitcode = WEXITSTATUS(wstatus);
444 
445  ast_log(LOG_TRACE, "run: pid %d exits %d\n", capture->pid, capture->exitcode);
446 
447  break;
448  }
449 
450  /* a function that does the opposite of ffs()
451  * would be handy here for finding the highest
452  * descriptor number.
453  */
454  nfds = MAX(fd0[1], MAX(fd1[0], fd2[0])) + 1;
455 
456  FD_ZERO(&readfds);
457  FD_ZERO(&writefds);
458 
459  if (fd0[1] != -1) {
460  if (data != NULL && datalen > 0)
461  FD_SET(fd0[1], &writefds);
462  }
463  if (fd1[0] != -1) {
464  FD_SET(fd1[0], &readfds);
465  }
466  if (fd2[0] != -1) {
467  FD_SET(fd2[0], &readfds);
468  }
469 
470  /* not clear that exception fds are meaningful
471  * with non-network descriptors.
472  */
473  n = select(nfds, &readfds, &writefds, NULL, NULL);
474 
475  /* A version of FD_ISSET() that is tolerant of -1 file descriptors */
476 #define SAFE_FD_ISSET(fd, setptr) ((fd) != -1 && FD_ISSET((fd), setptr))
477 
478  if (SAFE_FD_ISSET(fd0[1], &writefds)) {
479  n = write(fd0[1], data, datalen);
480  if (n > 0) {
481  data += n;
482  datalen -= MIN(datalen, n);
483  /* out of data, so close stdin */
484  if (datalen == 0)
485  zclose(fd0[1]);
486  } else {
487  zclose(fd0[1]);
488  }
489  }
490 
491  if (SAFE_FD_ISSET(fd1[0], &readfds)) {
492  n = read(fd1[0], buf, sizeof(buf));
493  if (n > 0) {
494  fwrite(buf, sizeof(char), n, out);
495  } else {
496  zclose(fd1[0]);
497  }
498  }
499 
500  if (SAFE_FD_ISSET(fd2[0], &readfds)) {
501  n = read(fd2[0], buf, sizeof(buf));
502  if (n > 0) {
503  fwrite(buf, sizeof(char), n, err);
504  } else {
505  zclose(fd2[0]);
506  }
507  }
508 
509 #undef SAFE_FD_ISSET
510  }
511  status = 1;
512 
513 cleanup:
515 
516  zfclose(cmd);
517  zfclose(out);
518  zfclose(err);
519 
520  zclose(fd0[1]);
521  zclose(fd1[0]);
522  zclose(fd1[1]);
523  zclose(fd2[0]);
524  zclose(fd2[1]);
525 
526  return status;
527  }
528 }
529 
530 /*
531  * These are the Java reserved words we need to munge so Jenkins
532  * doesn't barf on them.
533  */
534 static char *reserved_words[] = {
535  "abstract", "arguments", "as", "assert", "await",
536  "boolean", "break", "byte", "case", "catch", "char", "class",
537  "const", "continue", "debugger", "def", "default", "delete", "do",
538  "double", "else", "enum", "eval", "export", "extends", "false",
539  "final", "finally", "float", "for", "function", "goto", "if",
540  "implements", "import", "in", "instanceof", "int", "interface",
541  "let", "long", "native", "new", "null", "package", "private",
542  "protected", "public", "return", "short", "static", "strictfp",
543  "string", "super", "switch", "synchronized", "this", "throw", "throws",
544  "trait", "transient", "true", "try", "typeof", "var", "void",
545  "volatile", "while", "with", "yield" };
546 
547 static int is_reserved_word(const char *word)
548 {
549  int i;
550 
551  for (i = 0; i < ARRAY_LEN(reserved_words); i++) {
552  if (strcmp(word, reserved_words[i]) == 0) {
553  return 1;
554  }
555  }
556 
557  return 0;
558 }
559 
560 static void test_xml_entry(struct ast_test *test, FILE *f)
561 {
562  /* We need a copy of the category skipping past the initial '/' */
563  char *test_cat = ast_strdupa(test->info.category + 1);
564  char *next_cat;
565  char *test_name = (char *)test->info.name;
566  struct ast_str *category = ast_str_create(strlen(test->info.category) + 32);
567 
568  if (!category || test->state == AST_TEST_NOT_RUN) {
569  ast_free(category);
570 
571  return;
572  }
573 
574  while ((next_cat = ast_strsep(&test_cat, '/', AST_STRSEP_TRIM))) {
575  char *prefix = "";
576 
577  if (is_reserved_word(next_cat)) {
578  prefix = "_";
579  }
580  ast_str_append(&category, 0, ".%s%s", prefix, next_cat);
581  }
582  test_cat = ast_str_buffer(category);
583  /* Skip past the initial '.' */
584  test_cat++;
585 
586  if (is_reserved_word(test->info.name)) {
587  size_t name_length = strlen(test->info.name) + 2;
588 
589  test_name = ast_alloca(name_length);
590  snprintf(test_name, name_length, "_%s", test->info.name);
591  }
592 
593  fprintf(f, "\t\t<testcase time=\"%u.%u\" classname=\"%s\" name=\"%s\"%s>\n",
594  test->time / 1000, test->time % 1000,
595  test_cat, test_name,
596  test->state == AST_TEST_PASS ? "/" : "");
597 
598  ast_free(category);
599 
600  if (test->state == AST_TEST_FAIL) {
601  fprintf(f, "\t\t\t<failure><![CDATA[\n%s\n\t\t]]></failure>\n",
602  S_OR(ast_str_buffer(test->status_str), "NA"));
603  fprintf(f, "\t\t</testcase>\n");
604  }
605 
606 }
607 
608 static void test_txt_entry(struct ast_test *test, FILE *f)
609 {
610  if (!f || !test) {
611  return;
612  }
613 
614  fprintf(f, "\nName: %s\n", test->info.name);
615  fprintf(f, "Category: %s\n", test->info.category);
616  fprintf(f, "Summary: %s\n", test->info.summary);
617  fprintf(f, "Description: %s\n", test->info.description);
618  fprintf(f, "Result: %s\n", test_result2str[test->state]);
619  if (test->state != AST_TEST_NOT_RUN) {
620  fprintf(f, "Time: %u\n", test->time);
621  }
622  if (test->state == AST_TEST_FAIL) {
623  fprintf(f, "Error Description: %s\n\n", S_OR(ast_str_buffer(test->status_str), "NA"));
624  }
625 }
626 
627 /*!
628  * \internal
629  * \brief Executes registered unit tests
630  *
631  * \param name of test to run (optional)
632  * \param category category to run (optional)
633  * \param cli args for cli test updates (optional)
634  *
635  * \return number of tests executed.
636  *
637  * \note This function has three modes of operation
638  * -# When given a name and category, a matching individual test will execute if found.
639  * -# When given only a category all matching tests within that category will execute.
640  * -# If given no name or category all registered tests will execute.
641  */
642 static int test_execute_multiple(const char *name, const char *category, struct ast_cli_args *cli)
643 {
644  char result_buf[32] = { 0 };
645  struct ast_test *test = NULL;
646  enum test_mode mode = TEST_ALL; /* 3 modes, 0 = run all, 1 = only by category, 2 = only by name and category */
647  int execute = 0;
648  int res = 0;
649 
650  if (!ast_strlen_zero(category)) {
651  if (!ast_strlen_zero(name)) {
652  mode = TEST_NAME_CATEGORY;
653  } else {
654  mode = TEST_CATEGORY;
655  }
656  }
657 
658  AST_LIST_LOCK(&tests);
659  /* clear previous execution results */
660  memset(&last_results, 0, sizeof(last_results));
661  AST_LIST_TRAVERSE(&tests, test, entry) {
662 
663  execute = 0;
664  switch (mode) {
665  case TEST_CATEGORY:
666  if (!test_cat_cmp(test->info.category, category) && !test->info.explicit_only) {
667  execute = 1;
668  }
669  break;
670  case TEST_NAME_CATEGORY:
671  if (!(test_cat_cmp(test->info.category, category)) && !(strcmp(test->info.name, name))) {
672  execute = 1;
673  }
674  break;
675  case TEST_ALL:
676  execute = !test->info.explicit_only;
677  }
678 
679  if (execute) {
680  if (cli) {
681  ast_cli(cli->fd, "START %s - %s \n", test->info.category, test->info.name);
682  }
683 
684  /* set the test status update argument. it is ok if cli is NULL */
685  test->cli = cli;
686 
687  /* execute the test and save results */
688  test_execute(test);
689 
690  test->cli = NULL;
691 
692  /* update execution specific counts here */
693  last_results.last_time += test->time;
694  if (test->state == AST_TEST_PASS) {
695  last_results.last_passed++;
696  } else if (test->state == AST_TEST_FAIL) {
697  last_results.last_failed++;
698  }
699 
700  if (cli) {
701  term_color(result_buf,
702  test_result2str[test->state],
703  (test->state == AST_TEST_FAIL) ? COLOR_RED : COLOR_GREEN,
704  0,
705  sizeof(result_buf));
706  ast_cli(cli->fd, "END %s - %s Time: %s%ums Result: %s\n",
707  test->info.category,
708  test->info.name,
709  test->time ? "" : "<",
710  test->time ? test->time : 1,
711  result_buf);
712  }
713  }
714 
715  /* update total counts as well during this iteration
716  * even if the current test did not execute this time */
717  last_results.total_time += test->time;
718  if (test->state != AST_TEST_NOT_RUN) {
719  last_results.total_tests++;
720  if (test->state == AST_TEST_PASS) {
721  last_results.total_passed++;
722  } else {
723  last_results.total_failed++;
724  }
725  }
726  }
727  res = last_results.last_passed + last_results.last_failed;
728  AST_LIST_UNLOCK(&tests);
729 
730  return res;
731 }
732 
733 /*!
734  * \internal
735  * \brief Generate test results.
736  *
737  * \param name of test result to generate (optional)
738  * \param category category to generate (optional)
739  * \param xml_path path to xml file to generate (optional)
740  * \param txt_path path to txt file to generate (optional)
741  *
742  * \retval 0 success
743  * \retval -1 failure
744  *
745  * \note This function has three modes of operation.
746  * -# When given both a name and category, results will be generated for that single test.
747  * -# When given only a category, results for every test within the category will be generated.
748  * -# When given no name or category, results for every registered test will be generated.
749  *
750  * In order for the results to be generated, an xml and or txt file path must be provided.
751  */
752 static int test_generate_results(const char *name, const char *category, const char *xml_path, const char *txt_path)
753 {
754  enum test_mode mode = TEST_ALL; /* 0 generate all, 1 generate by category only, 2 generate by name and category */
755  FILE *f_xml = NULL, *f_txt = NULL;
756  int res = 0;
757  struct ast_test *test = NULL;
758 
759  /* verify at least one output file was given */
760  if (ast_strlen_zero(xml_path) && ast_strlen_zero(txt_path)) {
761  return -1;
762  }
763 
764  /* define what mode is to be used */
765  if (!ast_strlen_zero(category)) {
766  if (!ast_strlen_zero(name)) {
767  mode = TEST_NAME_CATEGORY;
768  } else {
769  mode = TEST_CATEGORY;
770  }
771  }
772  /* open files for writing */
773  if (!ast_strlen_zero(xml_path)) {
774  if (!(f_xml = fopen(xml_path, "w"))) {
775  ast_log(LOG_WARNING, "Could not open file %s for xml test results\n", xml_path);
776  res = -1;
777  goto done;
778  }
779  }
780  if (!ast_strlen_zero(txt_path)) {
781  if (!(f_txt = fopen(txt_path, "w"))) {
782  ast_log(LOG_WARNING, "Could not open file %s for text output of test results\n", txt_path);
783  res = -1;
784  goto done;
785  }
786  }
787 
788  AST_LIST_LOCK(&tests);
789  /* xml header information */
790  if (f_xml) {
791  /*
792  * http://confluence.atlassian.com/display/BAMBOO/JUnit+parsing+in+Bamboo
793  */
794  fprintf(f_xml, "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
795  fprintf(f_xml, "<testsuites>\n");
796  fprintf(f_xml, "\t<testsuite errors=\"0\" time=\"%u.%u\" tests=\"%u\" failures=\"%u\" "
797  "name=\"AsteriskUnitTests\">\n",
798  last_results.total_time / 1000, last_results.total_time % 1000,
799  last_results.total_tests, last_results.total_failed);
800  fprintf(f_xml, "\t\t<properties>\n");
801  fprintf(f_xml, "\t\t\t<property name=\"version\" value=\"%s\"/>\n", ast_get_version());
802  fprintf(f_xml, "\t\t</properties>\n");
803  }
804 
805  /* txt header information */
806  if (f_txt) {
807  fprintf(f_txt, "Asterisk Version: %s\n", ast_get_version());
808  fprintf(f_txt, "Asterisk Version Number: %s\n", ast_get_version_num());
809  fprintf(f_txt, "Number of Tests: %u\n", last_results.total_tests);
810  fprintf(f_txt, "Number of Tests Executed: %u\n", (last_results.total_passed + last_results.total_failed));
811  fprintf(f_txt, "Passed Tests: %u\n", last_results.total_passed);
812  fprintf(f_txt, "Failed Tests: %u\n", last_results.total_failed);
813  fprintf(f_txt, "Total Execution Time: %u\n", last_results.total_time);
814  }
815 
816  /* export each individual test */
817  AST_LIST_TRAVERSE(&tests, test, entry) {
818  switch (mode) {
819  case TEST_CATEGORY:
820  if (!test_cat_cmp(test->info.category, category)) {
821  test_xml_entry(test, f_xml);
822  test_txt_entry(test, f_txt);
823  }
824  break;
825  case TEST_NAME_CATEGORY:
826  if (!(strcmp(test->info.category, category)) && !(strcmp(test->info.name, name))) {
827  test_xml_entry(test, f_xml);
828  test_txt_entry(test, f_txt);
829  }
830  break;
831  case TEST_ALL:
832  test_xml_entry(test, f_xml);
833  test_txt_entry(test, f_txt);
834  }
835  }
836  AST_LIST_UNLOCK(&tests);
837 
838 done:
839  if (f_xml) {
840  fprintf(f_xml, "\t</testsuite>\n");
841  fprintf(f_xml, "</testsuites>\n");
842  fclose(f_xml);
843  }
844  if (f_txt) {
845  fclose(f_txt);
846  }
847 
848  return res;
849 }
850 
851 /*!
852  * \internal
853  * \brief adds test to container sorted first by category then by name
854  *
855  * \retval 0 success
856  * \retval -1 failure
857  */
858 static int test_insert(struct ast_test *test)
859 {
860  /* This is a slow operation that may need to be optimized in the future
861  * as the test framework expands. At the moment we are doing string
862  * comparisons on every item within the list to insert in sorted order. */
863 
864  AST_LIST_LOCK(&tests);
865  AST_LIST_INSERT_SORTALPHA(&tests, test, entry, info.category);
866  AST_LIST_UNLOCK(&tests);
867 
868  return 0;
869 }
870 
871 /*!
872  * \internal
873  * \brief removes test from container
874  *
875  * \return ast_test removed from list on success
876  * \retval NULL on failure
877  */
878 static struct ast_test *test_remove(ast_test_cb_t *cb)
879 {
880  struct ast_test *cur = NULL;
881 
882  AST_LIST_LOCK(&tests);
883  AST_LIST_TRAVERSE_SAFE_BEGIN(&tests, cur, entry) {
884  if (cur->cb == cb) {
886  break;
887  }
888  }
890  AST_LIST_UNLOCK(&tests);
891 
892  return cur;
893 }
894 
895 /*!
896  * \brief compares two test categories to determine if cat1 resides in cat2
897  * \internal
898  *
899  * \retval 0 true
900  * \retval non-zero false
901  */
902 
903 static int test_cat_cmp(const char *cat1, const char *cat2)
904 {
905  int len1 = 0;
906  int len2 = 0;
907 
908  if (!cat1 || !cat2) {
909  return -1;
910  }
911 
912  len1 = strlen(cat1);
913  len2 = strlen(cat2);
914 
915  if (len2 > len1) {
916  return -1;
917  }
918 
919  return strncmp(cat1, cat2, len2) ? 1 : 0;
920 }
921 
922 /*!
923  * \internal
924  * \brief free an ast_test object and all it's data members
925  */
926 static struct ast_test *test_free(struct ast_test *test)
927 {
928  if (!test) {
929  return NULL;
930  }
931 
932  ast_free(test->status_str);
933  ast_free(test);
934 
935  return NULL;
936 }
937 
938 /*!
939  * \internal
940  * \brief allocate an ast_test object.
941  */
942 static struct ast_test *test_alloc(ast_test_cb_t *cb)
943 {
944  struct ast_test *test;
945 
946  test = ast_calloc(1, sizeof(*test));
947  if (!test) {
948  ast_log(LOG_ERROR, "Failed to allocate test, registration failed.\n");
949  return NULL;
950  }
951 
952  test->cb = cb;
953 
954  test->cb(&test->info, TEST_INIT, test);
955 
956  if (ast_strlen_zero(test->info.name)) {
957  ast_log(LOG_ERROR, "Test has no name, test registration refused.\n");
958  return test_free(test);
959  }
960 
961  if (ast_strlen_zero(test->info.category)) {
962  ast_log(LOG_ERROR, "Test %s has no category, test registration refused.\n",
963  test->info.name);
964  return test_free(test);
965  }
966 
967  if (test->info.category[0] != '/' || test->info.category[strlen(test->info.category) - 1] != '/') {
968  ast_log(LOG_WARNING, "Test category '%s' for test '%s' is missing a leading or trailing slash.\n",
969  test->info.category, test->info.name);
970  /*
971  * Flag an error anyways so test_registrations fails but allow the
972  * test to be registered.
973  */
974  ++registration_errors;
975  }
976 
977  if (ast_strlen_zero(test->info.summary)) {
978  ast_log(LOG_ERROR, "Test %s%s has no summary, test registration refused.\n",
979  test->info.category, test->info.name);
980  return test_free(test);
981  }
982  if (test->info.summary[strlen(test->info.summary) - 1] == '\n') {
983  ast_log(LOG_WARNING, "Test %s%s summary has a trailing newline.\n",
984  test->info.category, test->info.name);
985  /*
986  * Flag an error anyways so test_registrations fails but allow the
987  * test to be registered.
988  */
989  ++registration_errors;
990  }
991 
992  if (ast_strlen_zero(test->info.description)) {
993  ast_log(LOG_ERROR, "Test %s%s has no description, test registration refused.\n",
994  test->info.category, test->info.name);
995  return test_free(test);
996  }
997  if (test->info.description[strlen(test->info.description) - 1] == '\n') {
998  ast_log(LOG_WARNING, "Test %s%s description has a trailing newline.\n",
999  test->info.category, test->info.name);
1000  /*
1001  * Flag an error anyways so test_registrations fails but allow the
1002  * test to be registered.
1003  */
1004  ++registration_errors;
1005  }
1006 
1007  if (!(test->status_str = ast_str_create(128))) {
1008  ast_log(LOG_ERROR, "Failed to allocate status_str for %s%s, test registration failed.\n",
1009  test->info.category, test->info.name);
1010  return test_free(test);
1011  }
1012 
1013  return test;
1014 }
1015 
1016 static char *complete_test_category(const char *word)
1017 {
1018  int wordlen = strlen(word);
1019  struct ast_test *test;
1020 
1021  AST_LIST_LOCK(&tests);
1022  AST_LIST_TRAVERSE(&tests, test, entry) {
1023  if (!strncasecmp(word, test->info.category, wordlen)) {
1024  if (ast_cli_completion_add(ast_strdup(test->info.category))) {
1025  break;
1026  }
1027  }
1028  }
1029  AST_LIST_UNLOCK(&tests);
1030 
1031  return NULL;
1032 }
1033 
1034 static char *complete_test_name(const char *word, const char *category)
1035 {
1036  int wordlen = strlen(word);
1037  struct ast_test *test;
1038 
1039  AST_LIST_LOCK(&tests);
1040  AST_LIST_TRAVERSE(&tests, test, entry) {
1041  if (!test_cat_cmp(test->info.category, category) && !strncasecmp(word, test->info.name, wordlen)) {
1042  if (ast_cli_completion_add(ast_strdup(test->info.name))) {
1043  break;
1044  }
1045  }
1046  }
1047  AST_LIST_UNLOCK(&tests);
1048 
1049  return NULL;
1050 }
1051 
1052 /* CLI commands */
1053 static char *test_cli_show_registered(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
1054 {
1055 #define FORMAT "%-25.25s %-30.30s %-40.40s %-13.13s\n"
1056  static const char * const option1[] = { "all", "category", NULL };
1057  static const char * const option2[] = { "name", NULL };
1058  struct ast_test *test = NULL;
1059  int count = 0;
1060  switch (cmd) {
1061  case CLI_INIT:
1062  e->command = "test show registered";
1063 
1064  e->usage =
1065  "Usage: 'test show registered' can be used in three ways.\n"
1066  " 1. 'test show registered all' shows all registered tests\n"
1067  " 2. 'test show registered category [test category]' shows all tests in the given\n"
1068  " category.\n"
1069  " 3. 'test show registered category [test category] name [test name]' shows all\n"
1070  " tests in a given category matching a given name\n";
1071  return NULL;
1072  case CLI_GENERATE:
1073  if (a->pos == 3) {
1074  return ast_cli_complete(a->word, option1, -1);
1075  }
1076  if (a->pos == 4 && !strcasecmp(a->argv[3], "category")) {
1077  return complete_test_category(a->word);
1078  }
1079  if (a->pos == 5) {
1080  return ast_cli_complete(a->word, option2, -1);
1081  }
1082  if (a->pos == 6) {
1083  return complete_test_name(a->word, a->argv[4]);
1084  }
1085  return NULL;
1086  case CLI_HANDLER:
1087  if ((a->argc < 4) || (a->argc == 6) || (a->argc > 7) ||
1088  ((a->argc == 4) && strcasecmp(a->argv[3], "all")) ||
1089  ((a->argc == 7) && strcasecmp(a->argv[5], "name"))) {
1090  return CLI_SHOWUSAGE;
1091  }
1092  ast_cli(a->fd, FORMAT, "Category", "Name", "Summary", "Test Result");
1093  ast_cli(a->fd, FORMAT, "--------", "----", "-------", "-----------");
1094  AST_LIST_LOCK(&tests);
1095  AST_LIST_TRAVERSE(&tests, test, entry) {
1096  if ((a->argc == 4) ||
1097  ((a->argc == 5) && !test_cat_cmp(test->info.category, a->argv[4])) ||
1098  ((a->argc == 7) && !strcmp(test->info.category, a->argv[4]) && !strcmp(test->info.name, a->argv[6]))) {
1099 
1100  ast_cli(a->fd, FORMAT, test->info.category, test->info.name,
1101  test->info.summary, test_result2str[test->state]);
1102  count++;
1103  }
1104  }
1105  AST_LIST_UNLOCK(&tests);
1106  ast_cli(a->fd, FORMAT, "--------", "----", "-------", "-----------");
1107  ast_cli(a->fd, "\n%d Registered Tests Matched\n", count);
1108  default:
1109  return NULL;
1110  }
1111 
1112  return CLI_SUCCESS;
1113 }
1114 
1115 static char *test_cli_execute_registered(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
1116 {
1117  static const char * const option1[] = { "all", "category", NULL };
1118  static const char * const option2[] = { "name", NULL };
1119 
1120  switch (cmd) {
1121  case CLI_INIT:
1122  e->command = "test execute";
1123  e->usage =
1124  "Usage: test execute can be used in three ways.\n"
1125  " 1. 'test execute all' runs all registered tests\n"
1126  " 2. 'test execute category [test category]' runs all tests in the given\n"
1127  " category.\n"
1128  " 3. 'test execute category [test category] name [test name]' runs all\n"
1129  " tests in a given category matching a given name\n";
1130  return NULL;
1131  case CLI_GENERATE:
1132  if (a->pos == 2) {
1133  return ast_cli_complete(a->word, option1, -1);
1134  }
1135  if (a->pos == 3 && !strcasecmp(a->argv[2], "category")) {
1136  return complete_test_category(a->word);
1137  }
1138  if (a->pos == 4) {
1139  return ast_cli_complete(a->word, option2, -1);
1140  }
1141  if (a->pos == 5) {
1142  return complete_test_name(a->word, a->argv[3]);
1143  }
1144  return NULL;
1145  case CLI_HANDLER:
1146 
1147  if (a->argc < 3|| a->argc > 6) {
1148  return CLI_SHOWUSAGE;
1149  }
1150 
1151  if ((a->argc == 3) && !strcasecmp(a->argv[2], "all")) { /* run all registered tests */
1152  ast_cli(a->fd, "Running all available tests...\n\n");
1153  test_execute_multiple(NULL, NULL, a);
1154  } else if (a->argc == 4) { /* run only tests within a category */
1155  ast_cli(a->fd, "Running all available tests matching category %s\n\n", a->argv[3]);
1156  test_execute_multiple(NULL, a->argv[3], a);
1157  } else if (a->argc == 6) { /* run only a single test matching the category and name */
1158  ast_cli(a->fd, "Running all available tests matching category %s and name %s\n\n", a->argv[3], a->argv[5]);
1159  test_execute_multiple(a->argv[5], a->argv[3], a);
1160  } else {
1161  return CLI_SHOWUSAGE;
1162  }
1163 
1164  AST_LIST_LOCK(&tests);
1165  if (!(last_results.last_passed + last_results.last_failed)) {
1166  ast_cli(a->fd, "--- No Tests Found! ---\n");
1167  }
1168  ast_cli(a->fd, "\n%u Test(s) Executed %u Passed %u Failed\n",
1169  (last_results.last_passed + last_results.last_failed),
1170  last_results.last_passed,
1171  last_results.last_failed);
1172  AST_LIST_UNLOCK(&tests);
1173  default:
1174  return NULL;
1175  }
1176 
1177  return CLI_SUCCESS;
1178 }
1179 
1180 static char *test_cli_show_results(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
1181 {
1182 #define FORMAT_RES_ALL1 "%s%s %-30.30s %-25.25s %-10.10s\n"
1183 #define FORMAT_RES_ALL2 "%s%s %-30.30s %-25.25s %s%ums\n"
1184  static const char * const option1[] = { "all", "failed", "passed", NULL };
1185  char result_buf[32] = { 0 };
1186  struct ast_test *test = NULL;
1187  int failed = 0;
1188  int passed = 0;
1189  int mode; /* 0 for show all, 1 for show fail, 2 for show passed */
1190 
1191  switch (cmd) {
1192  case CLI_INIT:
1193  e->command = "test show results";
1194  e->usage =
1195  "Usage: test show results can be used in three ways\n"
1196  " 1. 'test show results all' Displays results for all executed tests.\n"
1197  " 2. 'test show results passed' Displays results for all passed tests.\n"
1198  " 3. 'test show results failed' Displays results for all failed tests.\n";
1199  return NULL;
1200  case CLI_GENERATE:
1201  if (a->pos == 3) {
1202  return ast_cli_complete(a->word, option1, -1);
1203  }
1204  return NULL;
1205  case CLI_HANDLER:
1206 
1207  /* verify input */
1208  if (a->argc != 4) {
1209  return CLI_SHOWUSAGE;
1210  } else if (!strcasecmp(a->argv[3], "passed")) {
1211  mode = 2;
1212  } else if (!strcasecmp(a->argv[3], "failed")) {
1213  mode = 1;
1214  } else if (!strcasecmp(a->argv[3], "all")) {
1215  mode = 0;
1216  } else {
1217  return CLI_SHOWUSAGE;
1218  }
1219 
1220  ast_cli(a->fd, FORMAT_RES_ALL1, "Result", "", "Name", "Category", "Time");
1221  AST_LIST_LOCK(&tests);
1222  AST_LIST_TRAVERSE(&tests, test, entry) {
1223  if (test->state == AST_TEST_NOT_RUN) {
1224  continue;
1225  }
1226  test->state == AST_TEST_FAIL ? failed++ : passed++;
1227  if (!mode || ((mode == 1) && (test->state == AST_TEST_FAIL)) || ((mode == 2) && (test->state == AST_TEST_PASS))) {
1228  /* give our results pretty colors */
1229  term_color(result_buf, test_result2str[test->state],
1230  (test->state == AST_TEST_FAIL) ? COLOR_RED : COLOR_GREEN,
1231  0, sizeof(result_buf));
1232 
1233  ast_cli(a->fd, FORMAT_RES_ALL2,
1234  result_buf,
1235  " ",
1236  test->info.name,
1237  test->info.category,
1238  test->time ? " " : "<",
1239  test->time ? test->time : 1);
1240  }
1241  }
1242  AST_LIST_UNLOCK(&tests);
1243 
1244  ast_cli(a->fd, "%d Test(s) Executed %d Passed %d Failed\n", (failed + passed), passed, failed);
1245  default:
1246  return NULL;
1247  }
1248  return CLI_SUCCESS;
1249 }
1250 
1251 static char *test_cli_generate_results(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
1252 {
1253  static const char * const option[] = { "xml", "txt", NULL };
1254  const char *file = NULL;
1255  const char *type = "";
1256  int isxml = 0;
1257  int res = 0;
1258  struct ast_str *buf = NULL;
1259  struct timeval time = ast_tvnow();
1260 
1261  switch (cmd) {
1262  case CLI_INIT:
1263  e->command = "test generate results";
1264  e->usage =
1265  "Usage: 'test generate results'\n"
1266  " Generates test results in either xml or txt format. An optional \n"
1267  " file path may be provided to specify the location of the xml or\n"
1268  " txt file\n"
1269  " \nExample usage:\n"
1270  " 'test generate results xml' this writes to a default file\n"
1271  " 'test generate results xml /path/to/file.xml' writes to specified file\n";
1272  return NULL;
1273  case CLI_GENERATE:
1274  if (a->pos == 3) {
1275  return ast_cli_complete(a->word, option, -1);
1276  }
1277  return NULL;
1278  case CLI_HANDLER:
1279 
1280  /* verify input */
1281  if (a->argc < 4 || a->argc > 5) {
1282  return CLI_SHOWUSAGE;
1283  } else if (!strcasecmp(a->argv[3], "xml")) {
1284  type = "xml";
1285  isxml = 1;
1286  } else if (!strcasecmp(a->argv[3], "txt")) {
1287  type = "txt";
1288  } else {
1289  return CLI_SHOWUSAGE;
1290  }
1291 
1292  if (a->argc == 5) {
1293  file = a->argv[4];
1294  } else {
1295  if (!(buf = ast_str_create(256))) {
1296  return NULL;
1297  }
1298  ast_str_set(&buf, 0, "%s/asterisk_test_results-%ld.%s", ast_config_AST_LOG_DIR, (long) time.tv_sec, type);
1299 
1300  file = ast_str_buffer(buf);
1301  }
1302 
1303  if (isxml) {
1304  res = test_generate_results(NULL, NULL, file, NULL);
1305  } else {
1306  res = test_generate_results(NULL, NULL, NULL, file);
1307  }
1308 
1309  if (!res) {
1310  ast_cli(a->fd, "Results Generated Successfully: %s\n", S_OR(file, ""));
1311  } else {
1312  ast_cli(a->fd, "Results Could Not Be Generated: %s\n", S_OR(file, ""));
1313  }
1314 
1315  ast_free(buf);
1316  default:
1317  return NULL;
1318  }
1319 
1320  return CLI_SUCCESS;
1321 }
1322 
1323 static struct ast_cli_entry test_cli[] = {
1324  AST_CLI_DEFINE(test_cli_show_registered, "show registered tests"),
1325  AST_CLI_DEFINE(test_cli_execute_registered, "execute registered tests"),
1326  AST_CLI_DEFINE(test_cli_show_results, "show last test results"),
1327  AST_CLI_DEFINE(test_cli_generate_results, "generate test results to file"),
1328 };
1329 
1330 struct stasis_topic *ast_test_suite_topic(void)
1331 {
1332  return test_suite_topic;
1333 }
1334 
1335 /*!
1336  * \since 12
1337  * \brief A wrapper object that can be ao2 ref counted around an \ref ast_json blob
1338  */
1339 struct ast_test_suite_message_payload {
1340  struct ast_json *blob; /*!< The actual blob that we want to deliver */
1341 };
1342 
1343 /*! \internal
1344  * \since 12
1345  * \brief Destructor for \ref ast_test_suite_message_payload
1346  */
1347 static void test_suite_message_payload_dtor(void *obj)
1348 {
1349  struct ast_test_suite_message_payload *payload = obj;
1350 
1351  if (payload->blob) {
1352  ast_json_unref(payload->blob);
1353  }
1354 }
1355 
1356 struct ast_json *ast_test_suite_get_blob(struct ast_test_suite_message_payload *payload)
1357 {
1358  return payload->blob;
1359 }
1360 
1361 static struct ast_manager_event_blob *test_suite_event_to_ami(struct stasis_message *msg)
1362 {
1363  RAII_VAR(struct ast_str *, packet_string, ast_str_create(128), ast_free);
1364  struct ast_test_suite_message_payload *payload;
1365  struct ast_json *blob;
1366  const char *type;
1367 
1368  payload = stasis_message_data(msg);
1369  if (!payload) {
1370  return NULL;
1371  }
1372  blob = ast_test_suite_get_blob(payload);
1373  if (!blob) {
1374  return NULL;
1375  }
1376 
1377  type = ast_json_string_get(ast_json_object_get(blob, "type"));
1378  if (ast_strlen_zero(type) || strcmp("testevent", type)) {
1379  return NULL;
1380  }
1381 
1382  ast_str_append(&packet_string, 0, "Type: StateChange\r\n");
1383  ast_str_append(&packet_string, 0, "State: %s\r\n",
1384  ast_json_string_get(ast_json_object_get(blob, "state")));
1385  ast_str_append(&packet_string, 0, "AppFile: %s\r\n",
1386  ast_json_string_get(ast_json_object_get(blob, "appfile")));
1387  ast_str_append(&packet_string, 0, "AppFunction: %s\r\n",
1388  ast_json_string_get(ast_json_object_get(blob, "appfunction")));
1389  ast_str_append(&packet_string, 0, "AppLine: %jd\r\n",
1390  ast_json_integer_get(ast_json_object_get(blob, "line")));
1391  ast_str_append(&packet_string, 0, "%s\r\n",
1392  ast_json_string_get(ast_json_object_get(blob, "data")));
1393 
1394  return ast_manager_event_blob_create(EVENT_FLAG_REPORTING,
1395  "TestEvent",
1396  "%s",
1397  ast_str_buffer(packet_string));
1398 }
1399 
1400 /*! \since 12
1401  * \brief The message type for test suite messages
1402  */
1403 STASIS_MESSAGE_TYPE_DEFN(ast_test_suite_message_type,
1404  .to_ami = test_suite_event_to_ami);
1405 
1406 void __ast_test_suite_event_notify(const char *file, const char *func, int line, const char *state, const char *fmt, ...)
1407 {
1408  RAII_VAR(struct ast_test_suite_message_payload *, payload,
1409  NULL,
1410  ao2_cleanup);
1411  RAII_VAR(struct stasis_message *, msg, NULL, ao2_cleanup);
1412  RAII_VAR(struct ast_str *, buf, NULL, ast_free);
1413  va_list ap;
1414 
1415  if (!ast_test_suite_message_type()) {
1416  return;
1417  }
1418 
1419  buf = ast_str_create(128);
1420  if (!buf) {
1421  return;
1422  }
1423 
1424  payload = ao2_alloc(sizeof(*payload), test_suite_message_payload_dtor);
1425  if (!payload) {
1426  return;
1427  }
1428 
1429  va_start(ap, fmt);
1430  ast_str_set_va(&buf, 0, fmt, ap);
1431  va_end(ap);
1432  payload->blob = ast_json_pack("{s: s, s: s, s: s, s: s, s: i, s: s}",
1433  "type", "testevent",
1434  "state", state,
1435  "appfile", file,
1436  "appfunction", func,
1437  "line", line,
1438  "data", ast_str_buffer(buf));
1439  if (!payload->blob) {
1440  return;
1441  }
1442  msg = stasis_message_create(ast_test_suite_message_type(), payload);
1443  if (!msg) {
1444  return;
1445  }
1446  stasis_publish(ast_test_suite_topic(), msg);
1447 }
1448 
1449 AST_TEST_DEFINE(test_registrations)
1450 {
1451  switch (cmd) {
1452  case TEST_INIT:
1453  info->name = "registrations";
1454  info->category = "/main/test/";
1455  info->summary = "Validate Test Registration Data.";
1456  info->description = "Validate Test Registration Data.";
1457  return AST_TEST_NOT_RUN;
1458  case TEST_EXECUTE:
1459  break;
1460  }
1461 
1462  if (registration_errors) {
1463  ast_test_status_update(test,
1464  "%d test registration error%s occurred. See startup logs for details.\n",
1465  registration_errors, registration_errors > 1 ? "s" : "");
1466  return AST_TEST_FAIL;
1467  }
1468 
1469  return AST_TEST_PASS;
1470 }
1471 
1472 static void test_cleanup(void)
1473 {
1474  AST_TEST_UNREGISTER(test_registrations);
1475  ast_cli_unregister_multiple(test_cli, ARRAY_LEN(test_cli));
1476  ao2_cleanup(test_suite_topic);
1477  test_suite_topic = NULL;
1478  STASIS_MESSAGE_TYPE_CLEANUP(ast_test_suite_message_type);
1479 }
1480 #endif /* TEST_FRAMEWORK */
1481 
1482 int ast_test_init(void)
1483 {
1484 #ifdef TEST_FRAMEWORK
1485  ast_register_cleanup(test_cleanup);
1486 
1487  /* Create stasis topic */
1488  test_suite_topic = stasis_topic_create("testsuite:all");
1489  if (!test_suite_topic) {
1490  return -1;
1491  }
1492 
1493  if (STASIS_MESSAGE_TYPE_INIT(ast_test_suite_message_type) != 0) {
1494  return -1;
1495  }
1496 
1497  AST_TEST_REGISTER(test_registrations);
1498 
1499  /* Register cli commands */
1500  ast_cli_register_multiple(test_cli, ARRAY_LEN(test_cli));
1501 #endif
1502 
1503  return 0;
1504 }
1505 
Contains all the initialization information required to store a new test definition.
Definition: test.h:235
Struct containing info for an AMI event to send out.
Definition: manager.h:502
pid_t pid
process id of child
Definition: test.h:227
size_t errlen
length of buffer holding stderr
Definition: test.h:225
char * outbuf
buffer holding stdout
Definition: test.h:219
#define AST_LIST_LOCK(head)
Locks a list.
Definition: linkedlists.h:40
Asterisk main include file. File version handling, generic pbx functions.
static SQLHSTMT execute(struct odbc_obj *obj, void *data, int silent)
Common execution function for SQL queries.
Definition: func_odbc.c:471
struct ast_json * ast_json_pack(char const *format,...)
Helper for creating complex JSON values.
Definition: json.c:612
void ast_unreplace_sigchld(void)
Restore the SIGCHLD handler.
Definition: extconf.c:815
Asterisk version information.
int ast_cli_unregister_multiple(struct ast_cli_entry *e, int len)
Unregister multiple commands.
Definition: clicompat.c:30
Time-related functions and macros.
void ast_json_unref(struct ast_json *value)
Decrease refcount on value. If refcount reaches zero, value is freed.
Definition: json.c:73
const char * ast_get_version(void)
Retrieve the Asterisk version string.
Definition: version.c:18
#define STASIS_MESSAGE_TYPE_INIT(name)
Boiler-plate messaging macro for initializing message types.
Definition: stasis.h:1493
Stasis Message Bus API. See Stasis Message Bus API for detailed documentation.
descriptor for a cli entry.
Definition: cli.h:171
#define AST_LIST_UNLOCK(head)
Attempts to unlock a list.
Definition: linkedlists.h:140
char * ast_str_buffer(const struct ast_str *buf)
Returns the string buffer within the ast_str buf.
Definition: strings.h:761
int exitcode
exit code of child
Definition: test.h:229
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:1030
Test Framework API.
#define STASIS_MESSAGE_TYPE_CLEANUP(name)
Boiler-plate messaging macro for cleaning up message types.
Definition: stasis.h:1515
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:1139
#define ast_cli_register_multiple(e, len)
Register multiple commands.
Definition: cli.h:265
struct timeval ast_tvnow(void)
Returns current timeval. Meant to replace calls to gettimeofday().
Definition: time.h:159
int64_t ast_tvdiff_ms(struct timeval end, struct timeval start)
Computes the difference (in milliseconds) between two struct timeval instances.
Definition: time.h:107
#define ast_strdup(str)
A wrapper for strdup()
Definition: astmm.h:241
#define AST_LIST_TRAVERSE_SAFE_END
Closes a safe loop traversal block.
Definition: linkedlists.h:615
struct ast_manager_event_blob * ast_manager_event_blob_create(int event_flags, const char *manager_event, const char *extra_fields_fmt,...)
Construct a ast_manager_event_blob.
Definition: manager.c:10563
Utility functions.
char * ast_cli_complete(const char *word, const char *const choices[], int pos)
Definition: main/cli.c:1846
#define AST_LIST_INSERT_SORTALPHA(head, elm, field, sortfield)
Inserts a list entry into a alphabetically sorted list.
Definition: linkedlists.h:751
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:1113
int ast_test_init(void)
Definition: test.c:1482
static void cleanup(void)
Clean up any old apps that we don't need any more.
Definition: res_stasis.c:327
Asterisk JSON abstraction layer.
int ast_register_cleanup(void(*func)(void))
Register a function to be executed before Asterisk gracefully exits.
Definition: clicompat.c:19
Asterisk file paths, configured in asterisk.conf.
#define ast_strdupa(s)
duplicate a string in memory from the stack
Definition: astmm.h:298
#define AST_LIST_REMOVE_CURRENT(field)
Removes the current entry from a list during a traversal.
Definition: linkedlists.h:557
const char * ast_json_string_get(const struct ast_json *string)
Get the value of a JSON string.
Definition: json.c:283
char * ast_strsep(char **s, const char sep, uint32_t flags)
Act like strsep but ignore separators inside quotes.
Definition: utils.c:1835
A set of macros to manage forward-linked lists.
struct stasis_topic * stasis_topic_create(const char *name)
Create a new topic.
Definition: stasis.c:617
void ast_replace_sigchld(void)
Replace the SIGCHLD handler.
Definition: extconf.c:801
#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:291
char * term_color(char *outbuf, const char *inbuf, int fgcolor, int bgcolor, int maxout)
Colorize a specified string by adding terminal color codes.
Definition: term.c:235
#define ast_alloca(size)
call __builtin_alloca to ensure we get gcc builtin semantics
Definition: astmm.h:288
#define STASIS_MESSAGE_TYPE_DEFN(name,...)
Boiler-plate messaging macro for defining public message types.
Definition: stasis.h:1440
size_t outlen
length of buffer holding stdout
Definition: test.h:221
Support for dynamic strings.
Definition: strings.h:623
void * stasis_message_data(const struct stasis_message *msg)
Get the data contained in a message.
struct stasis_message * stasis_message_create(struct stasis_message_type *type, void *data)
Create a new message.
char * errbuf
buffer holding stderr
Definition: test.h:223
#define AST_LIST_TRAVERSE(head, var, field)
Loops over (traverses) the entries in a list.
Definition: linkedlists.h:491
#define AST_LIST_ENTRY(type)
Declare a forward link structure inside a list entry.
Definition: linkedlists.h:410
void stasis_publish(struct stasis_topic *topic, struct stasis_message *message)
Publish a message to a topic's subscribers.
Definition: stasis.c:1511
char * command
Definition: cli.h:186
#define ast_calloc(num, len)
A wrapper for calloc()
Definition: astmm.h:202
Support for logging to various files, console and syslog Configuration in file logger.conf.
Prototypes for public functions only of internal interest,.
A capture of running an external process.
Definition: test.h:217
const char * ast_get_version_num(void)
Retrieve the numeric Asterisk version.
Definition: version.c:23
const char * usage
Definition: cli.h:177
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:693
Standard Command Line Interface.
struct ast_json * ast_json_object_get(struct ast_json *object, const char *key)
Get a field from a JSON object.
Definition: json.c:407
#define S_OR(a, b)
returns the equivalent of logic or for strings: first one if not empty, otherwise second one...
Definition: strings.h:80
#define AST_TEST_DEFINE(hdr)
Definition: test.h:126
Abstract JSON element (object, array, string, int, ...).
Definition: search.h:40
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:529
int ast_cli_completion_add(char *value)
Add a result to a request for completion options.
Definition: main/cli.c:2761
intmax_t ast_json_integer_get(const struct ast_json *integer)
Get the value from a JSON integer.
Definition: json.c:332
#define RAII_VAR(vartype, varname, initval, dtor)
Declare a variable that will call a destructor function when it goes out of scope.
Definition: utils.h:941
Application convenience functions, designed to give consistent look and feel to Asterisk apps...
#define ast_str_create(init_len)
Create a malloc'ed dynamic length string.
Definition: strings.h:659