|
|
#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(); |
|
|
} |