#include "../../unity/unity.h" #include #include #include #include #include #include #include #include /* Unity required hooks */ void setUp(void) { /* Force a consistent locale for help text */ setenv("LC_ALL", "C", 1); setenv("LANGUAGE", "C", 1); setenv("LANG", "C", 1); } void tearDown(void) { /* nothing */ } /* Helper to read all data from fd into a malloc'd buffer; NUL-terminate. */ static int read_all_fd(int fd, char **out_buf, size_t *out_len) { size_t cap = 1024; size_t len = 0; char *buf = (char *)malloc(cap + 1); if (!buf) return -1; for (;;) { if (len == cap) { size_t ncap = cap * 2; char *nbuf = (char *)realloc(buf, ncap + 1); if (!nbuf) { free(buf); return -1; } buf = nbuf; cap = ncap; } ssize_t n = read(fd, buf + len, cap - len); if (n < 0) { if (errno == EINTR) continue; free(buf); return -1; } if (n == 0) break; len += (size_t)n; } buf[len] = '\0'; *out_buf = buf; if (out_len) *out_len = len; return 0; } /* Run usage(status_arg) in a child, capturing stdout, stderr, and exit status. Returns NULL on success, or a malloc'd error message string on failure. */ static char *run_usage_capture(int status_arg, char **out_stdout, size_t *out_stdout_len, char **out_stderr, size_t *out_stderr_len, int *out_exit_code) { int out_pipe[2] = {-1,-1}; int err_pipe[2] = {-1,-1}; if (pipe(out_pipe) < 0) { char *msg; asprintf(&msg, "pipe(out) failed: %s", strerror(errno)); return msg; } if (pipe(err_pipe) < 0) { close(out_pipe[0]); close(out_pipe[1]); char *msg; asprintf(&msg, "pipe(err) failed: %s", strerror(errno)); return msg; } 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]); char *msg; asprintf(&msg, "fork failed: %s", strerror(errno)); return msg; } if (pid == 0) { /* Child: redirect stdout/stderr */ /* Ensure locale is C for predictable output */ setenv("LC_ALL", "C", 1); setenv("LANGUAGE", "C", 1); setenv("LANG", "C", 1); /* Connect pipes */ if (dup2(out_pipe[1], STDOUT_FILENO) < 0) _exit(255); if (dup2(err_pipe[1], STDERR_FILENO) < 0) _exit(255); /* Close fds not needed in child */ close(out_pipe[0]); close(out_pipe[1]); close(err_pipe[0]); close(err_pipe[1]); /* Call the function under test; it will exit(status_arg). */ usage(status_arg); /* Should not reach here; ensure child terminates */ _exit(254); } /* Parent: close write ends, read outputs */ close(out_pipe[1]); close(err_pipe[1]); char *captured_out = NULL; char *captured_err = NULL; size_t len_out = 0, len_err = 0; int rc_out = read_all_fd(out_pipe[0], &captured_out, &len_out); int rc_err = read_all_fd(err_pipe[0], &captured_err, &len_err); close(out_pipe[0]); close(err_pipe[0]); int wstatus = 0; pid_t w = waitpid(pid, &wstatus, 0); if (w < 0) { free(captured_out); free(captured_err); char *msg; asprintf(&msg, "waitpid failed: %s", strerror(errno)); return msg; } if (rc_out != 0 || rc_err != 0) { free(captured_out); free(captured_err); char *msg; asprintf(&msg, "read_all_fd failed (out=%d, err=%d)", rc_out, rc_err); return msg; } int exit_code = -1; if (WIFEXITED(wstatus)) { exit_code = WEXITSTATUS(wstatus); } else if (WIFSIGNALED(wstatus)) { free(captured_out); free(captured_err); char *msg; asprintf(&msg, "child terminated by signal %d", WTERMSIG(wstatus)); return msg; } else { free(captured_out); free(captured_err); char *msg; asprintf(&msg, "child did not exit normally"); return msg; } if (out_stdout) *out_stdout = captured_out; else free(captured_out); if (out_stderr) *out_stderr = captured_err; else free(captured_err); if (out_stdout_len) *out_stdout_len = len_out; if (out_stderr_len) *out_stderr_len = len_err; if (out_exit_code) *out_exit_code = exit_code; return NULL; } static int str_contains(const char *haystack, const char *needle) { if (!haystack || !needle) return 0; return strstr(haystack, needle) != NULL; } /* Tests */ void test_usage_success_outputs_help(void) { char *out = NULL, *err = NULL; size_t out_len = 0, err_len = 0; int exit_code = -1; char *msg = run_usage_capture(0, &out, &out_len, &err, &err_len, &exit_code); TEST_ASSERT_NULL_MESSAGE(msg, msg ? msg : ""); /* Exit status must be 0 */ TEST_ASSERT_EQUAL_INT(0, exit_code); /* Full help should go to stdout, not stderr */ TEST_ASSERT_NOT_NULL(out); TEST_ASSERT_TRUE(out_len > 0); TEST_ASSERT_NOT_NULL(err); TEST_ASSERT_EQUAL_UINT64(0, (unsigned long long)err_len); /* Check for key substrings in help text */ TEST_ASSERT_TRUE_MESSAGE(str_contains(out, "Usage:"), "Help text missing 'Usage:'"); TEST_ASSERT_TRUE_MESSAGE(str_contains(out, "Create named pipes"), "Help text missing description line"); TEST_ASSERT_TRUE_MESSAGE(str_contains(out, "-m, --mode=MODE"), "Help text missing mode option"); TEST_ASSERT_TRUE_MESSAGE(str_contains(out, "--context"), "Help text missing context option"); TEST_ASSERT_TRUE_MESSAGE(str_contains(out, "-Z"), "Help text missing -Z option"); free(out); free(err); } void test_usage_nonzero_shows_try_help_and_exit_status_1(void) { char *out = NULL, *err = NULL; size_t out_len = 0, err_len = 0; int exit_code = -1; char *msg = run_usage_capture(1, &out, &out_len, &err, &err_len, &exit_code); TEST_ASSERT_NULL_MESSAGE(msg, msg ? msg : ""); TEST_ASSERT_EQUAL_INT(1, exit_code); /* Expect message on stderr, none on stdout */ TEST_ASSERT_NOT_NULL(out); TEST_ASSERT_EQUAL_UINT64(0, (unsigned long long)out_len); TEST_ASSERT_NOT_NULL(err); TEST_ASSERT_TRUE(err_len > 0); TEST_ASSERT_TRUE_MESSAGE(str_contains(err, "Try"), "stderr missing 'Try' message"); TEST_ASSERT_TRUE_MESSAGE(str_contains(err, "--help"), "stderr missing '--help' hint"); free(out); free(err); } void test_usage_nonzero_propagates_exit_status_77(void) { char *out = NULL, *err = NULL; size_t out_len = 0, err_len = 0; int exit_code = -1; char *msg = run_usage_capture(77, &out, &out_len, &err, &err_len, &exit_code); TEST_ASSERT_NULL_MESSAGE(msg, msg ? msg : ""); TEST_ASSERT_EQUAL_INT(77, exit_code); /* Same pattern: message on stderr, stdout empty */ TEST_ASSERT_EQUAL_UINT64(0, (unsigned long long)out_len); TEST_ASSERT_TRUE(err_len > 0); TEST_ASSERT_TRUE_MESSAGE(str_contains(err, "--help"), "stderr missing '--help' hint"); free(out); free(err); } /* Unity main */ int main(void) { UNITY_BEGIN(); RUN_TEST(test_usage_success_outputs_help); RUN_TEST(test_usage_nonzero_shows_try_help_and_exit_status_1); RUN_TEST(test_usage_nonzero_propagates_exit_status_77); return UNITY_END(); }