File size: 7,803 Bytes
78d2150
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
#include "../../unity/unity.h"
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>

/* 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();
}