File size: 5,967 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 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 |
#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 <fcntl.h>
#include <errno.h>
#include <locale.h>
/* The program includes system.h before including this test, so program_name
should already be declared. We only reference it here. */
static char *read_all_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 == cap) {
size_t ncap = cap * 2;
char *nbuf = (char *)realloc(buf, ncap);
if (!nbuf) { free(buf); return NULL; }
buf = nbuf;
cap = ncap;
}
ssize_t n = read(fd, buf + len, cap - len);
if (n > 0) {
len += (size_t)n;
continue;
} else if (n == 0) {
break;
} else {
if (errno == EINTR) continue;
free(buf);
return NULL;
}
}
/* NUL-terminate for convenience */
if (len == cap) {
char *nbuf = (char *)realloc(buf, cap + 1);
if (!nbuf) { free(buf); return NULL; }
buf = nbuf;
}
buf[len] = '\0';
if (out_len) *out_len = len;
return buf;
}
/* Helper to run usage(status) in a child, capturing stdout/stderr and exitcode. */
static int capture_usage(int status,
char **outp, size_t *outlenp,
char **errp, size_t *errlenp,
int *exitcodep)
{
int pout[2];
int perr[2];
if (pipe(pout) == -1) return -1;
if (pipe(perr) == -1) { close(pout[0]); close(pout[1]); return -1; }
fflush(stdout);
fflush(stderr);
pid_t pid = fork();
if (pid == -1) {
close(pout[0]); close(pout[1]);
close(perr[0]); close(perr[1]);
return -1;
}
if (pid == 0) {
/* Child: redirect stdout/stderr and call usage */
/* Ensure locale is predictable so gettext returns English strings. */
setlocale(LC_ALL, "C");
/* Set program_name to expected value for messages. */
extern char const *program_name;
program_name = "factor";
/* Redirect */
close(pout[0]);
close(perr[0]);
if (dup2(pout[1], STDOUT_FILENO) == -1) _exit(127);
if (dup2(perr[1], STDERR_FILENO) == -1) _exit(127);
close(pout[1]);
close(perr[1]);
/* Call function under test; it will exit(status). */
usage(status);
/* Should not reach here, but ensure child exits. */
_exit(127);
}
/* Parent */
close(pout[1]);
close(perr[1]);
char *out = read_all_fd(pout[0], outlenp);
char *err = read_all_fd(perr[0], errlenp);
close(pout[0]);
close(perr[0]);
int wstatus = 0;
if (waitpid(pid, &wstatus, 0) == -1) {
free(out); free(err);
return -1;
}
int code = 0;
if (WIFEXITED(wstatus)) code = WEXITSTATUS(wstatus);
else if (WIFSIGNALED(wstatus)) code = 128 + WTERMSIG(wstatus);
else code = -1;
if (exitcodep) *exitcodep = code;
if (outp) *outp = out; else free(out);
if (errp) *errp = err; else free(err);
return 0;
}
void setUp(void) {
/* nothing */
}
void tearDown(void) {
/* nothing */
}
static void assert_contains(const char *haystack, const char *needle)
{
TEST_ASSERT_NOT_NULL_MESSAGE(haystack, "haystack is NULL");
TEST_ASSERT_NOT_NULL_MESSAGE(needle, "needle is NULL");
TEST_ASSERT_NOT_NULL_MESSAGE(strstr(haystack, needle), "Expected substring not found");
}
void test_usage_success_outputs_usage_and_exits_zero(void)
{
char *out = NULL, *err = NULL;
size_t outlen = 0, errlen = 0;
int exitcode = -1;
int rc = capture_usage(EXIT_SUCCESS, &out, &outlen, &err, &errlen, &exitcode);
TEST_ASSERT_EQUAL_INT(0, rc);
/* Validate exit code */
TEST_ASSERT_EQUAL_INT(EXIT_SUCCESS, exitcode);
/* Should write to stdout, not stderr */
TEST_ASSERT(out != NULL);
TEST_ASSERT(outlen > 0);
TEST_ASSERT(err != NULL);
TEST_ASSERT_EQUAL_UINT64(0, (unsigned long long)errlen);
/* Check for key substrings */
assert_contains(out, "Usage: factor ");
assert_contains(out, "[OPTION]");
assert_contains(out, "-h, --exponents");
free(out);
free(err);
}
void test_usage_nonzero_writes_try_help_to_stderr_and_exits_status(void)
{
char *out = NULL, *err = NULL;
size_t outlen = 0, errlen = 0;
int exitcode = -1;
int rc = capture_usage(EXIT_FAILURE, &out, &outlen, &err, &errlen, &exitcode);
TEST_ASSERT_EQUAL_INT(0, rc);
/* Exit code should match provided status */
TEST_ASSERT_EQUAL_INT(EXIT_FAILURE, exitcode);
/* Should write to stderr, not stdout */
TEST_ASSERT(out != NULL);
TEST_ASSERT_EQUAL_UINT64(0, (unsigned long long)outlen);
TEST_ASSERT(err != NULL);
TEST_ASSERT(errlen > 0);
/* Check try-help style content contains program name and --help */
assert_contains(err, "factor");
assert_contains(err, "--help");
free(out);
free(err);
}
void test_usage_propagates_arbitrary_nonzero_exit_code(void)
{
char *out = NULL, *err = NULL;
size_t outlen = 0, errlen = 0;
int exitcode = -1;
int status = 42; /* within 8-bit exit code range */
int rc = capture_usage(status, &out, &outlen, &err, &errlen, &exitcode);
TEST_ASSERT_EQUAL_INT(0, rc);
/* Exit code should be exactly the provided status */
TEST_ASSERT_EQUAL_INT(status, exitcode);
/* Nonzero status: expect message on stderr */
TEST_ASSERT(errlen > 0);
free(out);
free(err);
}
int main(void)
{
UNITY_BEGIN();
RUN_TEST(test_usage_success_outputs_usage_and_exits_zero);
RUN_TEST(test_usage_nonzero_writes_try_help_to_stderr_and_exits_status);
RUN_TEST(test_usage_propagates_arbitrary_nonzero_exit_code);
return UNITY_END();
} |