Asterisk - The Open Source Telephony Project GIT-master-7e7a603
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
1016static 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
1034static 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)) {
1043 break;
1044 }
1045 }
1046 }
1047 AST_LIST_UNLOCK(&tests);
1048
1049 return NULL;
1050}
1051
1052/* CLI commands */
1053static 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
1115static 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
1180static 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
1251static 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
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
1323static 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
1330struct 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 */
1339struct 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 */
1347static 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
1356struct ast_json *ast_test_suite_get_blob(struct ast_test_suite_message_payload *payload)
1357{
1358 return payload->blob;
1359}
1360
1361static 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
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",
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",
1391 ast_str_append(&packet_string, 0, "%s\r\n",
1393
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 */
1403STASIS_MESSAGE_TYPE_DEFN(ast_test_suite_message_type,
1404 .to_ami = test_suite_event_to_ami);
1405
1406void __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
1449AST_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) {
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
1472static 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
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
Prototypes for public functions only of internal interest,.
jack_status_t status
Definition: app_jack.c:146
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:393
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:2756
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:1841
@ 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:471
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:10548
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:617
#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:1511
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:502
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
Definition: search.h:40
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:1482
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