#include "../../unity/unity.h" #include #include #include #include #include #include #include #include /* Target function under test: void usage(int status); declared earlier in od.c */ 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 + 1024 > 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 - 1); if (n < 0) { if (errno == EINTR) continue; /* read error */ free(buf); return NULL; } if (n == 0) break; /* EOF */ len += (size_t)n; } buf[len] = '\0'; if (out_len) *out_len = len; return buf; } /* Helper to run usage(status) in a child, capturing stdout and stderr. */ static int run_usage_and_capture(int status, char **out_str, char **err_str, 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) { /* fork failed */ close(out_pipe[0]); close(out_pipe[1]); close(err_pipe[0]); close(err_pipe[1]); return -1; } if (pid == 0) { /* Child: redirect stdout/stderr, set locale, set program name, call usage */ /* Ensure English output */ setenv("LC_ALL", "C", 1); setenv("LANGUAGE", "C", 1); setlocale(LC_ALL, "C"); /* Prepare redirection */ close(out_pipe[0]); close(err_pipe[0]); if (dup2(out_pipe[1], STDOUT_FILENO) < 0) _exit(127); if (dup2(err_pipe[1], STDERR_FILENO) < 0) _exit(127); close(out_pipe[1]); close(err_pipe[1]); /* Ensure program_name is set for formatted messages */ /* set_program_name is declared via headers included earlier in od.c */ extern void set_program_name(const char *); set_program_name("od"); /* Call the function under test; it will exit(status). */ usage(status); /* Not reached */ _exit(255); } /* Parent: close write ends and read output */ close(out_pipe[1]); close(err_pipe[1]); size_t out_len = 0, err_len = 0; char *out_buf = read_all_from_fd(out_pipe[0], &out_len); char *err_buf = read_all_from_fd(err_pipe[0], &err_len); close(out_pipe[0]); close(err_pipe[0]); int status_wait = 0; pid_t w = waitpid(pid, &status_wait, 0); if (w < 0) { if (out_buf) free(out_buf); if (err_buf) free(err_buf); return -1; } int code = -1; if (WIFEXITED(status_wait)) code = WEXITSTATUS(status_wait); else if (WIFSIGNALED(status_wait)) code = 128 + WTERMSIG(status_wait); if (exit_code) *exit_code = code; if (out_str) *out_str = out_buf; else if (out_buf) free(out_buf); if (err_str) *err_str = err_buf; else if (err_buf) free(err_buf); return 0; } void setUp(void) { /* No global setup required */ } void tearDown(void) { /* No global teardown required */ } static void assert_substr(const char *haystack, const char *needle) { TEST_ASSERT_NOT_NULL_MESSAGE(haystack, "Captured text is NULL"); TEST_ASSERT_NOT_NULL_MESSAGE(needle, "Needle is NULL"); TEST_ASSERT_NOT_NULL_MESSAGE(strstr(haystack, needle), "Expected substring not found"); } void test_usage_success_outputs_help_and_exits_success(void) { char *out = NULL; char *err = NULL; int code = -1; int rc = run_usage_and_capture(EXIT_SUCCESS, &out, &err, &code); TEST_ASSERT_EQUAL_INT_MESSAGE(0, rc, "run_usage_and_capture failed"); /* Verify exit status */ TEST_ASSERT_EQUAL_INT(EXIT_SUCCESS, code); /* For success case, help goes to stdout, stderr should be empty */ TEST_ASSERT_NOT_NULL(out); TEST_ASSERT_TRUE_MESSAGE(out[0] != '\0', "stdout should not be empty for --help"); TEST_ASSERT_NOT_NULL(err); TEST_ASSERT_TRUE_MESSAGE(err[0] == '\0', "stderr should be empty for --help"); /* Verify some key substrings of the help text */ assert_substr(out, "Usage: od "); assert_substr(out, "--address-radix="); assert_substr(out, "--endian="); assert_substr(out, "--width"); free(out); free(err); } void test_usage_failure_outputs_try_help_to_stderr_and_exits_failure(void) { char *out = NULL; char *err = NULL; int code = -1; int rc = run_usage_and_capture(EXIT_FAILURE, &out, &err, &code); TEST_ASSERT_EQUAL_INT_MESSAGE(0, rc, "run_usage_and_capture failed"); TEST_ASSERT_EQUAL_INT(EXIT_FAILURE, code); /* For failure case, try-help goes to stderr */ TEST_ASSERT_NOT_NULL(out); /* stdout should be empty or nearly empty */ TEST_ASSERT_TRUE_MESSAGE(out[0] == '\0', "stdout should be empty when emitting try-help"); TEST_ASSERT_NOT_NULL(err); assert_substr(err, "Try 'od --help' for more information."); free(out); free(err); } void test_usage_custom_nonzero_status_propagates_exit_code_and_try_help(void) { char *out = NULL; char *err = NULL; int code = -1; /* Use a non-standard nonzero exit status */ int wanted = 77; int rc = run_usage_and_capture(wanted, &out, &err, &code); TEST_ASSERT_EQUAL_INT_MESSAGE(0, rc, "run_usage_and_capture failed"); TEST_ASSERT_EQUAL_INT(wanted, code); TEST_ASSERT_NOT_NULL(err); assert_substr(err, "Try 'od --help' for more information."); free(out); free(err); } int main(void) { UNITY_BEGIN(); RUN_TEST(test_usage_success_outputs_help_and_exits_success); RUN_TEST(test_usage_failure_outputs_try_help_to_stderr_and_exits_failure); RUN_TEST(test_usage_custom_nonzero_status_propagates_exit_code_and_try_help); return UNITY_END(); }