|
|
#include "../../unity/unity.h" |
|
|
#include <stdlib.h> |
|
|
#include <string.h> |
|
|
#include <unistd.h> |
|
|
#include <sys/wait.h> |
|
|
#include <fcntl.h> |
|
|
#include <errno.h> |
|
|
#include <locale.h> |
|
|
#include <stdio.h> |
|
|
#include <signal.h> |
|
|
|
|
|
|
|
|
extern char const *program_name; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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; |
|
|
} |
|
|
|
|
|
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; |
|
|
} |
|
|
|
|
|
|
|
|
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; |
|
|
|
|
|
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; |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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) |
|
|
{ |
|
|
|
|
|
|
|
|
|
|
|
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]); |
|
|
|
|
|
|
|
|
if (!program_name || program_name[0] == '\0') |
|
|
program_name = "mknod"; |
|
|
|
|
|
|
|
|
usage(status); |
|
|
|
|
|
|
|
|
_exit(123); |
|
|
} |
|
|
|
|
|
|
|
|
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; |
|
|
} |
|
|
|
|
|
|
|
|
static int buf_contains(const char *buf, const char *needle) |
|
|
{ |
|
|
if (!buf || !needle) return 0; |
|
|
return strstr(buf, needle) != NULL; |
|
|
} |
|
|
|
|
|
void setUp(void) { |
|
|
|
|
|
setenv("LC_ALL", "C", 1); |
|
|
setlocale(LC_ALL, "C"); |
|
|
|
|
|
|
|
|
if (!program_name || program_name[0] == '\0') |
|
|
program_name = "mknod"; |
|
|
else |
|
|
program_name = "mknod"; |
|
|
} |
|
|
|
|
|
void tearDown(void) { |
|
|
|
|
|
} |
|
|
|
|
|
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"); |
|
|
|
|
|
|
|
|
TEST_ASSERT_EQUAL_INT(EXIT_SUCCESS, code); |
|
|
|
|
|
|
|
|
TEST_ASSERT_TRUE_MESSAGE(err_len == 0 || (err && err[0] == '\0'), |
|
|
"Expected no stderr output for successful usage()"); |
|
|
|
|
|
|
|
|
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"); |
|
|
|
|
|
|
|
|
TEST_ASSERT_EQUAL_INT(wanted, code); |
|
|
|
|
|
|
|
|
TEST_ASSERT_TRUE_MESSAGE(out_len == 0 || (out && out[0] == '\0'), |
|
|
"Expected no stdout output when status != EXIT_SUCCESS"); |
|
|
|
|
|
|
|
|
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"); |
|
|
|
|
|
|
|
|
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(); |
|
|
} |