|
|
#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(); |
|
|
} |