| #include "../../unity/unity.h" |
| #include <stdlib.h> |
| #include <string.h> |
| #include <stdio.h> |
| #include <unistd.h> |
| #include <fcntl.h> |
| #include <errno.h> |
| #include <sys/wait.h> |
| #include <sys/types.h> |
|
|
| |
| |
|
|
| |
| static int saved_stdout_fd = -1; |
|
|
| static int redirect_stdout_to_fd(int newfd) |
| { |
| if (saved_stdout_fd != -1) |
| return -1; |
| int dupfd = dup(STDOUT_FILENO); |
| if (dupfd < 0) |
| return -1; |
| if (dup2(newfd, STDOUT_FILENO) < 0) |
| { |
| int e = errno; |
| close(dupfd); |
| errno = e; |
| return -1; |
| } |
| saved_stdout_fd = dupfd; |
| return 0; |
| } |
|
|
| static int redirect_stdout_to_devnull(void) |
| { |
| int devnull = open("/dev/null", O_WRONLY); |
| if (devnull < 0) |
| return -1; |
| int rc = redirect_stdout_to_fd(devnull); |
| int e = errno; |
| close(devnull); |
| errno = e; |
| return rc; |
| } |
|
|
| static int restore_stdout(void) |
| { |
| if (saved_stdout_fd == -1) |
| return -1; |
| int rc = dup2(saved_stdout_fd, STDOUT_FILENO); |
| int e = errno; |
| close(saved_stdout_fd); |
| saved_stdout_fd = -1; |
| errno = e; |
| return rc; |
| } |
|
|
| |
| static void fill_fd_nonblock_to_full(int fd) |
| { |
| char buf[4096]; |
| memset(buf, 0xCD, sizeof buf); |
| for (;;) |
| { |
| ssize_t wr = write(fd, buf, sizeof buf); |
| if (wr < 0) |
| { |
| if (errno == EAGAIN) |
| break; |
| |
| break; |
| } |
| else if (wr == 0) |
| { |
| |
| break; |
| } |
| |
| } |
| } |
|
|
| |
| static int parse_records_out(const char *buf, long long *full, long long *partial) |
| { |
| const char *p = strstr(buf, "records out"); |
| if (!p) |
| return -1; |
|
|
| |
| const char *line_start = p; |
| while (line_start > buf && line_start[-1] != '\n') |
| line_start--; |
|
|
| |
| long long f = -1, pa = -1; |
| |
| |
| |
| |
| if (sscanf(line_start, "%lld+%lld", &f, &pa) == 2) |
| { |
| if (full) *full = f; |
| if (partial) *partial = pa; |
| return 0; |
| } |
| return -1; |
| } |
|
|
| void setUp(void) { |
| |
| } |
|
|
| void tearDown(void) { |
| |
| if (saved_stdout_fd != -1) |
| restore_stdout(); |
| } |
|
|
| |
| void test_write_output_success_basic(void) |
| { |
| |
| output_blocksize = 1024; |
| oc = 123; |
| w_bytes = 0; |
| w_full = 0; |
| w_partial = 0; |
| conversions_mask = 0; |
| output_flags = 0; |
|
|
| |
| if (obuf) |
| { |
| |
| free(obuf); |
| obuf = NULL; |
| } |
| obuf = (char*)malloc(output_blocksize); |
| TEST_ASSERT_NOT_NULL(obuf); |
| memset(obuf, 'A', output_blocksize); |
|
|
| |
| TEST_ASSERT_EQUAL_INT(0, redirect_stdout_to_devnull()); |
|
|
| |
| write_output(); |
|
|
| |
| TEST_ASSERT_EQUAL_INT(0, restore_stdout()); |
|
|
| |
| TEST_ASSERT_EQUAL_INT64((int64_t)output_blocksize, (int64_t)w_bytes); |
| TEST_ASSERT_EQUAL_INT64(1, (int64_t)w_full); |
| TEST_ASSERT_EQUAL_INT64(0, (int64_t)w_partial); |
| TEST_ASSERT_EQUAL_UINT(0, oc); |
|
|
| |
| free(obuf); |
| obuf = NULL; |
| } |
|
|
| |
| void test_write_output_short_write_zero_bytes_exits(void) |
| { |
| fflush(stdout); |
| fflush(stderr); |
| pid_t pid = fork(); |
| TEST_ASSERT_TRUE(pid >= 0); |
|
|
| if (pid == 0) |
| { |
| |
|
|
| output_blocksize = 4096; |
| oc = 7; |
| w_bytes = 0; |
| w_full = 0; |
| w_partial = 0; |
| r_full = 0; |
| r_partial = 0; |
| conversions_mask = 0; |
| output_flags = 0; |
| output_file = "child_stdout_zero"; |
|
|
| if (obuf) |
| { |
| free(obuf); |
| obuf = NULL; |
| } |
| obuf = (char*)malloc(output_blocksize); |
| if (!obuf) _exit(123); |
| memset(obuf, 'B', output_blocksize); |
|
|
| int p[2]; |
| if (pipe(p) != 0) _exit(124); |
|
|
| |
| int flags = fcntl(p[1], F_GETFL); |
| if (flags < 0) _exit(125); |
| if (fcntl(p[1], F_SETFL, flags | O_NONBLOCK) != 0) _exit(126); |
|
|
| if (dup2(p[1], STDOUT_FILENO) < 0) _exit(127); |
| close(p[1]); |
|
|
| |
| |
| fill_fd_nonblock_to_full(STDOUT_FILENO); |
|
|
| |
| write_output(); |
|
|
| |
| _exit(200); |
| } |
| else |
| { |
| int status = 0; |
| waitpid(pid, &status, 0); |
| TEST_ASSERT_TRUE(WIFEXITED(status)); |
| TEST_ASSERT_EQUAL_INT(EXIT_FAILURE, WEXITSTATUS(status)); |
| } |
| } |
|
|
| |
| void test_write_output_short_write_nonzero_increments_partial(void) |
| { |
| fflush(stdout); |
| fflush(stderr); |
| pid_t pid = fork(); |
| TEST_ASSERT_TRUE(pid >= 0); |
|
|
| if (pid == 0) |
| { |
| |
| output_blocksize = 4096; |
| oc = 5; |
| w_bytes = 0; |
| w_full = 0; |
| w_partial = 0; |
| r_full = 0; |
| r_partial = 0; |
| conversions_mask = 0; |
| output_flags = 0; |
| output_file = "child_stdout_partial"; |
|
|
| if (obuf) |
| { |
| free(obuf); |
| obuf = NULL; |
| } |
| obuf = (char*)malloc(output_blocksize); |
| if (!obuf) _exit(223); |
| memset(obuf, 'C', output_blocksize); |
|
|
| int pout[2]; |
| if (pipe(pout) != 0) _exit(224); |
| |
| if (dup2(pout[1], STDERR_FILENO) < 0) _exit(225); |
| close(pout[1]); |
| int perr_read = pout[0]; |
|
|
| int p[2]; |
| if (pipe(p) != 0) _exit(226); |
|
|
| |
| int flags = fcntl(p[1], F_GETFL); |
| if (flags < 0) _exit(227); |
| if (fcntl(p[1], F_SETFL, flags | O_NONBLOCK) != 0) _exit(228); |
| if (dup2(p[1], STDOUT_FILENO) < 0) _exit(229); |
| close(p[1]); |
|
|
| |
| fill_fd_nonblock_to_full(STDOUT_FILENO); |
| char tmp; |
| ssize_t r = read(p[0], &tmp, 1); |
| (void)r; |
| |
|
|
| |
| write_output(); |
|
|
| _exit(240); |
| } |
| else |
| { |
| |
| int status = 0; |
| waitpid(pid, &status, 0); |
| TEST_ASSERT_TRUE(WIFEXITED(status)); |
| TEST_ASSERT_EQUAL_INT(EXIT_FAILURE, WEXITSTATUS(status)); |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| int cap[2]; |
| TEST_ASSERT_EQUAL_INT(0, pipe(cap)); |
| pid_t pid2 = fork(); |
| TEST_ASSERT_TRUE(pid2 >= 0); |
| if (pid2 == 0) |
| { |
| |
| output_blocksize = 4096; |
| oc = 5; |
| w_bytes = 0; |
| w_full = 0; |
| w_partial = 0; |
| r_full = 0; |
| r_partial = 0; |
| conversions_mask = 0; |
| output_flags = 0; |
| output_file = "child2_stdout_partial"; |
|
|
| if (obuf) |
| { |
| free(obuf); |
| obuf = NULL; |
| } |
| obuf = (char*)malloc(output_blocksize); |
| if (!obuf) _exit(253); |
| memset(obuf, 'D', output_blocksize); |
|
|
| |
| if (dup2(cap[1], STDERR_FILENO) < 0) _exit(254); |
| close(cap[1]); |
| |
| int p[2]; |
| if (pipe(p) != 0) _exit(255); |
| int flags = fcntl(p[1], F_GETFL); |
| if (flags < 0) _exit(256); |
| if (fcntl(p[1], F_SETFL, flags | O_NONBLOCK) != 0) _exit(257); |
| if (dup2(p[1], STDOUT_FILENO) < 0) _exit(258); |
| close(p[1]); |
|
|
| fill_fd_nonblock_to_full(STDOUT_FILENO); |
| char tmp; |
| (void)read(p[0], &tmp, 1); |
|
|
| write_output(); |
|
|
| _exit(260); |
| } |
| else |
| { |
| close(cap[1]); |
| char buffer[8192]; |
| ssize_t total = 0; |
| for (;;) |
| { |
| ssize_t nr = read(cap[0], buffer + total, sizeof(buffer) - total - 1); |
| if (nr > 0) |
| { |
| total += nr; |
| if ((size_t)total >= sizeof(buffer) - 1) |
| break; |
| } |
| else if (nr == 0) |
| break; |
| else if (errno == EINTR) |
| continue; |
| else |
| break; |
| } |
| if (total < (ssize_t)sizeof(buffer)) |
| buffer[total] = '\0'; |
| else |
| buffer[sizeof(buffer) - 1] = '\0'; |
| close(cap[0]); |
|
|
| int st2 = 0; |
| waitpid(pid2, &st2, 0); |
| TEST_ASSERT_TRUE(WIFEXITED(st2)); |
| TEST_ASSERT_EQUAL_INT(EXIT_FAILURE, WEXITSTATUS(st2)); |
|
|
| long long full = -1, partial = -1; |
| |
| TEST_ASSERT_EQUAL_INT(0, parse_records_out(buffer, &full, &partial)); |
| TEST_ASSERT_EQUAL_INT64(0, full); |
| TEST_ASSERT_EQUAL_INT64(1, partial); |
| } |
| } |
| } |
|
|
| int main(void) |
| { |
| UNITY_BEGIN(); |
| RUN_TEST(test_write_output_success_basic); |
| RUN_TEST(test_write_output_short_write_zero_bytes_exits); |
| RUN_TEST(test_write_output_short_write_nonzero_increments_partial); |
| return UNITY_END(); |
| } |