|
|
#include "../../unity/unity.h" |
|
|
#include <stdlib.h> |
|
|
#include <string.h> |
|
|
#include <stdio.h> |
|
|
#include <unistd.h> |
|
|
#include <sys/types.h> |
|
|
#include <sys/wait.h> |
|
|
#include <errno.h> |
|
|
#include <fcntl.h> |
|
|
|
|
|
|
|
|
void setUp(void) { |
|
|
|
|
|
setenv("LC_ALL", "C", 1); |
|
|
setenv("LANGUAGE", "C", 1); |
|
|
setenv("LANG", "C", 1); |
|
|
} |
|
|
|
|
|
void tearDown(void) { |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
static int read_all_fd(int fd, char **out_buf, size_t *out_len) { |
|
|
size_t cap = 1024; |
|
|
size_t len = 0; |
|
|
char *buf = (char *)malloc(cap + 1); |
|
|
if (!buf) return -1; |
|
|
|
|
|
for (;;) { |
|
|
if (len == cap) { |
|
|
size_t ncap = cap * 2; |
|
|
char *nbuf = (char *)realloc(buf, ncap + 1); |
|
|
if (!nbuf) { |
|
|
free(buf); |
|
|
return -1; |
|
|
} |
|
|
buf = nbuf; |
|
|
cap = ncap; |
|
|
} |
|
|
ssize_t n = read(fd, buf + len, cap - len); |
|
|
if (n < 0) { |
|
|
if (errno == EINTR) continue; |
|
|
free(buf); |
|
|
return -1; |
|
|
} |
|
|
if (n == 0) break; |
|
|
len += (size_t)n; |
|
|
} |
|
|
buf[len] = '\0'; |
|
|
*out_buf = buf; |
|
|
if (out_len) *out_len = len; |
|
|
return 0; |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
static char *run_usage_capture(int status_arg, |
|
|
char **out_stdout, size_t *out_stdout_len, |
|
|
char **out_stderr, size_t *out_stderr_len, |
|
|
int *out_exit_code) { |
|
|
int out_pipe[2] = {-1,-1}; |
|
|
int err_pipe[2] = {-1,-1}; |
|
|
if (pipe(out_pipe) < 0) { |
|
|
char *msg; |
|
|
asprintf(&msg, "pipe(out) failed: %s", strerror(errno)); |
|
|
return msg; |
|
|
} |
|
|
if (pipe(err_pipe) < 0) { |
|
|
close(out_pipe[0]); close(out_pipe[1]); |
|
|
char *msg; |
|
|
asprintf(&msg, "pipe(err) failed: %s", strerror(errno)); |
|
|
return msg; |
|
|
} |
|
|
|
|
|
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]); |
|
|
char *msg; |
|
|
asprintf(&msg, "fork failed: %s", strerror(errno)); |
|
|
return msg; |
|
|
} |
|
|
|
|
|
if (pid == 0) { |
|
|
|
|
|
|
|
|
setenv("LC_ALL", "C", 1); |
|
|
setenv("LANGUAGE", "C", 1); |
|
|
setenv("LANG", "C", 1); |
|
|
|
|
|
|
|
|
if (dup2(out_pipe[1], STDOUT_FILENO) < 0) _exit(255); |
|
|
if (dup2(err_pipe[1], STDERR_FILENO) < 0) _exit(255); |
|
|
|
|
|
|
|
|
close(out_pipe[0]); close(out_pipe[1]); |
|
|
close(err_pipe[0]); close(err_pipe[1]); |
|
|
|
|
|
|
|
|
usage(status_arg); |
|
|
|
|
|
|
|
|
_exit(254); |
|
|
} |
|
|
|
|
|
|
|
|
close(out_pipe[1]); |
|
|
close(err_pipe[1]); |
|
|
|
|
|
char *captured_out = NULL; |
|
|
char *captured_err = NULL; |
|
|
size_t len_out = 0, len_err = 0; |
|
|
|
|
|
int rc_out = read_all_fd(out_pipe[0], &captured_out, &len_out); |
|
|
int rc_err = read_all_fd(err_pipe[0], &captured_err, &len_err); |
|
|
|
|
|
close(out_pipe[0]); |
|
|
close(err_pipe[0]); |
|
|
|
|
|
int wstatus = 0; |
|
|
pid_t w = waitpid(pid, &wstatus, 0); |
|
|
if (w < 0) { |
|
|
free(captured_out); |
|
|
free(captured_err); |
|
|
char *msg; |
|
|
asprintf(&msg, "waitpid failed: %s", strerror(errno)); |
|
|
return msg; |
|
|
} |
|
|
|
|
|
if (rc_out != 0 || rc_err != 0) { |
|
|
free(captured_out); |
|
|
free(captured_err); |
|
|
char *msg; |
|
|
asprintf(&msg, "read_all_fd failed (out=%d, err=%d)", rc_out, rc_err); |
|
|
return msg; |
|
|
} |
|
|
|
|
|
int exit_code = -1; |
|
|
if (WIFEXITED(wstatus)) { |
|
|
exit_code = WEXITSTATUS(wstatus); |
|
|
} else if (WIFSIGNALED(wstatus)) { |
|
|
free(captured_out); |
|
|
free(captured_err); |
|
|
char *msg; |
|
|
asprintf(&msg, "child terminated by signal %d", WTERMSIG(wstatus)); |
|
|
return msg; |
|
|
} else { |
|
|
free(captured_out); |
|
|
free(captured_err); |
|
|
char *msg; |
|
|
asprintf(&msg, "child did not exit normally"); |
|
|
return msg; |
|
|
} |
|
|
|
|
|
if (out_stdout) *out_stdout = captured_out; else free(captured_out); |
|
|
if (out_stderr) *out_stderr = captured_err; else free(captured_err); |
|
|
if (out_stdout_len) *out_stdout_len = len_out; |
|
|
if (out_stderr_len) *out_stderr_len = len_err; |
|
|
if (out_exit_code) *out_exit_code = exit_code; |
|
|
|
|
|
return NULL; |
|
|
} |
|
|
|
|
|
static int str_contains(const char *haystack, const char *needle) { |
|
|
if (!haystack || !needle) return 0; |
|
|
return strstr(haystack, needle) != NULL; |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
void test_usage_success_outputs_help(void) { |
|
|
char *out = NULL, *err = NULL; |
|
|
size_t out_len = 0, err_len = 0; |
|
|
int exit_code = -1; |
|
|
|
|
|
char *msg = run_usage_capture(0, &out, &out_len, &err, &err_len, &exit_code); |
|
|
TEST_ASSERT_NULL_MESSAGE(msg, msg ? msg : ""); |
|
|
|
|
|
|
|
|
TEST_ASSERT_EQUAL_INT(0, exit_code); |
|
|
|
|
|
|
|
|
TEST_ASSERT_NOT_NULL(out); |
|
|
TEST_ASSERT_TRUE(out_len > 0); |
|
|
TEST_ASSERT_NOT_NULL(err); |
|
|
TEST_ASSERT_EQUAL_UINT64(0, (unsigned long long)err_len); |
|
|
|
|
|
|
|
|
TEST_ASSERT_TRUE_MESSAGE(str_contains(out, "Usage:"), "Help text missing 'Usage:'"); |
|
|
TEST_ASSERT_TRUE_MESSAGE(str_contains(out, "Create named pipes"), "Help text missing description line"); |
|
|
TEST_ASSERT_TRUE_MESSAGE(str_contains(out, "-m, --mode=MODE"), "Help text missing mode option"); |
|
|
TEST_ASSERT_TRUE_MESSAGE(str_contains(out, "--context"), "Help text missing context option"); |
|
|
TEST_ASSERT_TRUE_MESSAGE(str_contains(out, "-Z"), "Help text missing -Z option"); |
|
|
|
|
|
free(out); |
|
|
free(err); |
|
|
} |
|
|
|
|
|
void test_usage_nonzero_shows_try_help_and_exit_status_1(void) { |
|
|
char *out = NULL, *err = NULL; |
|
|
size_t out_len = 0, err_len = 0; |
|
|
int exit_code = -1; |
|
|
|
|
|
char *msg = run_usage_capture(1, &out, &out_len, &err, &err_len, &exit_code); |
|
|
TEST_ASSERT_NULL_MESSAGE(msg, msg ? msg : ""); |
|
|
|
|
|
TEST_ASSERT_EQUAL_INT(1, exit_code); |
|
|
|
|
|
|
|
|
TEST_ASSERT_NOT_NULL(out); |
|
|
TEST_ASSERT_EQUAL_UINT64(0, (unsigned long long)out_len); |
|
|
TEST_ASSERT_NOT_NULL(err); |
|
|
TEST_ASSERT_TRUE(err_len > 0); |
|
|
|
|
|
TEST_ASSERT_TRUE_MESSAGE(str_contains(err, "Try"), "stderr missing 'Try' message"); |
|
|
TEST_ASSERT_TRUE_MESSAGE(str_contains(err, "--help"), "stderr missing '--help' hint"); |
|
|
|
|
|
free(out); |
|
|
free(err); |
|
|
} |
|
|
|
|
|
void test_usage_nonzero_propagates_exit_status_77(void) { |
|
|
char *out = NULL, *err = NULL; |
|
|
size_t out_len = 0, err_len = 0; |
|
|
int exit_code = -1; |
|
|
|
|
|
char *msg = run_usage_capture(77, &out, &out_len, &err, &err_len, &exit_code); |
|
|
TEST_ASSERT_NULL_MESSAGE(msg, msg ? msg : ""); |
|
|
|
|
|
TEST_ASSERT_EQUAL_INT(77, exit_code); |
|
|
|
|
|
|
|
|
TEST_ASSERT_EQUAL_UINT64(0, (unsigned long long)out_len); |
|
|
TEST_ASSERT_TRUE(err_len > 0); |
|
|
TEST_ASSERT_TRUE_MESSAGE(str_contains(err, "--help"), "stderr missing '--help' hint"); |
|
|
|
|
|
free(out); |
|
|
free(err); |
|
|
} |
|
|
|
|
|
|
|
|
int main(void) { |
|
|
UNITY_BEGIN(); |
|
|
RUN_TEST(test_usage_success_outputs_help); |
|
|
RUN_TEST(test_usage_nonzero_shows_try_help_and_exit_status_1); |
|
|
RUN_TEST(test_usage_nonzero_propagates_exit_status_77); |
|
|
return UNITY_END(); |
|
|
} |