1 /* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */ 2 /* util/profile/t_profile.c - profile library regression tests */ 3 /* 4 * Copyright (C) 2021 by the Massachusetts Institute of Technology. 5 * All rights reserved. 6 * 7 * Redistribution and use in source and binary forms, with or without 8 * modification, are permitted provided that the following conditions 9 * are met: 10 * 11 * * Redistributions of source code must retain the above copyright 12 * notice, this list of conditions and the following disclaimer. 13 * 14 * * Redistributions in binary form must reproduce the above copyright 15 * notice, this list of conditions and the following disclaimer in 16 * the documentation and/or other materials provided with the 17 * distribution. 18 * 19 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 20 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 21 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS 22 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 23 * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 24 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 25 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 26 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 27 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, 28 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 29 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED 30 * OF THE POSSIBILITY OF SUCH DAMAGE. 31 */ 32 33 #include <assert.h> 34 #include <stdarg.h> 35 #include <stdio.h> 36 #include <stdlib.h> 37 #include <string.h> 38 #include <time.h> 39 #include <unistd.h> 40 #include <utime.h> 41 #include "profile.h" 42 43 static void 44 check(long code) 45 { 46 assert(code == 0); 47 } 48 49 static void 50 check_fail(long code, long expected) 51 { 52 assert(code == expected); 53 } 54 55 static void 56 write_file(const char *name, int nlines, ...) 57 { 58 FILE *f; 59 va_list ap; 60 int i; 61 62 (void)unlink(name); 63 f = fopen(name, "w"); 64 assert(f != NULL); 65 va_start(ap, nlines); 66 for (i = 0; i < nlines; i++) 67 fprintf(f, "%s\n", va_arg(ap, char *)); 68 va_end(ap); 69 fclose(f); 70 } 71 72 /* Regression test for #2685 (profile iterator breaks when modifications 73 * made) */ 74 static void 75 test_iterate() 76 { 77 profile_t p; 78 void *iter; 79 const char *names[] = { "test section 1", "child_section", "child", NULL }; 80 const char *values[] = { "slick", "harry", "john", NULL }; 81 char *name, *value; 82 int i; 83 84 check(profile_init_path("test2.ini", &p)); 85 86 /* Iterate and check for the expected values. */ 87 check(profile_iterator_create(p, names, 0, &iter)); 88 for (i = 0;; i++) { 89 check(profile_iterator(&iter, &name, &value)); 90 if (name == NULL && value == NULL) 91 break; 92 assert(strcmp(name, names[2]) == 0); 93 assert(values[i] != NULL); 94 assert(strcmp(value, values[i]) == 0); 95 profile_release_string(name); 96 profile_release_string(value); 97 } 98 assert(values[i] == NULL); 99 profile_iterator_free(&iter); 100 101 /* Iterate again, deleting each value as we go. Flush the result to a 102 * separate file. */ 103 check(profile_iterator_create(p, names, 0, &iter)); 104 for (;;) { 105 check(profile_iterator(&iter, NULL, &value)); 106 if (value == NULL) 107 break; 108 check(profile_update_relation(p, names, value, NULL)); 109 profile_release_string(value); 110 } 111 profile_iterator_free(&iter); 112 (void)unlink("test3.ini"); 113 profile_flush_to_file(p, "test3.ini"); 114 115 profile_abandon(p); 116 117 /* Check that no values for the section are found in the resulting file. */ 118 check(profile_init_path("test3.ini", &p)); 119 check(profile_iterator_create(p, names, 0, &iter)); 120 check(profile_iterator(&iter, &name, &value)); 121 assert(name == NULL && value == NULL); 122 profile_iterator_free(&iter); 123 profile_abandon(p); 124 } 125 126 /* 127 * Regression test for a 1.4-era bug where updating the underlying file data of 128 * a profile object lost track of the flag indicating that it was part of the 129 * global shared profiles list. 130 */ 131 static void 132 test_shared() 133 { 134 profile_t a, b; 135 struct utimbuf times; 136 137 system("cp test2.ini test3.ini"); 138 139 /* Create an entry in the shared table. */ 140 check(profile_init_path("test3.ini", &a)); 141 142 /* 143 * Force an update of the underlying data. With the bug present, the 144 * shared flag is erroneously cleared. The easiest way to force an update 145 * is to reopen the file (since we don't enforce the one-stat-per-second 146 * limit during open) after changing the timestamp. 147 */ 148 times.actime = time(NULL) + 2; 149 times.modtime = times.actime; 150 utime("test3.ini", ×); 151 check(profile_init_path("test3.ini", &b)); 152 profile_release(b); 153 154 /* Release the profile. With the bug present, a dangling reference is left 155 * behind in the shared table. */ 156 profile_release(a); 157 158 /* Open the profile again to dereference the dangling pointer if one was 159 * created. */ 160 check(profile_init_path("test3.ini", &a)); 161 profile_release(a); 162 } 163 164 /* Regression test for #2950 (profile_clear_relation not reflected within 165 * handle where deletion is performed) */ 166 static void 167 test_clear() 168 { 169 profile_t p; 170 const char *names[] = { "test section 1", "quux", NULL }; 171 char **values, **dummy; 172 173 check(profile_init_path("test2.ini", &p)); 174 check(profile_get_values(p, names, &values)); 175 check(profile_clear_relation(p, names)); 176 check_fail(profile_get_values(p, names, &dummy), PROF_NO_RELATION); 177 check(profile_add_relation(p, names, values[0])); 178 profile_free_list(values); 179 check(profile_get_values(p, names, &values)); 180 assert(values[0] != NULL && values[1] == NULL); 181 profile_free_list(values); 182 profile_abandon(p); 183 } 184 185 static void 186 test_include() 187 { 188 profile_t p; 189 const char *names[] = { "test section 1", "bar", NULL }; 190 char **values; 191 192 /* Test expected error code when including nonexistent file. */ 193 write_file("testinc.ini", 1, "include does-not-exist"); 194 check_fail(profile_init_path("testinc.ini", &p), PROF_FAIL_INCLUDE_FILE); 195 196 /* Test expected error code when including nonexistent directory. */ 197 write_file("testinc.ini", 1, "includedir does-not-exist"); 198 check_fail(profile_init_path("testinc.ini", &p), PROF_FAIL_INCLUDE_DIR); 199 200 /* Test including a file. */ 201 write_file("testinc.ini", 1, "include test2.ini"); 202 check(profile_init_path("testinc.ini", &p)); 203 check(profile_get_values(p, names, &values)); 204 assert(strcmp(values[0], "foo") == 0 && values[1] == NULL); 205 profile_free_list(values); 206 profile_release(p); 207 208 /* 209 * Test including a directory. Put four copies of test2.ini inside the 210 * directory, two with invalid names. Check that we get two values for one 211 * of the variables. 212 */ 213 system("rm -rf test_include_dir"); 214 system("mkdir test_include_dir"); 215 system("cp test2.ini test_include_dir/a"); 216 system("cp test2.ini test_include_dir/a~"); 217 system("cp test2.ini test_include_dir/b.conf"); 218 system("cp test2.ini test_include_dir/b.conf.rpmsave"); 219 write_file("testinc.ini", 1, "includedir test_include_dir"); 220 check(profile_init_path("testinc.ini", &p)); 221 check(profile_get_values(p, names, &values)); 222 assert(strcmp(values[0], "foo") == 0); 223 assert(strcmp(values[1], "foo") == 0); 224 assert(values[2] == NULL); 225 profile_free_list(values); 226 profile_release(p); 227 228 /* Directly list the directory in the profile path and try again. */ 229 check(profile_init_path("test_include_dir", &p)); 230 check(profile_get_values(p, names, &values)); 231 assert(strcmp(values[0], "foo") == 0); 232 assert(strcmp(values[1], "foo") == 0); 233 assert(values[2] == NULL); 234 profile_free_list(values); 235 profile_release(p); 236 } 237 238 /* Test syntactic independence of included profile files. */ 239 static void 240 test_independence() 241 { 242 profile_t p; 243 const char *names1[] = { "sec1", "var", "a", NULL }; 244 const char *names2[] = { "sec2", "b", NULL }; 245 const char *names3[] = { "sec1", "var", "c", NULL }; 246 char **values; 247 248 write_file("testinc.ini", 6, "[sec1]", "var = {", "a = 1", 249 "include testinc2.ini", "c = 3", "}"); 250 write_file("testinc2.ini", 2, "[sec2]", "b = 2"); 251 252 check(profile_init_path("testinc.ini", &p)); 253 check(profile_get_values(p, names1, &values)); 254 assert(strcmp(values[0], "1") == 0 && values[1] == NULL); 255 profile_free_list(values); 256 check(profile_get_values(p, names2, &values)); 257 assert(strcmp(values[0], "2") == 0 && values[1] == NULL); 258 profile_free_list(values); 259 check(profile_get_values(p, names3, &values)); 260 assert(strcmp(values[0], "3") == 0 && values[1] == NULL); 261 profile_free_list(values); 262 profile_release(p); 263 } 264 265 /* Regression test for #7971 (deleted sections should not be iterable) */ 266 static void 267 test_delete_section() 268 { 269 profile_t p; 270 const char *sect[] = { "test section 1", NULL }; 271 const char *newrel[] = { "test section 1", "testkey", NULL }; 272 const char *oldrel[] = { "test section 1", "child", NULL }; 273 char **values; 274 275 check(profile_init_path("test2.ini", &p)); 276 277 /* Remove and replace a section. */ 278 check(profile_rename_section(p, sect, NULL)); 279 check(profile_add_relation(p, sect, NULL)); 280 check(profile_add_relation(p, newrel, "6")); 281 282 /* Check that we can read the new relation but not the old one. */ 283 check(profile_get_values(p, newrel, &values)); 284 assert(strcmp(values[0], "6") == 0 && values[1] == NULL); 285 profile_free_list(values); 286 check_fail(profile_get_values(p, oldrel, &values), PROF_NO_RELATION); 287 profile_abandon(p); 288 } 289 290 /* Regression test for #7971 (profile_clear_relation() error with deleted node 291 * at end of value set) */ 292 static void 293 test_delete_clear_relation() 294 { 295 profile_t p; 296 const char *names[] = { "test section 1", "testkey", NULL }; 297 298 check(profile_init_path("test2.ini", &p)); 299 check(profile_add_relation(p, names, "1")); 300 check(profile_add_relation(p, names, "2")); 301 check(profile_update_relation(p, names, "2", NULL)); 302 check(profile_clear_relation(p, names)); 303 profile_abandon(p); 304 } 305 306 /* Test that order of relations is preserved if some relations are deleted. */ 307 static void 308 test_delete_ordering() 309 { 310 profile_t p; 311 const char *names[] = { "test section 1", "testkey", NULL }; 312 char **values; 313 314 check(profile_init_path("test2.ini", &p)); 315 check(profile_add_relation(p, names, "1")); 316 check(profile_add_relation(p, names, "2")); 317 check(profile_add_relation(p, names, "3")); 318 check(profile_update_relation(p, names, "2", NULL)); 319 check(profile_add_relation(p, names, "4")); 320 check(profile_get_values(p, names, &values)); 321 assert(strcmp(values[0], "1") == 0); 322 assert(strcmp(values[1], "3") == 0); 323 assert(strcmp(values[2], "4") == 0); 324 assert(values[3] == NULL); 325 profile_free_list(values); 326 profile_abandon(p); 327 } 328 329 /* Regression test for #8431 (profile_flush_to_file erroneously changes flag 330 * state on source object) */ 331 static void 332 test_flush_to_file() 333 { 334 profile_t p; 335 336 /* Flush a profile object to a file without making any changes, so that the 337 * source object is still within g_shared_trees. */ 338 check(profile_init_path("test2.ini", &p)); 339 unlink("test3.ini"); 340 check(profile_flush_to_file(p, "test3.ini")); 341 profile_release(p); 342 343 /* Check for a dangling reference in g_shared_trees by creating another 344 * profile object. */ 345 profile_init_path("test2.ini", &p); 346 profile_release(p); 347 } 348 349 /* Regression test for #7863 (multiply-specified subsections should 350 * be merged) */ 351 static void 352 test_merge_subsections() 353 { 354 profile_t p; 355 const char *n1[] = { "test section 2", "child_section2", "child", NULL }; 356 const char *n2[] = { "test section 2", "child_section2", "chores", NULL }; 357 char **values; 358 359 check(profile_init_path("test2.ini", &p)); 360 361 check(profile_get_values(p, n1, &values)); 362 assert(strcmp(values[0], "slick") == 0); 363 assert(strcmp(values[1], "harry") == 0); 364 assert(strcmp(values[2], "john\tb ") == 0); 365 assert(strcmp(values[3], "ron") == 0); 366 assert(values[4] == NULL); 367 profile_free_list(values); 368 369 check(profile_get_values(p, n2, &values)); 370 assert(strcmp(values[0], "cleaning") == 0 && values[1] == NULL); 371 profile_free_list(values); 372 373 profile_release(p); 374 } 375 376 int 377 main() 378 { 379 test_iterate(); 380 test_shared(); 381 test_clear(); 382 test_include(); 383 test_independence(); 384 test_delete_section(); 385 test_delete_clear_relation(); 386 test_delete_ordering(); 387 test_flush_to_file(); 388 test_merge_subsections(); 389 } 390