1 /*
2 * test-fileio.c
3 * Tests for the public libpkgconf file i/o API.
4 *
5 * SPDX-License-Identifier: pkgconf
6 *
7 * Copyright (c) 2026 pkgconf authors (see AUTHORS).
8 *
9 * Permission to use, copy, modify, and/or distribute this software for any
10 * purpose with or without fee is hereby granted, provided that the above
11 * copyright notice and this permission notice appear in all copies.
12 *
13 * This software is provided 'as is' and without any warranty, express or
14 * implied. In no event shall the authors be liable for any damages arising
15 * from the use of this software.
16 */
17
18 #include "test-api.h"
19
20 static FILE *
fmemstream(const char * contents)21 fmemstream(const char *contents)
22 {
23 FILE *f = tmpfile();
24
25 TEST_ASSERT_NONNULL(f);
26
27 fwrite(contents, 1, strlen(contents), f);
28 rewind(f);
29
30 return f;
31 }
32
33 static void
test_fgetline_no_trailing_newline(void)34 test_fgetline_no_trailing_newline(void)
35 {
36 pkgconf_buffer_t buf = PKGCONF_BUFFER_INITIALIZER;
37 FILE *f = fmemstream("hello");
38
39 TEST_ASSERT_TRUE(pkgconf_fgetline(&buf, f));
40 TEST_ASSERT_STRCMP_EQ(pkgconf_buffer_str(&buf), "hello");
41
42 pkgconf_buffer_reset(&buf);
43 TEST_ASSERT_FALSE(pkgconf_fgetline(&buf, f));
44
45 fclose(f);
46 pkgconf_buffer_finalize(&buf);
47 }
48
49 static void
test_fgetline_empty_stream(void)50 test_fgetline_empty_stream(void)
51 {
52 pkgconf_buffer_t buf = PKGCONF_BUFFER_INITIALIZER;
53 FILE *f = fmemstream("");
54
55 TEST_ASSERT_FALSE(pkgconf_fgetline(&buf, f));
56
57 fclose(f);
58 pkgconf_buffer_finalize(&buf);
59 }
60
61 static void
test_fgetline_lf(void)62 test_fgetline_lf(void)
63 {
64 pkgconf_buffer_t buf = PKGCONF_BUFFER_INITIALIZER;
65 FILE *f = fmemstream("hello\nworld\n");
66
67 TEST_ASSERT_TRUE(pkgconf_fgetline(&buf, f));
68 TEST_ASSERT_STRCMP_EQ(pkgconf_buffer_str(&buf), "hello");
69
70 pkgconf_buffer_reset(&buf);
71 TEST_ASSERT_TRUE(pkgconf_fgetline(&buf, f));
72 TEST_ASSERT_STRCMP_EQ(pkgconf_buffer_str(&buf), "world");
73
74 pkgconf_buffer_reset(&buf);
75 TEST_ASSERT_FALSE(pkgconf_fgetline(&buf, f));
76
77 fclose(f);
78 pkgconf_buffer_finalize(&buf);
79 }
80
81 static void
test_fgetline_crlf(void)82 test_fgetline_crlf(void)
83 {
84 pkgconf_buffer_t buf = PKGCONF_BUFFER_INITIALIZER;
85 FILE *f = fmemstream("hello\r\nworld\r\n");
86
87 TEST_ASSERT_TRUE(pkgconf_fgetline(&buf, f));
88 TEST_ASSERT_STRCMP_EQ(pkgconf_buffer_str(&buf), "hello");
89
90 pkgconf_buffer_reset(&buf);
91 TEST_ASSERT_TRUE(pkgconf_fgetline(&buf, f));
92 TEST_ASSERT_STRCMP_EQ(pkgconf_buffer_str(&buf), "world");
93
94 pkgconf_buffer_reset(&buf);
95 TEST_ASSERT_FALSE(pkgconf_fgetline(&buf, f));
96
97 fclose(f);
98 pkgconf_buffer_finalize(&buf);
99 }
100
101 static void
test_fgetline_lone_cr(void)102 test_fgetline_lone_cr(void)
103 {
104 pkgconf_buffer_t buf = PKGCONF_BUFFER_INITIALIZER;
105 FILE *f = fmemstream("hello\rworld\r");
106
107 TEST_ASSERT_TRUE(pkgconf_fgetline(&buf, f));
108 TEST_ASSERT_STRCMP_EQ(pkgconf_buffer_str(&buf), "hello");
109
110 pkgconf_buffer_reset(&buf);
111 TEST_ASSERT_TRUE(pkgconf_fgetline(&buf, f));
112 TEST_ASSERT_STRCMP_EQ(pkgconf_buffer_str(&buf), "world");
113
114 pkgconf_buffer_reset(&buf);
115 TEST_ASSERT_FALSE(pkgconf_fgetline(&buf, f));
116
117 fclose(f);
118 pkgconf_buffer_finalize(&buf);
119 }
120
121 static void
test_fgetline_backslash_continuation_lf(void)122 test_fgetline_backslash_continuation_lf(void)
123 {
124 pkgconf_buffer_t buf = PKGCONF_BUFFER_INITIALIZER;
125 FILE *f = fmemstream("foo\\\nbar\n");
126
127 TEST_ASSERT_TRUE(pkgconf_fgetline(&buf, f));
128 TEST_ASSERT_STRCMP_EQ(pkgconf_buffer_str(&buf), "foobar");
129
130 fclose(f);
131 pkgconf_buffer_finalize(&buf);
132 }
133
134 static void
test_fgetline_backslash_continuation_crlf(void)135 test_fgetline_backslash_continuation_crlf(void)
136 {
137 pkgconf_buffer_t buf = PKGCONF_BUFFER_INITIALIZER;
138 FILE *f = fmemstream("foo\\\r\nbar\r\n");
139
140 TEST_ASSERT_TRUE(pkgconf_fgetline(&buf, f));
141 TEST_ASSERT_STRCMP_EQ(pkgconf_buffer_str(&buf), "foobar");
142
143 pkgconf_buffer_reset(&buf);
144 TEST_ASSERT_FALSE(pkgconf_fgetline(&buf, f));
145
146 fclose(f);
147 pkgconf_buffer_finalize(&buf);
148 }
149
150 static void
test_fgetline_backslash_continuation_lone_cr(void)151 test_fgetline_backslash_continuation_lone_cr(void)
152 {
153 pkgconf_buffer_t buf = PKGCONF_BUFFER_INITIALIZER;
154 FILE *f = fmemstream("foo\\\rbar\r");
155
156 TEST_ASSERT_TRUE(pkgconf_fgetline(&buf, f));
157 TEST_ASSERT_STRCMP_EQ(pkgconf_buffer_str(&buf), "foobar");
158
159 pkgconf_buffer_reset(&buf);
160 TEST_ASSERT_FALSE(pkgconf_fgetline(&buf, f));
161
162 fclose(f);
163 pkgconf_buffer_finalize(&buf);
164 }
165
166 // A backslash not immediately followed by a newline is not a continuation,
167 // so it must be preserved literally in the output.
168 static void
test_fgetline_backslash_not_continuation(void)169 test_fgetline_backslash_not_continuation(void)
170 {
171 pkgconf_buffer_t buf = PKGCONF_BUFFER_INITIALIZER;
172 FILE *f = fmemstream("foo\\bar\n");
173
174 TEST_ASSERT_TRUE(pkgconf_fgetline(&buf, f));
175 TEST_ASSERT_STRCMP_EQ(pkgconf_buffer_str(&buf), "foo\\bar");
176
177 pkgconf_buffer_reset(&buf);
178 TEST_ASSERT_FALSE(pkgconf_fgetline(&buf, f));
179
180 fclose(f);
181 pkgconf_buffer_finalize(&buf);
182 }
183
184 // fgets() only stops on '\n', a full read buffer, or EOF. NOT on on a lone '\r'.
185 // If a '\r' happens to be the very last byte fgets() manages to read
186 // before the buffer fills up, the matching '\n' is still unread in the
187 // stream and isn't visible to pkgconf_fgetline()'s lookahead, so the CRLF
188 // pair gets split across two fgets() calls and is misparsed as two lines.
189 static void
test_fgetline_crlf_split_across_fgets_buffer(void)190 test_fgetline_crlf_split_across_fgets_buffer(void)
191 {
192 pkgconf_buffer_t buf = PKGCONF_BUFFER_INITIALIZER;
193 size_t prefix_len = PKGCONF_ITEM_SIZE - 2;
194 char *content = malloc(prefix_len + strlen("\r\nworld\n") + 1);
195 FILE *f;
196
197 TEST_ASSERT_NONNULL(content);
198 memset(content, 'a', prefix_len);
199 memcpy(content + prefix_len, "\r\nworld\n", strlen("\r\nworld\n") + 1);
200
201 f = fmemstream(content);
202 free(content);
203
204 TEST_ASSERT_TRUE(pkgconf_fgetline(&buf, f));
205 TEST_ASSERT_EQ(strlen(pkgconf_buffer_str(&buf)), prefix_len);
206
207 pkgconf_buffer_reset(&buf);
208 TEST_ASSERT_TRUE(pkgconf_fgetline(&buf, f));
209 TEST_ASSERT_STRCMP_EQ(pkgconf_buffer_str(&buf), "world");
210
211 pkgconf_buffer_reset(&buf);
212 TEST_ASSERT_FALSE(pkgconf_fgetline(&buf, f));
213
214 fclose(f);
215 pkgconf_buffer_finalize(&buf);
216 }
217
218 int
main(int argc,const char ** argv)219 main(int argc, const char **argv)
220 {
221 (void) argc;
222 const char *basename = pkgconf_path_find_basename(argv[0]);
223
224 TEST_RUN(basename, test_fgetline_no_trailing_newline);
225 TEST_RUN(basename, test_fgetline_empty_stream);
226 TEST_RUN(basename, test_fgetline_lf);
227 TEST_RUN(basename, test_fgetline_crlf);
228 TEST_RUN(basename, test_fgetline_lone_cr);
229 TEST_RUN(basename, test_fgetline_backslash_continuation_lf);
230 TEST_RUN(basename, test_fgetline_backslash_continuation_crlf);
231 TEST_RUN(basename, test_fgetline_backslash_continuation_lone_cr);
232 TEST_RUN(basename, test_fgetline_backslash_not_continuation);
233 TEST_RUN(basename, test_fgetline_crlf_split_across_fgets_buffer);
234
235 return EXIT_SUCCESS;
236 }
237