File size: 8,617 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
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
#include "../../unity/unity.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>

/* We are included into the same translation unit as fmt and its statics. */
/* Use the existing internal functions/variables declared above. */

/* Forward declare the target to silence potential warnings in some compilers. */
static bool fmt (FILE *f, char const *file);
static void set_prefix (char *p);

/* Helpers local to tests. Keep names unique to avoid collisions. */

static void test_set_width(int w)
{
    /* max_width and goal_width are static globals in the program. */
    /* Goal width defaults to 93% of width (100 - LEEWAY). */
    max_width = w;
    goal_width = (int)((long long)w * (100 - LEEWAY) / 100);
    if (goal_width <= 0) goal_width = w; /* Fallback safety */
}

static void test_reset_options(void)
{
    /* Reset option flags to defaults. */
    crown = false;
    tagged = false;
    split = false;
    uniform = false;

    /* Reset prefix state. When prefix_length==0, get_prefix ignores prefix. */
    prefix = "";
    prefix_full_length = 0;
    prefix_lead_space = 0;
    prefix_length = 0;

    /* Reset dynamic state that could influence formatting. */
    in_column = 0;
    out_column = 0;
    tabs = false;
    prefix_indent = 0;
    first_indent = 0;
    other_indent = 0;
    next_char = '\0';
    next_prefix_indent = 0;
    last_line_length = 0;
}

/* Redirect stdout to a temporary file, run fmt on the provided input,
   restore stdout, and return the captured output as a heap-allocated string.
   The function fills *out_ok with fmt's return value. */
static char *run_fmt_and_capture(const char *input, const char *file_label, bool *out_ok)
{
    /* Prepare input file for fmt. It will be closed by fmt on return. */
    FILE *in = tmpfile();
    if (!in) return NULL;
    size_t in_len = strlen(input);
    if (in_len && fwrite(input, 1, in_len, in) != in_len) {
        fclose(in);
        return NULL;
    }
    fflush(in);
    rewind(in);

    /* Redirect stdout to a tmpfile. */
    FILE *cap = tmpfile();
    if (!cap) {
        fclose(in);
        return NULL;
    }
    int saved_stdout_fd = dup(fileno(stdout));
    if (saved_stdout_fd < 0) {
        fclose(in);
        fclose(cap);
        return NULL;
    }
    fflush(stdout);
    if (dup2(fileno(cap), fileno(stdout)) < 0) {
        fclose(in);
        fclose(cap);
        close(saved_stdout_fd);
        return NULL;
    }

    /* Call fmt while stdout is redirected. Do not use Unity asserts here. */
    bool ok = fmt(in, file_label);

    /* Flush and restore stdout. */
    fflush(stdout);
    if (dup2(saved_stdout_fd, fileno(stdout)) < 0) {
        /* Fatal; but attempt to continue. */
    }
    close(saved_stdout_fd);

    /* Read captured output into a buffer. */
    fflush(cap);
    fseek(cap, 0, SEEK_END);
    long cap_len = ftell(cap);
    if (cap_len < 0) cap_len = 0;
    fseek(cap, 0, SEEK_SET);

    char *buf = (char *)malloc((size_t)cap_len + 1);
    if (!buf) {
        fclose(cap);
        return NULL;
    }
    size_t nread = fread(buf, 1, (size_t)cap_len, cap);
    buf[nread] = '\0';
    fclose(cap);

    if (out_ok) *out_ok = ok;
    return buf;
}

/* Normalize a text to space-separated words (single spaces, no newlines),
   used to compare content ignoring line breaks and spacing variations. */
static char *normalize_words(const char *s)
{
    size_t len = strlen(s);
    /* Worst-case allocate same length; normalization won't expand size. */
    char *out = (char *)malloc(len + 1);
    if (!out) return NULL;
    size_t oi = 0;
    int in_word = 0;
    for (size_t i = 0; i < len; i++) {
        unsigned char c = (unsigned char)s[i];
        int is_ws = (c == ' ' || c == '\t' || c == '\n' || c == '\r');
        if (!is_ws) {
            out[oi++] = c;
            in_word = 1;
        } else {
            if (in_word) {
                out[oi++] = ' ';
                in_word = 0;
            }
        }
    }
    /* Remove trailing space if any. */
    if (oi > 0 && out[oi-1] == ' ')
        oi--;
    out[oi] = '\0';
    return out;
}

/* Count the length in columns of a single line (no tabs expected in tests). */
static size_t line_visible_length(const char *start, const char *end)
{
    /* end points just past '\n' or end-of-buffer; count visible chars before '\n'. */
    size_t n = 0;
    for (const char *p = start; p < end; p++) {
        if (*p == '\n') break;
        n++;
    }
    return n;
}

