Asterisk - The Open Source Telephony Project GIT-master-8924258
All Data Structures Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Macros Modules Pages
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"
40#include "asterisk/utils.h"
41#include "asterisk/cli.h"
42#include "asterisk/term.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 */
65struct stasis_topic *test_suite_topic;
66
67/*! This array corresponds to the values defined in the ast_test_state enum */
68static 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 */
75struct 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 */
97static 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
107enum 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 */
150static AST_LIST_HEAD_STATIC(tests, ast_test);
151
152static struct ast_test *test_alloc(ast_test_cb_t *cb);
153static struct ast_test *test_free(struct ast_test *test);
154static int test_insert(struct ast_test *test);
155static struct ast_test *test_remove(ast_test_cb_t *cb);
156static int test_cat_cmp(const char *cat1, const char *cat2);
157static int registration_errors = 0;
158
159void 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
180int __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
206int 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
223int 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
240int 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
264int 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 */
284static void test_execute(struct ast_test *test)
285{
286 struct timeval begin;
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 }
304exit:
305 test->time = ast_tvdiff_ms(ast_tvnow(), begin);
306}
307
308void 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
316void ast_test_capture_init(struct ast_test_capture *capture)
317{
318 capture->outbuf = capture->errbuf = NULL;
319 capture->pid = capture->exitcode = -1;
320}
321
322void 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
338int 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
513cleanup:
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 */
534static 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
547static 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
560static 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
608static 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 */
642static 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) {
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 */
752static 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
838done:
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 */
858static 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 */
878static 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
903static 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 */
926static 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 */
942static 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
1016struct ast_cli_args *ast_test_get_cli_args(struct ast_test *test)
1017{
1018 return test->cli;
1019}
1020
1021static char *complete_test_category(const char *word)
1022{
1023 int wordlen = strlen(word);
1024 struct ast_test *test;
1025
1026 AST_LIST_LOCK(&tests);
1027 AST_LIST_TRAVERSE(&tests, test, entry) {
1028 if (!strncasecmp(word, test->info.category, wordlen)) {
1029 if (ast_cli_completion_add(ast_strdup(test->info.category))) {
1030 break;
1031 }
1032 }
1033 }
1034 AST_LIST_UNLOCK(&tests);
1035
1036 return NULL;
1037}
1038
1039static char *complete_test_name(const char *word, const char *category)
1040{
1041 int wordlen = strlen(word);
1042 struct ast_test *test;
1043
1044 AST_LIST_LOCK(&tests);
1045 AST_LIST_TRAVERSE(&tests, test, entry) {
1046 if (!test_cat_cmp(test->info.category, category) && !strncasecmp(word, test->info.name, wordlen)) {
1048 break;
1049 }
1050 }
1051 }
1052 AST_LIST_UNLOCK(&tests);
1053
1054 return NULL;
1055}
1056
1057/* CLI commands */
1058static char *test_cli_show_registered(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
1059{
1060#define FORMAT "%-25.25s %-30.30s %-40.40s %-13.13s\n"
1061 static const char * const option1[] = { "all", "category", NULL };
1062 static const char * const option2[] = { "name", NULL };
1063 struct ast_test *test = NULL;
1064 int count = 0;
1065 switch (cmd) {
1066 case CLI_INIT:
1067 e->command = "test show registered";
1068
1069 e->usage =
1070 "Usage: 'test show registered' can be used in three ways.\n"
1071 " 1. 'test show registered all' shows all registered tests\n"
1072 " 2. 'test show registered category [test category]' shows all tests in the given\n"
1073 " category.\n"
1074 " 3. 'test show registered category [test category] name [test name]' shows all\n"
1075 " tests in a given category matching a given name\n";
1076 return NULL;
1077 case CLI_GENERATE:
1078 if (a->pos == 3) {
1079 return ast_cli_complete(a->word, option1, -1);
1080 }
1081 if (a->pos == 4 && !strcasecmp(a->argv[3], "category")) {
1082 return complete_test_category(a->word);
1083 }
1084 if (a->pos == 5) {
1085 return ast_cli_complete(a->word, option2, -1);
1086 }
1087 if (a->pos == 6) {
1088 return complete_test_name(a->word, a->argv[4]);
1089 }
1090 return NULL;
1091 case CLI_HANDLER:
1092 if ((a->argc < 4) || (a->argc == 6) || (a->argc > 7) ||
1093 ((a->argc == 4) && strcasecmp(a->argv[3], "all")) ||
1094 ((a->argc == 7) && strcasecmp(a->argv[5], "name"))) {
1095 return CLI_SHOWUSAGE;
1096 }
1097 ast_cli(a->fd, FORMAT, "Category", "Name", "Summary", "Test Result");
1098 ast_cli(a->fd, FORMAT, "--------", "----", "-------", "-----------");
1099 AST_LIST_LOCK(&tests);
1100 AST_LIST_TRAVERSE(&tests, test, entry) {
1101 if ((a->argc == 4) ||
1102 ((a->argc == 5) && !test_cat_cmp(test->info.category, a->argv[4])) ||
1103 ((a->argc == 7) && !strcmp(test->info.category, a->argv[4]) && !strcmp(test->info.name, a->argv[6]))) {
1104
1105 ast_cli(a->fd, FORMAT, test->info.category, test->info.name,
1106 test->info.summary, test_result2str[test->state]);
1107 count++;
1108 }
1109 }
1110 AST_LIST_UNLOCK(&tests);
1111 ast_cli(a->fd, FORMAT, "--------", "----", "-------", "-----------");
1112 ast_cli(a->fd, "\n%d Registered Tests Matched\n", count);
1113 default:
1114 return NULL;
1115 }
1116
1117 return CLI_SUCCESS;
1118}
1119
1120static char *test_cli_execute_registered(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
1121{
1122 static const char * const option1[] = { "all", "category", NULL };
1123 static const char * const option2[] = { "name", "options", NULL };
1124 static const char * const option3[] = { "options", NULL };
1125
1126 switch (cmd) {
1127 case CLI_INIT:
1128 e->command = "test execute";
1129 e->usage =
1130 "Usage: test execute can be used in several ways.\n"
1131 " 1. 'test execute all' runs all registered tests\n"
1132 " 2. 'test execute category [test category]' runs all tests in the given\n"
1133 " category.\n"
1134 " 3. 'test execute category [test category] options [test option]...' runs all\n"
1135 " tests in the given category with options supplied to each test\n"
1136 " 4. 'test execute category [test category] name [test name]' runs all\n"
1137 " tests in a given category matching a given name\n"
1138 " 5. 'test execute category [test category] name [test name] options [test option]...' runs all\n"
1139 " tests in a given category matching a given name with the specified options\n"
1140 ;
1141 return NULL;
1142 case CLI_GENERATE:
1143 if (a->pos == 2) {
1144 return ast_cli_complete(a->word, option1, -1);
1145 }
1146 if (a->pos == 3 && !strcasecmp(a->argv[2], "category")) {
1147 return complete_test_category(a->word);
1148 }
1149 if (a->pos == 4) {
1150 return ast_cli_complete(a->word, option2, -1);
1151 }
1152 if (a->pos == 5 && !strcasecmp(a->argv[4], "name")) {
1153 return complete_test_name(a->word, a->argv[3]);
1154 }
1155 if (a->pos == 5 && !strcasecmp(a->argv[4], "options")) {
1156 return NULL;
1157 }
1158 if (a->pos == 6 && !strcasecmp(a->argv[4], "name")) {
1159 return ast_cli_complete(a->word, option3, -1);
1160 }
1161 return NULL;
1162 case CLI_HANDLER:
1163
1164 if (a->argc < 3) {
1165 return CLI_SHOWUSAGE;
1166 }
1167
1168 if ((a->argc == 3) && !strcasecmp(a->argv[2], "all")) { /* run all registered tests */
1169 ast_cli(a->fd, "Running all available tests...\n\n");
1170 test_execute_multiple(NULL, NULL, a);
1171 } else if (a->argc == 4) { /* run only tests within a category */
1172 ast_cli(a->fd, "Running all available tests matching category %s\n\n", a->argv[3]);
1173 test_execute_multiple(NULL, a->argv[3], a);
1174 } else if (a->argc >= 6 && !strcasecmp(a->argv[4], "options")) { /* run only tests within a category */
1175 ast_cli(a->fd, "Running all available tests matching category %s with options\n\n", a->argv[3]);
1176 test_execute_multiple(NULL, a->argv[3], a);
1177 } else if (a->argc == 6 && !strcasecmp(a->argv[4], "name")) { /* run only a single test matching the category and name */
1178 ast_cli(a->fd, "Running all available tests matching category %s and name %s\n\n", a->argv[3], a->argv[5]);
1179 test_execute_multiple(a->argv[5], a->argv[3], a);
1180 } else if (a->argc > 7) { /* run only a single test matching the category and name */
1181 ast_cli(a->fd, "Running all available tests matching category %s and name %s with options\n\n", a->argv[3], a->argv[5]);
1182 test_execute_multiple(a->argv[5], a->argv[3], a);
1183 } else {
1184 return CLI_SHOWUSAGE;
1185 }
1186
1187 AST_LIST_LOCK(&tests);
1188 if (!(last_results.last_passed + last_results.last_failed)) {
1189 ast_cli(a->fd, "--- No Tests Found! ---\n");
1190 }
1191 ast_cli(a->fd, "\n%u Test(s) Executed %u Passed %u Failed\n",
1192 (last_results.last_passed + last_results.last_failed),
1193 last_results.last_passed,
1194 last_results.last_failed);
1195 AST_LIST_UNLOCK(&tests);
1196 default:
1197 return NULL;
1198 }
1199
1200 return CLI_SUCCESS;
1201}
1202
1203static char *test_cli_show_results(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
1204{
1205#define FORMAT_RES_ALL1 "%s%s %-30.30s %-25.25s %-10.10s\n"
1206#define FORMAT_RES_ALL2 "%s%s %-30.30s %-25.25s %s%ums\n"
1207 static const char * const option1[] = { "all", "failed", "passed", NULL };
1208 char result_buf[32] = { 0 };
1209 struct ast_test *test = NULL;
1210 int failed = 0;
1211 int passed = 0;
1212 int mode; /* 0 for show all, 1 for show fail, 2 for show passed */
1213
1214 switch (cmd) {
1215 case CLI_INIT:
1216 e->command = "test show results";
1217 e->usage =
1218 "Usage: test show results can be used in three ways\n"
1219 " 1. 'test show results all' Displays results for all executed tests.\n"
1220 " 2. 'test show results passed' Displays results for all passed tests.\n"
1221 " 3. 'test show results failed' Displays results for all failed tests.\n";
1222 return NULL;
1223 case CLI_GENERATE:
1224 if (a->pos == 3) {
1225 return ast_cli_complete(a->word, option1, -1);
1226 }
1227 return NULL;
1228 case CLI_HANDLER:
1229
1230 /* verify input */
1231 if (a->argc != 4) {
1232 return CLI_SHOWUSAGE;
1233 } else if (!strcasecmp(a->argv[3], "passed")) {
1234 mode = 2;
1235 } else if (!strcasecmp(a->argv[3], "failed")) {
1236 mode = 1;
1237 } else if (!strcasecmp(a->argv[3], "all")) {
1238 mode = 0;
1239 } else {
1240 return CLI_SHOWUSAGE;
1241 }
1242
1243 ast_cli(a->fd, FORMAT_RES_ALL1, "Result", "", "Name", "Category", "Time");
1244 AST_LIST_LOCK(&tests);
1245 AST_LIST_TRAVERSE(&tests, test, entry) {
1246 if (test->state == AST_TEST_NOT_RUN) {
1247 continue;
1248 }
1249 test->state == AST_TEST_FAIL ? failed++ : passed++;
1250 if (!mode || ((mode == 1) && (test->state == AST_TEST_FAIL)) || ((mode == 2) && (test->state == AST_TEST_PASS))) {
1251 /* give our results pretty colors */
1252 term_color(result_buf, test_result2str[test->state],
1253 (test->state == AST_TEST_FAIL) ? COLOR_RED : COLOR_GREEN,
1254 0, sizeof(result_buf));
1255
1256 ast_cli(a->fd, FORMAT_RES_ALL2,
1257 result_buf,
1258 " ",
1259 test->info.name,
1260 test->info.category,
1261 test->time ? " " : "<",
1262 test->time ? test->time : 1);
1263 }
1264 }
1265 AST_LIST_UNLOCK(&tests);
1266
1267 ast_cli(a->fd, "%d Test(s) Executed %d Passed %d Failed\n", (failed + passed), passed, failed);
1268 default:
1269 return NULL;
1270 }
1271 return CLI_SUCCESS;
1272}
1273
1274static char *test_cli_generate_results(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
1275{
1276 static const char * const option[] = { "xml", "txt", NULL };
1277 const char *file = NULL;
1278 const char *type = "";
1279 int isxml = 0;
1280 int res = 0;
1281 struct ast_str *buf = NULL;
1282 struct timeval time = ast_tvnow();
1283
1284 switch (cmd) {
1285 case CLI_INIT:
1286 e->command = "test generate results";
1287 e->usage =
1288 "Usage: 'test generate results'\n"
1289 " Generates test results in either xml or txt format. An optional \n"
1290 " file path may be provided to specify the location of the xml or\n"
1291 " txt file\n"
1292 " \nExample usage:\n"
1293 " 'test generate results xml' this writes to a default file\n"
1294 " 'test generate results xml /path/to/file.xml' writes to specified file\n";
1295 return NULL;
1296 case CLI_GENERATE:
1297 if (a->pos == 3) {
1298 return ast_cli_complete(a->word, option, -1);
1299 }
1300 return NULL;
1301 case CLI_HANDLER:
1302
1303 /* verify input */
1304 if (a->argc < 4 || a->argc > 5) {
1305 return CLI_SHOWUSAGE;
1306 } else if (!strcasecmp(a->argv[3], "xml")) {
1307 type = "xml";
1308 isxml = 1;
1309 } else if (!strcasecmp(a->argv[3], "txt")) {
1310 type = "txt";
1311 } else {
1312 return CLI_SHOWUSAGE;
1313 }
1314
1315 if (a->argc == 5) {
1316 file = a->argv[4];
1317 } else {
1318 if (!(buf = ast_str_create(256))) {
1319 return NULL;
1320 }
1321 ast_str_set(&buf, 0, "%s/asterisk_test_results-%ld.%s", ast_config_AST_LOG_DIR, (long) time.tv_sec, type);
1322
1324 }
1325
1326 if (isxml) {
1327 res = test_generate_results(NULL, NULL, file, NULL);
1328 } else {
1329 res = test_generate_results(NULL, NULL, NULL, file);
1330 }
1331
1332 if (!res) {
1333 ast_cli(a->fd, "Results Generated Successfully: %s\n", S_OR(file, ""));
1334 } else {
1335 ast_cli(a->fd, "Results Could Not Be Generated: %s\n", S_OR(file, ""));
1336 }
1337
1338 ast_free(buf);
1339 default:
1340 return NULL;
1341 }
1342
1343 return CLI_SUCCESS;
1344}
1345
1346static struct ast_cli_entry test_cli[] = {
1347 AST_CLI_DEFINE(test_cli_show_registered, "show registered tests"),
1348 AST_CLI_DEFINE(test_cli_execute_registered, "execute registered tests"),
1349 AST_CLI_DEFINE(test_cli_show_results, "show last test results"),
1350 AST_CLI_DEFINE(test_cli_generate_results, "generate test results to file"),
1351};
1352
1353struct stasis_topic *ast_test_suite_topic(void)
1354{
1355 return test_suite_topic;
1356}
1357
1358/*!
1359 * \since 12
1360 * \brief A wrapper object that can be ao2 ref counted around an \ref ast_json blob
1361 */
1362struct ast_test_suite_message_payload {
1363 struct ast_json *blob; /*!< The actual blob that we want to deliver */
1364};
1365
1366/*! \internal
1367 * \since 12
1368 * \brief Destructor for \ref ast_test_suite_message_payload
1369 */
1370static void test_suite_message_payload_dtor(void *obj)
1371{
1372 struct ast_test_suite_message_payload *payload = obj;
1373
1374 if (payload->blob) {
1375 ast_json_unref(payload->blob);
1376 }
1377}
1378
1379struct ast_json *ast_test_suite_get_blob(struct ast_test_suite_message_payload *payload)
1380{
1381 return payload->blob;
1382}
1383
1384static struct ast_manager_event_blob *test_suite_event_to_ami(struct stasis_message *msg)
1385{
1386 RAII_VAR(struct ast_str *, packet_string, ast_str_create(128), ast_free);
1387 struct ast_test_suite_message_payload *payload;
1388 struct ast_json *blob;
1389 const char *type;
1390
1391 payload = stasis_message_data(msg);
1392 if (!payload) {
1393 return NULL;
1394 }
1395 blob = ast_test_suite_get_blob(payload);
1396 if (!blob) {
1397 return NULL;
1398 }
1399
1401 if (ast_strlen_zero(type) || strcmp("testevent", type)) {
1402 return NULL;
1403 }
1404
1405 ast_str_append(&packet_string, 0, "Type: StateChange\r\n");
1406 ast_str_append(&packet_string, 0, "State: %s\r\n",
1408 ast_str_append(&packet_string, 0, "AppFile: %s\r\n",
1409 ast_json_string_get(ast_json_object_get(blob, "appfile")));
1410 ast_str_append(&packet_string, 0, "AppFunction: %s\r\n",
1411 ast_json_string_get(ast_json_object_get(blob, "appfunction")));
1412 ast_str_append(&packet_string, 0, "AppLine: %jd\r\n",
1414 ast_str_append(&packet_string, 0, "%s\r\n",
1416
1418 "TestEvent",
1419 "%s",
1420 ast_str_buffer(packet_string));
1421}
1422
1423/*! \since 12
1424 * \brief The message type for test suite messages
1425 */
1426STASIS_MESSAGE_TYPE_DEFN(ast_test_suite_message_type,
1427 .to_ami = test_suite_event_to_ami);
1428
1429void __ast_test_suite_event_notify(const char *file, const char *func, int line, const char *state, const char *fmt, ...)
1430{
1431 RAII_VAR(struct ast_test_suite_message_payload *, payload,
1432 NULL,
1433 ao2_cleanup);
1434 RAII_VAR(struct stasis_message *, msg, NULL, ao2_cleanup);
1435 RAII_VAR(struct ast_str *, buf, NULL, ast_free);
1436 va_list ap;
1437
1438 if (!ast_test_suite_message_type()) {
1439 return;
1440 }
1441
1442 buf = ast_str_create(128);
1443 if (!buf) {
1444 return;
1445 }
1446
1447 payload = ao2_alloc(sizeof(*payload), test_suite_message_payload_dtor);
1448 if (!payload) {
1449 return;
1450 }
1451
1452 va_start(ap, fmt);
1453 ast_str_set_va(&buf, 0, fmt, ap);
1454 va_end(ap);
1455 payload->blob = ast_json_pack("{s: s, s: s, s: s, s: s, s: i, s: s}",
1456 "type", "testevent",
1457 "state", state,
1458 "appfile", file,
1459 "appfunction", func,
1460 "line", line,
1461 "data", ast_str_buffer(buf));
1462 if (!payload->blob) {
1463 return;
1464 }
1465 msg = stasis_message_create(ast_test_suite_message_type(), payload);
1466 if (!msg) {
1467 return;
1468 }
1469 stasis_publish(ast_test_suite_topic(), msg);
1470}
1471
1472AST_TEST_DEFINE(test_registrations)
1473{
1474 switch (cmd) {
1475 case TEST_INIT:
1476 info->name = "registrations";
1477 info->category = "/main/test/";
1478 info->summary = "Validate Test Registration Data.";
1479 info->description = "Validate Test Registration Data.";
1480 return AST_TEST_NOT_RUN;
1481 case TEST_EXECUTE:
1482 break;
1483 }
1484
1485 if (registration_errors) {
1487 "%d test registration error%s occurred. See startup logs for details.\n",
1488 registration_errors, registration_errors > 1 ? "s" : "");
1489 return AST_TEST_FAIL;
1490 }
1491
1492 return AST_TEST_PASS;
1493}
1494
1495static void test_cleanup(void)
1496{
1497 AST_TEST_UNREGISTER(test_registrations);
1498 ast_cli_unregister_multiple(test_cli, ARRAY_LEN(test_cli));
1499 ao2_cleanup(test_suite_topic);
1500 test_suite_topic = NULL;
1501 STASIS_MESSAGE_TYPE_CLEANUP(ast_test_suite_message_type);
1502}
1503#endif /* TEST_FRAMEWORK */
1504
1506{
1507#ifdef TEST_FRAMEWORK
1508 ast_register_cleanup(test_cleanup);
1509
1510 /* Create stasis topic */
1511 test_suite_topic = stasis_topic_create("testsuite:all");
1512 if (!test_suite_topic) {
1513 return -1;
1514 }
1515
1516 if (STASIS_MESSAGE_TYPE_INIT(ast_test_suite_message_type) != 0) {
1517 return -1;
1518 }
1519
1520 AST_TEST_REGISTER(test_registrations);
1521
1522 /* Register cli commands */
1523 ast_cli_register_multiple(test_cli, ARRAY_LEN(test_cli));
1524#endif
1525
1526 return 0;
1527}
1528
Prototypes for public functions only of internal interest,.
jack_status_t status
Definition: app_jack.c:149
Asterisk version information.
const char * ast_get_version(void)
Retrieve the Asterisk version string.
const char * ast_get_version_num(void)
Retrieve the numeric Asterisk version.
Asterisk main include file. File version handling, generic pbx functions.
int ast_register_cleanup(void(*func)(void))
Register a function to be executed before Asterisk gracefully exits.
Definition: clicompat.c:19
void ast_std_free(void *ptr)
Definition: astmm.c:1734
#define ast_alloca(size)
call __builtin_alloca to ensure we get gcc builtin semantics
Definition: astmm.h:288
#define ast_free(a)
Definition: astmm.h:180
#define ast_strdup(str)
A wrapper for strdup()
Definition: astmm.h:241
#define ast_strdupa(s)
duplicate a string in memory from the stack
Definition: astmm.h:298
#define ast_calloc(num, len)
A wrapper for calloc()
Definition: astmm.h:202
#define ast_log
Definition: astobj2.c:42
#define ao2_cleanup(obj)
Definition: astobj2.h:1934
#define ao2_alloc(data_size, destructor_fn)
Definition: astobj2.h:409
enum cc_state state
Definition: ccss.c:399
static PGresult * result
Definition: cel_pgsql.c:84
static const char type[]
Definition: chan_ooh323.c:109
#define FORMAT
Standard Command Line Interface.
#define CLI_SHOWUSAGE
Definition: cli.h:45
#define CLI_SUCCESS
Definition: cli.h:44
int ast_cli_unregister_multiple(struct ast_cli_entry *e, int len)
Unregister multiple commands.
Definition: clicompat.c:30
#define AST_CLI_DEFINE(fn, txt,...)
Definition: cli.h:197
int ast_cli_completion_add(char *value)
Add a result to a request for completion options.
Definition: main/cli.c:2768
void ast_cli(int fd, const char *fmt,...)
Definition: clicompat.c:6
char * ast_cli_complete(const char *word, const char *const choices[], int pos)
Definition: main/cli.c:1853
@ CLI_HANDLER
Definition: cli.h:154
@ CLI_INIT
Definition: cli.h:152
@ CLI_GENERATE
Definition: cli.h:153
#define ast_cli_register_multiple(e, len)
Register multiple commands.
Definition: cli.h:265
static struct ast_cli_entry cli[]
Definition: codec_dahdi.c:265
short word
char buf[BUFSIZE]
Definition: eagi_proxy.c:66
static const char name[]
Definition: format_mp3.c:68
static struct ast_threadstorage result_buf
Definition: func_json.c:85
static SQLHSTMT execute(struct odbc_obj *obj, void *data, int silent)
Common execution function for SQL queries.
Definition: func_odbc.c:485
static char prefix[MAX_PREFIX]
Definition: http.c:144
Application convenience functions, designed to give consistent look and feel to Asterisk apps.
void ast_replace_sigchld(void)
Replace the SIGCHLD handler.
Definition: extconf.c:801
void ast_unreplace_sigchld(void)
Restore the SIGCHLD handler.
Definition: extconf.c:815
Support for logging to various files, console and syslog Configuration in file logger....
#define LOG_ERROR
#define LOG_WARNING
#define LOG_TRACE
Asterisk JSON abstraction layer.
void ast_json_unref(struct ast_json *value)
Decrease refcount on value. If refcount reaches zero, value is freed.
Definition: json.c:73
struct ast_json * ast_json_pack(char const *format,...)
Helper for creating complex JSON values.
Definition: json.c:612
const char * ast_json_string_get(const struct ast_json *string)
Get the value of a JSON string.
Definition: json.c:283
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
intmax_t ast_json_integer_get(const struct ast_json *integer)
Get the value from a JSON integer.
Definition: json.c:332
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:291
#define AST_LIST_INSERT_SORTALPHA(head, elm, field, sortfield)
Inserts a list entry into a alphabetically sorted list.
Definition: linkedlists.h:751
#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
#define AST_LIST_TRAVERSE_SAFE_END
Closes a safe loop traversal block.
Definition: linkedlists.h:615
#define AST_LIST_LOCK(head)
Locks a list.
Definition: linkedlists.h:40
#define AST_LIST_TRAVERSE_SAFE_BEGIN(head, var, field)
Loops safely over (traverses) the entries in a list.
Definition: linkedlists.h:529
#define AST_LIST_REMOVE_CURRENT(field)
Removes the current entry from a list during a traversal.
Definition: linkedlists.h:557
#define AST_LIST_UNLOCK(head)
Attempts to unlock a list.
Definition: linkedlists.h:140
#define WEXITSTATUS(status)
#define WIFEXITED(status)
int errno
#define EVENT_FLAG_REPORTING
Definition: manager.h:84
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:10237
def info(msg)
Asterisk file paths, configured in asterisk.conf.
const char * ast_config_AST_LOG_DIR
Definition: options.c:159
static void * cleanup(void *unused)
Definition: pbx_realtime.c:124
static void to_ami(struct ast_sip_subscription *sub, struct ast_str **buf)
static int cleanup_cb(void *obj, void *arg, int flags)
Definition: res_stasis.c:309
#define NULL
Definition: resample.c:96
#define FD_SET(fd, fds)
Definition: select.h:58
#define FD_ZERO(a)
Definition: select.h:49
Stasis Message Bus API. See Stasis Message Bus API for detailed documentation.
#define STASIS_MESSAGE_TYPE_CLEANUP(name)
Boiler-plate messaging macro for cleaning up message types.
Definition: stasis.h:1515
struct stasis_topic * stasis_topic_create(const char *name)
Create a new topic.
Definition: stasis.c:644
#define STASIS_MESSAGE_TYPE_INIT(name)
Boiler-plate messaging macro for initializing message types.
Definition: stasis.h:1493
void * stasis_message_data(const struct stasis_message *msg)
Get the data contained in a message.
#define STASIS_MESSAGE_TYPE_DEFN(name,...)
Boiler-plate messaging macro for defining public message types.
Definition: stasis.h:1440
struct stasis_message * stasis_message_create(struct stasis_message_type *type, void *data)
Create a new message.
void stasis_publish(struct stasis_topic *topic, struct stasis_message *message)
Publish a message to a topic's subscribers.
Definition: stasis.c:1538
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
char * ast_str_buffer(const struct ast_str *buf)
Returns the string buffer within the ast_str buf.
Definition: strings.h:761
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
#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
static force_inline int attribute_pure ast_strlen_zero(const char *s)
Definition: strings.h:65
@ AST_STRSEP_TRIM
Definition: strings.h:256
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
#define ast_str_create(init_len)
Create a malloc'ed dynamic length string.
Definition: strings.h:659
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
char * ast_strsep(char **s, const char sep, uint32_t flags)
Act like strsep but ignore separators inside quotes.
Definition: utils.c:1835
descriptor for a cli entry.
Definition: cli.h:171
char * command
Definition: cli.h:186
const char * usage
Definition: cli.h:177
Abstract JSON element (object, array, string, int, ...).
Struct containing info for an AMI event to send out.
Definition: manager.h:503
Support for dynamic strings.
Definition: strings.h:623
A capture of running an external process.
Definition: test.h:217
char * outbuf
buffer holding stdout
Definition: test.h:219
char * errbuf
buffer holding stderr
Definition: test.h:223
size_t errlen
length of buffer holding stderr
Definition: test.h:225
pid_t pid
process id of child
Definition: test.h:227
size_t outlen
length of buffer holding stdout
Definition: test.h:221
int exitcode
exit code of child
Definition: test.h:229
Contains all the initialization information required to store a new test definition.
Definition: test.h:235
const char * name
Definition: test_logger.c:44
Handy terminal functions for vt* terms.
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 COLOR_RED
Definition: term.h:52
#define COLOR_GREEN
Definition: term.h:54
int ast_test_init(void)
Definition: test.c:1505
Test Framework API.
@ TEST_INIT
Definition: test.h:200
@ TEST_EXECUTE
Definition: test.h:201
#define ast_test_debug(test, fmt,...)
Definition: test.h:130
#define AST_TEST_REGISTER(cb)
Definition: test.h:127
#define ast_test_status_update(a, b, c...)
Definition: test.h:129
#define AST_TEST_UNREGISTER(cb)
Definition: test.h:128
#define AST_TEST_DEFINE(hdr)
Definition: test.h:126
ast_test_result_state
Definition: test.h:193
@ AST_TEST_PASS
Definition: test.h:195
@ AST_TEST_FAIL
Definition: test.h:196
@ AST_TEST_NOT_RUN
Definition: test.h:194
int done
Definition: test_amihooks.c:48
#define TEST_CATEGORY
Definition: test_bridging.c:43
const char cat1[]
Definition: test_config.c:68
const char cat2[]
Definition: test_config.c:76
static struct test_val a
static struct test_listener_data * test_alloc(void)
Time-related functions and macros.
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
struct timeval ast_tvnow(void)
Returns current timeval. Meant to replace calls to gettimeofday().
Definition: time.h:159
FILE * out
Definition: utils/frame.c:33
Utility functions.
#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
#define MIN(a, b)
Definition: utils.h:231
#define ARRAY_LEN(a)
Definition: utils.h:666
#define MAX(a, b)
Definition: utils.h:233