From 5809d879b097b9915609cdafc23314eda1bb79d2 Mon Sep 17 00:00:00 2001 From: Philip Prindeville Date: Tue, 3 May 2022 12:12:10 -0600 Subject: [PATCH] test: Add test coverage for capture child process output ASTERISK-30037 #close Change-Id: I0273e85eeeb6b8e46703f24cd74d84f3daf0a69a --- configure | 14 ++ configure.ac | 10 ++ makeopts.in | 1 + tests/Makefile | 2 + tests/test_capture.c | 379 +++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 406 insertions(+) create mode 100644 tests/test_capture.c diff --git a/configure b/configure index 573f4ef3dc..168d791981 100755 --- a/configure +++ b/configure @@ -711,6 +711,7 @@ AST_RPATH AST_NATIVE_ARCH AST_SHADOW_WARNINGS AST_NO_STRINGOP_TRUNCATION +AST_NO_FORMAT_Y2K AST_NO_FORMAT_TRUNCATION AST_NO_STRICT_OVERFLOW AST_FORTIFY_SOURCE @@ -20949,6 +20950,19 @@ printf "%s\n" "no" >&6; } fi +{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for -Wno-format-y2k" >&5 +printf %s "checking for -Wno-format-y2k... " >&6; } +if $(${CC} -Wno-format-y2k -Werror -S -o /dev/null -xc /dev/null > /dev/null 2>&1); then + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: yes" >&5 +printf "%s\n" "yes" >&6; } + AST_NO_FORMAT_Y2K=-Wno-format-y2k +else + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: no" >&5 +printf "%s\n" "no" >&6; } + AST_NO_FORMAT_Y2K= +fi + + { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for -Wno-stringop-truncation" >&5 printf %s "checking for -Wno-stringop-truncation... " >&6; } if $(${CC} -Wno-stringop-truncation -Werror -S -o /dev/null -xc /dev/null > /dev/null 2>&1); then diff --git a/configure.ac b/configure.ac index 5ee72b426b..b7a206d90d 100644 --- a/configure.ac +++ b/configure.ac @@ -1398,6 +1398,16 @@ else fi AC_SUBST(AST_NO_FORMAT_TRUNCATION) +AC_MSG_CHECKING(for -Wno-format-y2k) +if $(${CC} -Wno-format-y2k -Werror -S -o /dev/null -xc /dev/null > /dev/null 2>&1); then + AC_MSG_RESULT(yes) + AST_NO_FORMAT_Y2K=-Wno-format-y2k +else + AC_MSG_RESULT(no) + AST_NO_FORMAT_Y2K= +fi +AC_SUBST(AST_NO_FORMAT_Y2K) + AC_MSG_CHECKING(for -Wno-stringop-truncation) if $(${CC} -Wno-stringop-truncation -Werror -S -o /dev/null -xc /dev/null > /dev/null 2>&1); then AC_MSG_RESULT(yes) diff --git a/makeopts.in b/makeopts.in index e99cd502a6..4f3778fa9b 100644 --- a/makeopts.in +++ b/makeopts.in @@ -120,6 +120,7 @@ AST_DECLARATION_AFTER_STATEMENT=@AST_DECLARATION_AFTER_STATEMENT@ AST_TRAMPOLINES=@AST_TRAMPOLINES@ AST_NO_STRICT_OVERFLOW=@AST_NO_STRICT_OVERFLOW@ AST_NO_FORMAT_TRUNCATION=@AST_NO_FORMAT_TRUNCATION@ +AST_NO_FORMAT_Y2K=@AST_NO_FORMAT_Y2K@ AST_NO_STRINGOP_TRUNCATION=@AST_NO_STRINGOP_TRUNCATION@ AST_SHADOW_WARNINGS=@AST_SHADOW_WARNINGS@ AST_NESTED_FUNCTIONS=@AST_NESTED_FUNCTIONS@ diff --git a/tests/Makefile b/tests/Makefile index 715c3f8593..a3fc9dc990 100644 --- a/tests/Makefile +++ b/tests/Makefile @@ -20,5 +20,7 @@ all: _all include $(ASTTOPDIR)/Makefile.moddir_rules test_astobj2.o: _ASTCFLAGS+=$(call get_menuselect_cflags,AO2_DEBUG) +# can't use '%y' in strftime() without warnings since it's not y2k compliant +test_capture.o: _ASTCFLAGS+=$(AST_NO_FORMAT_Y2K) test_strings.o: _ASTCFLAGS+=$(AST_NO_FORMAT_TRUNCATION) $(AST_NO_STRINGOP_TRUNCATION) test_voicemail_api.o: _ASTCFLAGS+=$(AST_NO_FORMAT_TRUNCATION) diff --git a/tests/test_capture.c b/tests/test_capture.c new file mode 100644 index 0000000000..33809df9ab --- /dev/null +++ b/tests/test_capture.c @@ -0,0 +1,379 @@ +/* + * Asterisk -- An open source telephony toolkit. + * + * Copyright (C) 2022, Philip Prindeville + * + * Philip Prindeville + * + * See http://www.asterisk.org for more information about + * the Asterisk project. Please do not directly contact + * any of the maintainers of this project for assistance; + * the project provides a web site, mailing lists and IRC + * channels for your use. + * + * This program is free software, distributed under the terms of + * the GNU General Public License Version 2. See the LICENSE file + * at the top of the source tree. + */ + +/*! + * \file + * \brief Make basic use of capture capability in test framework. + * + * \author\verbatim Philip Prindeville \endverbatim + * + * Exercise the capture capabilities built into the test framework so + * that external commands might be used to generate validating results + * used on corroborating tests. + * \ingroup tests + */ + +/*** MODULEINFO + TEST_FRAMEWORK + core + ***/ + +#include "asterisk.h" + +#include "asterisk/utils.h" +#include "asterisk/module.h" +#include "asterisk/test.h" + +AST_TEST_DEFINE(test_capture_true) +{ + int status = AST_TEST_FAIL; + struct ast_test_capture cap; + const char *command = "true"; + char *const args[] = { "true", NULL }; + + switch (cmd) { + case TEST_INIT: + info->name = "test_capture_true"; + info->category = "/main/test_capture/"; + info->summary = "capture true exit unit test"; + info->description = + "Capture exit code from true command."; + return AST_TEST_NOT_RUN; + case TEST_EXECUTE: + break; + } + + ast_test_status_update(test, "Executing true exit test...\n"); + + if (!ast_check_command_in_path(command)) { + ast_test_status_update(test, "couldn't find %s\n", command); + return status; + } + + if (ast_test_capture_command(&cap, command, args, NULL, 0) != 1) { + ast_test_status_update(test, "ast_test_capture_command() failed\n"); + return status; + } + + if (cap.outlen != 0) { + ast_test_status_update(test, "unexpected value for stdout\n"); + goto cleanup; + } + + if (cap.errlen != 0) { + ast_test_status_update(test, "unexpected value for stderr\n"); + goto cleanup; + } + + if (cap.pid == -1) { + ast_test_status_update(test, "invalid process id\n"); + goto cleanup; + } + + if (cap.exitcode != 0) { + ast_test_status_update(test, "child exited %d\n", cap.exitcode); + goto cleanup; + } + + status = AST_TEST_PASS; + +cleanup: + ast_test_capture_free(&cap); + + return status; +} + +AST_TEST_DEFINE(test_capture_false) +{ + int status = AST_TEST_FAIL; + struct ast_test_capture cap; + const char *command = "false"; + char *const args[] = { "false", NULL }; + + switch (cmd) { + case TEST_INIT: + info->name = "test_capture_false"; + info->category = "/main/test_capture/"; + info->summary = "capture false exit unit test"; + info->description = + "Capture exit code from false command."; + return AST_TEST_NOT_RUN; + case TEST_EXECUTE: + break; + } + + ast_test_status_update(test, "Executing false exit test...\n"); + + if (!ast_check_command_in_path(command)) { + ast_test_status_update(test, "couldn't find %s\n", command); + return status; + } + + if (ast_test_capture_command(&cap, command, args, NULL, 0) != 1) { + ast_test_status_update(test, "ast_test_capture_command() failed\n"); + return status; + } + + if (cap.outlen != 0) { + ast_test_status_update(test, "unexpected value for stdout\n"); + goto cleanup; + } + + if (cap.errlen != 0) { + ast_test_status_update(test, "unexpected value for stderr\n"); + goto cleanup; + } + + if (cap.pid == -1) { + ast_test_status_update(test, "invalid process id\n"); + goto cleanup; + } + + if (cap.exitcode != 1) { + ast_test_status_update(test, "child exited %d\n", cap.exitcode); + goto cleanup; + } + + status = AST_TEST_PASS; + +cleanup: + ast_test_capture_free(&cap); + + return status; +} + +AST_TEST_DEFINE(test_capture_with_stdin) +{ + int status = AST_TEST_FAIL; + struct ast_test_capture cap; + const char *command = "base64"; + char *const args[] = { "base64", NULL }; + const char data[] = "Mary had a little lamb."; + const unsigned datalen = sizeof(data) - 1; + const char output[] = "TWFyeSBoYWQgYSBsaXR0bGUgbGFtYi4=\n"; + const unsigned outputlen = sizeof(output) - 1; + + switch (cmd) { + case TEST_INIT: + info->name = "test_capture_with_stdin"; + info->category = "/main/test_capture/"; + info->summary = "capture with stdin unit test"; + info->description = + "Capture output from stdin transformation command."; + return AST_TEST_NOT_RUN; + case TEST_EXECUTE: + break; + } + + ast_test_status_update(test, "Executing stdin test...\n"); + + if (!ast_check_command_in_path(command)) { + ast_test_status_update(test, "couldn't find %s\n", command); + return status; + } + + if (ast_test_capture_command(&cap, command, args, data, datalen) != 1) { + ast_test_status_update(test, "ast_test_capture_command() failed\n"); + return status; + } + + if (cap.outlen != outputlen || memcmp(cap.outbuf, output, cap.outlen)) { + ast_test_status_update(test, "unexpected value for stdout\n"); + goto cleanup; + } + + if (cap.errlen != 0) { + ast_test_status_update(test, "unexpected value for stderr\n"); + goto cleanup; + } + + if (cap.pid == -1) { + ast_test_status_update(test, "invalid process id\n"); + goto cleanup; + } + + if (cap.exitcode != 0) { + ast_test_status_update(test, "child exited %d\n", cap.exitcode); + goto cleanup; + } + + status = AST_TEST_PASS; + +cleanup: + ast_test_capture_free(&cap); + + return status; +} + +AST_TEST_DEFINE(test_capture_with_dynamic) +{ + int status = AST_TEST_FAIL; + struct ast_test_capture cap; + const char *command = "date"; + char *args[] = { "date", "DATE", "FORMAT", NULL }; + char date[40]; + const char format[] = "+%a, %d %b %y %T %z"; + const char format2[] = "%a, %d %b %y %T %z\n"; + char myresult[64]; + unsigned myresultlen; + time_t now; + struct tm *tm; + + switch (cmd) { + case TEST_INIT: + info->name = "test_capture_with_dynamic"; + info->category = "/main/test_capture/"; + info->summary = "capture with dynamic argument unit test"; + info->description = + "Capture output from dynamic transformation command."; + return AST_TEST_NOT_RUN; + case TEST_EXECUTE: + break; + } + + ast_test_status_update(test, "Executing dynamic argument test...\n"); + + if (!ast_check_command_in_path(command)) { + ast_test_status_update(test, "couldn't find %s\n", command); + return status; + } + + time(&now); + snprintf(date, sizeof(date), "--date=@%lu", now); + + tm = localtime(&now); + strftime(myresult, sizeof(myresult), format2, tm); + myresultlen = strlen(myresult); + + args[1] = date; + args[2] = (char *)format; + + if (ast_test_capture_command(&cap, command, args, NULL, 0) != 1) { + ast_test_status_update(test, "ast_test_capture_command() failed\n"); + return status; + } + + if (cap.outlen != myresultlen || memcmp(cap.outbuf, myresult, cap.outlen)) { + ast_test_status_update(test, "unexpected value for stdout\n"); + goto cleanup; + } + + if (cap.errlen != 0) { + ast_test_status_update(test, "unexpected value for stderr\n"); + goto cleanup; + } + + if (cap.pid == -1) { + ast_test_status_update(test, "invalid process id\n"); + goto cleanup; + } + + if (cap.exitcode != 0) { + ast_test_status_update(test, "child exited %d\n", cap.exitcode); + goto cleanup; + } + + status = AST_TEST_PASS; + +cleanup: + ast_test_capture_free(&cap); + + return status; +} + +AST_TEST_DEFINE(test_capture_stdout_stderr) +{ + int status = AST_TEST_FAIL; + struct ast_test_capture cap; + const char *command = "sh"; + char *const args[] = { "sh", "-c", "echo -n 'foo' >&2 ; echo -n 'zzz' >&1 ; echo -n 'bar' >&2", NULL }; + + switch (cmd) { + case TEST_INIT: + info->name = "test_capture_stdout_stderr"; + info->category = "/main/test_capture/"; + info->summary = "capture stdout & stderr unit test"; + info->description = + "Capture both stdout and stderr from shell."; + return AST_TEST_NOT_RUN; + case TEST_EXECUTE: + break; + } + + ast_test_status_update(test, "Executing stdout/stderr test...\n"); + + if (!ast_check_command_in_path(command)) { + ast_test_status_update(test, "couldn't find %s\n", command); + return status; + } + + if (ast_test_capture_command(&cap, command, args, NULL, 0) != 1) { + ast_test_status_update(test, "ast_test_capture_command() failed\n"); + return status; + } + + if (cap.outlen != 3 || memcmp(cap.outbuf, "zzz", 3)) { + ast_test_status_update(test, "unexpected value for stdout\n"); + goto cleanup; + } + + if (cap.errlen != 6 || memcmp(cap.errbuf, "foobar", 6)) { + ast_test_status_update(test, "unexpected value for stderr\n"); + goto cleanup; + } + + if (cap.pid == -1) { + ast_test_status_update(test, "invalid process id\n"); + goto cleanup; + } + + if (cap.exitcode != 0) { + ast_test_status_update(test, "child exited %d\n", cap.exitcode); + goto cleanup; + } + + status = AST_TEST_PASS; + +cleanup: + ast_test_capture_free(&cap); + + return status; +} + +static int unload_module(void) +{ + AST_TEST_UNREGISTER(test_capture_with_stdin); + AST_TEST_UNREGISTER(test_capture_with_dynamic); + AST_TEST_UNREGISTER(test_capture_stdout_stderr); + AST_TEST_UNREGISTER(test_capture_true); + AST_TEST_UNREGISTER(test_capture_false); + return 0; +} + +static int load_module(void) +{ + AST_TEST_REGISTER(test_capture_with_stdin); + AST_TEST_REGISTER(test_capture_with_dynamic); + AST_TEST_REGISTER(test_capture_stdout_stderr); + AST_TEST_REGISTER(test_capture_true); + AST_TEST_REGISTER(test_capture_false); + return AST_MODULE_LOAD_SUCCESS; +} + +AST_MODULE_INFO_STANDARD(ASTERISK_GPL_KEY, "Capture support test"); +