| #include "../../unity/unity.h" |
| #include <stdlib.h> |
| #include <string.h> |
| #include <unistd.h> |
| #include <sys/wait.h> |
| #include <fcntl.h> |
| #include <errno.h> |
| #include <stdio.h> |
| #include <stdbool.h> |
|
|
| |
| static int read_all(int fd, char **out_buf, size_t *out_len) |
| { |
| char tmp[4096]; |
| size_t cap = 0; |
| size_t len = 0; |
| char *buf = NULL; |
|
|
| for (;;) |
| { |
| ssize_t n = read(fd, tmp, sizeof tmp); |
| if (n < 0) |
| { |
| if (errno == EINTR) |
| continue; |
| free(buf); |
| return -1; |
| } |
| if (n == 0) |
| break; |
|
|
| if (len + (size_t)n + 1 > cap) |
| { |
| size_t newcap = cap ? cap * 2 : 8192; |
| while (newcap < len + (size_t)n + 1) |
| newcap *= 2; |
| char *nb = (char *)realloc(buf, newcap); |
| if (!nb) |
| { |
| free(buf); |
| errno = ENOMEM; |
| return -1; |
| } |
| buf = nb; |
| cap = newcap; |
| } |
| memcpy(buf + len, tmp, (size_t)n); |
| len += (size_t)n; |
| } |
|
|
| if (!buf) |
| { |
| buf = (char *)malloc(1); |
| if (!buf) |
| return -1; |
| buf[0] = '\0'; |
| len = 0; |
| } |
| else |
| { |
| buf[len] = '\0'; |
| } |
|
|
| *out_buf = buf; |
| *out_len = len; |
| return 0; |
| } |
|
|
| typedef struct { |
| char *out_buf; |
| size_t out_len; |
| char *err_buf; |
| size_t err_len; |
| int exit_code; |
| } usage_result; |
|
|
| |
| static int run_usage_and_capture(int status, usage_result *res) |
| { |
| 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) |
| { |
| |
| if (dup2(out_pipe[1], STDOUT_FILENO) < 0) _exit(127); |
| if (dup2(err_pipe[1], STDERR_FILENO) < 0) _exit(127); |
|
|
| close(out_pipe[0]); close(out_pipe[1]); |
| close(err_pipe[0]); close(err_pipe[1]); |
|
|
| |
| usage(status); |
|
|
| |
| _exit(255); |
| } |
| else |
| { |
| |
| close(out_pipe[1]); |
| close(err_pipe[1]); |
|
|
| usage_result tmp = {0}; |
| if (read_all(out_pipe[0], &tmp.out_buf, &tmp.out_len) < 0) |
| { |
| close(out_pipe[0]); |
| close(err_pipe[0]); |
| return -1; |
| } |
| if (read_all(err_pipe[0], &tmp.err_buf, &tmp.err_len) < 0) |
| { |
| close(out_pipe[0]); |
| close(err_pipe[0]); |
| free(tmp.out_buf); |
| return -1; |
| } |
|
|
| close(out_pipe[0]); |
| close(err_pipe[0]); |
|
|
| int wstatus = 0; |
| if (waitpid(pid, &wstatus, 0) < 0) |
| { |
| free(tmp.out_buf); |
| free(tmp.err_buf); |
| return -1; |
| } |
|
|
| if (WIFEXITED(wstatus)) |
| tmp.exit_code = WEXITSTATUS(wstatus); |
| else |
| tmp.exit_code = -1; |
|
|
| *res = tmp; |
| return 0; |
| } |
| } |
|
|
| |
| static bool contains_substr(const char *hay, const char *needle) |
| { |
| if (!hay || !needle) return false; |
| return strstr(hay, needle) != NULL; |
| } |
|
|
| |
| extern const char *program_name; |
|
|
| void setUp(void) { |
| |
| setenv("LC_ALL", "C", 1); |
| |
| program_name = "pathchk"; |
| } |
|
|
| void tearDown(void) { |
| |
| } |
|
|
| |
| void test_usage_success_outputs_help_and_exits_zero(void) |
| { |
| usage_result r = {0}; |
| int rc = run_usage_and_capture(EXIT_SUCCESS, &r); |
| TEST_ASSERT_EQUAL_INT_MESSAGE(0, rc, "Failed to run usage in child process"); |
|
|
| |
| TEST_ASSERT_EQUAL_INT_MESSAGE(0, r.exit_code, "usage(EXIT_SUCCESS) did not exit(0)"); |
|
|
| |
| TEST_ASSERT_TRUE_MESSAGE(contains_substr(r.out_buf, "Usage:"), "Help text missing 'Usage:'"); |
| TEST_ASSERT_TRUE_MESSAGE(contains_substr(r.out_buf, "pathchk"), "Help text missing program name"); |
| TEST_ASSERT_TRUE_MESSAGE(contains_substr(r.out_buf, "-p"), "Help text missing '-p' option"); |
| TEST_ASSERT_TRUE_MESSAGE(contains_substr(r.out_buf, "-P"), "Help text missing '-P' option"); |
| TEST_ASSERT_TRUE_MESSAGE(contains_substr(r.out_buf, "--portability"), "Help text missing '--portability' option"); |
| TEST_ASSERT_TRUE_MESSAGE(contains_substr(r.out_buf, "--help"), "Help text missing '--help' description"); |
| TEST_ASSERT_TRUE_MESSAGE(contains_substr(r.out_buf, "--version"), "Help text missing '--version' description"); |
|
|
| |
| TEST_ASSERT_TRUE_MESSAGE(r.err_len == 0 || (r.err_buf && r.err_buf[0] == '\0'), |
| "Expected no stderr output for usage(EXIT_SUCCESS)"); |
|
|
| free(r.out_buf); |
| free(r.err_buf); |
| } |
|
|
| |
| void test_usage_failure_emits_try_help_on_stderr_and_propagates_status(void) |
| { |
| int requested_status = 2; |
| usage_result r = {0}; |
| int rc = run_usage_and_capture(requested_status, &r); |
| TEST_ASSERT_EQUAL_INT_MESSAGE(0, rc, "Failed to run usage in child process"); |
|
|
| |
| TEST_ASSERT_EQUAL_INT_MESSAGE(requested_status, r.exit_code, |
| "usage(nonzero) did not propagate exit status"); |
|
|
| |
| TEST_ASSERT_TRUE_MESSAGE(contains_substr(r.err_buf, "Try"), |
| "Expected stderr to contain a 'Try' hint"); |
| TEST_ASSERT_TRUE_MESSAGE(contains_substr(r.err_buf, "--help"), |
| "Expected stderr hint to mention '--help'"); |
| TEST_ASSERT_TRUE_MESSAGE(contains_substr(r.err_buf, "pathchk"), |
| "Expected stderr hint to mention the program name"); |
|
|
| |
| TEST_ASSERT_FALSE_MESSAGE(contains_substr(r.out_buf, "Usage:"), |
| "Did not expect full help on stdout for nonzero status"); |
|
|
| free(r.out_buf); |
| free(r.err_buf); |
| } |
|
|
| int main(void) |
| { |
| UNITY_BEGIN(); |
| RUN_TEST(test_usage_success_outputs_help_and_exits_zero); |
| RUN_TEST(test_usage_failure_emits_try_help_on_stderr_and_propagates_status); |
| return UNITY_END(); |
| } |