File size: 5,121 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
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
#include "../../unity/unity.h"

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>

/* The test file is included into the od translation unit, so it has
   access to the internal functions and globals declared above, such as:
   - static bool read_char (int *c);
   - static bool open_next_file (void);
   - static bool check_and_close (int in_errno);
   - static FILE *in_stream;
   - static char const *const *file_list; */

/* Helper: create a temporary file with the specified content. Returns a
   malloc-allocated string containing the path which the caller must free. */
static char *make_tempfile_with_content(const char *content)
{
    char tmpl[] = "/tmp/od_read_char_test_XXXXXX";
    int fd = mkstemp(tmpl);
    TEST_ASSERT_TRUE_MESSAGE(fd >= 0, "mkstemp failed");

    if (content && content[0] != '\0') {
        size_t len = strlen(content);
        ssize_t wr = write(fd, content, len);
        TEST_ASSERT_EQUAL_INT_MESSAGE((int)len, (int)wr, "write failed");
    }
    close(fd);

    char *path = (char *)malloc(strlen(tmpl) + 1);
    TEST_ASSERT_NOT_NULL(path);
    strcpy(path, tmpl);
    return path;
}

/* Helper: close stream if open and reset pointer. */
static void close_stream_if_open(void)
{
    if (in_stream != NULL) {
        /* Pass 0 so check_and_close checks ferror itself */
        (void)check_and_close(0);
    }
}

void setUp(void) {
    /* Ensure a clean slate before each test */
    close_stream_if_open();
    /* Reset file_list to a benign state (no files). We use a static
       local array here to avoid lifetime issues. */
    static const char *empty_list[] = { NULL };
    file_list = empty_list;
}

void tearDown(void) {
    /* Close any stream that may still be open */
    close_stream_if_open();
}

/* Test: basic single byte read from a file. */
void test_read_char_single_byte(void)
{
    char *p = make_tempfile_with_content("Z");

    const char *flist[] = { p, NULL };
    file_list = flist;

    /* Open first (and only) file via program helper to set globals consistently */
    TEST_ASSERT_TRUE(open_next_file());

    int ch = 0;
    TEST_ASSERT_TRUE(read_char(&ch));
    TEST_ASSERT_EQUAL_INT('Z', ch);

    /* Cleanup */
    close_stream_if_open();
    unlink(p);
    free(p);
}

/* Test: sequential reads across multiple bytes without hitting EOF in read_char. */
void test_read_char_three_bytes(void)
{
    char *p = make_tempfile_with_content("ABC");

    const char *flist[] = { p, NULL };
    file_list = flist;

    TEST_ASSERT_TRUE(open_next_file());

    int ch = 0;
    TEST_ASSERT_TRUE(read_char(&ch));
    TEST_ASSERT_EQUAL_INT('A', ch);
    TEST_ASSERT_TRUE(read_char(&ch));
    TEST_ASSERT_EQUAL_INT('B', ch);
    TEST_ASSERT_TRUE(read_char(&ch));
    TEST_ASSERT_EQUAL_INT('C', ch);

    /* Do not call a 4th time to avoid transitioning to EOF here. */

    close_stream_if_open();
    unlink(p);
    free(p);
}

/* Test: handle EOF on an empty file with no next file. Should return true and set *c = EOF. */
void test_read_char_eof_no_next_file(void)
{
    char *p = make_tempfile_with_content("");

    const char *flist[] = { p, NULL };
    file_list = flist;

    TEST_ASSERT_TRUE(open_next_file());

    int ch = 123; /* init to non-EOF */
    TEST_ASSERT_TRUE(read_char(&ch));
    TEST_ASSERT_EQUAL_INT(EOF, ch);

    /* Subsequent call with in_stream now NULL should also yield EOF and true. */
    ch = 456;
    TEST_ASSERT_TRUE(read_char(&ch));
    TEST_ASSERT_EQUAL_INT(EOF, ch);

    close_stream_if_open();
    unlink(p);
    free(p);
}

/* Test: transition across two files: first has 'A', second has 'B'. */
void test_read_char_transition_to_next_file(void)
{
    char *p1 = make_tempfile_with_content("A");
    char *p2 = make_tempfile_with_content("B");

    const char *flist[] = { p1, p2, NULL };
    file_list = flist;

    TEST_ASSERT_TRUE(open_next_file());

    int ch = 0;
    /* Read from first file */
    TEST_ASSERT_TRUE(read_char(&ch));
    TEST_ASSERT_EQUAL_INT('A', ch);

    /* Next call should hit EOF on first, advance to second, and read 'B' */
    ch = 0;
    TEST_ASSERT_TRUE(read_char(&ch));
    TEST_ASSERT_EQUAL_INT('B', ch);

    /* Third call: both files exhausted -> EOF */
    ch = 0;
    TEST_ASSERT_TRUE(read_char(&ch));
    TEST_ASSERT_EQUAL_INT(EOF, ch);

    close_stream_if_open();
    unlink(p1);
    unlink(p2);
    free(p1);
    free(p2);
}

/* Test: when in_stream is initially NULL (no file opened), read_char should return true and EOF. */
void test_read_char_with_null_stream(void)
{
    /* Ensure no open stream and no files */
    close_stream_if_open();
    static const char *empty_list[] = { NULL };
    file_list = empty_list;

    int ch = 0;
    TEST_ASSERT_TRUE(read_char(&ch));
    TEST_ASSERT_EQUAL_INT(EOF, ch);
}

int main(void)
{
    UNITY_BEGIN();
    RUN_TEST(test_read_char_single_byte);
    RUN_TEST(test_read_char_three_bytes);
    RUN_TEST(test_read_char_eof_no_next_file);
    RUN_TEST(test_read_char_transition_to_next_file);
    RUN_TEST(test_read_char_with_null_stream);
    return UNITY_END();
}