#include "../../unity/unity.h" #include #include #include /* The test file is included after the program, so we can access the program's internal (file-scope static) data and helpers directly. */ /* Forward declarations for clarity are unnecessary since we're included after definitions, but we annotate intent here: we use - static bool get_line (FILE *fp, struct line **linep, int which) - static void freeline (struct line *line) - static void free_spareline (void) and we access globals like prevline, spareline, line_no, eolchar, check_input_order, join_field_1/2, issued_disorder_warning, etc. */ /* Helper: create a temporary FILE* preloaded with the provided data. */ static FILE* make_temp_file_with(const char* data) { FILE* fp = tmpfile(); TEST_ASSERT_NOT_NULL_MESSAGE(fp, "tmpfile failed"); size_t len = strlen(data); if (len) { size_t nw = fwrite(data, 1, len, fp); TEST_ASSERT_EQUAL_size_t(len, nw); } rewind(fp); return fp; } /* Helper: free and null any prevline/spareline state to keep tests isolated. */ static void cleanup_lines_state(void) { for (int i = 0; i < 2; ++i) { if (prevline[i]) { freeline(prevline[i]); free(prevline[i]); prevline[i] = NULL; } if (spareline[i]) { freeline(spareline[i]); free(spareline[i]); spareline[i] = NULL; } } } void setUp(void) { /* Reset line-related state */ cleanup_lines_state(); line_no[0] = 0; line_no[1] = 0; /* Reset parsing and behavior globals to simple defaults */ eolchar = '\n'; /* Ensure we never call check_order during these tests */ check_input_order = CHECK_ORDER_DISABLED; /* Reset other global flags that could affect behavior */ seen_unpairable = false; issued_disorder_warning[0] = false; issued_disorder_warning[1] = false; join_field_1 = -1; join_field_2 = -1; } void tearDown(void) { cleanup_lines_state(); } static void assert_field_eq(const struct field* f, const char* expect) { size_t elen = strlen(expect); TEST_ASSERT_EQUAL_size_t(elen, (size_t)f->len); TEST_ASSERT_EQUAL_INT(0, memcmp(f->beg, expect, elen)); } void test_get_line_basic_parsing_file1(void) { FILE* fp = make_temp_file_with("a b c\n"); struct line* line = NULL; bool ok = get_line(fp, &line, 1); TEST_ASSERT_TRUE(ok); TEST_ASSERT_NOT_NULL(line); TEST_ASSERT_NOT_NULL(line->buf.buffer); TEST_ASSERT_EQUAL_UINT64(1, line_no[0]); TEST_ASSERT_EQUAL_UINT64(0, line_no[1]); TEST_ASSERT_EQUAL_PTR(line, prevline[0]); /* Basic field parsing with default blank separators */ TEST_ASSERT_EQUAL_INT(3, (int)line->nfields); assert_field_eq(&line->fields[0], "a"); assert_field_eq(&line->fields[1], "b"); assert_field_eq(&line->fields[2], "c"); /* Buffer should end with eolchar */ TEST_ASSERT_TRUE(line->buf.length > 0); TEST_ASSERT_EQUAL_CHAR('\n', line->buf.buffer[line->buf.length - 1]); fclose(fp); } void test_get_line_spareline_swap_prevents_overwrite(void) { /* Two lines; ensure spareline swap triggers on second read when *linep equals prevline */ FILE* fp = make_temp_file_with("x y\nxy z\n"); struct line* line = NULL; bool ok1 = get_line(fp, &line, 1); TEST_ASSERT_TRUE(ok1); struct line* first_line = line; /* This equals prevline[0] now */ /* Read next line using the same line pointer; this triggers the swap to spareline */ bool ok2 = get_line(fp, &line, 1); TEST_ASSERT_TRUE(ok2); /* After swap, spareline[0] should hold the previous line, and prevline[0] should be the new line */ TEST_ASSERT_EQUAL_PTR(first_line, spareline[0]); TEST_ASSERT_EQUAL_PTR(line, prevline[0]); TEST_ASSERT_EQUAL_UINT64(2, line_no[0]); /* Verify content of the newly read line */ TEST_ASSERT_NOT_NULL(line); TEST_ASSERT_EQUAL_INT(2, (int)line->nfields); assert_field_eq(&line->fields[0], "xy"); assert_field_eq(&line->fields[1], "z"); /* Ensure the first line's data remains intact in spareline */ TEST_ASSERT_NOT_NULL(spareline[0]); TEST_ASSERT_TRUE(spareline[0]->nfields >= 2); assert_field_eq(&spareline[0]->fields[0], "x"); assert_field_eq(&spareline[0]->fields[1], "y"); fclose(fp); } void test_get_line_empty_line_zero_fields(void) { FILE* fp = make_temp_file_with("\n"); struct line* line = NULL; bool ok = get_line(fp, &line, 1); TEST_ASSERT_TRUE(ok); TEST_ASSERT_NOT_NULL(line); TEST_ASSERT_EQUAL_INT(0, (int)line->nfields); fclose(fp); } void test_get_line_eof_returns_false_and_frees_buffers(void) { FILE* fp = make_temp_file_with("a\n"); struct line* line = NULL; bool ok1 = get_line(fp, &line, 1); TEST_ASSERT_TRUE(ok1); TEST_ASSERT_NOT_NULL(line); struct line* prev = line; /* equals prevline[0] */ /* Next call should hit EOF. Since we pass the same pointer, swap logic runs. */ bool ok2 = get_line(fp, &line, 1); TEST_ASSERT_FALSE(ok2); /* After false return, the (new) line struct remains allocated but buffers are freed. */ TEST_ASSERT_NOT_NULL(line); TEST_ASSERT_NULL(line->buf.buffer); TEST_ASSERT_NULL(line->fields); /* prevline[0] remains the previous successfully-read line; swap set spareline[0]=prev too */ TEST_ASSERT_EQUAL_PTR(prev, prevline[0]); TEST_ASSERT_EQUAL_PTR(prev, spareline[0]); fclose(fp); } void test_get_line_parsing_with_leading_blanks(void) { FILE* fp = make_temp_file_with(" hello world \n"); struct line* line = NULL; bool ok = get_line(fp, &line, 1); TEST_ASSERT_TRUE(ok); TEST_ASSERT_NOT_NULL(line); TEST_ASSERT_EQUAL_INT(2, (int)line->nfields); assert_field_eq(&line->fields[0], "hello"); assert_field_eq(&line->fields[1], "world"); fclose(fp); } void test_get_line_file2_independent_state(void) { FILE* fp1 = make_temp_file_with("a1 b1\n"); FILE* fp2 = make_temp_file_with("a2 b2 c2\n"); struct line* l1 = NULL; struct line* l2 = NULL; bool ok1 = get_line(fp1, &l1, 1); bool ok2 = get_line(fp2, &l2, 2); TEST_ASSERT_TRUE(ok1); TEST_ASSERT_TRUE(ok2); TEST_ASSERT_EQUAL_UINT64(1, line_no[0]); TEST_ASSERT_EQUAL_UINT64(1, line_no[1]); TEST_ASSERT_EQUAL_PTR(l1, prevline[0]); TEST_ASSERT_EQUAL_PTR(l2, prevline[1]); TEST_ASSERT_EQUAL_INT(2, (int)l1->nfields); assert_field_eq(&l1->fields[0], "a1"); assert_field_eq(&l1->fields[1], "b1"); TEST_ASSERT_EQUAL_INT(3, (int)l2->nfields); assert_field_eq(&l2->fields[0], "a2"); assert_field_eq(&l2->fields[1], "b2"); assert_field_eq(&l2->fields[2], "c2"); fclose(fp1); fclose(fp2); } int main(void) { UNITY_BEGIN(); RUN_TEST(test_get_line_basic_parsing_file1); RUN_TEST(test_get_line_spareline_swap_prevents_overwrite); RUN_TEST(test_get_line_empty_line_zero_fields); RUN_TEST(test_get_line_eof_returns_false_and_frees_buffers); RUN_TEST(test_get_line_parsing_with_leading_blanks); RUN_TEST(test_get_line_file2_independent_state); return UNITY_END(); }