#include "../../unity/unity.h" #include #include #include #include #include #include #include #include #include /* 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(); }