#include "../../unity/unity.h" #include #include #include #include #include #include #include #include /* Target under test: void usage (int status); set_program_name is provided by the program's included headers earlier. */ 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 == cap) { size_t ncap = cap * 2; char *nb = (char *)realloc(buf, ncap); if (!nb) { free(buf); return NULL; } buf = nb; cap = ncap; } ssize_t n = read(fd, buf + len, cap - len); if (n < 0) { if (errno == EINTR) continue; free(buf); return NULL; } if (n == 0) break; len += (size_t)n; } /* NUL-terminate for convenience, though length is authoritative. */ if (len == cap) { char *nb = (char *)realloc(buf, cap + 1); if (!nb) { free(buf); return NULL; } buf = nb; cap += 1; } buf[len] = '\0'; if (out_len) *out_len = len; return buf; } static int spawn_usage_and_capture(int status, const char *progname, char **out_stdout, size_t *len_stdout, char **out_stderr, size_t *len_stderr, int *exit_code) { int out_pipe[2]; int err_pipe[2]; if (pipe(out_pipe) != 0) return -1; if (pipe(err_pipe) != 0) { close(out_pipe[0]); close(out_pipe[1]); return -1; } 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 -1; } else if (pid == 0) { /* Child: redirect stdout/stderr to pipes, then call usage. */ /* Close read ends in child. */ close(out_pipe[0]); close(err_pipe[0]); /* Redirect stdout and stderr. */ if (dup2(out_pipe[1], STDOUT_FILENO) == -1) _exit(255); if (dup2(err_pipe[1], STDERR_FILENO) == -1) _exit(255); /* Close the duplicated fds. */ close(out_pipe[1]); close(err_pipe[1]); if (progname && *progname) set_program_name(progname); usage(status); /* Should not reach here; if we do, fail the child distinctly. */ _exit(254); } /* Parent: close write ends, read from read ends. */ close(out_pipe[1]); close(err_pipe[1]); char *captured_out = read_all_from_fd(out_pipe[0], len_stdout); char *captured_err = read_all_from_fd(err_pipe[0], len_stderr); close(out_pipe[0]); close(err_pipe[0]); int wstatus = 0; pid_t w = 0; do { w = waitpid(pid, &wstatus, 0); } while (w < 0 && errno == EINTR); if (w < 0) { free(captured_out); free(captured_err); return -1; } int ec = -1; if (WIFEXITED(wstatus)) ec = WEXITSTATUS(wstatus); else if (WIFSIGNALED(wstatus)) ec = 128 + WTERMSIG(wstatus); if (exit_code) *exit_code = ec; if (out_stdout) *out_stdout = captured_out; else free(captured_out); if (out_stderr) *out_stderr = captured_err; else free(captured_err); return 0; } static int contains_substr(const char *hay, const char *needle) { if (!hay || !needle) return 0; return strstr(hay, needle) != NULL; } void setUp(void) { /* No global setup needed. */ } void tearDown(void) { /* No global teardown needed. */ } void test_usage_success_outputs_help_to_stdout_only(void) { char *sout = NULL, *serr = NULL; size_t lout = 0, lerr = 0; int ec = -1; int rc = spawn_usage_and_capture(EXIT_SUCCESS, "env", &sout, &lout, &serr, &lerr, &ec); TEST_ASSERT_EQUAL_INT(0, rc); TEST_ASSERT_EQUAL_INT(0, ec); TEST_ASSERT_NOT_NULL(sout); TEST_ASSERT_TRUE_MESSAGE(lout > 0, "Expected non-empty stdout from usage(EXIT_SUCCESS)"); TEST_ASSERT_NOT_NULL(serr); TEST_ASSERT_EQUAL_UINT32(0u, (unsigned)lerr); free(sout); free(serr); } void test_usage_nonzero_status_emits_try_help_to_stderr_and_exits_with_code(void) { char *sout = NULL, *serr = NULL; size_t lout = 0, lerr = 0; int ec = -1; int desired_status = 3; int rc = spawn_usage_and_capture(desired_status, "env", &sout, &lout, &serr, &lerr, &ec); TEST_ASSERT_EQUAL_INT(0, rc); TEST_ASSERT_EQUAL_INT(desired_status, ec); TEST_ASSERT_NOT_NULL(sout); TEST_ASSERT_EQUAL_UINT32(0u, (unsigned)lout); TEST_ASSERT_NOT_NULL(serr); TEST_ASSERT_TRUE_MESSAGE(lerr > 0, "Expected non-empty stderr for non-success usage()"); TEST_ASSERT_TRUE_MESSAGE(contains_substr(serr, "--help"), "Expected '--help' hint in stderr output"); free(sout); free(serr); } void test_usage_uses_program_name_in_output(void) { char *sout = NULL, *serr = NULL; size_t lout = 0, lerr = 0; int ec = -1; const char *pname = "my-env-test"; int rc = spawn_usage_and_capture(EXIT_SUCCESS, pname, &sout, &lout, &serr, &lerr, &ec); TEST_ASSERT_EQUAL_INT(0, rc); TEST_ASSERT_EQUAL_INT(0, ec); TEST_ASSERT_NOT_NULL(sout); TEST_ASSERT_TRUE_MESSAGE(lout > 0, "Expected non-empty stdout from usage(EXIT_SUCCESS)"); TEST_ASSERT_TRUE_MESSAGE(contains_substr(sout, pname), "Expected program name to appear in usage output"); TEST_ASSERT_NOT_NULL(serr); TEST_ASSERT_EQUAL_UINT32(0u, (unsigned)lerr); free(sout); free(serr); } int main(void) { UNITY_BEGIN(); RUN_TEST(test_usage_success_outputs_help_to_stdout_only); RUN_TEST(test_usage_nonzero_status_emits_try_help_to_stderr_and_exits_with_code); RUN_TEST(test_usage_uses_program_name_in_output); return UNITY_END(); }