File size: 4,949 Bytes
78d2150
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
#include "../../unity/unity.h"
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <sys/wait.h>
#include <signal.h>

/* 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();
}