File size: 6,297 Bytes
78d2150 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 |
#include "../../unity/unity.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <errno.h>
/* usage(int) is defined in the program source.
program_name is declared in the program headers included before this file. */
typedef struct {
char *output; /* NUL-terminated captured output */
size_t length; /* number of bytes captured (not including NUL) */
int exited; /* 1 if child exited normally */
int exit_code; /* exit status if exited==1 */
int signaled; /* 1 if child terminated via signal */
int term_signal; /* signal number if signaled==1 */
} ChildResult;
static void free_child_result(ChildResult *r) {
if (!r) return;
free(r->output);
r->output = NULL;
r->length = 0;
}
/* Read all data from fd into a malloc'd NUL-terminated buffer. */
static char *read_all_from_fd(int fd, size_t *out_len) {
size_t cap = 4096;
size_t len = 0;
char *buf = (char *)malloc(cap);
if (!buf) {
return NULL;
}
for (;;) {
if (len + 2048 > cap) {
size_t new_cap = cap * 2;
char *new_buf = (char *)realloc(buf, new_cap);
if (!new_buf) {
free(buf);
return NULL;
}
buf = new_buf;
cap = new_cap;
}
ssize_t n = read(fd, buf + len, cap - len - 1);
if (n < 0) {
if (errno == EINTR) continue;
free(buf);
return NULL;
}
if (n == 0) break;
len += (size_t)n;
}
buf[len] = '\0';
if (out_len) *out_len = len;
return buf;
}
/* Run usage(status) in a child process, capturing either stdout or stderr. */
static ChildResult run_usage_and_capture(int status, int capture_stdout) {
ChildResult res;
memset(&res, 0, sizeof(res));
int pipefd[2];
if (pipe(pipefd) != 0) {
/* On failure, keep defaults; tests will fail appropriately. */
return res;
}
fflush(stdout);
fflush(stderr);
pid_t pid = fork();
if (pid == 0) {
/* Child */
/* Redirect selected stream to pipe write end. */
if (capture_stdout) {
if (dup2(pipefd[1], STDOUT_FILENO) < 0) _exit(127);
} else {
if (dup2(pipefd[1], STDERR_FILENO) < 0) _exit(127);
}
close(pipefd[0]);
close(pipefd[1]);
/* Ensure predictable English output for help text. */
if (status == EXIT_SUCCESS) {
setenv("LC_ALL", "C", 1);
/* Set program_name to expected value for substitutions in messages. */
extern char const *program_name;
program_name = "dirname";
}
/* Call the target function; it will exit(status). */
usage(status);
/* Should never reach here. */
_exit(255);
}
/* Parent */
close(pipefd[1]);
/* Read all output from child. */
res.output = read_all_from_fd(pipefd[0], &res.length);
close(pipefd[0]);
int wstatus = 0;
if (waitpid(pid, &wstatus, 0) >= 0) {
if (WIFEXITED(wstatus)) {
res.exited = 1;
res.exit_code = WEXITSTATUS(wstatus);
} else if (WIFSIGNALED(wstatus)) {
res.signaled = 1;
res.term_signal = WTERMSIG(wstatus);
}
}
return res;
}
void setUp(void) {
/* Setup code here, or leave empty */
}
void tearDown(void) {
/* Cleanup code here, or leave empty */
}
/* Test: usage with nonzero status exits with that status and writes something to stderr. */
void test_usage_nonzero_status_exits_and_writes_stderr(void) {
int status_to_test = 2;
ChildResult r = run_usage_and_capture(status_to_test, /*capture_stdout=*/0);
TEST_ASSERT_TRUE_MESSAGE(r.exited || r.signaled, "Child did not terminate properly");
TEST_ASSERT_TRUE_MESSAGE(!r.signaled, "Child terminated by signal");
TEST_ASSERT_EQUAL_INT(status_to_test, r.exit_code);
/* We expect some guidance message on stderr. Ensure it's non-empty. */
TEST_ASSERT_NOT_NULL(r.output);
TEST_ASSERT_TRUE_MESSAGE(r.length > 0, "Expected some stderr output for nonzero status");
free_child_result(&r);
}
/* Test: usage with nonzero status does not write to stdout. */
void test_usage_nonzero_status_no_stdout_output(void) {
int status_to_test = 1;
ChildResult r = run_usage_and_capture(status_to_test, /*capture_stdout=*/1);
TEST_ASSERT_TRUE_MESSAGE(r.exited || r.signaled, "Child did not terminate properly");
TEST_ASSERT_TRUE_MESSAGE(!r.signaled, "Child terminated by signal");
TEST_ASSERT_EQUAL_INT(status_to_test, r.exit_code);
/* For error usage, stdout should remain empty. */
TEST_ASSERT_NOT_NULL(r.output);
TEST_ASSERT_EQUAL_UINT64_MESSAGE(0u, (unsigned long long)r.length, "Expected no stdout output for nonzero status");
free_child_result(&r);
}
/* Test: usage with EXIT_SUCCESS prints help/usage text to stdout and exits 0. */
void test_usage_success_prints_help_and_exits_zero(void) {
ChildResult r = run_usage_and_capture(EXIT_SUCCESS, /*capture_stdout=*/1);
TEST_ASSERT_TRUE_MESSAGE(r.exited || r.signaled, "Child did not terminate properly");
TEST_ASSERT_TRUE_MESSAGE(!r.signaled, "Child terminated by signal");
TEST_ASSERT_EQUAL_INT(0, r.exit_code);
TEST_ASSERT_NOT_NULL(r.output);
/* Basic sanity checks on help content in C locale. */
TEST_ASSERT_TRUE_MESSAGE(strstr(r.output, "Usage: ") != NULL, "Help text should contain 'Usage:'");
TEST_ASSERT_TRUE_MESSAGE(strstr(r.output, "Examples:") != NULL, "Help text should contain 'Examples:'");
TEST_ASSERT_TRUE_MESSAGE(strstr(r.output, "-z, --zero") != NULL, "Help text should document the -z, --zero option");
/* Ensure program name appears somewhere (set earlier to 'dirname'). */
TEST_ASSERT_TRUE_MESSAGE(strstr(r.output, "dirname") != NULL, "Help text should mention the program name");
free_child_result(&r);
}
int main(void) {
UNITY_BEGIN();
RUN_TEST(test_usage_nonzero_status_exits_and_writes_stderr);
RUN_TEST(test_usage_nonzero_status_no_stdout_output);
RUN_TEST(test_usage_success_prints_help_and_exits_zero);
return UNITY_END();
} |