/*
* 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
/** Newer versions of features.h needs _DEFAULT_SOURCE. */
#define _DEFAULT_SOURCE
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <signal.h>
#include <errno.h>
#include <assert.h>
#include <sys/wait.h>
#include <stdio.h>
#include <string.h>
#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 PCUT_OUTCOME_FAIL;
} else {
return PCUT_OUTCOME_PASS;
}
}
if (WIFSIGNALED(status)) {
return PCUT_OUTCOME_INTERNAL_ERROR;
}
return status;
}
/** Run the test in a forked environment and report the result.
*
* @param self_path Ignored.
* @param test Test to be run.
*/
int pcut_run_test_forking(const char *self_path, pcut_item_t *test) {
int link_stdout[2], link_stderr[2];
int rc, status, outcome;
size_t stderr_size;
PCUT_UNUSED(self_path);
before_test_start(test);
rc = pipe(link_stdout);
if (rc == -1) {
pcut_snprintf(error_message_buffer, OUTPUT_BUFFER_SIZE - 1,
"pipe() failed: %s.", strerror(rc));
pcut_report_test_done(test, PCUT_OUTCOME_INTERNAL_ERROR, error_message_buffer, NULL, NULL);
return PCUT_OUTCOME_INTERNAL_ERROR;
}
rc = pipe(link_stderr);
if (rc == -1) {
pcut_snprintf(error_message_buffer, OUTPUT_BUFFER_SIZE - 1,
"pipe() failed: %s.", strerror(rc));
pcut_report_test_done(test, PCUT_OUTCOME_INTERNAL_ERROR, error_message_buffer, NULL, NULL);
return PCUT_OUTCOME_INTERNAL_ERROR;
}
child_pid = fork();
if (child_pid == (pid_t)-1) {
pcut_snprintf(error_message_buffer, OUTPUT_BUFFER_SIZE - 1,
"fork() failed: %s.", strerror(rc));
outcome = PCUT_OUTCOME_INTERNAL_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]);
outcome = pcut_run_test_forked(test);
exit(outcome);
}
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);
outcome = 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, outcome, extra_output_buffer, OUTPUT_BUFFER_SIZE);
return outcome;
}
void pcut_hook_before_test(pcut_item_t *test) {
PCUT_UNUSED(test);
/* Do nothing. */
}