xref: /freebsd/contrib/pkgconf/tests/api/test-fileio.c (revision 592efe252472a3385acf36b1f49ecf710a7f3d9c)
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