/* * Utility functions for tests that use subprocesses. * * Provides utility functions for subprocess manipulation. Specifically, * provides a function, run_setup, which runs a command and bails if it fails, * using its error message as the bail output, and is_function_output, which * runs a function in a subprocess and checks its output and exit status * against expected values. * * The canonical version of this file is maintained in the rra-c-util package, * which can be found at . * * Written by Russ Allbery * Copyright 2002, 2004, 2005 Russ Allbery * Copyright 2009, 2010, 2011 * The Board of Trustees of the Leland Stanford Junior University * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. */ #include #include #include #include #include #include /* * Given a function, an expected exit status, and expected output, runs that * function in a subprocess, capturing stdout and stderr via a pipe, and * returns the function output in newly allocated memory. Also captures the * process exit status. */ static void run_child_function(test_function_type function, void *data, int *status, char **output) { int fds[2]; pid_t child; char *buf; ssize_t count, ret, buflen; int rval; /* Flush stdout before we start to avoid odd forking issues. */ fflush(stdout); /* Set up the pipe and call the function, collecting its output. */ if (pipe(fds) == -1) sysbail("can't create pipe"); child = fork(); if (child == (pid_t) -1) { sysbail("can't fork"); } else if (child == 0) { /* In child. Set up our stdout and stderr. */ close(fds[0]); if (dup2(fds[1], 1) == -1) _exit(255); if (dup2(fds[1], 2) == -1) _exit(255); /* Now, run the function and exit successfully if it returns. */ (*function)(data); fflush(stdout); _exit(0); } else { /* * In the parent; close the extra file descriptor, read the output if * any, and then collect the exit status. */ close(fds[1]); buflen = BUFSIZ; buf = bmalloc(buflen); count = 0; do { ret = read(fds[0], buf + count, buflen - count - 1); if (ret > 0) count += ret; if (count >= buflen - 1) { buflen += BUFSIZ; buf = brealloc(buf, buflen); } } while (ret > 0); buf[count < 0 ? 0 : count] = '\0'; if (waitpid(child, &rval, 0) == (pid_t) -1) sysbail("waitpid failed"); close(fds[0]); } /* Store the output and return. */ *status = rval; *output = buf; } /* * Given a function, data to pass to that function, an expected exit status, * and expected output, runs that function in a subprocess, capturing stdout * and stderr via a pipe, and compare the combination of stdout and stderr * with the expected output and the exit status with the expected status. * Expects the function to always exit (not die from a signal). */ void is_function_output(test_function_type function, void *data, int status, const char *output, const char *format, ...) { char *buf, *msg; int rval; va_list args; run_child_function(function, data, &rval, &buf); /* Now, check the results against what we expected. */ va_start(args, format); bvasprintf(&msg, format, args); va_end(args); ok(WIFEXITED(rval), "%s (exited)", msg); is_int(status, WEXITSTATUS(rval), "%s (status)", msg); is_string(output, buf, "%s (output)", msg); free(buf); free(msg); } /* * A helper function for run_setup. This is a function to run an external * command, suitable for passing into run_child_function. The expected * argument must be an argv array, with argv[0] being the command to run. */ static void exec_command(void *data) { char *const *argv = data; execvp(argv[0], argv); } /* * Given a command expressed as an argv struct, with argv[0] the name or path * to the command, run that command. If it exits with a non-zero status, use * the part of its output up to the first newline as the error message when * calling bail. */ void run_setup(const char *const argv[]) { char *output, *p; int status; run_child_function(exec_command, (void *) argv, &status, &output); if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) { p = strchr(output, '\n'); if (p != NULL) *p = '\0'; bail("%s", output); } free(output); }