#include "../../unity/unity.h" #include #include #include #include #include #include #include /* Access the global program_name used by usage(). */ extern char const *program_name; /* Forward declaration of the target function. */ void usage (int status); /* Helper: run usage(status) in a child, capturing either stdout or stderr. capture_fd must be STDOUT_FILENO or STDERR_FILENO. progname will be assigned to program_name in the child before calling usage. On return, *out_buf is a malloc'ed, NUL-terminated buffer with captured output (caller must free), and *exit_code is the child's exit code if it exited normally (or -1 otherwise). Returns 0 on success, -1 on setup error. */ static int run_usage_and_capture(int status_to_pass, int capture_fd, const char *progname, char **out_buf, int *exit_code) { if (!out_buf || !exit_code) return -1; *out_buf = NULL; *exit_code = -1; int p[2]; if (pipe(p) < 0) return -1; fflush(stdout); fflush(stderr); pid_t pid = fork(); if (pid < 0) { int saved = errno; close(p[0]); close(p[1]); errno = saved; return -1; } if (pid == 0) { /* Child: redirect selected fd to pipe write end. */ /* Ensure parent's stdout is unaffected; redirection only in child. */ close(p[0]); /* close read end */ if (dup2(p[1], capture_fd) < 0) { /* If dup2 fails, exit with distinctive code. */ _exit(127); } /* Close the duplicated fd as pipe write end is now on capture_fd. */ close(p[1]); /* Set the program name to control output. */ program_name = progname ? progname : "mv"; /* Call the target; it will call exit(status_to_pass). */ usage(status_to_pass); /* Should not reach here. */ _exit(126); } else { /* Parent: read from pipe and wait for child. */ close(p[1]); /* close write end */ size_t cap = 1024; size_t len = 0; char *buf = (char *)malloc(cap); if (!buf) { close(p[0]); /* Best effort: reap child. */ int st; waitpid(pid, &st, 0); return -1; } ssize_t n; while ((n = read(p[0], buf + len, cap - len)) > 0) { len += (size_t)n; if (len == cap) { size_t newcap = cap * 2; char *nb = (char *)realloc(buf, newcap); if (!nb) { free(buf); close(p[0]); int st; waitpid(pid, &st, 0); return -1; } buf = nb; cap = newcap; } } close(p[0]); /* NUL-terminate */ if (len == cap) { char *nb = (char *)realloc(buf, cap + 1); if (!nb) { free(buf); int st; waitpid(pid, &st, 0); return -1; } buf = nb; } buf[len] = '\0'; int st = 0; if (waitpid(pid, &st, 0) < 0) { free(buf); return -1; } *out_buf = buf; if (WIFEXITED(st)) *exit_code = WEXITSTATUS(st); else *exit_code = -1; return 0; } } /* Utility: check if substring exists. */ static int contains_substr(const char *haystack, const char *needle) { return haystack && needle && strstr(haystack, needle) != NULL; } void setUp(void) { /* No global setup required. Each test sets program_name in child. */ } void tearDown(void) { /* No global teardown required. */ } void test_usage_success_writes_help_to_stdout_and_exits_zero(void) { char *out = NULL; int code = -1; int rc = run_usage_and_capture(0, STDOUT_FILENO, "mv", &out, &code); TEST_ASSERT_EQUAL_INT(0, rc); TEST_ASSERT_NOT_NULL(out); TEST_ASSERT_EQUAL_INT(0, code); /* Verify key parts of the help text are present on stdout. */ TEST_ASSERT_TRUE(contains_substr(out, "Usage:")); TEST_ASSERT_TRUE(contains_substr(out, "mv")); TEST_ASSERT_TRUE(contains_substr(out, "Rename SOURCE to DEST")); TEST_ASSERT_TRUE(contains_substr(out, "--backup")); TEST_ASSERT_TRUE(contains_substr(out, "--verbose")); free(out); } void test_usage_failure_writes_try_help_to_stderr_and_exits_nonzero(void) { char *err = NULL; int code = -1; int rc = run_usage_and_capture(1, STDERR_FILENO, "mv", &err, &code); TEST_ASSERT_EQUAL_INT(0, rc); TEST_ASSERT_NOT_NULL(err); TEST_ASSERT_EQUAL_INT(1, code); /* Typical message: "Try 'mv --help' for more information." */ TEST_ASSERT_TRUE(contains_substr(err, "Try")); TEST_ASSERT_TRUE(contains_substr(err, "mv")); TEST_ASSERT_TRUE(contains_substr(err, "--help")); free(err); } void test_usage_success_does_not_write_to_stderr(void) { char *err = NULL; int code = -1; int rc = run_usage_and_capture(0, STDERR_FILENO, "mv", &err, &code); TEST_ASSERT_EQUAL_INT(0, rc); TEST_ASSERT_NOT_NULL(err); TEST_ASSERT_EQUAL_INT(0, code); /* Expect stderr to be empty for successful usage(status==0). */ TEST_ASSERT_EQUAL_UINT32(0u, (unsigned)strlen(err)); free(err); } void test_usage_failure_does_not_write_to_stdout(void) { char *out = NULL; int code = -1; int rc = run_usage_and_capture(1, STDOUT_FILENO, "mv", &out, &code); TEST_ASSERT_EQUAL_INT(0, rc); TEST_ASSERT_NOT_NULL(out); TEST_ASSERT_EQUAL_INT(1, code); /* Expect stdout to be empty for failure usage(status!=0). */ TEST_ASSERT_EQUAL_UINT32(0u, (unsigned)strlen(out)); free(out); } void test_usage_uses_program_name_variable(void) { const char *custom = "my-mv"; char *out = NULL; int code = -1; int rc = run_usage_and_capture(0, STDOUT_FILENO, custom, &out, &code); TEST_ASSERT_EQUAL_INT(0, rc); TEST_ASSERT_NOT_NULL(out); TEST_ASSERT_EQUAL_INT(0, code); /* Ensure custom program name appears in the usage heading. */ TEST_ASSERT_TRUE(contains_substr(out, "Usage: my-mv")); free(out); } int main(void) { UNITY_BEGIN(); RUN_TEST(test_usage_success_writes_help_to_stdout_and_exits_zero); RUN_TEST(test_usage_failure_writes_try_help_to_stderr_and_exits_nonzero); RUN_TEST(test_usage_success_does_not_write_to_stderr); RUN_TEST(test_usage_failure_does_not_write_to_stdout); RUN_TEST(test_usage_uses_program_name_variable); return UNITY_END(); }