File size: 9,102 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
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
#include "../../unity/unity.h"

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <locale.h>
#include <time.h>
#include <fcntl.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/stat.h>

/* Prototypes of target under test exist from the including translation unit.
   We rely on inclusion order to make static symbols visible here. */

/* Helper: create a temporary file with specified content, return malloc'ed path */
static char *create_tempfile_with_content(const char *content)
{
    char tmpl[] = "/tmp/tests_batch_convert_XXXXXX";
    int fd = mkstemp(tmpl);
    if (fd == -1) {
        return NULL;
    }
    size_t len = strlen(content);
    ssize_t w = write(fd, content, len);
    (void)w; /* ignore short write in tests; content is small */
    close(fd);
    return strdup(tmpl);
}

/* Helper: capture stdout/stderr to temporary files.
   Do not use Unity assertions while capture is active. */
typedef struct {
    int saved_fd;
    FILE *tmp;
} Capture;

static void capture_begin(int std_fd, Capture *cap)
{
    fflush(NULL);
    cap->saved_fd = dup(std_fd);
    cap->tmp = tmpfile();
    /* If tmpfile() fails, keep behavior undefined for the test; proceed anyway. */
    if (cap->tmp) {
        int tfd = fileno(cap->tmp);
        dup2(tfd, std_fd);
    }
}

static char *capture_end(int std_fd, Capture *cap)
{
    fflush(NULL);
    char *buf = NULL;
    size_t size = 0;

    if (cap->tmp) {
        long cur = ftell(cap->tmp);
        (void)cur;
        fseek(cap->tmp, 0, SEEK_SET);

        /* Read entire file into buffer */
        size_t capsz = 1024;
        buf = (char *)malloc(capsz);
        if (!buf) buf = NULL;
        size_t used = 0;
        while (1) {
            if (used + 512 > capsz) {
                capsz *= 2;
                char *nb = (char *)realloc(buf, capsz);
                if (!nb) { free(buf); buf = NULL; break; }
                buf = nb;
            }
            size_t n = fread(buf + used, 1, 512, cap->tmp);
            used += n;
            if (n < 512) {
                if (feof(cap->tmp)) break;
                if (ferror(cap->tmp)) break;
            }
        }
        if (buf) {
            /* NUL-terminate */
            if (used == 0) {
                buf[0] = '\0';
            } else {
                buf[used] = '\0';
            }
        }
    }

    /* Restore original FD */
    if (cap->saved_fd >= 0)
        dup2(cap->saved_fd, std_fd);
    if (cap->saved_fd >= 0)
        close(cap->saved_fd);
    if (cap->tmp)
        fclose(cap->tmp);

    return buf ? buf : strdup("");
}

/* Helper: temporarily replace stdin (fd 0) with a file for reading. */
typedef struct {
    int saved_fd0;
} StdinSwap;

static int stdin_swap_begin(const char *path, StdinSwap *ss)
{
    fflush(NULL);
    ss->saved_fd0 = dup(STDIN_FILENO);
    int fd = open(path, O_RDONLY);
    if (fd < 0) return -1;
    if (dup2(fd, STDIN_FILENO) < 0) {
        close(fd);
        return -1;
    }
    close(fd);
    return 0;
}

static void stdin_swap_end(StdinSwap *ss)
{
    /* batch_convert fclose(stdin), which closes fd 0; restore original */
    if (ss->saved_fd0 >= 0) {
        dup2(ss->saved_fd0, STDIN_FILENO);
        close(ss->saved_fd0);
    }
}

/* Ensure deterministic environment for tests */
void setUp(void) {
    /* English messages and C locale to avoid localization issues */
    setenv("LC_ALL", "C", 1);
    setlocale(LC_ALL, "C");

    /* Use UTC for timezone-dependent formatting */
    setenv("TZ", "UTC", 1);
    tzset();

    /* Reset parse_datetime_flags if visible */
    /* It's defined in the including translation unit, so we can reference it directly. */
    extern unsigned int parse_datetime_flags;
    parse_datetime_flags = 0;
}

void tearDown(void) {
    /* No-op */
}

/* Test: single valid line "@0" with format "%s" => prints "0\n", returns true */
void test_batch_convert_single_valid_line(void)
{
    char *path = create_tempfile_with_content("@0\n");
    TEST_ASSERT_NOT_NULL_MESSAGE(path, "Failed to create temp file");

    Capture out_cap = { -1, NULL };
    Capture err_cap = { -1, NULL };
    capture_begin(STDOUT_FILENO, &out_cap);
    capture_begin(STDERR_FILENO, &err_cap);

    timezone_t tz = 0; /* local/UTC as per TZ */
    bool ok = batch_convert(path, "%s", false, tz, "UTC");

    char *out_s = capture_end(STDOUT_FILENO, &out_cap);
    char *err_s = capture_end(STDERR_FILENO, &err_cap);

    TEST_ASSERT_TRUE(ok);
    TEST_ASSERT_NOT_NULL(out_s);
    TEST_ASSERT_EQUAL_STRING("0\n", out_s);
    TEST_ASSERT_NOT_NULL(err_s);
    TEST_ASSERT_EQUAL_INT(0, (int)strlen(err_s)); /* no errors */

    free(out_s);
    free(err_s);
    remove(path);
    free(path);
}

/* Test: multiple lines with one invalid line should return false,
   produce output only for valid lines, and emit error diagnostics. */
