#include "../../unity/unity.h" #include #include #include #include #include #include #include #include /* Access to static globals/functions is available since this file is included into the same translation unit after their definitions. */ static int saved_stdin_fd = -1; /* Helper: set STDIN to the read end of a new pipe; return write end */ static int make_pipe_and_set_stdin(void) { int fds[2]; if (pipe(fds) != 0) { TEST_FAIL_MESSAGE("pipe() failed"); } /* fds[0] is read end, fds[1] is write end */ if (dup2(fds[0], STDIN_FILENO) < 0) { close(fds[0]); close(fds[1]); TEST_FAIL_MESSAGE("dup2() failed to set STDIN"); } close(fds[0]); /* read end duplicated to STDIN; close original */ return fds[1]; /* return write end to caller */ } void setUp(void) { /* Save current STDIN so we can restore it after each test */ saved_stdin_fd = dup(STDIN_FILENO); if (saved_stdin_fd < 0) { TEST_FAIL_MESSAGE("dup(STDIN_FILENO) failed in setUp"); } /* Reset EOF flag before each test */ have_read_eof = false; /* Ensure caught_signals is an empty set to keep cleanup() safe if triggered */ sigemptyset(&caught_signals); } void tearDown(void) { /* Restore STDIN */ if (saved_stdin_fd >= 0) { if (dup2(saved_stdin_fd, STDIN_FILENO) < 0) { TEST_FAIL_MESSAGE("dup2() failed restoring STDIN"); } close(saved_stdin_fd); saved_stdin_fd = -1; } } /* Test: when max_n_bytes == 0, returns 0 and does not touch have_read_eof */ void test_read_input_zero_max_does_not_change_eof(void) { have_read_eof = false; char buf[4] = {0}; idx_t n = read_input(buf, 0); TEST_ASSERT_EQUAL_INT(0, (int)n); TEST_ASSERT_FALSE(have_read_eof); have_read_eof = true; n = read_input(buf, 0); TEST_ASSERT_EQUAL_INT(0, (int)n); TEST_ASSERT_TRUE(have_read_eof); } /* Test: reads available bytes and does not set EOF */ void test_read_input_reads_available_bytes(void) { int w = make_pipe_and_set_stdin(); const char *msg = "hello world"; size_t len = strlen(msg); ssize_t wr = write(w, msg, len); close(w); TEST_ASSERT_EQUAL_INT((int)len, (int)wr); char buf[64]; memset(buf, 0, sizeof buf); idx_t n = read_input(buf, (idx_t)sizeof buf); TEST_ASSERT_EQUAL_INT((int)len, (int)n); TEST_ASSERT_EQUAL_MEMORY(msg, buf, len); TEST_ASSERT_FALSE(have_read_eof); } /* Test: EOF handling sets have_read_eof and returns 0; remains true on subsequent calls */ void test_read_input_sets_eof_when_no_data(void) { int w = make_pipe_and_set_stdin(); /* No write; close writer to produce EOF */ close(w); char buf[8]; idx_t n1 = read_input(buf, (idx_t)sizeof buf); TEST_ASSERT_EQUAL_INT(0, (int)n1); TEST_ASSERT_TRUE(have_read_eof); /* Subsequent call should also return 0 and keep EOF true */ idx_t n2 = read_input(buf, (idx_t)sizeof buf); TEST_ASSERT_EQUAL_INT(0, (int)n2); TEST_ASSERT_TRUE(have_read_eof); } /* Test: bounded read respects max_n_bytes less than available data */ void test_read_input_respects_max_bytes_limit(void) { int w = make_pipe_and_set_stdin(); const char *msg = "abcdef"; ssize_t wr = write(w, msg, 6); close(w); TEST_ASSERT_EQUAL_INT(6, (int)wr); char buf[4] = {0}; idx_t n = read_input(buf, 3); TEST_ASSERT_EQUAL_INT(3, (int)n); TEST_ASSERT_EQUAL_MEMORY("abc", buf, 3); TEST_ASSERT_FALSE(have_read_eof); } /* Test: error path when STDIN is invalid causes process to exit with EXIT_FAILURE. Use fork to isolate the exit from the test runner. */ void test_read_input_on_bad_fd_exits_failure(void) { fflush(stdout); fflush(stderr); pid_t pid = fork(); TEST_ASSERT_TRUE_MESSAGE(pid >= 0, "fork() failed"); if (pid == 0) { /* Child: invalidate STDIN and call read_input */ sigemptyset(&caught_signals); /* ensure cleanup() uses a known signal set */ close(STDIN_FILENO); char buf[1]; /* This should print an error and exit(EXIT_FAILURE) via cleanup_fatal */ (void)read_input(buf, 1); /* If we get here, the function did not exit as expected */ _exit(0); } else { int status = 0; pid_t wp = waitpid(pid, &status, 0); TEST_ASSERT_EQUAL_INT_MESSAGE(pid, wp, "waitpid failed"); TEST_ASSERT_TRUE(WIFEXITED(status)); TEST_ASSERT_EQUAL_INT(EXIT_FAILURE, WEXITSTATUS(status)); } } int main(void) { UNITY_BEGIN(); RUN_TEST(test_read_input_zero_max_does_not_change_eof); RUN_TEST(test_read_input_reads_available_bytes); RUN_TEST(test_read_input_sets_eof_when_no_data); RUN_TEST(test_read_input_respects_max_bytes_limit); RUN_TEST(test_read_input_on_bad_fd_exits_failure); return UNITY_END(); }