#include "../../unity/unity.h" #include #include #include #include #include #include #include #include #include /* program_name is declared by coreutils headers included before this test */ extern char const *program_name; /* Helper: read all data from fd into a dynamically-allocated buffer. Returns 0 on success, -1 on error. On success, *buf points to malloc'd memory (NUL-terminated for convenience) and *len to its length. */ static int read_all_fd(int fd, char **buf, size_t *len) { size_t cap = 1024; size_t used = 0; char *out = (char *)malloc(cap); if (!out) return -1; for (;;) { if (used == cap) { size_t ncap = cap * 2; char *tmp = (char *)realloc(out, ncap); if (!tmp) { free(out); return -1; } out = tmp; cap = ncap; } ssize_t n = read(fd, out + used, cap - used); if (n > 0) { used += (size_t)n; continue; } if (n == 0) break; if (errno == EINTR) continue; free(out); return -1; } /* NUL-terminate for strstr convenience */ if (used == cap) { char *tmp = (char *)realloc(out, cap + 1); if (!tmp) { free(out); return -1; } out = tmp; } out[used] = '\0'; *buf = out; *len = used; return 0; } /* Helper: create a pipe with CLOEXEC if available, else set it manually. */ static int make_pipe_cloexec(int p[2]) { #ifdef O_CLOEXEC if (pipe2(p, O_CLOEXEC) == 0) return 0; if (errno != ENOSYS && errno != EINVAL) return -1; #endif if (pipe(p) != 0) return -1; /* set CLOEXEC on both ends */ for (int i = 0; i < 2; i++) { int flags = fcntl(p[i], F_GETFD); if (flags >= 0) fcntl(p[i], F_SETFD, flags | FD_CLOEXEC); } return 0; } /* Run usage(status) in a child, capturing its stdout and stderr. On success returns 0 and populates outputs (caller must free *out and *err). On failure returns -1. */ static int run_usage_and_capture(int status, char **out, size_t *out_len, char **err, size_t *err_len, int *exit_code) { int out_p[2] = {-1,-1}; int err_p[2] = {-1,-1}; if (make_pipe_cloexec(out_p) != 0) return -1; if (make_pipe_cloexec(err_p) != 0) { close(out_p[0]); close(out_p[1]); return -1; } fflush(stdout); fflush(stderr); pid_t pid = fork(); if (pid < 0) { close(out_p[0]); close(out_p[1]); close(err_p[0]); close(err_p[1]); return -1; } if (pid == 0) { /* Child: redirect stdout/stderr to pipes */ /* Avoid stdio buffering issues by setting line buffering or unbuffered */ /* But since we dup the fds, exit() will flush appropriately */ if (dup2(out_p[1], STDOUT_FILENO) < 0) _exit(127); if (dup2(err_p[1], STDERR_FILENO) < 0) _exit(127); close(out_p[0]); close(out_p[1]); close(err_p[0]); close(err_p[1]); /* Ensure program_name has a value */ if (!program_name || program_name[0] == '\0') program_name = "mknod"; /* Call the function under test; it should not return. */ usage(status); /* If it returns, exit with sentinel code. */ _exit(123); } /* Parent */ close(out_p[1]); close(err_p[1]); char *out_buf = NULL, *err_buf = NULL; size_t out_sz = 0, err_sz = 0; int r1 = read_all_fd(out_p[0], &out_buf, &out_sz); int r2 = read_all_fd(err_p[0], &err_buf, &err_sz); close(out_p[0]); close(err_p[0]); int wstatus = 0; pid_t w = -1; do { w = waitpid(pid, &wstatus, 0); } while (w < 0 && errno == EINTR); if (r1 != 0 || r2 != 0 || w < 0) { free(out_buf); free(err_buf); return -1; } int code = -1; if (WIFEXITED(wstatus)) code = WEXITSTATUS(wstatus); else if (WIFSIGNALED(wstatus)) code = 128 + WTERMSIG(wstatus); *out = out_buf; *out_len = out_sz; *err = err_buf; *err_len = err_sz; *exit_code = code; return 0; } /* Helper: check if buffer contains substring (ASCII) */ static int buf_contains(const char *buf, const char *needle) { if (!buf || !needle) return 0; return strstr(buf, needle) != NULL; } void setUp(void) { /* Force C locale for deterministic messages */ setenv("LC_ALL", "C", 1); setlocale(LC_ALL, "C"); /* Ensure program_name is set consistently for messages */ if (!program_name || program_name[0] == '\0') program_name = "mknod"; else program_name = "mknod"; } void tearDown(void) { /* Nothing to cleanup */ } static void free_captured(char *out, char *err) { free(out); free(err); } void test_usage_success_outputs_usage_and_exits_zero(void) { char *out = NULL, *err = NULL; size_t out_len = 0, err_len = 0; int code = -1; int rc = run_usage_and_capture(EXIT_SUCCESS, &out, &out_len, &err, &err_len, &code); TEST_ASSERT_EQUAL_INT_MESSAGE(0, rc, "run_usage_and_capture failed"); /* Check exit code */ TEST_ASSERT_EQUAL_INT(EXIT_SUCCESS, code); /* stderr should be empty for success usage */ TEST_ASSERT_TRUE_MESSAGE(err_len == 0 || (err && err[0] == '\0'), "Expected no stderr output for successful usage()"); /* stdout should contain usage header and key content */ TEST_ASSERT_TRUE_MESSAGE(buf_contains(out, "Usage: mknod"), "stdout should contain 'Usage: mknod'"); TEST_ASSERT_TRUE_MESSAGE(buf_contains(out, "TYPE may be"), "stdout should mention 'TYPE may be'"); TEST_ASSERT_TRUE_MESSAGE(buf_contains(out, "block (buffered) special file"), "stdout should describe block special file"); free_captured(out, err); } void test_usage_nonzero_status_emits_try_help_and_exits_status(void) { char *out = NULL, *err = NULL; size_t out_len = 0, err_len = 0; int code = -1; int wanted = 2; int rc = run_usage_and_capture(wanted, &out, &out_len, &err, &err_len, &code); TEST_ASSERT_EQUAL_INT_MESSAGE(0, rc, "run_usage_and_capture failed"); /* Expect the exact exit status */ TEST_ASSERT_EQUAL_INT(wanted, code); /* stdout should be empty in the error path */ TEST_ASSERT_TRUE_MESSAGE(out_len == 0 || (out && out[0] == '\0'), "Expected no stdout output when status != EXIT_SUCCESS"); /* stderr should contain a try-help message mentioning '--help' and program name */ TEST_ASSERT_TRUE_MESSAGE(buf_contains(err, "--help"), "stderr should contain '--help' hint"); TEST_ASSERT_TRUE_MESSAGE(buf_contains(err, "mknod"), "stderr should mention program name 'mknod'"); TEST_ASSERT_TRUE_MESSAGE(buf_contains(err, "Try"), "stderr should start with a 'Try' hint"); free_captured(out, err); } void test_usage_success_includes_mode_option_line(void) { char *out = NULL, *err = NULL; size_t out_len = 0, err_len = 0; int code = -1; int rc = run_usage_and_capture(EXIT_SUCCESS, &out, &out_len, &err, &err_len, &code); TEST_ASSERT_EQUAL_INT_MESSAGE(0, rc, "run_usage_and_capture failed"); TEST_ASSERT_EQUAL_INT(EXIT_SUCCESS, code); TEST_ASSERT_TRUE_MESSAGE(buf_contains(out, "-m, --mode"), "stdout should document the -m, --mode option"); /* Ensure no unexpected stderr output */ TEST_ASSERT_TRUE_MESSAGE(err_len == 0 || (err && err[0] == '\0'), "Expected no stderr output for successful usage()"); free_captured(out, err); } int main(void) { UNITY_BEGIN(); RUN_TEST(test_usage_success_outputs_usage_and_exits_zero); RUN_TEST(test_usage_nonzero_status_emits_try_help_and_exits_status); RUN_TEST(test_usage_success_includes_mode_option_line); return UNITY_END(); }