coreutils / tests /date /tests_for_batch_convert.c
AryaWu's picture
Upload folder using huggingface_hub
78d2150 verified
#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();
}