File size: 8,712 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
#include "../../unity/unity.h"

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <locale.h>
#include <stdint.h>
#include <stdbool.h>
#include <unistd.h>

/* Access to program internals (these are in the same translation unit). */
/* Globals from the program that we adjust in tests. */
extern struct field_range_pair *frp; /* declared in set-fields.h, included in the program */

/* Helper: capture output while calling process_line. */
typedef struct {
    char *out;     /* captured stdout string (malloc'd) */
    int retval;    /* return value from process_line */
    int err;       /* 0 on success, else nonzero */
} CapResult;

static CapResult capture_process_line_call(const char *input, bool newline)
{
    CapResult r = {0};

    /* Create a modifiable copy of input because process_line mutates it. */
    char *mutable_line = NULL;
    size_t inlen = strlen(input);
    mutable_line = (char *)malloc(inlen + 1);
    if (!mutable_line) { r.err = 1; return r; }
    memcpy(mutable_line, input, inlen + 1);

    /* Redirect stdout to a temporary file. */
    FILE *tmp = tmpfile();
    if (!tmp) { free(mutable_line); r.err = 2; return r; }
    fflush(stdout);
    int saved_fd = dup(fileno(stdout));
    if (saved_fd < 0) { fclose(tmp); free(mutable_line); r.err = 3; return r; }
    if (dup2(fileno(tmp), fileno(stdout)) < 0) {
        close(saved_fd); fclose(tmp); free(mutable_line); r.err = 4; return r;
    }

    /* Call the target function while stdout is redirected. */
    int rv = process_line(mutable_line, newline);

    /* Flush and rewind to read captured output. */
    fflush(stdout);
    fflush(tmp);
    fseek(tmp, 0, SEEK_END);
    long sz = ftell(tmp);
    if (sz < 0) sz = 0;
    fseek(tmp, 0, SEEK_SET);
    char *buf = (char *)malloc((size_t)sz + 1);
    if (!buf) {
        /* Restore stdout before returning. */
        dup2(saved_fd, fileno(stdout));
        close(saved_fd);
        fclose(tmp);
        free(mutable_line);
        r.err = 5; return r;
    }
    size_t rd = fread(buf, 1, (size_t)sz, tmp);
    buf[rd] = '\0';

    /* Restore stdout. */
    dup2(saved_fd, fileno(stdout));
    close(saved_fd);
    fclose(tmp);

    r.out = buf;
    r.retval = rv;
    r.err = 0;

    free(mutable_line);
    return r;
}

/* Minimal environment setup helpers (operate on program globals). */
static void set_locale_basics(void)
{
    /* Ensure a simple, predictable locale: C/POSIX */
    setlocale(LC_ALL, "C");

    /* The following identifiers are static globals in the program file.
       Since this test is included in the same translation unit, we can set them. */
    extern const char *decimal_point;
    extern int decimal_point_length;
    extern const char *thousands_sep;
    extern int thousands_sep_length;
    extern unsigned char line_delim;

    /* Force simple decimal and no thousands separator. */
    decimal_point = ".";
    decimal_point_length = 1;
    thousands_sep = "";
    thousands_sep_length = 0;

    line_delim = '\n';
}

static void reset_numfmt_state(void)
{
    /* Reset a subset of globals to defaults that affect processing/printing. */
    extern const char *delimiter;
    extern int auto_padding;
    extern intmax_t padding_width;
    extern int zero_padding_width;
    extern long int user_precision;
    extern const char *suffix;
    extern const char *unit_separator;
    extern int grouping;
    extern enum inval_type inval_style;

    delimiter = NULL;
    auto_padding = 0;
    padding_width = 0;
    zero_padding_width = 0;
    user_precision = -1;
    suffix = NULL;
    unit_separator = NULL;
    grouping = 0;
    inval_style = inval_abort; /* default; tests that need ignore will set it */
}

/* Field range helpers: arrays to control which fields are included. */
static struct field_range_pair FRP_NONE[] = {
    { UINTMAX_MAX, 0 } /* terminator only: include no fields */
};

static struct field_range_pair FRP_ALL[] = {
    { 1, UINTMAX_MAX },
    { UINTMAX_MAX, 0 }
};

static struct field_range_pair FRP_FIRST_ONLY[] = {
    { 1, 1 },
    { UINTMAX_MAX, 0 }
};

void setUp(void) {
    set_locale_basics();
    reset_numfmt_state();
}

void tearDown(void) {
    /* nothing to clean up */
}

