#include "../../unity/unity.h" #include #include #include #include #include #include /* The test file is included at the end of the csplit source, so we have access to the internal static globals and functions used by load_buffer. */ /* Forward declarations for static symbols from the program (available due to inclusion). */ 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 helper from program; available due to inclusion. */ static void free_buffer (struct buffer_record *buf); static int saved_stdin = -1; /* Helper: write all bytes */ 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; } /* Helper: set STDIN to a pipe providing 'data' (len bytes). */ static void set_stdin_from_data(const char *data, size_t len) { int fds[2]; TEST_ASSERT_EQUAL_INT(0, pipe(fds)); /* Write all data then close write-end to signal EOF. */ TEST_ASSERT_EQUAL_INT(0, write_all(fds[1], data, len)); close(fds[1]); /* Redirect read end to STDIN */ 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]); } /* Helper: free any buffers in the linked list and reset pointer */ static void free_all_buffers(void) { while (head != NULL) { struct buffer_record *b = head; head = head->next; free_buffer(b); } head = NULL; } /* Helper: reset all internal state related to buffering/reading. */ 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) { /* Save original stdin once */ if (saved_stdin == -1) { saved_stdin = dup(STDIN_FILENO); TEST_ASSERT_TRUE(saved_stdin >= 0); } reset_internal_state(); } void tearDown(void) { /* Restore stdin */ if (saved_stdin >= 0) { dup2(saved_stdin, STDIN_FILENO); } reset_internal_state(); } /* Test: empty input -> load_buffer returns false, no buffers created, EOF seen. */ 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); } /* Test: multiple short lines in a single buffer. */ 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); /* Verify contents of lines */ struct line *l = head->line_start; TEST_ASSERT_NOT_NULL(l); TEST_ASSERT_TRUE(l->used >= 3); /* All three are in first control block for small count */ 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); /* Because we didn't exhaust input via remove_line(), have_read_eof may be false */ TEST_ASSERT_EQUAL_INT(0, (int)hold_count); } /* Test: a very long line exceeding START_SIZE should force buffer growth and still succeed. */ 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'; /* just to vary */ 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); } /* Test: input with incomplete last line should leave remainder in hold_area after first call, and a second call should create a new buffer holding that final partial line. */ 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); /* After first call, the trailing 'bar' should be in hold_area with hold_count == 3 */ 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); /* We haven't seen EOF in the first successful read */ /* Call load_buffer again to process the held partial line (now at EOF) */ bool got2 = load_buffer(); TEST_ASSERT_TRUE(got2); /* Now head should have the first buffer, and head->next the second buffer with the partial line */ 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); } /* Test: single incomplete line only (no newline at all) should still produce one line in a single call. */ 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); /* After success, no leftover hold_area */ TEST_ASSERT_EQUAL_INT(0, (int)hold_count); } /* Unity main */ 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(); }