diff --git a/tests/common/Makefile.in b/tests/common/Makefile.in index 2c2894d25b..f9cdcb03cd 100644 --- a/tests/common/Makefile.in +++ b/tests/common/Makefile.in @@ -6,7 +6,8 @@ include @TOP_OBJDIR@/src/config/Makefile.libtool MODULE_CFLAGS=-I$(TOP_OBJDIR) -LT_objs = config.lo files.lo misc.lo network.lo rxkad.lo servers.lo ubik.lo +LT_objs = config.lo exec.lo files.lo misc.lo network.lo rxkad.lo servers.lo \ + ubik.lo LT_libs = $(LIB_rfc3961) $(LIB_roken) LT_deps = $(top_builddir)/tests/tap/libafstest_tap.la \ $(top_builddir)/src/util/liboafs_util.la diff --git a/tests/common/common.h b/tests/common/common.h index 5baddfc3d8..7d43782fbd 100644 --- a/tests/common/common.h +++ b/tests/common/common.h @@ -30,6 +30,25 @@ extern char *afstest_mkdtemp(char *template); struct afsconf_dir; extern int afstest_AddDESKeyFile(struct afsconf_dir *dir); +/* exec.c */ + +struct afstest_cmdinfo { + char *command; /**< command to run (as given to e.g. system()) */ + int exit_code; /**< expected exit code */ + const char *output; /**< expected command output */ + int fd; /**< fd to read output from */ + + /* The following fields are not to be set by the caller; they are set when + * running the given command. */ + char *fd_descr; /**< string description of 'fd' (e.g. "stdout") */ + pid_t child; /**< child pid (after command has started) */ + FILE *pipe_fh; /**< pipe from child (after started) */ +}; + +extern int is_command(struct afstest_cmdinfo *cmdinfo, + const char *format, ...) + AFS_ATTRIBUTE_FORMAT(__printf__, 2, 3); + /* files.c */ extern char *afstest_src_path(char *path); diff --git a/tests/common/exec.c b/tests/common/exec.c new file mode 100644 index 0000000000..7670cd043e --- /dev/null +++ b/tests/common/exec.c @@ -0,0 +1,198 @@ +/* + * Copyright (c) 2020 Sine Nomine Associates. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR `AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/*! + * Common exec-related functions for testing programs + */ + +#include +#include + +#include +#include + +#include +#include + +#include + +#include "common.h" + +static void +afstest_command_start(struct afstest_cmdinfo *cmdinfo) +{ + int code; + int pipefd[2]; + + if (cmdinfo->fd == STDOUT_FILENO) { + cmdinfo->fd_descr = "stdout"; + } else if (cmdinfo->fd == STDERR_FILENO) { + cmdinfo->fd_descr = "stderr"; + } else { + bail("unknown exec fd %d", cmdinfo->fd); + } + + code = pipe(pipefd); + if (code < 0) { + sysbail("pipe"); + } + + cmdinfo->child = fork(); + if (cmdinfo->child < 0) { + sysbail("fork"); + } + + if (cmdinfo->child == 0) { + /* Child pid */ + char *cmd = "/bin/sh"; + char *argv[] = { "sh", "-c", cmdinfo->command, NULL }; + + /* Redirect stdout/stderr to our pipe */ + code = dup2(pipefd[1], cmdinfo->fd); + if (code < 0) { + sysdiag("dup2"); + exit(1); + } + + close(pipefd[0]); + close(pipefd[1]); + + execv(cmd, argv); + sysdiag("execv"); + exit(1); + } + + /* Parent pid */ + + close(pipefd[1]); + + cmdinfo->pipe_fh = fdopen(pipefd[0], "r"); + if (cmdinfo->pipe_fh == NULL) { + sysbail("fdopen"); + } +} + +static int +command_end_v(struct afstest_cmdinfo *cmdinfo, const char *format, va_list args) +{ + int code; + int success; + size_t buflen; + char *buf = NULL; + int nbytes; + int status = -1; + + opr_Assert(cmdinfo->child != 0); + + /* + * Read 1 byte more than the 'cmdinfo->output' string. If that's less than + * 4095 bytes, read 4095 bytes (just so we read a decent amount of data, in + * case we unexpectedly get a lot of data back). + */ + buflen = strlen(cmdinfo->output)+1; + if (buflen < 4095) { + buflen = 4095; + } + buf = calloc(1, buflen+1); + if (buf == NULL) { + sysbail("calloc"); + } + + /* + * Note that we must read the return value of fread() into something, + * because it's declared with warn_unused_result in some glibc. We don't + * actually care about the result, but pretend to care so we don't trigger + * a warning. + */ + nbytes = fread(buf, 1, buflen, cmdinfo->pipe_fh); + if (ferror(cmdinfo->pipe_fh) != 0) { + bail("error reading from %s pipe (read %d bytes)", + cmdinfo->fd_descr, nbytes); + } + fclose(cmdinfo->pipe_fh); + cmdinfo->pipe_fh = NULL; + + code = waitpid(cmdinfo->child, &status, 0); + if (code <= 0) { + sysbail("waitpid"); + } + + success = 0; + + if (WIFEXITED(status) && WEXITSTATUS(status) != cmdinfo->exit_code) { + int code = WEXITSTATUS(status); + diag("command %s:", cmdinfo->command); + diag("exit code wanted: %d", cmdinfo->exit_code); + diag(" exit code seen: %d", code); + diag(" "); + diag("%s: %s", cmdinfo->fd_descr, buf); + + } else if (WIFSIGNALED(status)) { + diag("command %s was killed by signal %d", cmdinfo->command, + WTERMSIG(status)); + + } else if (!WIFEXITED(status)) { + /* handle non-exited, non-signalled, just in case */ + diag("command %s died with weird status 0x%x", cmdinfo->command, + status); + + } else if (strcmp(cmdinfo->output, buf) != 0) { + diag("command %s:", cmdinfo->command); + diag("wanted %s: %s", cmdinfo->fd_descr, cmdinfo->output); + diag(" seen %s: %s", cmdinfo->fd_descr, buf); + + } else { + success = 1; + } + + okv(success, format, args); + + cmdinfo->child = 0; + free(buf); + buf = NULL; + + return success; +} + +/** + * Test if running the given command matches the given output and exit code. + * + * @param[in] cmdinfo Command to run, and expected output and exit code + * @param[in] format Message to print for the test (a la 'is_int' etc) + * @return whether the test succeeded + */ +int +is_command(struct afstest_cmdinfo *cmdinfo, const char *format, ...) +{ + va_list args; + int ret; + + afstest_command_start(cmdinfo); + + va_start(args, format); + ret = command_end_v(cmdinfo, format, args); + va_end(args); + + return ret; +} diff --git a/tests/volser/vos-t.c b/tests/volser/vos-t.c index bfe3ae8db9..c58a651cf0 100644 --- a/tests/volser/vos-t.c +++ b/tests/volser/vos-t.c @@ -26,11 +26,9 @@ TestListAddrs(struct ubik_client *client, char *dirname) { int code; bulkaddrs addrs; - pid_t pid; - int outpipe[2]; - int status; - char *buffer; - size_t len; + char *vos; + char *cmd = NULL; + struct afstest_cmdinfo cmdinfo; afs_uint32 addrsA[] = {0x0a000000}; afs_uint32 addrsB[] = {0x0a000001, 0x0a000002, 0x0a000003, 0x0a000004, @@ -57,30 +55,22 @@ TestListAddrs(struct ubik_client *client, char *dirname) is_int(0, code, "Second address registration succeeds"); /* Now we need to run vos ListAddrs and see what happens ... */ - if (pipe(outpipe) < 0) { - perror("pipe"); - exit(1); - } - pid = fork(); - if (pid == 0) { - char *vos; + vos = afstest_obj_path("src/volser/vos"); - dup2(outpipe[1], STDOUT_FILENO); /* Redirect stdout into pipe */ - close(outpipe[0]); - close(outpipe[1]); + cmd = afstest_asprintf("'%s' listaddrs -config '%s' " + "-noauth -noresolve", + vos, dirname); - vos = afstest_obj_path("src/volser/vos"); - execl(vos, "vos", - "listaddrs", "-config", dirname, "-noauth", "-noresolve", NULL); - exit(1); - } - close(outpipe[1]); - buffer = malloc(4096); - len = read(outpipe[0], buffer, 4096); - waitpid(pid, &status, 0); - buffer[len]='\0'; - is_string(expecting, buffer, "vos output matches"); - free(buffer); + memset(&cmdinfo, 0, sizeof(cmdinfo)); + cmdinfo.exit_code = 0; + cmdinfo.output = expecting; + cmdinfo.fd = STDOUT_FILENO; + cmdinfo.command = cmd; + + is_command(&cmdinfo, "vos output matches"); + + free(vos); + free(cmd); } int