/* Test: default whitespace delimiter, no fields included => output identical to input; newline appended. */
void test_process_line_whitespace_preserved_with_newline(void)
{
    extern const char *delimiter;
    delimiter = NULL;  /* whitespace mode */
    frp = FRP_NONE;    /* include no fields => no numeric parsing/conversion */

    const char *in = " 123   456\t789  ";
    CapResult r = capture_process_line_call(in, true);
    TEST_ASSERT_EQUAL_INT_MESSAGE(0, r.err, "capture failed");

    /* Expected: identical input plus a newline. */
    size_t exp_len = strlen(in) + 1;
    char *exp = (char *)malloc(exp_len + 1);
    TEST_ASSERT_NOT_NULL(exp);
    strcpy(exp, in);
    exp[exp_len - 1] = '\n';
    exp[exp_len] = '\0';

    TEST_ASSERT_EQUAL_STRING(exp, r.out);
    TEST_ASSERT_TRUE(r.retval);

    free(exp);
    free(r.out);
}

/* Test: custom simple delimiter, no fields included => output identical; newline appended. */
void test_process_line_custom_delimiter_simple(void)
{
    extern const char *delimiter;
    delimiter = ",";
    frp = FRP_NONE;

    const char *in = "a,b,c";
    CapResult r = capture_process_line_call(in, true);
    TEST_ASSERT_EQUAL_INT_MESSAGE(0, r.err, "capture failed");

    char expected[16];
    snprintf(expected, sizeof expected, "%s\n", in);

    TEST_ASSERT_EQUAL_STRING(expected, r.out);
    TEST_ASSERT_TRUE(r.retval);

    free(r.out);
}

/* Test: multibyte delimiter, no newline requested. */
void test_process_line_multibyte_delimiter_no_newline(void)
{
    extern const char *delimiter;
    delimiter = "\xE2\x98\x83"; /* UTF-8 snowman: ☃ */
    frp = FRP_NONE;

    const char *in = "x\xE2\x98\x83y\xE2\x98\x83z"; /* x☃y☃z */
    CapResult r = capture_process_line_call(in, false);
    TEST_ASSERT_EQUAL_INT_MESSAGE(0, r.err, "capture failed");

    TEST_ASSERT_EQUAL_STRING(in, r.out);
    TEST_ASSERT_TRUE(r.retval);

    free(r.out);
}

/* Test: empty fields with delimiter are preserved exactly. */
void test_process_line_empty_fields_preserved_with_delimiter(void)
{
    extern const char *delimiter;
    delimiter = ",";
    frp = FRP_NONE;

    const char *in = "a,,b,,";
    CapResult r = capture_process_line_call(in, true);
    TEST_ASSERT_EQUAL_INT_MESSAGE(0, r.err, "capture failed");

    char expected[32];
    snprintf(expected, sizeof expected, "%s\n", in);

    TEST_ASSERT_EQUAL_STRING(expected, r.out);
    TEST_ASSERT_TRUE(r.retval);

    free(r.out);
}

/* Test: invalid number in included field returns false, output remains unchanged. */
void test_process_line_invalid_number_included_field_returns_false(void)
{
    /* Include first field only using FRP_FIRST_ONLY (or simply frp=NULL behavior),
       ensure invalid input doesn't abort and returns false. */
    extern enum inval_type inval_style;
    inval_style = inval_ignore; /* avoid exiting on invalid input */

    frp = FRP_FIRST_ONLY;       /* include first field */
    extern const char *delimiter;
    delimiter = NULL;           /* whitespace */

    const char *in = "abc def"; /* 'abc' is not a number */
    CapResult r = capture_process_line_call(in, true);
    TEST_ASSERT_EQUAL_INT_MESSAGE(0, r.err, "capture failed");

    char expected[32];
    snprintf(expected, sizeof expected, "%s\n", in);

    TEST_ASSERT_EQUAL_STRING(expected, r.out);
    TEST_ASSERT_FALSE(r.retval);

    free(r.out);
}

/* Test: include all fields with valid numbers, should return true and print numbers. */
void test_process_line_convert_all_fields_valid(void)
{
    /* With default scale_to=none and no formatting overrides,
       integer numbers should print unchanged. */
    frp = FRP_ALL; /* include all fields */
    extern const char *delimiter;
    delimiter = NULL;

    const char *in = "123 456";
    CapResult r = capture_process_line_call(in, true);
    TEST_ASSERT_EQUAL_INT_MESSAGE(0, r.err, "capture failed");

    char expected[32];
    snprintf(expected, sizeof expected, "%s\n", in);

    TEST_ASSERT_EQUAL_STRING(expected, r.out);
    TEST_ASSERT_TRUE(r.retval);

    free(r.out);
}

int main(void)
{
    UNITY_BEGIN();
    RUN_TEST(test_process_line_whitespace_preserved_with_newline);
    RUN_TEST(test_process_line_custom_delimiter_simple);
    RUN_TEST(test_process_line_multibyte_delimiter_no_newline);
    RUN_TEST(test_process_line_empty_fields_preserved_with_delimiter);
    RUN_TEST(test_process_line_invalid_number_included_field_returns_false);
    RUN_TEST(test_process_line_convert_all_fields_valid);
    return UNITY_END();
}