/* Unity setUp/tearDown */
void setUp(void) {
    test_reset_options();
    test_set_width(80); /* Safe default; tests set as needed. */
}

void tearDown(void) {
    /* nothing */
}

/* Tests */

void test_fmt_basic_no_wrap(void)
{
    test_reset_options();
    test_set_width(80);

    const char *input =
        "Hello world\n"
        "Second line\n"
        "\n"
        "Third paragraph line\n";

    bool ok = false;
    char *out = run_fmt_and_capture(input, "basic_no_wrap", &ok);

    TEST_ASSERT_NOT_NULL_MESSAGE(out, "Failed to capture fmt output");
    TEST_ASSERT_TRUE_MESSAGE(ok, "fmt should succeed for simple input");

    /* Expect unchanged (since lines are short and no prefix). */
    TEST_ASSERT_EQUAL_STRING(input, out);

    free(out);
}

void test_fmt_wrap_lines_constraints(void)
{
    test_reset_options();
    test_set_width(10); /* small width to force wrapping */

    const char *input = "one two three four five\n";
    bool ok = false;
    char *out = run_fmt_and_capture(input, "wrap_constraints", &ok);

    TEST_ASSERT_NOT_NULL_MESSAGE(out, "Failed to capture fmt output");
    TEST_ASSERT_TRUE_MESSAGE(ok, "fmt should succeed");

    /* Verify that no output line exceeds max_width, and words preserved. */
    const char *p = out;
    while (*p) {
        const char *line_end = strchr(p, '\n');
        if (!line_end) line_end = p + strlen(p);
        size_t vis = line_visible_length(p, line_end);
        TEST_ASSERT_LESS_OR_EQUAL_UINT32_MESSAGE((unsigned)max_width, (unsigned)max_width, "sanity");
        TEST_ASSERT_LESS_OR_EQUAL_UINT_MESSAGE((unsigned)max_width, (unsigned)vis);
        /* Correction: The assertion should ensure vis <= max_width */
        TEST_ASSERT_TRUE_MESSAGE(vis <= (size_t)max_width, "A line exceeds max_width");
        p = (*line_end == '\n') ? line_end + 1 : line_end;
    }

    /* Normalize words and compare with expected words sequence. */
    char *norm_out = normalize_words(out);
    char *norm_in = normalize_words(input);
    TEST_ASSERT_NOT_NULL(norm_out);
    TEST_ASSERT_NOT_NULL(norm_in);
    TEST_ASSERT_EQUAL_STRING_MESSAGE(norm_in, norm_out, "Words/content changed by formatting");

    free(norm_out);
    free(norm_in);
    free(out);
}

void test_fmt_prefix_trimming_and_reattach(void)
{
    test_reset_options();
    test_set_width(80);

    /* Use a prefix with leading/trailing spaces: "  >   " */
    char *pf = (char *)malloc(8);
    TEST_ASSERT_NOT_NULL(pf);
    strcpy(pf, "  >   ");
    set_prefix(pf);

    const char *input =
        "  >Hello\n"
        "  >world\n"
        "\n";

    bool ok = false;
    char *out = run_fmt_and_capture(input, "prefix_trim", &ok);

    /* After capture, reset prefix state and free buffer to avoid dangling. */
    prefix = "";
    prefix_full_length = 0;
    prefix_lead_space = 0;
    prefix_length = 0;
    free(pf);

    TEST_ASSERT_NOT_NULL(out);
    TEST_ASSERT_TRUE_MESSAGE(ok, "fmt should succeed with prefixed lines");

    /* The trimmed prefix should be reattached (no trailing spaces emitted). */
    const char *expected =
        "  >Hello\n"
        "  >world\n"
        "\n";
    TEST_ASSERT_EQUAL_STRING(expected, out);

    free(out);
}

void test_fmt_returns_false_on_read_error(void)
{
    test_reset_options();
    test_set_width(80);

    /* Create a readable FILE* and then close its underlying FD to induce read error. */
    FILE *in = tmpfile();
    TEST_ASSERT_NOT_NULL(in);
    const char *input = "data\n";
    fwrite(input, 1, strlen(input), in);
    fflush(in);
    rewind(in);

    int fd = fileno(in);
    /* Close the underlying file descriptor. Further reads should set ferror. */
    close(fd);

    bool ok = fmt(in, "read_error_expected");
    /* fmt should detect the error and return false. */
    TEST_ASSERT_FALSE_MESSAGE(ok, "fmt should return false on read error");
}

/* main */
int main(void)
{
    UNITY_BEGIN();

    RUN_TEST(test_fmt_basic_no_wrap);
    RUN_TEST(test_fmt_wrap_lines_constraints);
    RUN_TEST(test_fmt_prefix_trimming_and_reattach);
    RUN_TEST(test_fmt_returns_false_on_read_error);

    return UNITY_END();
}