| #include "../../unity/unity.h" |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <stdbool.h> |
| #include <stdint.h> |
| #include <unistd.h> |
| #include <fcntl.h> |
| #include <sys/types.h> |
| #include <sys/stat.h> |
| #include <errno.h> |
|
|
| |
| |
|
|
| extern void dummy_reference_to_quiet_linker(void); |
|
|
| |
| |
| |
|
|
| |
| static int make_input_pipe(const char *data, size_t len) { |
| int p[2]; |
| if (pipe(p) != 0) return -1; |
| ssize_t off = 0; |
| while ((size_t)off < len) { |
| ssize_t w = write(p[1], data + off, len - off); |
| if (w < 0) { close(p[0]); close(p[1]); return -1; } |
| off += w; |
| } |
| close(p[1]); |
| return p[0]; |
| } |
|
|
| |
| static int begin_capture_stdout(int *saved_stdout_fd, int *capture_read_fd) { |
| int p[2]; |
| if (pipe(p) != 0) return -1; |
| if (fflush(stdout) != 0) { |
| close(p[0]); close(p[1]); return -1; |
| } |
| int saved = dup(STDOUT_FILENO); |
| if (saved < 0) { close(p[0]); close(p[1]); return -1; } |
| if (dup2(p[1], STDOUT_FILENO) < 0) { |
| close(saved); close(p[0]); close(p[1]); return -1; |
| } |
| close(p[1]); |
| *saved_stdout_fd = saved; |
| *capture_read_fd = p[0]; |
| return 0; |
| } |
|
|
| |
| static char *end_capture_stdout(int saved_stdout_fd, int capture_read_fd, size_t *out_len) { |
| |
| fflush(stdout); |
| dup2(saved_stdout_fd, STDOUT_FILENO); |
| close(saved_stdout_fd); |
|
|
| |
| size_t cap = 1024; |
| size_t len = 0; |
| char *buf = (char *)malloc(cap); |
| if (!buf) { close(capture_read_fd); return NULL; } |
|
|
| while (1) { |
| char tmp[4096]; |
| ssize_t r = read(capture_read_fd, tmp, sizeof tmp); |
| if (r < 0) { free(buf); close(capture_read_fd); return NULL; } |
| if (r == 0) break; |
| if (len + (size_t)r > cap) { |
| size_t new_cap = (cap * 2 > len + (size_t)r) ? cap * 2 : len + (size_t)r; |
| char *nb = (char *)realloc(buf, new_cap); |
| if (!nb) { free(buf); close(capture_read_fd); return NULL; } |
| buf = nb; cap = new_cap; |
| } |
| memcpy(buf + len, tmp, r); |
| len += (size_t)r; |
| } |
| close(capture_read_fd); |
| *out_len = len; |
| return buf; |
| } |
|
|
| |
| static int run_head_lines_and_capture(int input_fd, const char *fname, uintmax_t n_lines, |
| bool *ret_ok, char **out_buf, size_t *out_len) { |
| int saved_out = -1, cap_r = -1; |
| if (begin_capture_stdout(&saved_out, &cap_r) != 0) { |
| return -1; |
| } |
|
|
| |
| bool ok = head_lines(fname, input_fd, n_lines); |
|
|
| |
| size_t got_len = 0; |
| char *data = end_capture_stdout(saved_out, cap_r, &got_len); |
| if (!data) { |
| *ret_ok = ok; |
| return -2; |
| } |
|
|
| *ret_ok = ok; |
| *out_buf = data; |
| *out_len = got_len; |
| return 0; |
| } |
|
|
| |
| static int make_temp_regular_file(const char *data, size_t len, char *out_path, size_t out_path_sz) { |
| const char *dir = "/tmp"; |
| char templ[256]; |
| snprintf(templ, sizeof templ, "%s/head_lines_test_XXXXXX", dir); |
| int tfd = mkstemp(templ); |
| if (tfd < 0) return -1; |
| |
| ssize_t off = 0; |
| while ((size_t)off < len) { |
| ssize_t w = write(tfd, data + off, len - off); |
| if (w < 0) { close(tfd); unlink(templ); return -1; } |
| off += w; |
| } |
| |
| if (lseek(tfd, 0, SEEK_SET) < 0) { close(tfd); unlink(templ); return -1; } |
|
|
| |
| int ro_fd = open(templ, O_RDONLY); |
| if (ro_fd < 0) { close(tfd); unlink(templ); return -1; } |
|
|
| |
| if (out_path && out_path_sz > 0) { |
| snprintf(out_path, out_path_sz, "%s", templ); |
| } |
|
|
| |
| unlink(templ); |
| close(tfd); |
| return ro_fd; |
| } |
|
|
| void setUp(void) { |
| |
| extern char line_end; |
| line_end = '\n'; |
| } |
|
|
| void tearDown(void) { |
| |
| } |
|
|
| static void assert_mem_eq(const char *exp, size_t exp_len, const char *got, size_t got_len) { |
| TEST_ASSERT_EQUAL_UINT64_MESSAGE((uint64_t)exp_len, (uint64_t)got_len, "Length mismatch"); |
| if (exp_len == got_len && exp_len > 0) { |
| TEST_ASSERT_EQUAL_INT_MESSAGE(0, memcmp(exp, got, exp_len), "Content mismatch"); |
| } |
| } |
|
|
| void test_head_lines_zero_lines(void) { |
| const char *in = "x\ny\nz\n"; |
| int fd = make_input_pipe(in, strlen(in)); |
| TEST_ASSERT_TRUE_MESSAGE(fd >= 0, "Failed to create input pipe"); |
|
|
| bool ok = false; char *out = NULL; size_t out_len = 0; |
| int rc = run_head_lines_and_capture(fd, "pipe", 0, &ok, &out, &out_len); |
| close(fd); |
|
|
| TEST_ASSERT_EQUAL_INT_MESSAGE(0, rc, "Capture failed"); |
| TEST_ASSERT_TRUE_MESSAGE(ok, "head_lines returned false for zero lines"); |
| TEST_ASSERT_EQUAL_UINT64(0, (uint64_t)out_len); |
| free(out); |
| } |
|
|
| void test_head_lines_basic_two_lines(void) { |
| const char *in = "a\nb\nc\n"; |
| const char *exp = "a\nb\n"; |
| int fd = make_input_pipe(in, strlen(in)); |
| TEST_ASSERT_TRUE(fd >= 0); |
|
|
| bool ok = false; char *out = NULL; size_t out_len = 0; |
| int rc = run_head_lines_and_capture(fd, "pipe", 2, &ok, &out, &out_len); |
| close(fd); |
|
|
| TEST_ASSERT_EQUAL_INT(0, rc); |
| TEST_ASSERT_TRUE(ok); |
| assert_mem_eq(exp, strlen(exp), out, out_len); |
| free(out); |
| } |
|
|
| void test_head_lines_more_than_available(void) { |
| const char *in = "x\ny\n"; |
| int fd = make_input_pipe(in, strlen(in)); |
| TEST_ASSERT_TRUE(fd >= 0); |
|
|
| bool ok = false; char *out = NULL; size_t out_len = 0; |
| int rc = run_head_lines_and_capture(fd, "pipe", 10, &ok, &out, &out_len); |
| close(fd); |
|
|
| TEST_ASSERT_EQUAL_INT(0, rc); |
| TEST_ASSERT_TRUE(ok); |
| assert_mem_eq(in, strlen(in), out, out_len); |
| free(out); |
| } |
|
|
| void test_head_lines_no_trailing_newline(void) { |
| const char *in = "one\ntwo"; |
| int fd = make_input_pipe(in, strlen(in)); |
| TEST_ASSERT_TRUE(fd >= 0); |
|
|
| bool ok = false; char *out = NULL; size_t out_len = 0; |
| int rc = run_head_lines_and_capture(fd, "pipe", 2, &ok, &out, &out_len); |
| close(fd); |
|
|
| TEST_ASSERT_EQUAL_INT(0, rc); |
| TEST_ASSERT_TRUE(ok); |
| assert_mem_eq(in, strlen(in), out, out_len); |
| free(out); |
| } |
|
|
| void test_head_lines_multiple_reads_and_limit(void) { |
| |
| const int total_lines = 3000; |
| const int want_lines = 1500; |
| size_t in_len = (size_t)total_lines * 2; |
| char *in = (char *)malloc(in_len); |
| TEST_ASSERT_NOT_NULL(in); |
| for (int i = 0; i < total_lines; i++) { in[2*i] = 'x'; in[2*i + 1] = '\n'; } |
|
|
| int fd = make_input_pipe(in, in_len); |
| TEST_ASSERT_TRUE(fd >= 0); |
|
|
| bool ok = false; char *out = NULL; size_t out_len = 0; |
| int rc = run_head_lines_and_capture(fd, "pipe", (uintmax_t)want_lines, &ok, &out, &out_len); |
| close(fd); |
|
|
| TEST_ASSERT_EQUAL_INT(0, rc); |
| TEST_ASSERT_TRUE(ok); |
|
|
| size_t exp_len = (size_t)want_lines * 2; |
| TEST_ASSERT_EQUAL_UINT64((uint64_t)exp_len, (uint64_t)out_len); |
| for (size_t i = 0; i < out_len; i += 2) { |
| TEST_ASSERT_EQUAL_CHAR('x', out[i]); |
| TEST_ASSERT_EQUAL_CHAR('\n', out[i+1]); |
| } |
|
|
| free(out); |
| free(in); |
| } |
|
|
| void test_head_lines_regular_file_seeks_back(void) { |
| const char *in = "A\nB"; |
| char pathbuf[256]; |
| int fd = make_temp_regular_file(in, strlen(in), pathbuf, sizeof pathbuf); |
| TEST_ASSERT_TRUE_MESSAGE(fd >= 0, "Failed to create temp regular file"); |
|
|
| bool ok = false; char *out = NULL; size_t out_len = 0; |
| int rc = run_head_lines_and_capture(fd, pathbuf, 1, &ok, &out, &out_len); |
|
|
| TEST_ASSERT_EQUAL_INT(0, rc); |
| TEST_ASSERT_TRUE(ok); |
| const char *exp = "A\n"; |
| assert_mem_eq(exp, strlen(exp), out, out_len); |
|
|
| |
| off_t pos = lseek(fd, 0, SEEK_CUR); |
| TEST_ASSERT_EQUAL_INT(2, (int)pos); |
|
|
| |
| char c; |
| ssize_t r = read(fd, &c, 1); |
| TEST_ASSERT_EQUAL_INT(1, (int)r); |
| TEST_ASSERT_EQUAL_CHAR('B', c); |
|
|
| close(fd); |
| free(out); |
| } |
|
|
| void test_head_lines_zero_terminated_delimiter(void) { |
| extern char line_end; |
| line_end = '\0'; |
|
|
| |
| const char raw_in[] = { 'o','n','e','\0','t','w','o','\0','t','a','i','l' }; |
| const size_t raw_in_len = sizeof raw_in; |
| const char raw_exp[] = { 'o','n','e','\0','t','w','o','\0' }; |
| const size_t raw_exp_len = sizeof raw_exp; |
|
|
| int fd = make_input_pipe(raw_in, raw_in_len); |
| TEST_ASSERT_TRUE(fd >= 0); |
|
|
| bool ok = false; char *out = NULL; size_t out_len = 0; |
| int rc = run_head_lines_and_capture(fd, "pipe-z", 2, &ok, &out, &out_len); |
| close(fd); |
|
|
| TEST_ASSERT_EQUAL_INT(0, rc); |
| TEST_ASSERT_TRUE(ok); |
| assert_mem_eq(raw_exp, raw_exp_len, out, out_len); |
|
|
| free(out); |
|
|
| |
| line_end = '\n'; |
| } |
|
|
| void test_head_lines_read_error_invalid_fd(void) { |
| |
| bool ok = true; char *out = NULL; size_t out_len = 0; |
|
|
| |
| int saved_out = -1, cap_r = -1; |
| TEST_ASSERT_EQUAL_INT(0, begin_capture_stdout(&saved_out, &cap_r)); |
| bool ret = head_lines("invalid-fd", -1, 1); |
| char *captured = end_capture_stdout(saved_out, cap_r, &out_len); |
| |
| free(captured); |
|
|
| TEST_ASSERT_FALSE_MESSAGE(ret, "head_lines should return false on read error"); |
| } |
|
|
| int main(void) { |
| UNITY_BEGIN(); |
| RUN_TEST(test_head_lines_zero_lines); |
| RUN_TEST(test_head_lines_basic_two_lines); |
| RUN_TEST(test_head_lines_more_than_available); |
| RUN_TEST(test_head_lines_no_trailing_newline); |
| RUN_TEST(test_head_lines_multiple_reads_and_limit); |
| RUN_TEST(test_head_lines_regular_file_seeks_back); |
| RUN_TEST(test_head_lines_zero_terminated_delimiter); |
| RUN_TEST(test_head_lines_read_error_invalid_fd); |
| return UNITY_END(); |
| } |