File size: 4,847 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 |
#include "../../unity/unity.h"
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <fcntl.h>
#include <errno.h>
#include <signal.h>
/* Unity hooks */
void setUp(void) {
/* No setup needed */
}
void tearDown(void) {
/* No teardown needed */
}
/* Helper to read all data from an fd into a NUL-terminated buffer.
The returned buffer must be free()'d by the caller. */
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 *nb = (char*)realloc(buf, new_cap);
if (!nb) {
free(buf);
return NULL;
}
buf = nb;
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 stdout/stderr and exit code. */
struct usage_run_result {
int exit_code; /* child exit status (0-255 if exited normally, else 128+signal) */
char *out_buf; /* captured stdout (malloc'd) */
size_t out_len;
char *err_buf; /* captured stderr (malloc'd) */
size_t err_len;
int ok; /* 1 on success, 0 on setup error */
};
static struct usage_run_result run_usage_and_capture(int status_code) {
struct usage_run_result res;
memset(&res, 0, sizeof(res));
res.exit_code = -1;
res.ok = 0;
int out_pipe[2] = {-1, -1};
int err_pipe[2] = {-1, -1};
if (pipe(out_pipe) != 0) return res;
if (pipe(err_pipe) != 0) { close(out_pipe[0]); close(out_pipe[1]); return res; }
fflush(stdout);
fflush(stderr);
pid_t pid = fork();
if (pid < 0) {
close(out_pipe[0]); close(out_pipe[1]);
close(err_pipe[0]); close(err_pipe[1]);
return res;
}
if (pid == 0) {
/* Child: redirect stdout/stderr and call usage */
/* Close read ends */
(void)close(out_pipe[0]);
(void)close(err_pipe[0]);
/* Redirect stdout/stderr */
(void)dup2(out_pipe[1], STDOUT_FILENO);
(void)dup2(err_pipe[1], STDERR_FILENO);
/* Close the duplicated write ends */
(void)close(out_pipe[1]);
(void)close(err_pipe[1]);
/* Call the function under test; it should exit(). */
usage(status_code);
/* If it returns unexpectedly, exit with a distinctive code. */
_exit(127);
}
/* Parent */
(void)close(out_pipe[1]);
(void)close(err_pipe[1]);
/* Read all output */
res.out_buf = read_all_from_fd(out_pipe[0], &res.out_len);
res.err_buf = read_all_from_fd(err_pipe[0], &res.err_len);
(void)close(out_pipe[0]);
(void)close(err_pipe[0]);
int wstatus = 0;
pid_t w = waitpid(pid, &wstatus, 0);
if (w < 0) {
/* wait failed */
res.ok = 0;
return res;
}
if (WIFEXITED(wstatus)) {
res.exit_code = WEXITSTATUS(wstatus);
} else if (WIFSIGNALED(wstatus)) {
res.exit_code = 128 + WTERMSIG(wstatus);
} else {
res.exit_code = -2; /* unknown */
}
res.ok = 1;
return res;
}
/* Tests */
static void free_usage_result(struct usage_run_result *r) {
if (!r) return;
free(r->out_buf);
free(r->err_buf);
r->out_buf = NULL;
r->err_buf = NULL;
}
void test_usage_success_exits_zero_and_writes_to_stdout(void) {
struct usage_run_result r = run_usage_and_capture(EXIT_SUCCESS);
TEST_ASSERT_MESSAGE(r.ok, "Failed to set up or run usage()");
TEST_ASSERT_EQUAL_INT_MESSAGE(0, r.exit_code, "usage(EXIT_SUCCESS) should exit with status 0");
/* Expect some help/usage text on stdout */
TEST_ASSERT_TRUE_MESSAGE(r.out_len > 0, "Expected non-empty stdout for EXIT_SUCCESS");
/* We don't assert on stderr content due to localization; it may contain ancillary notes */
free_usage_result(&r);
}
void test_usage_failure_exits_failure_and_writes_try_help_to_stderr(void) {
struct usage_run_result r = run_usage_and_capture(EXIT_FAILURE);
TEST_ASSERT_MESSAGE(r.ok, "Failed to set up or run usage()");
TEST_ASSERT_EQUAL_INT_MESSAGE(EXIT_FAILURE, r.exit_code, "usage(EXIT_FAILURE) should exit with EXIT_FAILURE");
/* For failure, usage() is expected to not print the full usage to stdout. */
TEST_ASSERT_TRUE_MESSAGE(r.out_len == 0, "Expected empty stdout for non-success status");
/* Help hint should go to stderr; assert non-empty */
TEST_ASSERT_TRUE_MESSAGE(r.err_len > 0, "Expected non-empty stderr for non-success status");
free_usage_result(&r);
}
int main(void) {
UNITY_BEGIN();
RUN_TEST(test_usage_success_exits_zero_and_writes_to_stdout);
RUN_TEST(test_usage_failure_exits_failure_and_writes_try_help_to_stderr);
return UNITY_END();
} |