#include "../../unity/unity.h" #include #include #include #include /* The test file is included after the program source, so we can access internal symbols directly. We will set up the global state to drive flush_paragraph(). */ /* Prototypes for functions under test (already defined above in the TU). */ /* static void flush_paragraph(void); -- available since included before */ /* Globals from the program under test (declared earlier in same TU). */ extern char parabuf[]; /* paragraph character buffer */ extern char *wptr; /* first unused char position in parabuf */ extern int max_width; /* maximum line width */ extern int goal_width; /* preferred goal width */ extern int first_indent; /* indentation first line */ extern int other_indent; /* indentation other lines */ extern int last_line_length; /* used by fmt_paragraph */ extern int prefix_indent; /* prefix indent for current paragraph */ extern char const *prefix; /* prefix string */ extern int prefix_length; /* prefix length (trimmed) */ extern int prefix_full_length; /* prefix full length */ extern int prefix_lead_space; /* prefix leading spaces */ extern int out_column; /* output column tracker */ extern int tabs; /* whether tabs can be used */ /* Word array and pointer. Note: 'word' is a macro-renamed identifier in the main file, but our references will be preprocessed consistently. */ extern struct Word; /* forward reference for type completeness */ extern __typeof__(((struct Word*)0)->length) dummy_len_for_type; /* ensure type loaded */ extern __typeof__(((struct Word*)0)) dummy_word_type; /* ensure struct visible */ extern struct Word *word_limit; extern struct Word word[]; /* Helper: redirect stdout to a temporary file; returns 0 on success. */ static int redirect_stdout_start(char *path_buf, size_t path_buf_sz, FILE **cap_fp, int *saved_fd) { if (!path_buf || path_buf_sz < 32 || !cap_fp || !saved_fd) return -1; snprintf(path_buf, path_buf_sz, "/tmp/fmt_flush_paragraph_XXXXXX"); int fd = mkstemp(path_buf); if (fd < 0) return -1; FILE *fp = fdopen(fd, "w+"); if (!fp) { close(fd); return -1; } fflush(stdout); *saved_fd = dup(fileno(stdout)); if (*saved_fd < 0) { fclose(fp); unlink(path_buf); return -1; } if (dup2(fd, fileno(stdout)) < 0) { close(*saved_fd); fclose(fp); unlink(path_buf); return -1; } *cap_fp = fp; return 0; } /* Helper: restore stdout; leaves cap_fp open for reading. */ static void redirect_stdout_end(FILE *cap_fp, int saved_fd) { fflush(stdout); if (saved_fd >= 0) { dup2(saved_fd, fileno(stdout)); close(saved_fd); } fflush(stdout); (void)cap_fp; /* still open for caller to read */ } /* Convenience: reset globals to a clean baseline for tests. */ static void reset_globals_defaults(void) { /* No prefix, no indentation, no tabs. */ prefix = ""; prefix_length = 0; prefix_full_length = 0; prefix_lead_space = 0; prefix_indent = 0; first_indent = 0; other_indent = 0; out_column = 0; last_line_length = 0; tabs = 0; /* Set widths; goal equals max for stable behavior. */ max_width = 12; goal_width = 12; /* Clear buffer and pointers. */ wptr = parabuf; } /*--- Unity setUp/tearDown ---*/ void setUp(void) { reset_globals_defaults(); } void tearDown(void) { /* Nothing to clean explicitly. */ } /* Test 1: Special case where word_limit == word; flush_paragraph should write the entire parabuf to stdout and reset wptr to parabuf. */ void test_flush_paragraph_single_chunk_no_words(void) { /* Prepare parabuf with raw bytes. */ const char *payload = "XYZ"; memcpy(parabuf, payload, 3); wptr = parabuf + 3; /* Set word_limit to point to the start: no complete words present. */ word_limit = word; /* word == first element; indicates zero words */ char path[64]; FILE *cap = NULL; int saved = -1; TEST_ASSERT_EQUAL_INT(0, redirect_stdout_start(path, sizeof(path), &cap, &saved)); /* Do not use TEST_ASSERT while stdout is redirected. */ flush_paragraph(); redirect_stdout_end(cap, saved); /* Read captured output and verify. */ fseek(cap, 0, SEEK_SET); char buf[32] = {0}; size_t n = fread(buf, 1, sizeof(buf), cap); fclose(cap); /* Cleanup the file. */ unlink(path); TEST_ASSERT_EQUAL_size_t(3, n); TEST_ASSERT_EQUAL_UINT8_ARRAY(payload, buf, 3); /* wptr should be reset to the start of parabuf. */ TEST_ASSERT_EQUAL_PTR(parabuf, wptr); } /* Helper to initialize a simple 3-word paragraph with 5-char words and 1-space gaps, designed so the optimal split is after the 2nd word. */ static void init_three_word_paragraph(void) { /* Words: "aaaaa" "bbbbb" "ccccc" */ const char *w1 = "aaaaa"; const char *w2 = "bbbbb"; const char *w3 = "ccccc"; /* Place texts contiguously in parabuf. */ char *p = parabuf; memcpy(p, w1, 5); word[0].text = p; word[0].length = 5; p += 5; memcpy(p, w2, 5); word[1].text = p; word[1].length = 5; p += 5; memcpy(p, w3, 5); word[2].text = p; word[2].length = 5; p += 5; wptr = p; /* Set inter-word spaces to 1; flags to non-punctuation, non-final. */ word[0].space = 1; word[0].paren = 0; word[0].period = 0; word[0].punct = 0; word[0].final = 0; word[1].space = 1; word[1].paren = 0; word[1].period = 0; word[1].punct = 0; word[1].final = 0; word[2].space = 1; word[2].paren = 0; word[2].period = 0; word[2].punct = 0; word[2].final = 0; /* Three complete words. */ word_limit = word + 3; /* Width settings ensure that only two words (5 + 1 + 5 = 11) fit on a line. */ max_width = 12; goal_width = 12; first_indent = 0; other_indent = 0; last_line_length = 0; prefix = ""; prefix_length = 0; prefix_full_length = 0; prefix_indent = 0; tabs = 0; } /* Test 2: Multi-word paragraph split and compaction. After flush_paragraph, verify that the first two words are output as one line, and the remaining word is compacted to the front of parabuf and word array updated. */ void test_flush_paragraph_split_and_compact(void) { init_three_word_paragraph(); char path[64]; FILE *cap = NULL; int saved = -1; TEST_ASSERT_EQUAL_INT(0, redirect_stdout_start(path, sizeof(path), &cap, &saved)); /* Avoid Unity asserts while redirected. */ flush_paragraph(); redirect_stdout_end(cap, saved); /* Read captured output. */ fseek(cap, 0, SEEK_SET); char outbuf[128] = {0}; size_t n = fread(outbuf, 1, sizeof(outbuf), cap); fclose(cap); unlink(path); /* Should have printed the first line with two words and a newline. */ const char *expected_line = "aaaaa bbbbb\n"; size_t expected_len = strlen(expected_line); TEST_ASSERT_TRUE(n >= expected_len); TEST_ASSERT_EQUAL_MEMORY(expected_line, outbuf, expected_len); /* Buffer should now contain only the remaining word "ccccc". */ TEST_ASSERT_EQUAL_INT(5, (int)(wptr - parabuf)); TEST_ASSERT_EQUAL_MEMORY("ccccc", parabuf, 5); /* Word array should now start with the third word, and word_limit reduced. */ TEST_ASSERT_EQUAL_PTR(word + 1, word_limit); TEST_ASSERT_EQUAL_INT(5, word[0].length); TEST_ASSERT_EQUAL_MEMORY("ccccc", word[0].text, 5); TEST_ASSERT_EQUAL_INT(1, word[0].space); } int main(void) { UNITY_BEGIN(); RUN_TEST(test_flush_paragraph_single_chunk_no_words); RUN_TEST(test_flush_paragraph_split_and_compact); return UNITY_END(); }