#include "../../unity/unity.h" #include #include #include #include #include #include #include #include #include #include /* 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(); }