| #include "../../unity/unity.h" |
| #include <stdlib.h> |
| #include <stdio.h> |
| #include <string.h> |
| #include <unistd.h> |
| #include <sys/types.h> |
| #include <sys/wait.h> |
| #include <fcntl.h> |
| #include <errno.h> |
|
|
| |
| |
|
|
| 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; |
| } |
|
|
| |
| 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) |
| { |
| |
| (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); |
|
|
| |
| _exit (200); |
| } |
|
|
| |
| 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; |
|
|
| 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) { |
| |
| } |
|
|
| void tearDown(void) { |
| |
| } |
|
|
| |
|
|
| void test_usage_exit_zero_prints_full_help(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 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"); |
|
|
| |
| 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"); |
|
|
| |
| 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; |
| 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"); |
|
|
| |
| 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"); |
|
|
| |
| 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(); |
| } |