void test_batch_convert_multiple_lines_with_invalid(void)
{
    const char *content = "@1\nINVALID\n@2\n";
    char *path = create_tempfile_with_content(content);
    TEST_ASSERT_NOT_NULL_MESSAGE(path, "Failed to create temp file");

    Capture out_cap = { -1, NULL };
    Capture err_cap = { -1, NULL };
    capture_begin(STDOUT_FILENO, &out_cap);
    capture_begin(STDERR_FILENO, &err_cap);

    timezone_t tz = 0;
    bool ok = batch_convert(path, "%s", false, tz, "UTC");

    char *out_s = capture_end(STDOUT_FILENO, &out_cap);
    char *err_s = capture_end(STDERR_FILENO, &err_cap);

    TEST_ASSERT_FALSE(ok);
    TEST_ASSERT_NOT_NULL(out_s);
    /* Only valid lines (@1 and @2) should have produced output */
    TEST_ASSERT_EQUAL_STRING("1\n2\n", out_s);

    TEST_ASSERT_NOT_NULL(err_s);
    /* Should mention invalid date; do substring checks */
    TEST_ASSERT_NOT_EQUAL(NULL, strstr(err_s, "invalid date"));
    TEST_ASSERT_NOT_EQUAL(NULL, strstr(err_s, "INVALID"));

    free(out_s);
    free(err_s);
    remove(path);
    free(path);
}

/* Test: reading from stdin when filename is "-" */
void test_batch_convert_reads_from_stdin(void)
{
    char *path = create_tempfile_with_content("@3\n");
    TEST_ASSERT_NOT_NULL_MESSAGE(path, "Failed to create temp file");

    StdinSwap ss;
    int rc = stdin_swap_begin(path, &ss);
    TEST_ASSERT_EQUAL_INT_MESSAGE(0, rc, "Failed to swap stdin");

    Capture out_cap = { -1, NULL };
    Capture err_cap = { -1, NULL };
    capture_begin(STDOUT_FILENO, &out_cap);
    capture_begin(STDERR_FILENO, &err_cap);

    timezone_t tz = 0;
    bool ok = batch_convert("-", "%s", false, tz, "UTC");

    char *out_s = capture_end(STDOUT_FILENO, &out_cap);
    char *err_s = capture_end(STDERR_FILENO, &err_cap);

    stdin_swap_end(&ss);

    TEST_ASSERT_TRUE(ok);
    TEST_ASSERT_NOT_NULL(out_s);
    TEST_ASSERT_EQUAL_STRING("3\n", out_s);
    TEST_ASSERT_NOT_NULL(err_s);
    TEST_ASSERT_EQUAL_INT(0, (int)strlen(err_s));

    free(out_s);
    free(err_s);
    remove(path);
    free(path);
}

/* Test: use_c_locale path should format %a for @0 as "Thu" in C locale */
void test_batch_convert_use_c_locale(void)
{
    char *path = create_tempfile_with_content("@0\n");
    TEST_ASSERT_NOT_NULL(path);

    Capture out_cap = { -1, NULL };
    capture_begin(STDOUT_FILENO, &out_cap);

    timezone_t tz = 0;
    bool ok = batch_convert(path, "%a", true, tz, "UTC");

    char *out_s = capture_end(STDOUT_FILENO, &out_cap);

    TEST_ASSERT_TRUE(ok);
    TEST_ASSERT_NOT_NULL(out_s);
    /* 1970-01-01 was a Thursday */
    TEST_ASSERT_EQUAL_STRING("Thu\n", out_s);

    free(out_s);
    remove(path);
    free(path);
}

/* Test: PARSE_DATETIME_DEBUG emits a diagnostic; ensure it doesn't affect output */
void test_batch_convert_debug_flag_outputs_notice(void)
{
    char *path = create_tempfile_with_content("@0\n");
    TEST_ASSERT_NOT_NULL(path);

    extern unsigned int parse_datetime_flags;
    parse_datetime_flags |= PARSE_DATETIME_DEBUG;

    Capture out_cap = { -1, NULL };
    Capture err_cap = { -1, NULL };
    capture_begin(STDOUT_FILENO, &out_cap);
    capture_begin(STDERR_FILENO, &err_cap);

    const char *fmt = "TEST_FORMAT";
    timezone_t tz = 0;
    bool ok = batch_convert(path, fmt, false, tz, "UTC");

    char *out_s = capture_end(STDOUT_FILENO, &out_cap);
    char *err_s = capture_end(STDERR_FILENO, &err_cap);

    TEST_ASSERT_TRUE(ok);
    TEST_ASSERT_NOT_NULL(out_s);
    /* show_date should output the literal TEST_FORMAT for % not used; newline added */
    TEST_ASSERT_EQUAL_STRING("TEST_FORMAT\n", out_s);

    TEST_ASSERT_NOT_NULL(err_s);
    /* Debug message should include "output format:" and the format string */
    TEST_ASSERT_NOT_EQUAL(NULL, strstr(err_s, "output format:"));
    TEST_ASSERT_NOT_EQUAL(NULL, strstr(err_s, "TEST_FORMAT"));

    free(out_s);
    free(err_s);
    remove(path);
    free(path);
}

int main(void)
{
    UNITY_BEGIN();
    RUN_TEST(test_batch_convert_single_valid_line);
    RUN_TEST(test_batch_convert_multiple_lines_with_invalid);
    RUN_TEST(test_batch_convert_reads_from_stdin);
    RUN_TEST(test_batch_convert_use_c_locale);
    RUN_TEST(test_batch_convert_debug_flag_outputs_notice);
    return UNITY_END();
}