mirror of
https://github.com/asterisk/asterisk.git
synced 2025-09-05 12:16:00 +00:00
test: Add ability to capture child process output
ASTERISK-30037 Change-Id: Icbf84ce05addb197a458361c35d784e460d8d6c2
This commit is contained in:
committed by
George Joseph
parent
d13afaf302
commit
b9df2c481b
@@ -208,6 +208,27 @@ enum ast_test_command {
|
|||||||
*/
|
*/
|
||||||
struct ast_test;
|
struct ast_test;
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief A capture of running an external process.
|
||||||
|
*
|
||||||
|
* This contains a buffer holding stdout, another containing stderr,
|
||||||
|
* the process id of the child, and its exit code.
|
||||||
|
*/
|
||||||
|
struct ast_test_capture {
|
||||||
|
/*! \brief buffer holding stdout */
|
||||||
|
char *outbuf;
|
||||||
|
/*! \brief length of buffer holding stdout */
|
||||||
|
size_t outlen;
|
||||||
|
/*! \brief buffer holding stderr */
|
||||||
|
char *errbuf;
|
||||||
|
/*! \brief length of buffer holding stderr */
|
||||||
|
size_t errlen;
|
||||||
|
/*! \brief process id of child */
|
||||||
|
pid_t pid;
|
||||||
|
/*! \brief exit code of child */
|
||||||
|
int exitcode;
|
||||||
|
};
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
* \brief Contains all the initialization information required to store a new test definition
|
* \brief Contains all the initialization information required to store a new test definition
|
||||||
*/
|
*/
|
||||||
@@ -417,5 +438,40 @@ int __ast_test_status_update(const char *file, const char *func, int line, struc
|
|||||||
} \
|
} \
|
||||||
})
|
})
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief Release the storage (buffers) associated with capturing
|
||||||
|
* the output of an external child process.
|
||||||
|
*
|
||||||
|
* \since 19.4.0
|
||||||
|
*
|
||||||
|
* \param capture The structure describing the child process and its
|
||||||
|
* associated output.
|
||||||
|
*/
|
||||||
|
void ast_test_capture_free(struct ast_test_capture *capture);
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief Run a child process and capture its output and exit code.
|
||||||
|
*
|
||||||
|
* \!since 19.4.0
|
||||||
|
*
|
||||||
|
* \param capture The structure describing the child process and its
|
||||||
|
* associated output.
|
||||||
|
*
|
||||||
|
* \param file The name of the file to execute (uses $PATH to locate).
|
||||||
|
*
|
||||||
|
* \param argv The NULL-terminated array of arguments to pass to the
|
||||||
|
* child process, starting with the command name itself.
|
||||||
|
*
|
||||||
|
* \param data The buffer of input to be sent to child process's stdin;
|
||||||
|
* optional and may be NULL.
|
||||||
|
*
|
||||||
|
* \param datalen The length of the buffer, if not NULL, otherwise zero.
|
||||||
|
*
|
||||||
|
* \retval 1 for success
|
||||||
|
* \retval other failure
|
||||||
|
*/
|
||||||
|
|
||||||
|
int ast_test_capture_command(struct ast_test_capture *capture, const char *file, char *const argv[], const char *data, unsigned datalen);
|
||||||
|
|
||||||
#endif /* TEST_FRAMEWORK */
|
#endif /* TEST_FRAMEWORK */
|
||||||
#endif /* _AST_TEST_H */
|
#endif /* _AST_TEST_H */
|
||||||
|
@@ -167,6 +167,9 @@ lock.o: _ASTCFLAGS+=$(call get_menuselect_cflags,DETECT_DEADLOCKS)
|
|||||||
options.o: _ASTCFLAGS+=$(call get_menuselect_cflags,REF_DEBUG)
|
options.o: _ASTCFLAGS+=$(call get_menuselect_cflags,REF_DEBUG)
|
||||||
sched.o: _ASTCFLAGS+=$(call get_menuselect_cflags,DEBUG_SCHEDULER DUMP_SCHEDULER)
|
sched.o: _ASTCFLAGS+=$(call get_menuselect_cflags,DEBUG_SCHEDULER DUMP_SCHEDULER)
|
||||||
tcptls.o: _ASTCFLAGS+=$(OPENSSL_INCLUDE) -Wno-deprecated-declarations
|
tcptls.o: _ASTCFLAGS+=$(OPENSSL_INCLUDE) -Wno-deprecated-declarations
|
||||||
|
# since we're using open_memstream(), we need to release the buffer with
|
||||||
|
# the native free() function or we might get unexpected behavior.
|
||||||
|
test.o: _ASTCFLAGS+=-DASTMM_LIBC=ASTMM_IGNORE
|
||||||
uuid.o: _ASTCFLAGS+=$(UUID_INCLUDE)
|
uuid.o: _ASTCFLAGS+=$(UUID_INCLUDE)
|
||||||
stasis.o: _ASTCFLAGS+=$(call get_menuselect_cflags,AO2_DEBUG)
|
stasis.o: _ASTCFLAGS+=$(call get_menuselect_cflags,AO2_DEBUG)
|
||||||
time.o: _ASTCFLAGS+=-D_XOPEN_SOURCE=700
|
time.o: _ASTCFLAGS+=-D_XOPEN_SOURCE=700
|
||||||
|
248
main/test.c
248
main/test.c
@@ -48,6 +48,16 @@
|
|||||||
#include "asterisk/astobj2.h"
|
#include "asterisk/astobj2.h"
|
||||||
#include "asterisk/stasis.h"
|
#include "asterisk/stasis.h"
|
||||||
#include "asterisk/json.h"
|
#include "asterisk/json.h"
|
||||||
|
#include "asterisk/app.h" /* for ast_replace_sigchld(), etc. */
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <errno.h>
|
||||||
|
#include <fcntl.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <sys/types.h>
|
||||||
|
#include <sys/stat.h>
|
||||||
|
#include <sys/wait.h>
|
||||||
|
#include <signal.h>
|
||||||
|
|
||||||
/*! \since 12
|
/*! \since 12
|
||||||
* \brief The topic for test suite messages
|
* \brief The topic for test suite messages
|
||||||
@@ -100,6 +110,42 @@ enum test_mode {
|
|||||||
TEST_NAME_CATEGORY = 2,
|
TEST_NAME_CATEGORY = 2,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
#define zfclose(fp) \
|
||||||
|
({ if (fp != NULL) { \
|
||||||
|
fclose(fp); \
|
||||||
|
fp = NULL; \
|
||||||
|
} \
|
||||||
|
(void)0; \
|
||||||
|
})
|
||||||
|
|
||||||
|
#define zclose(fd) \
|
||||||
|
({ if (fd != -1) { \
|
||||||
|
close(fd); \
|
||||||
|
fd = -1; \
|
||||||
|
} \
|
||||||
|
(void)0; \
|
||||||
|
})
|
||||||
|
|
||||||
|
#define movefd(oldfd, newfd) \
|
||||||
|
({ if (oldfd != newfd) { \
|
||||||
|
dup2(oldfd, newfd); \
|
||||||
|
close(oldfd); \
|
||||||
|
oldfd = -1; \
|
||||||
|
} \
|
||||||
|
(void)0; \
|
||||||
|
})
|
||||||
|
|
||||||
|
#define lowerfd(oldfd) \
|
||||||
|
({ int newfd = dup(oldfd); \
|
||||||
|
if (newfd > oldfd) \
|
||||||
|
close(newfd); \
|
||||||
|
else { \
|
||||||
|
close(oldfd); \
|
||||||
|
oldfd = newfd; \
|
||||||
|
} \
|
||||||
|
(void)0; \
|
||||||
|
})
|
||||||
|
|
||||||
/*! List of registered test definitions */
|
/*! List of registered test definitions */
|
||||||
static AST_LIST_HEAD_STATIC(tests, ast_test);
|
static AST_LIST_HEAD_STATIC(tests, ast_test);
|
||||||
|
|
||||||
@@ -267,6 +313,207 @@ void ast_test_set_result(struct ast_test *test, enum ast_test_result_state state
|
|||||||
test->state = state;
|
test->state = state;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ast_test_capture_free(struct ast_test_capture *capture)
|
||||||
|
{
|
||||||
|
if (capture) {
|
||||||
|
free(capture->outbuf);
|
||||||
|
capture->outbuf = NULL;
|
||||||
|
free(capture->errbuf);
|
||||||
|
capture->errbuf = NULL;
|
||||||
|
}
|
||||||
|
capture->pid = -1;
|
||||||
|
capture->exitcode = -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int ast_test_capture_command(struct ast_test_capture *capture, const char *file, char *const argv[], const char *data, unsigned datalen)
|
||||||
|
{
|
||||||
|
int fd0[2] = { -1, -1 }, fd1[2] = { -1, -1 }, fd2[2] = { -1, -1 };
|
||||||
|
pid_t pid = -1;
|
||||||
|
int status = 0;
|
||||||
|
|
||||||
|
memset(capture, 0, sizeof(*capture));
|
||||||
|
capture->pid = capture->exitcode = -1;
|
||||||
|
|
||||||
|
if (data != NULL && datalen > 0) {
|
||||||
|
if (pipe(fd0) == -1) {
|
||||||
|
ast_log(LOG_ERROR, "Couldn't open stdin pipe: %s\n", strerror(errno));
|
||||||
|
goto cleanup;
|
||||||
|
}
|
||||||
|
fcntl(fd0[1], F_SETFL, fcntl(fd0[1], F_GETFL, 0) | O_NONBLOCK);
|
||||||
|
} else {
|
||||||
|
if ((fd0[0] = open("/dev/null", O_RDONLY)) == -1) {
|
||||||
|
ast_log(LOG_ERROR, "Couldn't open /dev/null: %s\n", strerror(errno));
|
||||||
|
goto cleanup;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pipe(fd1) == -1) {
|
||||||
|
ast_log(LOG_ERROR, "Couldn't open stdout pipe: %s\n", strerror(errno));
|
||||||
|
goto cleanup;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pipe(fd2) == -1) {
|
||||||
|
ast_log(LOG_ERROR, "Couldn't open stdout pipe: %s\n", strerror(errno));
|
||||||
|
goto cleanup;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* we don't want anyone else reaping our children */
|
||||||
|
ast_replace_sigchld();
|
||||||
|
|
||||||
|
if ((pid = fork()) == -1) {
|
||||||
|
ast_log(LOG_ERROR, "Failed to fork(): %s\n", strerror(errno));
|
||||||
|
goto cleanup;
|
||||||
|
|
||||||
|
} else if (pid == 0) {
|
||||||
|
fclose(stdin);
|
||||||
|
zclose(fd0[1]);
|
||||||
|
zclose(fd1[0]);
|
||||||
|
zclose(fd2[0]);
|
||||||
|
|
||||||
|
movefd(fd0[0], 0);
|
||||||
|
movefd(fd1[1], 1);
|
||||||
|
movefd(fd2[1], 2);
|
||||||
|
|
||||||
|
execvp(file, argv);
|
||||||
|
ast_log(LOG_ERROR, "Failed to execv(): %s\n", strerror(errno));
|
||||||
|
exit(1);
|
||||||
|
|
||||||
|
} else {
|
||||||
|
FILE *cmd = NULL, *out = NULL, *err = NULL;
|
||||||
|
|
||||||
|
char buf[BUFSIZ];
|
||||||
|
int wstatus, n, nfds;
|
||||||
|
fd_set readfds, writefds;
|
||||||
|
unsigned i;
|
||||||
|
|
||||||
|
zclose(fd0[0]);
|
||||||
|
zclose(fd1[1]);
|
||||||
|
zclose(fd2[1]);
|
||||||
|
|
||||||
|
lowerfd(fd0[1]);
|
||||||
|
lowerfd(fd1[0]);
|
||||||
|
lowerfd(fd2[0]);
|
||||||
|
|
||||||
|
if ((cmd = fmemopen(buf, sizeof(buf), "w")) == NULL) {
|
||||||
|
ast_log(LOG_ERROR, "Failed to open memory buffer: %s\n", strerror(errno));
|
||||||
|
kill(pid, SIGKILL);
|
||||||
|
goto cleanup;
|
||||||
|
}
|
||||||
|
for (i = 0; argv[i] != NULL; ++i) {
|
||||||
|
if (i > 0) {
|
||||||
|
fputc(' ', cmd);
|
||||||
|
}
|
||||||
|
fputs(argv[i], cmd);
|
||||||
|
}
|
||||||
|
zfclose(cmd);
|
||||||
|
|
||||||
|
ast_log(LOG_TRACE, "run: %.*s\n", (int)sizeof(buf), buf);
|
||||||
|
|
||||||
|
if ((out = open_memstream(&capture->outbuf, &capture->outlen)) == NULL) {
|
||||||
|
ast_log(LOG_ERROR, "Failed to open output buffer: %s\n", strerror(errno));
|
||||||
|
kill(pid, SIGKILL);
|
||||||
|
goto cleanup;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((err = open_memstream(&capture->errbuf, &capture->errlen)) == NULL) {
|
||||||
|
ast_log(LOG_ERROR, "Failed to open error buffer: %s\n", strerror(errno));
|
||||||
|
kill(pid, SIGKILL);
|
||||||
|
goto cleanup;
|
||||||
|
}
|
||||||
|
|
||||||
|
while (1) {
|
||||||
|
n = waitpid(pid, &wstatus, WNOHANG);
|
||||||
|
|
||||||
|
if (n == pid && WIFEXITED(wstatus)) {
|
||||||
|
zclose(fd0[1]);
|
||||||
|
zclose(fd1[0]);
|
||||||
|
zclose(fd2[0]);
|
||||||
|
zfclose(out);
|
||||||
|
zfclose(err);
|
||||||
|
|
||||||
|
capture->pid = pid;
|
||||||
|
capture->exitcode = WEXITSTATUS(wstatus);
|
||||||
|
|
||||||
|
ast_log(LOG_TRACE, "run: pid %d exits %d\n", capture->pid, capture->exitcode);
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* a function that does the opposite of ffs()
|
||||||
|
* would be handy here for finding the highest
|
||||||
|
* descriptor number.
|
||||||
|
*/
|
||||||
|
nfds = MAX(fd0[1], MAX(fd1[0], fd2[0])) + 1;
|
||||||
|
|
||||||
|
FD_ZERO(&readfds);
|
||||||
|
FD_ZERO(&writefds);
|
||||||
|
|
||||||
|
if (fd0[1] != -1) {
|
||||||
|
if (data != NULL && datalen > 0)
|
||||||
|
FD_SET(fd0[1], &writefds);
|
||||||
|
}
|
||||||
|
if (fd1[0] != -1) {
|
||||||
|
FD_SET(fd1[0], &readfds);
|
||||||
|
}
|
||||||
|
if (fd2[0] != -1) {
|
||||||
|
FD_SET(fd2[0], &readfds);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* not clear that exception fds are meaningful
|
||||||
|
* with non-network descriptors.
|
||||||
|
*/
|
||||||
|
n = select(nfds, &readfds, &writefds, NULL, NULL);
|
||||||
|
|
||||||
|
if (FD_ISSET(fd0[1], &writefds)) {
|
||||||
|
n = write(fd0[1], data, datalen);
|
||||||
|
if (n > 0) {
|
||||||
|
data += n;
|
||||||
|
datalen -= MIN(datalen, n);
|
||||||
|
/* out of data, so close stdin */
|
||||||
|
if (datalen == 0)
|
||||||
|
zclose(fd0[1]);
|
||||||
|
} else {
|
||||||
|
zclose(fd0[1]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (FD_ISSET(fd1[0], &readfds)) {
|
||||||
|
n = read(fd1[0], buf, sizeof(buf));
|
||||||
|
if (n > 0) {
|
||||||
|
fwrite(buf, sizeof(char), n, out);
|
||||||
|
} else {
|
||||||
|
zclose(fd1[0]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (FD_ISSET(fd2[0], &readfds)) {
|
||||||
|
n = read(fd2[0], buf, sizeof(buf));
|
||||||
|
if (n > 0) {
|
||||||
|
fwrite(buf, sizeof(char), n, err);
|
||||||
|
} else {
|
||||||
|
zclose(fd2[0]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
status = 1;
|
||||||
|
|
||||||
|
cleanup:
|
||||||
|
ast_unreplace_sigchld();
|
||||||
|
|
||||||
|
zfclose(cmd);
|
||||||
|
zfclose(out);
|
||||||
|
zfclose(err);
|
||||||
|
|
||||||
|
zclose(fd0[1]);
|
||||||
|
zclose(fd1[0]);
|
||||||
|
zclose(fd1[1]);
|
||||||
|
zclose(fd2[0]);
|
||||||
|
zclose(fd2[1]);
|
||||||
|
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* These are the Java reserved words we need to munge so Jenkins
|
* These are the Java reserved words we need to munge so Jenkins
|
||||||
* doesn't barf on them.
|
* doesn't barf on them.
|
||||||
@@ -1242,3 +1489,4 @@ int ast_test_init(void)
|
|||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user