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