| | #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> |
| |
|
| | |
| | |
| |
|
| | |
| | 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; |
| | close(fd); |
| | return strdup(tmpl); |
| | } |
| |
|
| | |
| | |
| | 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 (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); |
| |
|
| | |
| | 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) { |
| | |
| | if (used == 0) { |
| | buf[0] = '\0'; |
| | } else { |
| | buf[used] = '\0'; |
| | } |
| | } |
| | } |
| |
|
| | |
| | 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(""); |
| | } |
| |
|
| | |
| | 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) |
| | { |
| | |
| | if (ss->saved_fd0 >= 0) { |
| | dup2(ss->saved_fd0, STDIN_FILENO); |
| | close(ss->saved_fd0); |
| | } |
| | } |
| |
|
| | |
| | void setUp(void) { |
| | |
| | setenv("LC_ALL", "C", 1); |
| | setlocale(LC_ALL, "C"); |
| |
|
| | |
| | setenv("TZ", "UTC", 1); |
| | tzset(); |
| |
|
| | |
| | |
| | extern unsigned int parse_datetime_flags; |
| | parse_datetime_flags = 0; |
| | } |
| |
|
| | void tearDown(void) { |
| | |
| | } |
| |
|
| | |
| | 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; |
| | 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)); |
| |
|
| | free(out_s); |
| | free(err_s); |
| | remove(path); |
| | free(path); |
| | } |
| |
|
| | |
| | |
| | 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); |
| | |
| | TEST_ASSERT_EQUAL_STRING("1\n2\n", out_s); |
| |
|
| | TEST_ASSERT_NOT_NULL(err_s); |
| | |
| | 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); |
| | } |
| |
|
| | |
| | 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); |
| | } |
| |
|
| | |
| | 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); |
| | |
| | TEST_ASSERT_EQUAL_STRING("Thu\n", out_s); |
| |
|
| | free(out_s); |
| | remove(path); |
| | free(path); |
| | } |
| |
|
| | |
| | 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); |
| | |
| | TEST_ASSERT_EQUAL_STRING("TEST_FORMAT\n", out_s); |
| |
|
| | TEST_ASSERT_NOT_NULL(err_s); |
| | |
| | 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(); |
| | } |