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