/* * Copyright (c) 2013 Vojtech Horky * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * - Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * - 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. * - The name of the author may not be used to endorse or promote products * derived from this software without specific prior written permission. * * 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. */ /** @file * * Unix-specific functions for test execution via the fork() system call. */ /** We need _POSIX_SOURCE because of kill(). */ #define _POSIX_SOURCE /** We need _BSD_SOURCE because of snprintf() when compiling under C89. */ #define _BSD_SOURCE #include #include #include #include #include #include #include #include #include #include "../internal.h" /** Maximum size of stdout we are able to capture. */ #define OUTPUT_BUFFER_SIZE 8192 /** Buffer for assertion and other error messages. */ static char error_message_buffer[OUTPUT_BUFFER_SIZE]; /** Buffer for stdout from the test. */ static char extra_output_buffer[OUTPUT_BUFFER_SIZE]; /** Prepare for a new test. * * @param test Test that is about to be run. */ static void before_test_start(pcut_item_t *test) { pcut_report_test_start(test); memset(error_message_buffer, 0, OUTPUT_BUFFER_SIZE); memset(extra_output_buffer, 0, OUTPUT_BUFFER_SIZE); } /** PID of the forked process running the actual test. */ static pid_t child_pid; /** Signal handler that kills the child. * * @param sig Signal number. */ static void kill_child_on_alarm(int sig) { PCUT_UNUSED(sig); kill(child_pid, SIGKILL); } /** Read full buffer from given file descriptor. * * This function exists to overcome the possibility that read() may * not fill the full length of the provided buffer even when EOF is * not reached. * * @param fd Opened file descriptor. * @param buffer Buffer to store data into. * @param buffer_size Size of the @p buffer in bytes. * @return Number of actually read bytes. */ static size_t read_all(int fd, char *buffer, size_t buffer_size) { ssize_t actually_read; char *buffer_start = buffer; do { actually_read = read(fd, buffer, buffer_size); if (actually_read > 0) { buffer += actually_read; buffer_size -= actually_read; if (buffer_size == 0) { break; } } } while (actually_read > 0); if (buffer_start != buffer) { if (*(buffer - 1) == 10) { *(buffer - 1) = 0; buffer--; } } return buffer - buffer_start; } /** Convert program exit code to test outcome. * * @param status Status value from the wait() function. * @return Test outcome code. */ static int convert_wait_status_to_outcome(int status) { if (WIFEXITED(status)) { if (WEXITSTATUS(status) != 0) { return TEST_OUTCOME_FAIL; } else { return TEST_OUTCOME_PASS; } } if (WIFSIGNALED(status)) { return TEST_OUTCOME_ERROR; } return status; } /** Run the test in a forked environment and report the result. * * @param self_path Ignored. * @param test Test to be run. */ void pcut_run_test_forking(const char *self_path, pcut_item_t *test) { int link_stdout[2], link_stderr[2]; int rc, status; size_t stderr_size; PCUT_UNUSED(self_path); before_test_start(test); rc = pipe(link_stdout); if (rc == -1) { snprintf(error_message_buffer, OUTPUT_BUFFER_SIZE - 1, "pipe() failed: %s.", strerror(rc)); pcut_report_test_done(test, TEST_OUTCOME_ERROR, error_message_buffer, NULL, NULL); return; } rc = pipe(link_stderr); if (rc == -1) { snprintf(error_message_buffer, OUTPUT_BUFFER_SIZE - 1, "pipe() failed: %s.", strerror(rc)); pcut_report_test_done(test, TEST_OUTCOME_ERROR, error_message_buffer, NULL, NULL); return; } child_pid = fork(); if (child_pid == (pid_t)-1) { snprintf(error_message_buffer, OUTPUT_BUFFER_SIZE - 1, "fork() failed: %s.", strerror(rc)); rc = TEST_OUTCOME_ERROR; goto leave_close_pipes; } if (child_pid == 0) { /* We are the child. */ dup2(link_stdout[1], STDOUT_FILENO); close(link_stdout[0]); dup2(link_stderr[1], STDERR_FILENO); close(link_stderr[0]); rc = pcut_run_test_forked(test); exit(rc); } close(link_stdout[1]); close(link_stderr[1]); signal(SIGALRM, kill_child_on_alarm); alarm(pcut_get_test_timeout(test)); stderr_size = read_all(link_stderr[0], extra_output_buffer, OUTPUT_BUFFER_SIZE - 1); read_all(link_stdout[0], extra_output_buffer, OUTPUT_BUFFER_SIZE - 1 - stderr_size); wait(&status); alarm(0); rc = convert_wait_status_to_outcome(status); goto leave_close_parent_pipe; leave_close_pipes: close(link_stdout[1]); close(link_stderr[1]); leave_close_parent_pipe: close(link_stdout[0]); close(link_stderr[0]); pcut_report_test_done_unparsed(test, rc, extra_output_buffer, OUTPUT_BUFFER_SIZE); } void pcut_hook_before_test(pcut_item_t *test) { PCUT_UNUSED(test); /* Do nothing. */ }