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