#include "../../unity/unity.h" #include #include #include #include #include #include #include #include /* usage(int) is provided by the program under test. */ /* program_name is declared in headers included by the program source before this file. */ 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 ncap = cap * 2; char *nbuf = (char *) realloc (buf, ncap); if (!nbuf) { free (buf); return NULL; } buf = nbuf; cap = ncap; } ssize_t r = read (fd, buf + len, cap - len); if (r < 0) { if (errno == EINTR) continue; free (buf); return NULL; } if (r == 0) break; len += (size_t) r; } /* NUL-terminate to make strstr safe (though len is provided). */ if (len == cap) { char *nbuf = (char *) realloc (buf, cap + 1); if (!nbuf) { free (buf); return NULL; } buf = nbuf; } buf[len] = '\0'; if (out_len) *out_len = len; return buf; } static int run_usage_and_capture (int status_code, char **out_stdout, size_t *out_stdout_len, char **out_stderr, size_t *out_stderr_len, int *out_exit_status) { int pout[2]; int perr[2]; if (pipe (pout) != 0) return -1; if (pipe (perr) != 0) { close (pout[0]); close (pout[1]); return -1; } pid_t pid = fork (); if (pid < 0) { close (pout[0]); close (pout[1]); close (perr[0]); close (perr[1]); return -1; } if (pid == 0) { /* Child: redirect stdout/stderr and invoke usage(status_code). */ (void) setenv ("LC_ALL", "C", 1); if (dup2 (pout[1], STDOUT_FILENO) < 0) _exit (127); if (dup2 (perr[1], STDERR_FILENO) < 0) _exit (127); close (pout[0]); close (pout[1]); close (perr[0]); close (perr[1]); usage (status_code); /* Not reached. */ _exit (200); } /* Parent: close write ends, read output, wait for child. */ close (pout[1]); close (perr[1]); size_t out_len = 0, err_len = 0; char *out_buf = read_all_from_fd (pout[0], &out_len); char *err_buf = read_all_from_fd (perr[0], &err_len); close (pout[0]); close (perr[0]); int wstatus = 0; if (waitpid (pid, &wstatus, 0) < 0) { free (out_buf); free (err_buf); return -1; } int exit_status = -1; if (WIFEXITED (wstatus)) exit_status = WEXITSTATUS (wstatus); else exit_status = -2; /* Abnormal termination. */ if (out_stdout) *out_stdout = out_buf; else free (out_buf); if (out_stdout_len) *out_stdout_len = out_len; if (out_stderr) *out_stderr = err_buf; else free (err_buf); if (out_stderr_len) *out_stderr_len = err_len; if (out_exit_status) *out_exit_status = exit_status; return 0; } void setUp(void) { /* No global setup needed. */ } void tearDown(void) { /* No global teardown needed. */ } /* Tests */ void test_usage_exit_zero_prints_full_help(void) { /* program_name is provided by the program. Set it so help includes our chosen value. */ program_name = "mycomm"; char *capt_out = NULL; char *capt_err = NULL; size_t out_len = 0, err_len = 0; int exit_st = -1; int rc = run_usage_and_capture (EXIT_SUCCESS, &capt_out, &out_len, &capt_err, &err_len, &exit_st); TEST_ASSERT_EQUAL_INT_MESSAGE (0, rc, "Failed to run usage() in child process"); TEST_ASSERT_EQUAL_INT_MESSAGE (0, exit_st, "usage(EXIT_SUCCESS) should exit with status 0"); TEST_ASSERT_NOT_NULL_MESSAGE (capt_out, "Expected captured stdout buffer"); TEST_ASSERT_NOT_NULL_MESSAGE (capt_err, "Expected captured stderr buffer"); /* Full help should be on stdout; stderr should be empty. */ TEST_ASSERT_TRUE_MESSAGE (out_len > 0, "Expected non-empty stdout for full help"); TEST_ASSERT_EQUAL_UINT_MESSAGE (0, err_len, "Expected empty stderr for full help"); /* Check for key substrings in help text (locale forced to C in child). */ TEST_ASSERT_NOT_NULL_MESSAGE (strstr (capt_out, "Usage: mycomm"), "Help must include 'Usage: mycomm'"); TEST_ASSERT_NOT_NULL_MESSAGE (strstr (capt_out, "[OPTION]"), "Help must mention [OPTION]"); TEST_ASSERT_NOT_NULL_MESSAGE (strstr (capt_out, "Compare sorted files"), "Help should describe comparison"); TEST_ASSERT_NOT_NULL_MESSAGE (strstr (capt_out, "--check-order"), "Help should mention --check-order"); TEST_ASSERT_NOT_NULL_MESSAGE (strstr (capt_out, "--nocheck-order"), "Help should mention --nocheck-order"); TEST_ASSERT_NOT_NULL_MESSAGE (strstr (capt_out, "--output-delimiter"), "Help should mention --output-delimiter"); TEST_ASSERT_NOT_NULL_MESSAGE (strstr (capt_out, "--total"), "Help should mention --total"); TEST_ASSERT_NOT_NULL_MESSAGE (strstr (capt_out, "--zero-terminated"), "Help should mention --zero-terminated"); TEST_ASSERT_NOT_NULL_MESSAGE (strstr (capt_out, "Examples:"), "Help should include Examples section"); TEST_ASSERT_NOT_NULL_MESSAGE (strstr (capt_out, "mycomm -12 file1 file2"), "Examples should include 'mycomm -12 file1 file2'"); TEST_ASSERT_NOT_NULL_MESSAGE (strstr (capt_out, "mycomm -3 file1 file2"), "Examples should include 'mycomm -3 file1 file2'"); free (capt_out); free (capt_err); } void test_usage_nonzero_status_prints_try_help_to_stderr(void) { program_name = "mycomm"; char *capt_out = NULL; char *capt_err = NULL; size_t out_len = 0, err_len = 0; int exit_st = -1; int expected_status = 7; /* arbitrary non-zero */ int rc = run_usage_and_capture (expected_status, &capt_out, &out_len, &capt_err, &err_len, &exit_st); TEST_ASSERT_EQUAL_INT_MESSAGE (0, rc, "Failed to run usage() in child process"); TEST_ASSERT_EQUAL_INT_MESSAGE (expected_status, exit_st, "usage(nonzero) should exit with that status"); TEST_ASSERT_NOT_NULL_MESSAGE (capt_out, "Expected captured stdout buffer"); TEST_ASSERT_NOT_NULL_MESSAGE (capt_err, "Expected captured stderr buffer"); /* Error synopsis should be on stderr; stdout should be empty. */ TEST_ASSERT_EQUAL_UINT_MESSAGE (0, out_len, "Expected empty stdout when emitting try-help"); TEST_ASSERT_TRUE_MESSAGE (err_len > 0, "Expected non-empty stderr when emitting try-help"); /* Check the try-help message contains our program_name and '--help'. */ TEST_ASSERT_NOT_NULL_MESSAGE (strstr (capt_err, "Try "), "try-help should contain 'Try '"); TEST_ASSERT_NOT_NULL_MESSAGE (strstr (capt_err, "mycomm"), "try-help should include program_name"); TEST_ASSERT_NOT_NULL_MESSAGE (strstr (capt_err, "--help"), "try-help should suggest --help"); free (capt_out); free (capt_err); } int main(void) { UNITY_BEGIN(); RUN_TEST(test_usage_exit_zero_prints_full_help); RUN_TEST(test_usage_nonzero_status_prints_try_help_to_stderr); return UNITY_END(); }