#include "../../unity/unity.h" #include #include #include #include #include #include #include #include /* Helper to read all data from an fd into a malloc'd buffer. */ 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 new_cap = cap * 2; char *new_buf = (char*)realloc(buf, new_cap); if (!new_buf) { free(buf); return NULL; } buf = new_buf; cap = new_cap; } ssize_t r = read(fd, buf + len, cap - len); if (r < 0) { if (errno == EINTR) continue; free(buf); return NULL; } else if (r == 0) { break; } else { len += (size_t)r; } } /* NUL-terminate for convenience (not counted in out_len). */ if (len == cap) { char *new_buf = (char*)realloc(buf, cap + 1); if (!new_buf) { free(buf); return NULL; } buf = new_buf; } buf[len] = '\0'; if (out_len) *out_len = len; return buf; } /* Run usage(status) in a child, capturing stdout and stderr, and the exit code. */ static int run_usage_and_capture(int status, char **out_buf, size_t *out_len, char **err_buf, size_t *err_len, 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; } if (pid == 0) { /* Child: redirect stdout/stderr and call usage. No Unity asserts here. */ /* Force C locale for predictable messages (best-effort). */ setenv("LC_ALL", "C", 1); setenv("LANG", "C", 1); setlocale(LC_ALL, "C"); 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]); /* Call the function under test; it should exit(status). */ usage(status); /* Should never reach here. */ _exit(123); } /* Parent: close write ends, read outputs, wait for child. */ close(out_pipe[1]); close(err_pipe[1]); char *child_out = read_all_from_fd(out_pipe[0], out_len); char *child_err = read_all_from_fd(err_pipe[0], err_len); close(out_pipe[0]); close(err_pipe[0]); int wstatus = 0; if (waitpid(pid, &wstatus, 0) < 0) { if (child_out) free(child_out); if (child_err) free(child_err); return -1; } int code = -1; if (WIFEXITED(wstatus)) { code = WEXITSTATUS(wstatus); } else if (WIFSIGNALED(wstatus)) { /* Encode signal as negative for clarity. */ code = -WTERMSIG(wstatus); } if (exit_code) *exit_code = code; if (out_buf) *out_buf = child_out; else free(child_out); if (err_buf) *err_buf = child_err; else free(child_err); return 0; } void setUp(void) { /* No global state to set explicitly. */ } void tearDown(void) { /* No global state to clean. */ } /* Test that usage(EXIT_SUCCESS) prints full help to stdout and exits with 0. */ void test_usage_success_prints_help_and_exits_success(void) { char *out = NULL, *err = NULL; size_t outlen = 0, errlen = 0; int exit_code = -1; int rc = run_usage_and_capture(EXIT_SUCCESS, &out, &outlen, &err, &errlen, &exit_code); TEST_ASSERT_EQUAL_INT(0, rc); TEST_ASSERT_EQUAL_INT(0, exit_code); /* On success, help should go to stdout and stderr should be empty. */ TEST_ASSERT_NOT_NULL(out); TEST_ASSERT_TRUE(outlen > 0); TEST_ASSERT_NOT_NULL(err); TEST_ASSERT_EQUAL_size_t(0, errlen); /* Look for invariant option names that should be present in basenc help. */ TEST_ASSERT_NOT_NULL(strstr(out, "--decode")); TEST_ASSERT_NOT_NULL(strstr(out, "--wrap")); TEST_ASSERT_NOT_NULL(strstr(out, "--base64")); TEST_ASSERT_NOT_NULL(strstr(out, "--z85")); /* basenc-specific option */ free(out); free(err); } /* Test that usage(EXIT_FAILURE) emits a 'try --help' hint to stderr, and exits with failure. */ void test_usage_failure_emits_try_help_on_stderr_and_exits_failure(void) { char *out = NULL, *err = NULL; size_t outlen = 0, errlen = 0; int exit_code = -1; int rc = run_usage_and_capture(EXIT_FAILURE, &out, &outlen, &err, &errlen, &exit_code); TEST_ASSERT_EQUAL_INT(0, rc); TEST_ASSERT_EQUAL_INT(EXIT_FAILURE, exit_code); /* On failure, stdout should be empty, stderr should contain the hint with "--help". */ TEST_ASSERT_NOT_NULL(out); TEST_ASSERT_EQUAL_size_t(0, outlen); TEST_ASSERT_NOT_NULL(err); TEST_ASSERT_TRUE(errlen > 0); TEST_ASSERT_NOT_NULL(strstr(err, "--help")); free(out); free(err); } /* Test that basenc's multi-base options appear in the success help text. */ void test_usage_success_contains_multiple_basenc_modes(void) { char *out = NULL, *err = NULL; size_t outlen = 0, errlen = 0; int exit_code = -1; int rc = run_usage_and_capture(EXIT_SUCCESS, &out, &outlen, &err, &errlen, &exit_code); TEST_ASSERT_EQUAL_INT(0, rc); TEST_ASSERT_EQUAL_INT(0, exit_code); /* Ensure several basenc variants appear in the help text. */ TEST_ASSERT_NOT_NULL(out); TEST_ASSERT_NOT_NULL(strstr(out, "--base64")); TEST_ASSERT_NOT_NULL(strstr(out, "--base64url")); TEST_ASSERT_NOT_NULL(strstr(out, "--base58")); TEST_ASSERT_NOT_NULL(strstr(out, "--base32")); TEST_ASSERT_NOT_NULL(strstr(out, "--base32hex")); TEST_ASSERT_NOT_NULL(strstr(out, "--base16")); TEST_ASSERT_NOT_NULL(strstr(out, "--base2msbf")); TEST_ASSERT_NOT_NULL(strstr(out, "--base2lsbf")); TEST_ASSERT_NOT_NULL(strstr(out, "--z85")); free(out); free(err); } int main(void) { UNITY_BEGIN(); RUN_TEST(test_usage_success_prints_help_and_exits_success); RUN_TEST(test_usage_failure_emits_try_help_on_stderr_and_exits_failure); RUN_TEST(test_usage_success_contains_multiple_basenc_modes); return UNITY_END(); }