coreutils / tests /dd /tests_for_iread.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 <fcntl.h>
#include <errno.h>
#include <sys/stat.h>
/* We rely on internal linkage to access these symbols as this test
file is included into the dd translation unit. */
extern bool warn_partial_read; /* from dd.c */
extern int status_level; /* from dd.c */
extern int input_flags; /* from dd.c */
/* Helper structure for capturing stderr */
typedef struct {
int saved_fd;
int read_fd;
} StderrCapture;
static StderrCapture start_capture_stderr(void)
{
StderrCapture cap;
int pipefd[2];
fflush(stderr);
if (pipe(pipefd) != 0) {
/* If pipe fails, set to invalid to avoid crashes */
cap.saved_fd = -1;
cap.read_fd = -1;
return cap;
}
cap.saved_fd = dup(STDERR_FILENO);
dup2(pipefd[1], STDERR_FILENO);
close(pipefd[1]);
return cap;
}
static char *finish_capture_stderr(StderrCapture cap)
{
if (cap.saved_fd >= 0) {
fflush(stderr);
dup2(cap.saved_fd, STDERR_FILENO);
close(cap.saved_fd);
}
if (cap.read_fd < 0) {
char *empty = (char*)malloc(1);
if (empty) empty[0] = '\0';
return empty;
}
/* Read all data from pipe */
size_t capsize = 256;
size_t len = 0;
char *buf = (char*)malloc(capsize);
if (!buf) return NULL;
for (;;) {
char tmp[256];
ssize_t n = read(cap.read_fd, tmp, sizeof tmp);
if (n < 0) {
if (errno == EINTR) continue;
break;
}
if (n == 0) break;
if (len + (size_t)n + 1 > capsize) {
size_t newcap = (capsize * 2 > len + (size_t)n + 1) ? capsize * 2 : (len + (size_t)n + 1);
char *nb = (char*)realloc(buf, newcap);
if (!nb) { free(buf); close(cap.read_fd); return NULL; }
buf = nb;
capsize = newcap;
}
memcpy(buf + len, tmp, (size_t)n);
len += (size_t)n;
}
close(cap.read_fd);
buf[len] = '\0';
return buf;
}
/* Create a temporary file descriptor pre-filled with 'nbytes' bytes. */
static int temp_fd_with_n_bytes(size_t nbytes)
{
char tmpl[] = "/tmp/dd_iread_testXXXXXX";
int fd = mkstemp(tmpl);
if (fd < 0) return -1;
/* Unlink so it is removed after close; fd remains valid */
unlink(tmpl);
if (nbytes > 0) {
size_t to_write = nbytes;
char buf[256];
memset(buf, 'A', sizeof buf);
while (to_write > 0) {
size_t chunk = to_write < sizeof buf ? to_write : sizeof buf;
ssize_t w = write(fd, buf, chunk);
if (w < 0) { close(fd); return -1; }
to_write -= (size_t)w;
}
}
if (lseek(fd, 0, SEEK_SET) < 0) { close(fd); return -1; }
return fd;
}
/* Ensure iread's internal prev_nread is reset to 0 by calling iread on an empty file */
static void reset_prev_nread(void)
{
int fd = temp_fd_with_n_bytes(0);
TEST_ASSERT_MESSAGE(fd >= 0, "failed to create temp file for reset");
char buf[8];
ssize_t r = iread(fd, buf, (idx_t)sizeof buf);
/* On empty file, should read 0 bytes */
TEST_ASSERT_EQUAL_INT64(0, r);
close(fd);
}
void setUp(void) {
/* Reset relevant global flags to known state */
warn_partial_read = false;
status_level = 3; /* STATUS_DEFAULT */
input_flags = 0;
reset_prev_nread();
}
void tearDown(void) {
/* nothing */
}
/* Test: iread returns a full-sized read and emits no warning */
void test_iread_full_read_no_warning(void)
{
size_t size = 16;
int fd = temp_fd_with_n_bytes(32);
TEST_ASSERT(fd >= 0);
warn_partial_read = true; /* enable potential warning */
char buf[32];
StderrCapture cap = start_capture_stderr();
ssize_t r = iread(fd, buf, (idx_t)size);
char *out = finish_capture_stderr(cap);
TEST_ASSERT_EQUAL_INT64((ssize_t)size, r);
TEST_ASSERT_NOT_NULL(out);
/* No partial warning expected */
TEST_ASSERT_EQUAL_INT(0, strstr(out, "partial read") != NULL);
free(out);
close(fd);
}
/* Test: Two consecutive partial reads trigger a single warning and clear warn_partial_read. */
void test_iread_partial_twice_warning_once(void)
{
size_t req = 16;
size_t have = 10; /* force partial read */
int fd = temp_fd_with_n_bytes(have);
TEST_ASSERT(fd >= 0);
warn_partial_read = true;
char buf[32];
/* First partial read: no warning expected */
StderrCapture cap1 = start_capture_stderr();
ssize_t r1 = iread(fd, buf, (idx_t)req);
char *out1 = finish_capture_stderr(cap1);
TEST_ASSERT_EQUAL_INT64((ssize_t)have, r1);
TEST_ASSERT_NOT_NULL(out1);
TEST_ASSERT_EQUAL_INT(0, strstr(out1, "partial read") != NULL);
free(out1);
/* Rewind to cause a second partial read of same size */
TEST_ASSERT(lseek(fd, 0, SEEK_SET) == 0);
StderrCapture cap2 = start_capture_stderr();
ssize_t r2 = iread(fd, buf, (idx_t)req);
char *out2 = finish_capture_stderr(cap2);
TEST_ASSERT_EQUAL_INT64((ssize_t)have, r2);
TEST_ASSERT_NOT_NULL(out2);
/* Now we expect a warning message mentioning partial read */
TEST_ASSERT_MESSAGE(strstr(out2, "partial read") != NULL,
"Expected partial read warning not found on stderr");
free(out2);
/* warn_partial_read should have been cleared */
TEST_ASSERT_EQUAL_INT(0, warn_partial_read);
/* Rewind and ensure no further warnings when flag is cleared */
TEST_ASSERT(lseek(fd, 0, SEEK_SET) == 0);
StderrCapture cap3 = start_capture_stderr();
ssize_t r3 = iread(fd, buf, (idx_t)req);
char *out3 = finish_capture_stderr(cap3);
TEST_ASSERT_EQUAL_INT64((ssize_t)have, r3);
TEST_ASSERT_NOT_NULL(out3);
TEST_ASSERT_EQUAL_INT(0, strstr(out3, "partial read") != NULL);
free(out3);
close(fd);
}
/* Test: A full read followed by a partial read should not emit the warning,
since prev_nread was not partial. */
void test_iread_partial_after_full_no_warning(void)
{
size_t req = 16;
/* First: full read to set prev_nread to req */
int fd_full = temp_fd_with_n_bytes(32);
TEST_ASSERT(fd_full >= 0);
warn_partial_read = true;
char buf[32];
StderrCapture cap1 = start_capture_stderr();
ssize_t r1 = iread(fd_full, buf, (idx_t)req);
char *out1 = finish_capture_stderr(cap1);
TEST_ASSERT_EQUAL_INT64((ssize_t)req, r1);
TEST_ASSERT_NOT_NULL(out1);
TEST_ASSERT_EQUAL_INT(0, strstr(out1, "partial read") != NULL);
free(out1);
close(fd_full);
/* Now partial read */
int fd_part = temp_fd_with_n_bytes(10);
TEST_ASSERT(fd_part >= 0);
StderrCapture cap2 = start_capture_stderr();
ssize_t r2 = iread(fd_part, buf, (idx_t)req);
char *out2 = finish_capture_stderr(cap2);
TEST_ASSERT_EQUAL_INT64(10, r2);
TEST_ASSERT_NOT_NULL(out2);
/* No warning should be printed because previous read was full */
TEST_ASSERT_EQUAL_INT(0, strstr(out2, "partial read") != NULL);
free(out2);
/* warn_partial_read should still be true since it wasn't consumed */
TEST_ASSERT_EQUAL_INT(1, warn_partial_read);
close(fd_part);
}
/* Test: EOF returns 0 and does not print warnings. */
void test_iread_eof_returns_zero(void)
{
int fd = temp_fd_with_n_bytes(0);
TEST_ASSERT(fd >= 0);
warn_partial_read = true;
char buf[8];
StderrCapture cap = start_capture_stderr();
ssize_t r = iread(fd, buf, (idx_t)sizeof buf);
char *out = finish_capture_stderr(cap);
TEST_ASSERT_EQUAL_INT64(0, r);
TEST_ASSERT_NOT_NULL(out);
TEST_ASSERT_EQUAL_INT(0, strstr(out, "partial read") != NULL);
free(out);
close(fd);
}
int main(void)
{
UNITY_BEGIN();
RUN_TEST(test_iread_full_read_no_warning);
RUN_TEST(test_iread_partial_twice_warning_once);
RUN_TEST(test_iread_partial_after_full_no_warning);
RUN_TEST(test_iread_eof_returns_zero);
return UNITY_END();
}