| #include "../../unity/unity.h" |
| #include <unistd.h> |
| #include <fcntl.h> |
| #include <string.h> |
| #include <stdlib.h> |
| #include <stdio.h> |
| #include <errno.h> |
|
|
| |
| |
|
|
| |
| extern bool have_read_eof; |
| extern struct buffer_record *head; |
| extern char *hold_area; |
| extern idx_t hold_count; |
| extern intmax_t last_line_number; |
| extern intmax_t current_line; |
|
|
| |
| static void free_buffer (struct buffer_record *buf); |
|
|
| static int saved_stdin = -1; |
|
|
| |
| static int write_all(int fd, const void *buf, size_t len) { |
| const char *p = (const char*)buf; |
| size_t off = 0; |
| while (off < len) { |
| ssize_t w = write(fd, p + off, len - off); |
| if (w < 0) { |
| if (errno == EINTR) continue; |
| return -1; |
| } |
| off += (size_t)w; |
| } |
| return 0; |
| } |
|
|
| |
| static void set_stdin_from_data(const char *data, size_t len) { |
| int fds[2]; |
| TEST_ASSERT_EQUAL_INT(0, pipe(fds)); |
| |
| TEST_ASSERT_EQUAL_INT(0, write_all(fds[1], data, len)); |
| close(fds[1]); |
| |
| TEST_ASSERT_TRUE(fds[0] >= 0); |
| TEST_ASSERT_TRUE(saved_stdin >= 0); |
| TEST_ASSERT_NOT_EQUAL(-1, dup2(fds[0], STDIN_FILENO)); |
| close(fds[0]); |
| } |
|
|
| |
| static void free_all_buffers(void) { |
| while (head != NULL) { |
| struct buffer_record *b = head; |
| head = head->next; |
| free_buffer(b); |
| } |
| head = NULL; |
| } |
|
|
| |
| static void reset_internal_state(void) { |
| free_all_buffers(); |
| if (hold_area) { |
| free(hold_area); |
| hold_area = NULL; |
| } |
| hold_count = 0; |
| last_line_number = 0; |
| current_line = 0; |
| have_read_eof = false; |
| } |
|
|
| void setUp(void) { |
| |
| if (saved_stdin == -1) { |
| saved_stdin = dup(STDIN_FILENO); |
| TEST_ASSERT_TRUE(saved_stdin >= 0); |
| } |
| reset_internal_state(); |
| } |
|
|
| void tearDown(void) { |
| |
| if (saved_stdin >= 0) { |
| dup2(saved_stdin, STDIN_FILENO); |
| } |
| reset_internal_state(); |
| } |
|
|
| |
| void test_load_buffer_empty_input_returns_false(void) { |
| set_stdin_from_data("", 0); |
|
|
| bool got = load_buffer(); |
| TEST_ASSERT_FALSE(got); |
| TEST_ASSERT_EQUAL_PTR(NULL, head); |
| TEST_ASSERT_TRUE(have_read_eof); |
| TEST_ASSERT_EQUAL_INT64(0, last_line_number); |
| } |
|
|
| |
| void test_load_buffer_reads_multiple_short_lines(void) { |
| const char *data = "a\nb\nc\n"; |
| set_stdin_from_data(data, strlen(data)); |
|
|
| bool got = load_buffer(); |
| TEST_ASSERT_TRUE(got); |
| TEST_ASSERT_NOT_NULL(head); |
| TEST_ASSERT_EQUAL_INT(3, head->num_lines); |
| TEST_ASSERT_EQUAL_INT64(1, head->start_line); |
| TEST_ASSERT_EQUAL_INT64(1, head->first_available); |
|
|
| |
| struct line *l = head->line_start; |
| TEST_ASSERT_NOT_NULL(l); |
| TEST_ASSERT_TRUE(l->used >= 3); |
|
|
| struct cstring line0 = l->starts[0]; |
| struct cstring line1 = l->starts[1]; |
| struct cstring line2 = l->starts[2]; |
|
|
| TEST_ASSERT_EQUAL_INT(2, (int)line0.len); |
| TEST_ASSERT_EQUAL_INT(2, (int)line1.len); |
| TEST_ASSERT_EQUAL_INT(2, (int)line2.len); |
| TEST_ASSERT_TRUE(memcmp(line0.str, "a\n", 2) == 0); |
| TEST_ASSERT_TRUE(memcmp(line1.str, "b\n", 2) == 0); |
| TEST_ASSERT_TRUE(memcmp(line2.str, "c\n", 2) == 0); |
|
|
| |
| TEST_ASSERT_EQUAL_INT(0, (int)hold_count); |
| } |
|
|
| |
| void test_load_buffer_handles_long_line_larger_than_start_size(void) { |
| size_t long_len = (size_t)START_SIZE * 2 + 10; |
| char *buf = (char*)malloc(long_len + 1); |
| TEST_ASSERT_NOT_NULL(buf); |
| memset(buf, 'x', long_len); |
| buf[long_len - 1] = 'y'; |
| buf[long_len] = '\n'; |
|
|
| set_stdin_from_data(buf, long_len + 1); |
|
|
| bool got = load_buffer(); |
| TEST_ASSERT_TRUE(got); |
| TEST_ASSERT_NOT_NULL(head); |
| TEST_ASSERT_EQUAL_INT(1, head->num_lines); |
|
|
| struct line *l = head->line_start; |
| TEST_ASSERT_NOT_NULL(l); |
| TEST_ASSERT_TRUE(l->used >= 1); |
| struct cstring ln = l->starts[0]; |
| TEST_ASSERT_EQUAL_INT((int)(long_len + 1), (int)ln.len); |
| TEST_ASSERT_EQUAL_CHAR('x', ln.str[0]); |
| TEST_ASSERT_EQUAL_CHAR('y', ln.str[long_len - 1]); |
| TEST_ASSERT_EQUAL_CHAR('\n', ln.str[long_len]); |
|
|
| TEST_ASSERT_EQUAL_INT(0, (int)hold_count); |
|
|
| free(buf); |
| } |
|
|
| |
| |
| void test_load_buffer_incomplete_last_line_requires_second_call(void) { |
| const char *data = "foo\nbar"; |
| set_stdin_from_data(data, strlen(data)); |
|
|
| bool got1 = load_buffer(); |
| TEST_ASSERT_TRUE(got1); |
| TEST_ASSERT_NOT_NULL(head); |
| TEST_ASSERT_EQUAL_INT(1, head->num_lines); |
|
|
| |
| TEST_ASSERT_EQUAL_INT(3, (int)hold_count); |
| TEST_ASSERT_NOT_NULL(hold_area); |
| TEST_ASSERT_TRUE(memcmp(hold_area, "bar", 3) == 0); |
| TEST_ASSERT_FALSE(have_read_eof); |
|
|
| |
| bool got2 = load_buffer(); |
| TEST_ASSERT_TRUE(got2); |
|
|
| |
| TEST_ASSERT_NOT_NULL(head->next); |
| struct buffer_record *second = head->next; |
| TEST_ASSERT_EQUAL_INT(1, second->num_lines); |
|
|
| struct line *l2 = second->line_start; |
| TEST_ASSERT_NOT_NULL(l2); |
| struct cstring last = l2->starts[0]; |
| TEST_ASSERT_EQUAL_INT(3, (int)last.len); |
| TEST_ASSERT_TRUE(memcmp(last.str, "bar", 3) == 0); |
|
|
| TEST_ASSERT_TRUE(have_read_eof); |
| TEST_ASSERT_EQUAL_INT(0, (int)hold_count); |
| } |
|
|
| |
| void test_load_buffer_single_incomplete_line_only(void) { |
| const char *data = "xyz"; |
| set_stdin_from_data(data, strlen(data)); |
|
|
| bool got = load_buffer(); |
| TEST_ASSERT_TRUE(got); |
| TEST_ASSERT_NOT_NULL(head); |
| TEST_ASSERT_EQUAL_INT(1, head->num_lines); |
|
|
| struct line *l = head->line_start; |
| TEST_ASSERT_NOT_NULL(l); |
| struct cstring ln = l->starts[0]; |
| TEST_ASSERT_EQUAL_INT(3, (int)ln.len); |
| TEST_ASSERT_TRUE(memcmp(ln.str, "xyz", 3) == 0); |
|
|
| |
| TEST_ASSERT_EQUAL_INT(0, (int)hold_count); |
| } |
|
|
| |
| int main(void) { |
| UNITY_BEGIN(); |
| RUN_TEST(test_load_buffer_empty_input_returns_false); |
| RUN_TEST(test_load_buffer_reads_multiple_short_lines); |
| RUN_TEST(test_load_buffer_handles_long_line_larger_than_start_size); |
| RUN_TEST(test_load_buffer_incomplete_last_line_requires_second_call); |
| RUN_TEST(test_load_buffer_single_incomplete_line_only); |
| return UNITY_END(); |
| } |