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 * 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 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 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 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 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 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 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 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 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 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 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 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