|
|
#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> |
|
|
|
|
|
|
|
|
|
|
|
extern bool warn_partial_read; |
|
|
extern int status_level; |
|
|
extern int input_flags; |
|
|
|
|
|
|
|
|
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) { |
|
|
|
|
|
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; |
|
|
} |
|
|
|
|
|
|
|
|
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; |
|
|
} |
|
|
|
|
|
|
|
|
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(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; |
|
|
} |
|
|
|
|
|
|
|
|
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); |
|
|
|
|
|
TEST_ASSERT_EQUAL_INT64(0, r); |
|
|
close(fd); |
|
|
} |
|
|
|
|
|
void setUp(void) { |
|
|
|
|
|
warn_partial_read = false; |
|
|
status_level = 3; |
|
|
input_flags = 0; |
|
|
reset_prev_nread(); |
|
|
} |
|
|
|
|
|
void tearDown(void) { |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
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; |
|
|
|
|
|
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); |
|
|
|
|
|
TEST_ASSERT_EQUAL_INT(0, strstr(out, "partial read") != NULL); |
|
|
|
|
|
free(out); |
|
|
close(fd); |
|
|
} |
|
|
|
|
|
|
|
|
void test_iread_partial_twice_warning_once(void) |
|
|
{ |
|
|
size_t req = 16; |
|
|
size_t have = 10; |
|
|
int fd = temp_fd_with_n_bytes(have); |
|
|
TEST_ASSERT(fd >= 0); |
|
|
|
|
|
warn_partial_read = true; |
|
|
|
|
|
char buf[32]; |
|
|
|
|
|
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); |
|
|
|
|
|
|
|
|
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); |
|
|
|
|
|
TEST_ASSERT_MESSAGE(strstr(out2, "partial read") != NULL, |
|
|
"Expected partial read warning not found on stderr"); |
|
|
free(out2); |
|
|
|
|
|
|
|
|
TEST_ASSERT_EQUAL_INT(0, warn_partial_read); |
|
|
|
|
|
|
|
|
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); |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
void test_iread_partial_after_full_no_warning(void) |
|
|
{ |
|
|
size_t req = 16; |
|
|
|
|
|
|
|
|
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); |
|
|
|
|
|
|
|
|
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); |
|
|
|
|
|
TEST_ASSERT_EQUAL_INT(0, strstr(out2, "partial read") != NULL); |
|
|
free(out2); |
|
|
|
|
|
|
|
|
TEST_ASSERT_EQUAL_INT(1, warn_partial_read); |
|
|
|
|
|
close(fd_part); |
|
|
} |
|
|
|
|
|
|
|
|
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(); |
|
|
} |