|
|
#include "../../unity/unity.h" |
|
|
#include <stdlib.h> |
|
|
#include <string.h> |
|
|
#include <unistd.h> |
|
|
#include <sys/types.h> |
|
|
#include <sys/wait.h> |
|
|
#include <errno.h> |
|
|
#include <stdio.h> |
|
|
|
|
|
|
|
|
extern char const *program_name; |
|
|
|
|
|
|
|
|
void usage (int status); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
static int run_usage_and_capture(int status_to_pass, int capture_fd, |
|
|
const char *progname, |
|
|
char **out_buf, int *exit_code) |
|
|
{ |
|
|
if (!out_buf || !exit_code) return -1; |
|
|
*out_buf = NULL; |
|
|
*exit_code = -1; |
|
|
|
|
|
int p[2]; |
|
|
if (pipe(p) < 0) |
|
|
return -1; |
|
|
|
|
|
fflush(stdout); |
|
|
fflush(stderr); |
|
|
pid_t pid = fork(); |
|
|
if (pid < 0) |
|
|
{ |
|
|
int saved = errno; |
|
|
close(p[0]); close(p[1]); |
|
|
errno = saved; |
|
|
return -1; |
|
|
} |
|
|
|
|
|
if (pid == 0) |
|
|
{ |
|
|
|
|
|
|
|
|
close(p[0]); |
|
|
|
|
|
if (dup2(p[1], capture_fd) < 0) |
|
|
{ |
|
|
|
|
|
_exit(127); |
|
|
} |
|
|
|
|
|
close(p[1]); |
|
|
|
|
|
|
|
|
program_name = progname ? progname : "mv"; |
|
|
|
|
|
|
|
|
usage(status_to_pass); |
|
|
|
|
|
|
|
|
_exit(126); |
|
|
} |
|
|
else |
|
|
{ |
|
|
|
|
|
close(p[1]); |
|
|
|
|
|
size_t cap = 1024; |
|
|
size_t len = 0; |
|
|
char *buf = (char *)malloc(cap); |
|
|
if (!buf) |
|
|
{ |
|
|
close(p[0]); |
|
|
|
|
|
int st; waitpid(pid, &st, 0); |
|
|
return -1; |
|
|
} |
|
|
|
|
|
ssize_t n; |
|
|
while ((n = read(p[0], buf + len, cap - len)) > 0) |
|
|
{ |
|
|
len += (size_t)n; |
|
|
if (len == cap) |
|
|
{ |
|
|
size_t newcap = cap * 2; |
|
|
char *nb = (char *)realloc(buf, newcap); |
|
|
if (!nb) |
|
|
{ |
|
|
free(buf); |
|
|
close(p[0]); |
|
|
int st; waitpid(pid, &st, 0); |
|
|
return -1; |
|
|
} |
|
|
buf = nb; |
|
|
cap = newcap; |
|
|
} |
|
|
} |
|
|
close(p[0]); |
|
|
|
|
|
|
|
|
if (len == cap) |
|
|
{ |
|
|
char *nb = (char *)realloc(buf, cap + 1); |
|
|
if (!nb) |
|
|
{ |
|
|
free(buf); |
|
|
int st; waitpid(pid, &st, 0); |
|
|
return -1; |
|
|
} |
|
|
buf = nb; |
|
|
} |
|
|
buf[len] = '\0'; |
|
|
|
|
|
int st = 0; |
|
|
if (waitpid(pid, &st, 0) < 0) |
|
|
{ |
|
|
free(buf); |
|
|
return -1; |
|
|
} |
|
|
|
|
|
*out_buf = buf; |
|
|
if (WIFEXITED(st)) |
|
|
*exit_code = WEXITSTATUS(st); |
|
|
else |
|
|
*exit_code = -1; |
|
|
|
|
|
return 0; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
static int contains_substr(const char *haystack, const char *needle) |
|
|
{ |
|
|
return haystack && needle && strstr(haystack, needle) != NULL; |
|
|
} |
|
|
|
|
|
void setUp(void) { |
|
|
|
|
|
} |
|
|
|
|
|
void tearDown(void) { |
|
|
|
|
|
} |
|
|
|
|
|
void test_usage_success_writes_help_to_stdout_and_exits_zero(void) |
|
|
{ |
|
|
char *out = NULL; |
|
|
int code = -1; |
|
|
int rc = run_usage_and_capture(0, STDOUT_FILENO, "mv", &out, &code); |
|
|
TEST_ASSERT_EQUAL_INT(0, rc); |
|
|
TEST_ASSERT_NOT_NULL(out); |
|
|
TEST_ASSERT_EQUAL_INT(0, code); |
|
|
|
|
|
|
|
|
TEST_ASSERT_TRUE(contains_substr(out, "Usage:")); |
|
|
TEST_ASSERT_TRUE(contains_substr(out, "mv")); |
|
|
TEST_ASSERT_TRUE(contains_substr(out, "Rename SOURCE to DEST")); |
|
|
TEST_ASSERT_TRUE(contains_substr(out, "--backup")); |
|
|
TEST_ASSERT_TRUE(contains_substr(out, "--verbose")); |
|
|
|
|
|
free(out); |
|
|
} |
|
|
|
|
|
void test_usage_failure_writes_try_help_to_stderr_and_exits_nonzero(void) |
|
|
{ |
|
|
char *err = NULL; |
|
|
int code = -1; |
|
|
int rc = run_usage_and_capture(1, STDERR_FILENO, "mv", &err, &code); |
|
|
TEST_ASSERT_EQUAL_INT(0, rc); |
|
|
TEST_ASSERT_NOT_NULL(err); |
|
|
TEST_ASSERT_EQUAL_INT(1, code); |
|
|
|
|
|
|
|
|
TEST_ASSERT_TRUE(contains_substr(err, "Try")); |
|
|
TEST_ASSERT_TRUE(contains_substr(err, "mv")); |
|
|
TEST_ASSERT_TRUE(contains_substr(err, "--help")); |
|
|
|
|
|
free(err); |
|
|
} |
|
|
|
|
|
void test_usage_success_does_not_write_to_stderr(void) |
|
|
{ |
|
|
char *err = NULL; |
|
|
int code = -1; |
|
|
int rc = run_usage_and_capture(0, STDERR_FILENO, "mv", &err, &code); |
|
|
TEST_ASSERT_EQUAL_INT(0, rc); |
|
|
TEST_ASSERT_NOT_NULL(err); |
|
|
TEST_ASSERT_EQUAL_INT(0, code); |
|
|
|
|
|
|
|
|
TEST_ASSERT_EQUAL_UINT32(0u, (unsigned)strlen(err)); |
|
|
|
|
|
free(err); |
|
|
} |
|
|
|
|
|
void test_usage_failure_does_not_write_to_stdout(void) |
|
|
{ |
|
|
char *out = NULL; |
|
|
int code = -1; |
|
|
int rc = run_usage_and_capture(1, STDOUT_FILENO, "mv", &out, &code); |
|
|
TEST_ASSERT_EQUAL_INT(0, rc); |
|
|
TEST_ASSERT_NOT_NULL(out); |
|
|
TEST_ASSERT_EQUAL_INT(1, code); |
|
|
|
|
|
|
|
|
TEST_ASSERT_EQUAL_UINT32(0u, (unsigned)strlen(out)); |
|
|
|
|
|
free(out); |
|
|
} |
|
|
|
|
|
void test_usage_uses_program_name_variable(void) |
|
|
{ |
|
|
const char *custom = "my-mv"; |
|
|
char *out = NULL; |
|
|
int code = -1; |
|
|
int rc = run_usage_and_capture(0, STDOUT_FILENO, custom, &out, &code); |
|
|
TEST_ASSERT_EQUAL_INT(0, rc); |
|
|
TEST_ASSERT_NOT_NULL(out); |
|
|
TEST_ASSERT_EQUAL_INT(0, code); |
|
|
|
|
|
|
|
|
TEST_ASSERT_TRUE(contains_substr(out, "Usage: my-mv")); |
|
|
|
|
|
free(out); |
|
|
} |
|
|
|
|
|
int main(void) |
|
|
{ |
|
|
UNITY_BEGIN(); |
|
|
RUN_TEST(test_usage_success_writes_help_to_stdout_and_exits_zero); |
|
|
RUN_TEST(test_usage_failure_writes_try_help_to_stderr_and_exits_nonzero); |
|
|
RUN_TEST(test_usage_success_does_not_write_to_stderr); |
|
|
RUN_TEST(test_usage_failure_does_not_write_to_stdout); |
|
|
RUN_TEST(test_usage_uses_program_name_variable); |
|
|
return UNITY_END(); |
|
|
} |