1 /* 2 * test-runner.c 3 * test harness 4 * 5 * SPDX-License-Identifier: pkgconf 6 * 7 * Copyright (c) 2025 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 <libpkgconf/config.h> 19 #include <libpkgconf/libpkgconf.h> 20 #include <libpkgconf/stdinc.h> 21 #include <tests/win-shim.h> 22 #include <sys/types.h> 23 #ifndef _WIN32 24 # include <sys/wait.h> 25 #endif 26 #include <cli/core.h> 27 #include <cli/getopt_long.h> 28 #include <limits.h> 29 #include <assert.h> 30 31 #ifndef PKGCONF_LITE 32 # if !defined(_WIN32) && !defined(__HAIKU__) 33 # define PKGCONF_TEST_PLATFORM "unix" 34 # elif !defined(_WIN32) 35 # define PKGCONF_TEST_PLATFORM "haiku" 36 # else 37 # define PKGCONF_TEST_PLATFORM "windows" 38 # endif 39 #else // PKGCONF_LITE 40 # define PKGCONF_TEST_PLATFORM "lite" 41 #endif // PKGCONF_LITE 42 43 static void test_parser_warn(void *p, const char *fmt, ...) PRINTFLIKE(2, 3); 44 static void handle_substs(pkgconf_buffer_t *dest, const pkgconf_buffer_t *src, const char *pwd); 45 46 static pkgconf_buffer_t test_fixtures_dir = PKGCONF_BUFFER_INITIALIZER; 47 static pkgconf_buffer_t test_tool_dir = PKGCONF_BUFFER_INITIALIZER; 48 static bool debug = false; 49 50 typedef enum test_match_strategy_ 51 { 52 MATCH_EXACT = 0, 53 MATCH_PARTIAL, 54 MATCH_EMPTY, 55 } pkgconf_test_match_strategy_t; 56 57 typedef struct test_case_ 58 { 59 char *name; 60 char *testfile_dir; 61 62 pkgconf_list_t search_path; 63 pkgconf_buffer_t query; 64 65 pkgconf_list_t expected_stdout; 66 pkgconf_test_match_strategy_t match_stdout; 67 68 pkgconf_list_t expected_stderr; 69 pkgconf_test_match_strategy_t match_stderr; 70 71 pkgconf_buffer_t expected_stdout_file; 72 73 int exitcode; 74 uint64_t wanted_flags; 75 76 pkgconf_list_t env_vars; 77 78 pkgconf_buffer_t want_env_prefix; 79 pkgconf_buffer_t want_variable; 80 pkgconf_buffer_t fragment_filter; 81 82 pkgconf_buffer_t skip_platforms; 83 bool require_utf8_locale; 84 85 pkgconf_list_t define_variables; 86 87 int verbosity; 88 89 pkgconf_buffer_t atleast_version; 90 pkgconf_buffer_t exact_version; 91 pkgconf_buffer_t max_version; 92 93 pkgconf_buffer_t tool; 94 pkgconf_buffer_t tool_args; // TODO: tool-specific flags 95 96 pkgconf_list_t mkdirs; 97 #ifndef _WIN32 98 pkgconf_list_t symlinks; 99 #endif 100 pkgconf_list_t copies; 101 102 #ifndef PKGCONF_LITE 103 pkgconf_buffer_t want_personality; 104 #endif 105 } pkgconf_test_case_t; 106 107 typedef struct test_state_ 108 { 109 pkgconf_cli_state_t cli_state; 110 const pkgconf_test_case_t *testcase; 111 } pkgconf_test_state_t; 112 113 typedef struct test_environ_ 114 { 115 pkgconf_node_t node; 116 char *key; 117 char *value; 118 } pkgconf_test_environ_t; 119 120 typedef struct test_output_ 121 { 122 pkgconf_output_t output; 123 124 pkgconf_buffer_t o_stdout; 125 pkgconf_buffer_t o_stderr; 126 } pkgconf_test_output_t; 127 128 typedef struct test_flag_pair_ 129 { 130 const char *name; 131 uint64_t flag; 132 } pkgconf_test_flag_pair_t; 133 134 typedef void (*test_keyword_func_t)(pkgconf_test_case_t *testcase, const char *keyword, const char *warnprefix, const ptrdiff_t offset, const char *value); 135 136 typedef struct test_keyword_pair_ 137 { 138 const char *keyword; 139 const test_keyword_func_t func; 140 const ptrdiff_t offset; 141 } pkgconf_test_keyword_pair_t; 142 143 static void 144 test_environment_push(pkgconf_test_case_t *testcase, const char *key, const char *value) 145 { 146 pkgconf_test_environ_t *env = calloc(1, sizeof(*env)); 147 if (env == NULL) 148 return; 149 150 env->key = strdup(key); 151 env->value = strdup(value); 152 pkgconf_node_insert_tail(&env->node, env, &testcase->env_vars); 153 } 154 155 static void 156 test_environment_free(pkgconf_list_t *env_list) 157 { 158 pkgconf_node_t *iter, *iter_next; 159 160 PKGCONF_FOREACH_LIST_ENTRY_SAFE(env_list->head, iter_next, iter) 161 { 162 pkgconf_test_environ_t *env = iter->data; 163 164 pkgconf_node_delete(&env->node, env_list); 165 166 free(env->key); 167 free(env->value); 168 free(env); 169 } 170 } 171 172 static const char * 173 environ_lookup_handler(const pkgconf_client_t *client, const char *key) 174 { 175 pkgconf_test_state_t *state = client->client_data; 176 pkgconf_node_t *iter; 177 178 PKGCONF_FOREACH_LIST_ENTRY(state->testcase->env_vars.head, iter) 179 { 180 pkgconf_test_environ_t *env = iter->data; 181 182 if (!strcmp(key, env->key)) 183 { 184 char cwd[PATH_MAX] = {0}; 185 const char *pwd = getcwd(cwd, sizeof(cwd)); 186 187 pkgconf_buffer_t expanded = PKGCONF_BUFFER_INITIALIZER; 188 handle_substs(&expanded, PKGCONF_BUFFER_FROM_STR(env->value), pwd); 189 190 free(env->value); 191 env->value = strdup(pkgconf_buffer_str_or_empty(&expanded)); 192 pkgconf_buffer_finalize(&expanded); 193 194 return env->value; 195 } 196 } 197 198 return NULL; 199 } 200 201 #ifndef PKGCONF_LITE 202 static bool 203 debug_handler(const char *msg, const pkgconf_client_t *client, void *data) 204 { 205 (void) client; 206 (void) data; 207 fprintf(stderr, "%s", msg); 208 return true; 209 } 210 #endif // PKGCONF_LITE 211 212 static bool 213 error_handler(const char *msg, const pkgconf_client_t *client, void *data) 214 { 215 (void) data; 216 pkgconf_test_state_t *state = client->client_data; 217 pkgconf_output_fmt(client->output, state->testcase->wanted_flags & PKG_ERRORS_ON_STDOUT ? PKGCONF_OUTPUT_STDOUT : PKGCONF_OUTPUT_STDERR, "%s", msg); 218 return true; 219 } 220 221 static bool 222 write_handler(pkgconf_output_t *output, pkgconf_output_stream_t stream, const pkgconf_buffer_t *buffer) 223 { 224 pkgconf_test_output_t *out = (pkgconf_test_output_t *) output; 225 pkgconf_buffer_t *dest = stream == PKGCONF_OUTPUT_STDERR ? &out->o_stderr : &out->o_stdout; 226 227 pkgconf_buffer_append(dest, pkgconf_buffer_str(buffer)); 228 return true; 229 } 230 231 static pkgconf_output_t * 232 test_output(void) 233 { 234 static pkgconf_test_output_t output = 235 { 236 .output.write = write_handler, 237 }; 238 239 return &output.output; 240 } 241 242 static void 243 test_output_reset(pkgconf_test_output_t *out) 244 { 245 pkgconf_buffer_reset(&out->o_stdout); 246 pkgconf_buffer_reset(&out->o_stderr); 247 } 248 249 /* 250 * handle_substs: expand %TEST_FIXTURES_DIR%, %DIR_SEP%, and %PWD% 251 * in src into dest. pwd may be NULL, in which case %PWD% is left as-is 252 * (it should only appear in fields that are re-expanded after tmp_dir creation). 253 */ 254 static void 255 handle_substs(pkgconf_buffer_t *dest, const pkgconf_buffer_t *src, const char *pwd) 256 { 257 struct subst_pair 258 { 259 const char *key; 260 const char *value; 261 } subst_pairs[] = 262 { 263 {"%TEST_FIXTURES_DIR%", pkgconf_buffer_str(&test_fixtures_dir)}, 264 {"%DIR_SEP%", PKG_CONFIG_PATH_SEP_S}, 265 {"%PWD%", pwd != NULL ? pwd : "%PWD%"}, 266 }; 267 268 pkgconf_buffer_t workbuf_src = PKGCONF_BUFFER_INITIALIZER; 269 pkgconf_buffer_t workbuf_dest = PKGCONF_BUFFER_INITIALIZER; 270 271 if (!pkgconf_buffer_len(src)) 272 return; 273 274 pkgconf_buffer_append(&workbuf_dest, pkgconf_buffer_str(src)); 275 276 for (size_t i = 0; i < PKGCONF_ARRAY_SIZE(subst_pairs); i++) 277 { 278 pkgconf_buffer_reset(&workbuf_src); 279 pkgconf_buffer_append(&workbuf_src, pkgconf_buffer_str(&workbuf_dest)); 280 281 pkgconf_buffer_reset(&workbuf_dest); 282 pkgconf_buffer_subst(&workbuf_dest, &workbuf_src, subst_pairs[i].key, subst_pairs[i].value); 283 } 284 285 pkgconf_buffer_append(dest, pkgconf_buffer_str(&workbuf_dest)); 286 287 pkgconf_buffer_finalize(&workbuf_src); 288 pkgconf_buffer_finalize(&workbuf_dest); 289 } 290 291 static int 292 test_keyword_pair_cmp(const void *key, const void *ptr) 293 { 294 const pkgconf_test_keyword_pair_t *pair = ptr; 295 return strcasecmp(key, pair->keyword); 296 } 297 298 static void 299 test_keyword_set_int(pkgconf_test_case_t *testcase, const char *keyword, const char *warnprefix, const ptrdiff_t offset, const char *value) 300 { 301 (void) keyword; 302 (void) warnprefix; 303 304 int *dest = (int *)((char *) testcase + offset); 305 *dest = atoi(value); 306 } 307 308 static void 309 test_keyword_set_bool(pkgconf_test_case_t *testcase, const char *keyword, const char *warnprefix, const ptrdiff_t offset, const char *value) 310 { 311 (void) keyword; 312 (void) warnprefix; 313 314 bool *dest = (bool *)((char *) testcase + offset); 315 *dest = !strcasecmp(value, "true"); 316 } 317 318 static void 319 test_keyword_set_buffer(pkgconf_test_case_t *testcase, const char *keyword, const char *warnprefix, const ptrdiff_t offset, const char *value) 320 { 321 (void) keyword; 322 (void) warnprefix; 323 324 pkgconf_buffer_t *dest = (pkgconf_buffer_t *)((char *) testcase + offset); 325 handle_substs(dest, PKGCONF_BUFFER_FROM_STR((char *) value), NULL); 326 } 327 328 static void 329 test_keyword_extend_bufferset(pkgconf_test_case_t *testcase, const char *keyword, const char *warnprefix, const ptrdiff_t offset, const char *value) 330 { 331 (void) keyword; 332 (void) warnprefix; 333 334 pkgconf_list_t *dest = (pkgconf_list_t *)((char *) testcase + offset); 335 pkgconf_buffer_t buf = PKGCONF_BUFFER_INITIALIZER; 336 337 handle_substs(&buf, PKGCONF_BUFFER_FROM_STR((char *) value), NULL); 338 pkgconf_bufferset_extend(dest, &buf); 339 pkgconf_buffer_finalize(&buf); 340 } 341 342 static int 343 test_flag_pair_cmp(const void *key, const void *ptr) 344 { 345 const pkgconf_test_flag_pair_t *pair = ptr; 346 return strcasecmp(key, pair->name); 347 } 348 349 static const pkgconf_test_flag_pair_t test_flag_pairs[] = 350 { 351 {"cflags", PKG_CFLAGS}, 352 {"cflags-only-i", PKG_CFLAGS_ONLY_I}, 353 {"cflags-only-other", PKG_CFLAGS_ONLY_OTHER}, 354 {"debug", PKG_DEBUG}, 355 {"define-prefix", PKG_DEFINE_PREFIX}, 356 {"digraph", PKG_DIGRAPH}, 357 {"dont-define-prefix", PKG_DONT_DEFINE_PREFIX}, 358 {"dont-relocate-paths", PKG_DONT_RELOCATE_PATHS}, 359 {"dump-license", PKG_DUMP_LICENSE}, 360 {"dump-license-file", PKG_DUMP_LICENSE_FILE}, 361 {"dump-personality", PKG_DUMP_PERSONALITY}, 362 {"dump-source", PKG_DUMP_SOURCE}, 363 {"env-only", PKG_ENV_ONLY}, 364 {"errors-on-stdout", PKG_ERRORS_ON_STDOUT}, 365 {"exists", PKG_EXISTS}, 366 {"exists-cflags", PKG_EXISTS_CFLAGS}, 367 {"fragment-tree", PKG_FRAGMENT_TREE}, 368 {"ignore-conflicts", PKG_IGNORE_CONFLICTS}, 369 {"internal-cflags", PKG_INTERNAL_CFLAGS}, 370 {"keep-system-cflags", PKG_KEEP_SYSTEM_CFLAGS}, 371 {"keep-system-libs", PKG_KEEP_SYSTEM_LIBS}, 372 {"libs", PKG_LIBS}, 373 {"libs-only-ldpath", PKG_LIBS_ONLY_LDPATH}, 374 {"libs-only-libname", PKG_LIBS_ONLY_LIBNAME}, 375 {"libs-only-other", PKG_LIBS_ONLY_OTHER}, 376 {"link-abi", PKG_LINK_ABI}, 377 {"list", PKG_LIST}, 378 {"list-package-names", PKG_LIST_PACKAGE_NAMES}, 379 {"modversion", PKG_MODVERSION}, 380 {"msvc-syntax", PKG_MSVC_SYNTAX}, 381 {"newlines", PKG_NEWLINES}, 382 {"no-cache", PKG_NO_CACHE}, 383 {"no-provides", PKG_NO_PROVIDES}, 384 {"no-uninstalled", PKG_NO_UNINSTALLED}, 385 {"path", PKG_PATH}, 386 {"print-digraph-query-nodes", PKG_PRINT_DIGRAPH_QUERY_NODES}, 387 {"print-errors", PKG_PRINT_ERRORS}, 388 {"print-provides", PKG_PROVIDES}, 389 {"print-requires", PKG_REQUIRES}, 390 {"print-requires-private", PKG_REQUIRES_PRIVATE}, 391 {"print-variables", PKG_VARIABLES}, 392 {"pure", PKG_PURE}, 393 {"shared", PKG_SHARED}, 394 {"short-errors", PKG_SHORT_ERRORS}, 395 {"silence-errors", PKG_SILENCE_ERRORS}, 396 {"simulate", PKG_SIMULATE}, 397 {"solution", PKG_SOLUTION}, 398 {"static", PKG_STATIC}, 399 {"uninstalled", PKG_UNINSTALLED}, 400 {"validate", PKG_VALIDATE}, 401 }; 402 403 static void 404 test_keyword_set_wanted_flags(pkgconf_test_case_t *testcase, const char *keyword, const char *warnprefix, const ptrdiff_t offset, const char *value) 405 { 406 int i; 407 int flagcount; 408 char **flags = NULL; 409 410 (void) keyword; 411 (void) warnprefix; 412 (void) offset; 413 414 pkgconf_argv_split(value, &flagcount, &flags); 415 416 for (i = 0; i < flagcount; i++) 417 { 418 const char *flag = flags[i]; 419 const pkgconf_test_flag_pair_t *pair = bsearch(flag, 420 test_flag_pairs, PKGCONF_ARRAY_SIZE(test_flag_pairs), 421 sizeof(*pair), test_flag_pair_cmp); 422 423 if (pair == NULL) 424 continue; 425 426 testcase->wanted_flags |= pair->flag; 427 } 428 429 pkgconf_argv_free(flags); 430 } 431 432 static size_t 433 prefixed_path_split(const char *text, pkgconf_list_t *dirlist, const char *prefix) 434 { 435 size_t count = 0; 436 char *workbuf, *p, *iter; 437 438 if (text == NULL) 439 return 0; 440 441 iter = workbuf = strdup(text); 442 while ((p = strtok(iter, PKG_CONFIG_PATH_SEP_S)) != NULL) 443 { 444 pkgconf_buffer_t pathbuf = PKGCONF_BUFFER_INITIALIZER; 445 446 pkgconf_buffer_append(&pathbuf, prefix); 447 pkgconf_buffer_push_byte(&pathbuf, '/'); 448 pkgconf_buffer_append(&pathbuf, p); 449 pkgconf_path_add(pkgconf_buffer_str(&pathbuf), dirlist, false); 450 pkgconf_buffer_finalize(&pathbuf); 451 452 count++, iter = NULL; 453 } 454 free(workbuf); 455 456 return count; 457 } 458 459 static void 460 test_keyword_set_path_list(pkgconf_test_case_t *testcase, const char *keyword, const char *warnprefix, const ptrdiff_t offset, const char *value) 461 { 462 (void) keyword; 463 (void) warnprefix; 464 465 pkgconf_list_t *dest = (pkgconf_list_t *)((char *) testcase + offset); 466 prefixed_path_split(value, dest, pkgconf_buffer_str(&test_fixtures_dir)); 467 } 468 469 static void 470 test_keyword_set_match_strategy(pkgconf_test_case_t *testcase, const char *keyword, const char *warnprefix, const ptrdiff_t offset, const char *value) 471 { 472 (void) keyword; 473 (void) warnprefix; 474 475 pkgconf_test_match_strategy_t *dest = (pkgconf_test_match_strategy_t *)((char *) testcase + offset); 476 477 if (!strcasecmp(value, "partial")) 478 *dest = MATCH_PARTIAL; 479 480 if (!strcasecmp(value, "empty")) 481 *dest = MATCH_EMPTY; 482 } 483 484 static void 485 test_keyword_set_environment(pkgconf_test_case_t *testcase, const char *keyword, const char *warnprefix, const ptrdiff_t offset, const char *value) 486 { 487 (void) keyword; 488 (void) offset; 489 490 char *eq = strchr(value, '='); 491 if (eq == NULL) 492 { 493 fprintf(stderr, "%s: malformed Environment entry: %s\n", warnprefix, value); 494 return; 495 } 496 497 *eq++ = '\0'; 498 499 // store raw, vars are expanded at run time 500 test_environment_push(testcase, value, eq); 501 } 502 503 #ifdef _WIN32 504 static void 505 test_keyword_disabled(pkgconf_test_case_t *testcase, const char *keyword, const char *warnprefix, const ptrdiff_t offset, const char *value) 506 { 507 (void) testcase; 508 (void) keyword; 509 (void) warnprefix; 510 (void) offset; 511 (void) value; 512 } 513 #endif 514 515 static const pkgconf_test_keyword_pair_t test_keyword_pairs[] = 516 { 517 {"AtLeastVersion", test_keyword_set_buffer, offsetof(pkgconf_test_case_t, atleast_version)}, 518 {"DefineVariable", test_keyword_extend_bufferset, offsetof(pkgconf_test_case_t, define_variables)}, 519 {"Environment", test_keyword_set_environment, offsetof(pkgconf_test_case_t, env_vars)}, 520 {"ExactVersion", test_keyword_set_buffer, offsetof(pkgconf_test_case_t, exact_version)}, 521 {"ExpectedExitCode", test_keyword_set_int, offsetof(pkgconf_test_case_t, exitcode)}, 522 {"ExpectedStderr", test_keyword_extend_bufferset, offsetof(pkgconf_test_case_t, expected_stderr)}, 523 {"ExpectedStdout", test_keyword_extend_bufferset, offsetof(pkgconf_test_case_t, expected_stdout)}, 524 {"ExpectedStdoutFile", test_keyword_set_buffer, offsetof(pkgconf_test_case_t, expected_stdout_file)}, 525 {"FragmentFilter", test_keyword_set_buffer, offsetof(pkgconf_test_case_t, fragment_filter)}, 526 {"MatchStderr", test_keyword_set_match_strategy, offsetof(pkgconf_test_case_t, match_stderr)}, 527 {"MatchStdout", test_keyword_set_match_strategy, offsetof(pkgconf_test_case_t, match_stdout)}, 528 {"MaxVersion", test_keyword_set_buffer, offsetof(pkgconf_test_case_t, max_version)}, 529 {"PackageSearchPath", test_keyword_set_path_list, offsetof(pkgconf_test_case_t, search_path)}, 530 {"Query", test_keyword_set_buffer, offsetof(pkgconf_test_case_t, query)}, 531 {"RequireUtf8Locale", test_keyword_set_bool, offsetof(pkgconf_test_case_t, require_utf8_locale)}, 532 {"SetupCopy", test_keyword_extend_bufferset, offsetof(pkgconf_test_case_t, copies)}, 533 {"SetupMkdir", test_keyword_extend_bufferset, offsetof(pkgconf_test_case_t, mkdirs)}, 534 #ifdef _WIN32 535 {"SetupSymlink", test_keyword_disabled, 0}, 536 #else 537 {"SetupSymlink", test_keyword_extend_bufferset, offsetof(pkgconf_test_case_t, symlinks)}, 538 #endif 539 {"SkipPlatforms", test_keyword_set_buffer, offsetof(pkgconf_test_case_t, skip_platforms)}, 540 {"Tool", test_keyword_set_buffer, offsetof(pkgconf_test_case_t, tool)}, 541 {"ToolArgs", test_keyword_set_buffer, offsetof(pkgconf_test_case_t, tool_args)}, 542 {"VerbosityLevel", test_keyword_set_int, offsetof(pkgconf_test_case_t, verbosity)}, 543 {"WantedFlags", test_keyword_set_wanted_flags, offsetof(pkgconf_test_case_t, wanted_flags)}, 544 {"WantEnvPrefix", test_keyword_set_buffer, offsetof(pkgconf_test_case_t, want_env_prefix)}, 545 #ifndef PKGCONF_LITE 546 {"WantPersonality", test_keyword_set_buffer, offsetof(pkgconf_test_case_t, want_personality)}, 547 #endif 548 {"WantVariable", test_keyword_set_buffer, offsetof(pkgconf_test_case_t, want_variable)}, 549 }; 550 551 static void 552 test_keyword_set(void *data, const char *warnprefix, const char *keyword, const char *value) 553 { 554 pkgconf_test_case_t *testcase = data; 555 const pkgconf_test_keyword_pair_t *pair = bsearch(keyword, 556 test_keyword_pairs, PKGCONF_ARRAY_SIZE(test_keyword_pairs), 557 sizeof(*pair), test_keyword_pair_cmp); 558 559 if (pair == NULL || pair->func == NULL) 560 return; 561 562 pair->func(testcase, warnprefix, keyword, pair->offset, value); 563 } 564 565 static const pkgconf_parser_operand_func_t test_parser_ops[256] = 566 { 567 [':'] = (pkgconf_parser_operand_func_t) test_keyword_set, 568 }; 569 570 static void 571 test_parser_warn(void *p, const char *fmt, ...) 572 { 573 va_list va; 574 575 (void) p; 576 577 va_start(va, fmt); 578 vfprintf(stderr, fmt, va); 579 va_end(va); 580 } 581 582 static pkgconf_test_case_t * 583 load_test_case(char *testfile) 584 { 585 FILE *testf = fopen(testfile, "r"); 586 if (testf == NULL) 587 return NULL; 588 589 pkgconf_test_case_t *out = calloc(1, sizeof(*out)); 590 if (out == NULL) 591 goto cleanup; 592 593 char *nameptr; 594 if ((nameptr = strrchr(testfile, '/')) != NULL) 595 nameptr++; 596 else 597 nameptr = testfile; 598 599 out->name = strdup(nameptr); 600 601 // store directory containing the test file for ExpectedStdoutFile resolution 602 { 603 char *dirend = strrchr(testfile, '/'); 604 if (dirend != NULL) 605 { 606 size_t dirlen = (size_t)(dirend - testfile); 607 out->testfile_dir = calloc(1, dirlen + 1); 608 if (out->testfile_dir != NULL) 609 memcpy(out->testfile_dir, testfile, dirlen); 610 } 611 else 612 out->testfile_dir = strdup("."); 613 } 614 615 pkgconf_parser_parse(testf, out, test_parser_ops, test_parser_warn, testfile); 616 617 cleanup: 618 fclose(testf); 619 return out; 620 } 621 622 // we use a custom personality to ensure the tests are fully hermetic 623 static pkgconf_cross_personality_t * 624 personality_for_test(const pkgconf_test_case_t *testcase) 625 { 626 #ifndef PKGCONF_LITE 627 if (pkgconf_buffer_len(&testcase->want_personality)) 628 return pkgconf_cross_personality_find(pkgconf_buffer_str(&testcase->want_personality)); 629 #endif 630 631 pkgconf_cross_personality_t *pers = calloc(1, sizeof(*pers)); 632 if (pers == NULL) 633 return NULL; 634 635 pers->name = strdup("test"); 636 pkgconf_path_copy_list(&pers->dir_list, &testcase->search_path); 637 pkgconf_path_add("/test/sysroot/include", &pers->filter_includedirs, false); 638 pkgconf_path_add("/test/sysroot/lib", &pers->filter_libdirs, false); 639 640 return pers; 641 } 642 643 static bool 644 report_failure(pkgconf_test_match_strategy_t match, const pkgconf_buffer_t *expected, const pkgconf_buffer_t *actual, const char *buffername) 645 { 646 fprintf(stderr, 647 "================================================================================\n" 648 "%s did not%s match:\n" 649 " expected: [%s]\n" 650 " actual: [%s]\n" 651 "================================================================================\n", 652 buffername, match == MATCH_PARTIAL ? " partially" : "", 653 pkgconf_buffer_str_or_empty(expected), 654 pkgconf_buffer_str_or_empty(actual)); 655 656 return false; 657 } 658 659 static bool 660 test_match_buffer(pkgconf_test_match_strategy_t match, const pkgconf_buffer_t *expected, const pkgconf_buffer_t *actual, const char *buffername) 661 { 662 if (!pkgconf_buffer_len(expected) && match != MATCH_EMPTY) 663 return true; 664 665 if (!pkgconf_buffer_len(actual)) 666 { 667 if (match == MATCH_EMPTY) 668 return true; 669 670 return report_failure(match, expected, actual, buffername); 671 } 672 673 if (match == MATCH_PARTIAL) 674 return pkgconf_buffer_contains(actual, expected) ? true : report_failure(match, expected, actual, buffername); 675 676 return pkgconf_buffer_match(actual, expected) ? true : report_failure(match, expected, actual, buffername); 677 } 678 679 static bool 680 read_file_into_buffer(FILE *f, pkgconf_buffer_t *buf) 681 { 682 char tmp[4096] = {0}; 683 size_t n; 684 while ((n = fread(tmp, 1, sizeof(tmp), f)) > 0) 685 pkgconf_buffer_append_slice(buf, tmp, n); 686 687 if (ferror(f)) 688 return false; 689 690 return true; 691 } 692 693 static bool 694 open_file_into_buffer(const char *path, pkgconf_buffer_t *buf) 695 { 696 FILE *f = fopen(path, "r"); 697 if (f == NULL) 698 return false; 699 700 bool ok = read_file_into_buffer(f, buf); 701 fclose(f); 702 return ok; 703 } 704 705 static bool 706 copy_file(const char *dst, const char *src) 707 { 708 FILE *fsrc = fopen(src, "rb"); 709 if (!fsrc) 710 return false; 711 712 FILE *fdst = fopen(dst, "wb"); 713 if (!fdst) 714 { 715 int errno_save = errno; 716 fclose(fsrc); 717 errno = errno_save; 718 return false; 719 } 720 721 bool ok = true; 722 char buf[4096] = {0}; 723 size_t nr; 724 while ((nr = fread(buf, 1, sizeof(buf), fsrc)) > 0) 725 { 726 if (fwrite(buf, 1, nr, fdst) != nr) 727 { 728 ok = false; 729 break; 730 } 731 } 732 733 if (ferror(fsrc) || ferror(fdst)) 734 ok = false; 735 736 int errno_save = errno; 737 fclose(fsrc); 738 fclose(fdst); 739 errno = errno_save; 740 741 return ok; 742 } 743 744 /* 745 * Recursively remove a directory tree. 746 */ 747 static void 748 rmdir_recursive(const char *path) 749 { 750 DIR *dir = opendir(path); 751 if (dir == NULL) 752 return; 753 754 struct dirent *ent; 755 while ((ent = readdir(dir)) != NULL) 756 { 757 if (!strcmp(ent->d_name, ".") || !strcmp(ent->d_name, "..")) 758 continue; 759 760 pkgconf_buffer_t child = PKGCONF_BUFFER_INITIALIZER; 761 pkgconf_buffer_append(&child, path); 762 pkgconf_buffer_push_byte(&child, '/'); 763 pkgconf_buffer_append(&child, ent->d_name); 764 765 #ifdef _WIN32 766 if (_access(pkgconf_buffer_str(&child), 0) == 0) 767 { 768 // Get required buffer size 769 int size = MultiByteToWideChar(CP_ACP, 0, pkgconf_buffer_str(&child), -1, NULL, 0); 770 771 // Allocate and convert 772 wchar_t* wide_path = calloc(size, sizeof(wchar_t)); 773 assert(wide_path != NULL); 774 MultiByteToWideChar(CP_ACP, 0, pkgconf_buffer_str(&child), -1, wide_path, size); 775 776 DWORD attrs = GetFileAttributesW(wide_path); 777 778 free(wide_path); 779 780 if (attrs != INVALID_FILE_ATTRIBUTES && (attrs & FILE_ATTRIBUTE_DIRECTORY)) 781 rmdir_recursive(pkgconf_buffer_str(&child)); 782 else 783 unlink(pkgconf_buffer_str(&child)); 784 } 785 #else 786 struct stat st; 787 if (lstat(pkgconf_buffer_str(&child), &st) == 0) 788 { 789 if (S_ISDIR(st.st_mode)) 790 rmdir_recursive(pkgconf_buffer_str(&child)); 791 else 792 unlink(pkgconf_buffer_str(&child)); 793 } 794 #endif 795 796 pkgconf_buffer_finalize(&child); 797 } 798 799 closedir(dir); 800 rmdir(path); 801 } 802 803 /* 804 * Recursively make a directory tree. 805 */ 806 static bool 807 mkdir_recursive(const char *path) 808 { 809 if (!path) 810 return false; 811 812 const char *tmpstr = NULL; 813 pkgconf_buffer_t buf = PKGCONF_BUFFER_INITIALIZER; 814 size_t i = 0; 815 816 while (path[i]) 817 { 818 // Append characters up to the next separator 819 size_t start = i; 820 while (path[i] && path[i] != '/') 821 i++; 822 823 pkgconf_buffer_append_slice(&buf, path + start, i - start); 824 825 // If we hit a separator, try to create this component 826 if (path[i] == '/') 827 { 828 pkgconf_buffer_push_byte(&buf, '/'); 829 tmpstr = pkgconf_buffer_str(&buf); 830 if (tmpstr && mkdir(tmpstr, 0755) != 0 && errno != EEXIST) 831 { 832 pkgconf_buffer_finalize(&buf); 833 return false; 834 } 835 i++; // skip the separator 836 } 837 } 838 839 // Make the final directory (handles paths without trailing separator) 840 tmpstr = pkgconf_buffer_str(&buf); 841 bool ok = true; 842 if (tmpstr && strlen(tmpstr) > 0) 843 { 844 if (mkdir(tmpstr, 0755) != 0 && errno != EEXIST) 845 ok = false; 846 } 847 848 pkgconf_buffer_finalize(&buf); 849 return ok; 850 } 851 852 // Returns true if we need tmp_dir 853 static bool 854 needs_tmp_dir(const pkgconf_test_case_t *testcase) 855 { 856 #ifdef _WIN32 857 return testcase->mkdirs.head != NULL || testcase->copies.head != NULL; 858 #else // _WIN32 859 return testcase->mkdirs.head != NULL || testcase->copies.head != NULL || testcase->symlinks.head != NULL; 860 #endif // _WIN32 861 } 862 863 static int 864 run_tool(const pkgconf_test_case_t *testcase, pkgconf_buffer_t *o_stdout, pkgconf_buffer_t *o_stderr) 865 { 866 (void) o_stderr; // TODO: external tool stderr goes to real stderr for now 867 868 pkgconf_buffer_t cmdbuf = PKGCONF_BUFFER_INITIALIZER; 869 870 // build: <tool-dir>/<tool> <tool_args> 871 if (pkgconf_buffer_len(&test_tool_dir)) 872 { 873 pkgconf_buffer_append(&cmdbuf, pkgconf_buffer_str(&test_tool_dir)); 874 pkgconf_buffer_push_byte(&cmdbuf, '/'); 875 } 876 877 pkgconf_buffer_append(&cmdbuf, pkgconf_buffer_str(&testcase->tool)); 878 879 if (pkgconf_buffer_len(&testcase->tool_args)) 880 { 881 pkgconf_buffer_append(&cmdbuf, " "); 882 pkgconf_buffer_append(&cmdbuf, pkgconf_buffer_str(&testcase->tool_args)); 883 } 884 885 // Inject Environment vars for the child process 886 char tool_cwd[PATH_MAX] = {0}; 887 const char *pwd = getcwd(tool_cwd, sizeof(tool_cwd)); 888 889 pkgconf_node_t *iter; 890 PKGCONF_FOREACH_LIST_ENTRY(testcase->env_vars.head, iter) 891 { 892 pkgconf_test_environ_t *env = iter->data; 893 pkgconf_buffer_t expanded = PKGCONF_BUFFER_INITIALIZER; 894 handle_substs(&expanded, PKGCONF_BUFFER_FROM_STR(env->value), pwd); 895 setenv(env->key, pkgconf_buffer_str_or_empty(&expanded), 1); 896 pkgconf_buffer_finalize(&expanded); 897 } 898 899 FILE *pipe = popen(pkgconf_buffer_str(&cmdbuf), "r"); 900 pkgconf_buffer_finalize(&cmdbuf); 901 902 PKGCONF_FOREACH_LIST_ENTRY(testcase->env_vars.head, iter) 903 { 904 pkgconf_test_environ_t *env = iter->data; 905 unsetenv(env->key); 906 } 907 908 if (pipe == NULL) 909 { 910 fprintf(stderr, "popen failed for tool '%s': %s\n", 911 pkgconf_buffer_str(&testcase->tool), strerror(errno)); 912 return -1; 913 } 914 915 bool ok = read_file_into_buffer(pipe, o_stdout); 916 int saved_errno = errno; // pclose() will clobber errno, save it 917 int status = pclose(pipe); 918 if (!ok) 919 { 920 fprintf(stderr, "read failed into buffer for command '%s': %s", 921 pkgconf_buffer_str(&testcase->tool), strerror(saved_errno)); 922 return -1; 923 } 924 925 if (status == -1) 926 { 927 fprintf(stderr, "pclose failed for command '%s': %s\n", 928 pkgconf_buffer_str(&testcase->tool), strerror(errno)); 929 return -1; 930 } 931 932 #if defined(WIFEXITED) && defined(WEXITSTATUS) 933 if (WIFEXITED(status)) 934 return WEXITSTATUS(status); 935 936 fprintf(stderr, "command '%s' did not exit normally\n", 937 pkgconf_buffer_str(&testcase->tool)); 938 return -1; 939 #else 940 return status; 941 #endif 942 } 943 944 /* 945 * Split a bufferset entry on the first space into left and right halves. 946 * Caller must free *left_out and *right_out. 947 */ 948 static bool 949 split_pair(const char *entry, char **left_out, char **right_out) 950 { 951 if (entry == NULL) 952 return false; 953 954 const char *sp = strchr(entry, ' '); 955 if (sp == NULL) 956 return false; 957 958 *left_out = pkgconf_strndup(entry, (size_t)(sp - entry)); 959 *right_out = strdup(sp + 1); 960 return true; 961 } 962 963 /* 964 * run_setup: execute mkdirs, copies, and symlinks in order. 965 * Must be called after chdir() into the tmp_dir. 966 */ 967 static bool 968 run_setup(const pkgconf_test_case_t *testcase, const char *pwd) 969 { 970 pkgconf_node_t *iter; 971 972 // mkdirs: each entry is a single path, relative to tmp_dir 973 PKGCONF_FOREACH_LIST_ENTRY(testcase->mkdirs.head, iter) 974 { 975 pkgconf_bufferset_t *set = iter->data; 976 977 pkgconf_buffer_t path = PKGCONF_BUFFER_INITIALIZER; 978 handle_substs(&path, &set->buffer, pwd); 979 980 bool ok = mkdir_recursive(pkgconf_buffer_str(&path)); 981 pkgconf_buffer_finalize(&path); 982 983 if (!ok && errno != EEXIST) 984 { 985 fprintf(stderr, "SetupMkdir: mkdir '%s' failed: %s\n", 986 pkgconf_buffer_str_or_empty(&set->buffer), strerror(errno)); 987 return false; 988 } 989 } 990 991 // copies: "src dst", src relative to TEST_FIXTURES_DIR, dst relative to tmp_dir 992 PKGCONF_FOREACH_LIST_ENTRY(testcase->copies.head, iter) 993 { 994 pkgconf_bufferset_t *set = iter->data; 995 996 pkgconf_buffer_t expanded = PKGCONF_BUFFER_INITIALIZER; 997 handle_substs(&expanded, &set->buffer, pwd); 998 999 char *left = NULL, *right = NULL; 1000 if (!split_pair(pkgconf_buffer_str(&expanded), &left, &right)) 1001 { 1002 fprintf(stderr, "SetupCopy: malformed entry (expected 'src dst'): %s\n", 1003 pkgconf_buffer_str_or_empty(&set->buffer)); 1004 pkgconf_buffer_finalize(&expanded); 1005 return false; 1006 } 1007 pkgconf_buffer_finalize(&expanded); 1008 1009 pkgconf_buffer_t srcpath = PKGCONF_BUFFER_INITIALIZER; 1010 pkgconf_buffer_append(&srcpath, pkgconf_buffer_str(&test_fixtures_dir)); 1011 pkgconf_buffer_push_byte(&srcpath, '/'); 1012 pkgconf_buffer_append(&srcpath, left); 1013 1014 bool ok = copy_file(right, pkgconf_buffer_str(&srcpath)); 1015 pkgconf_buffer_finalize(&srcpath); 1016 if (!ok) 1017 { 1018 fprintf(stderr, "SetupCopy: failed to copy file '%s' to '%s': %s\n", left, right, strerror(errno)); 1019 free(left); 1020 free(right); 1021 return false; 1022 } 1023 1024 free(left); 1025 free(right); 1026 } 1027 1028 #ifndef _WIN32 1029 // symlinks: "target linkpath" — both may be relative to tmp_dir or absolute after %PWD% expansion 1030 PKGCONF_FOREACH_LIST_ENTRY(testcase->symlinks.head, iter) 1031 { 1032 pkgconf_bufferset_t *set = iter->data; 1033 1034 pkgconf_buffer_t expanded = PKGCONF_BUFFER_INITIALIZER; 1035 handle_substs(&expanded, &set->buffer, pwd); 1036 1037 char *target = NULL, *linkpath = NULL; 1038 if (!split_pair(pkgconf_buffer_str(&expanded), &target, &linkpath)) 1039 { 1040 fprintf(stderr, "SetupSymlink: malformed entry (expected 'target linkpath'): %s\n", 1041 pkgconf_buffer_str_or_empty(&set->buffer)); 1042 pkgconf_buffer_finalize(&expanded); 1043 return false; 1044 } 1045 pkgconf_buffer_finalize(&expanded); 1046 1047 unlink(linkpath); 1048 1049 if (symlink(target, linkpath) != 0) 1050 { 1051 fprintf(stderr, "SetupSymlink: symlink('%s', '%s') failed: %s\n", 1052 target, linkpath, strerror(errno)); 1053 free(target); 1054 free(linkpath); 1055 return false; 1056 } 1057 1058 free(target); 1059 free(linkpath); 1060 } 1061 #endif // _WIN32 1062 1063 return true; 1064 } 1065 1066 static void 1067 annotate_result(const pkgconf_test_case_t *testcase, int ret, const pkgconf_test_output_t *out) 1068 { 1069 pkgconf_buffer_t search_path_buf = PKGCONF_BUFFER_INITIALIZER; 1070 const pkgconf_node_t *iter; 1071 1072 PKGCONF_FOREACH_LIST_ENTRY(testcase->search_path.head, iter) 1073 { 1074 const pkgconf_path_t *path = iter->data; 1075 1076 if (pkgconf_buffer_len(&search_path_buf)) 1077 pkgconf_buffer_push_byte(&search_path_buf, ' '); 1078 1079 pkgconf_buffer_append(&search_path_buf, path->path); 1080 } 1081 1082 pkgconf_buffer_t wanted_flags_buf = PKGCONF_BUFFER_INITIALIZER; 1083 1084 for (size_t i = 0; i < PKGCONF_ARRAY_SIZE(test_flag_pairs); i++) 1085 { 1086 const pkgconf_test_flag_pair_t *pair = &test_flag_pairs[i]; 1087 1088 if ((testcase->wanted_flags & pair->flag) == pair->flag) 1089 { 1090 if (pkgconf_buffer_len(&wanted_flags_buf)) 1091 pkgconf_buffer_push_byte(&wanted_flags_buf, ' '); 1092 1093 pkgconf_buffer_append(&wanted_flags_buf, pair->name); 1094 } 1095 } 1096 1097 pkgconf_buffer_t env_buf = PKGCONF_BUFFER_INITIALIZER; 1098 1099 PKGCONF_FOREACH_LIST_ENTRY(testcase->env_vars.head, iter) 1100 { 1101 const pkgconf_test_environ_t *env = iter->data; 1102 1103 if (pkgconf_buffer_len(&env_buf)) 1104 pkgconf_buffer_append(&env_buf, "\n "); 1105 1106 pkgconf_buffer_append_fmt(&env_buf, "%s: %s", env->key, env->value); 1107 } 1108 1109 fprintf(stderr, 1110 "--------------------------------------------------------------------------------\n" 1111 "search-path: <%s>\n" 1112 "wanted-flags: <%s>\n" 1113 "environment:\n" 1114 " %s\n" 1115 "query: [%s]\n" 1116 "exit-code: %d\n" 1117 "verbosity: %d\n", 1118 pkgconf_buffer_str_or_empty(&search_path_buf), 1119 pkgconf_buffer_str_or_empty(&wanted_flags_buf), 1120 pkgconf_buffer_str_or_empty(&env_buf), 1121 pkgconf_buffer_str_or_empty(&testcase->query), 1122 ret, 1123 testcase->verbosity); 1124 1125 if (pkgconf_buffer_len(&testcase->tool)) 1126 fprintf(stderr, "tool: [%s] tool-args: [%s]\n", 1127 pkgconf_buffer_str_or_empty(&testcase->tool), 1128 pkgconf_buffer_str_or_empty(&testcase->tool_args)); 1129 1130 fprintf(stderr, "stdout: [%s]\n", 1131 pkgconf_buffer_str_or_empty(&out->o_stdout)); 1132 1133 PKGCONF_FOREACH_LIST_ENTRY(testcase->expected_stdout.head, iter) 1134 { 1135 pkgconf_bufferset_t *set = iter->data; 1136 1137 fprintf(stderr, 1138 "expected-stdout: [%s] (%s)\n", 1139 pkgconf_buffer_str_or_empty(&set->buffer), 1140 testcase->match_stdout == MATCH_PARTIAL ? "partial" : "exact"); 1141 } 1142 1143 if (pkgconf_buffer_len(&testcase->expected_stdout_file)) 1144 fprintf(stderr, "expected-stdout-file: [%s]\n", 1145 pkgconf_buffer_str_or_empty(&testcase->expected_stdout_file)); 1146 1147 fprintf(stderr, "stderr: [%s]\n", 1148 pkgconf_buffer_str_or_empty(&out->o_stderr)); 1149 1150 PKGCONF_FOREACH_LIST_ENTRY(testcase->expected_stderr.head, iter) 1151 { 1152 pkgconf_bufferset_t *set = iter->data; 1153 1154 fprintf(stderr, 1155 "expected-stderr: [%s] (%s)\n", 1156 pkgconf_buffer_str_or_empty(&set->buffer), 1157 testcase->match_stderr == MATCH_PARTIAL ? "partial" : "exact"); 1158 } 1159 1160 PKGCONF_FOREACH_LIST_ENTRY(testcase->define_variables.head, iter) 1161 { 1162 pkgconf_bufferset_t *set = iter->data; 1163 fprintf(stderr, "define-variable: [%s]\n", pkgconf_buffer_str_or_empty(&set->buffer)); 1164 } 1165 1166 fprintf(stderr, 1167 "want-env-prefix: [%s]\n" 1168 "fragment-filter: [%s]\n" 1169 "--------------------------------------------------------------------------------\n", 1170 pkgconf_buffer_str_or_empty(&testcase->want_env_prefix), 1171 pkgconf_buffer_str_or_empty(&testcase->fragment_filter)); 1172 1173 pkgconf_buffer_finalize(&search_path_buf); 1174 pkgconf_buffer_finalize(&wanted_flags_buf); 1175 pkgconf_buffer_finalize(&env_buf); 1176 } 1177 1178 static bool 1179 run_test_case(const pkgconf_test_case_t *testcase) 1180 { 1181 bool passed = true; 1182 1183 const pkgconf_buffer_t *our_platform = PKGCONF_BUFFER_FROM_STR(PKGCONF_TEST_PLATFORM); 1184 if (pkgconf_buffer_contains(&testcase->skip_platforms, our_platform)) 1185 { 1186 printf("# test skipped on %s\nSKIP: %s\n", 1187 pkgconf_buffer_str(our_platform), testcase->name); 1188 return true; 1189 } 1190 1191 if (testcase->require_utf8_locale && !pkgconf_is_locale_utf8()) 1192 { 1193 printf("# test skipped: requires a UTF-8 locale\nSKIP: %s\n", testcase->name); 1194 return true; 1195 } 1196 1197 // If the test has setup steps, create a new tmp_dir and chdir into it. 1198 char original_cwd[PATH_MAX] = {0}; 1199 char *tmp_dir = NULL; 1200 1201 if (getcwd(original_cwd, sizeof(original_cwd)) == NULL) 1202 { 1203 fprintf(stderr, "FAIL: getcwd failed: %s\n", strerror(errno)); 1204 return false; 1205 } 1206 1207 if (needs_tmp_dir(testcase)) 1208 { 1209 pkgconf_buffer_t tmp_buf = PKGCONF_BUFFER_INITIALIZER; 1210 pkgconf_buffer_append_fmt(&tmp_buf, "%s/pkgconf-test-XXXXXX", original_cwd); 1211 tmp_dir = pkgconf_buffer_freeze(&tmp_buf); 1212 1213 if (mkdtemp(tmp_dir) == NULL) 1214 { 1215 fprintf(stderr, "FAIL: mkdtemp failed: %s\n", strerror(errno)); 1216 free(tmp_dir); 1217 return false; 1218 } 1219 1220 if (chdir(tmp_dir) != 0) 1221 { 1222 fprintf(stderr, "FAIL: chdir('%s') failed: %s\n", tmp_dir, strerror(errno)); 1223 rmdir(tmp_dir); 1224 free(tmp_dir); 1225 return false; 1226 } 1227 1228 if (!run_setup(testcase, tmp_dir)) 1229 { 1230 fprintf(stderr, "FAIL: %s (setup failed)\n", testcase->name); 1231 chdir(original_cwd); 1232 rmdir_recursive(tmp_dir); 1233 free(tmp_dir); 1234 return false; 1235 } 1236 } 1237 1238 pkgconf_test_output_t *out = (pkgconf_test_output_t *) test_output(); 1239 int ret; 1240 1241 if (pkgconf_buffer_len(&testcase->tool)) 1242 { 1243 ret = run_tool(testcase, &out->o_stdout, &out->o_stderr); 1244 } 1245 else 1246 { 1247 pkgconf_cross_personality_t *personality = personality_for_test(testcase); 1248 pkgconf_test_state_t state = 1249 { 1250 .cli_state.want_flags = testcase->wanted_flags, 1251 .cli_state.want_env_prefix = pkgconf_buffer_str(&testcase->want_env_prefix), 1252 .cli_state.want_variable = pkgconf_buffer_str(&testcase->want_variable), 1253 .cli_state.want_fragment_filter = pkgconf_buffer_str(&testcase->fragment_filter), 1254 .cli_state.required_module_version = pkgconf_buffer_str(&testcase->atleast_version), 1255 .cli_state.required_exact_module_version = pkgconf_buffer_str(&testcase->exact_version), 1256 .cli_state.required_max_module_version = pkgconf_buffer_str(&testcase->max_version), 1257 .cli_state.verbosity = testcase->verbosity, 1258 .testcase = testcase, 1259 }; 1260 1261 pkgconf_client_init(&state.cli_state.pkg_client, error_handler, NULL, personality, &state, environ_lookup_handler); 1262 pkgconf_client_set_output(&state.cli_state.pkg_client, &out->output); 1263 1264 pkgconf_node_t *iter; 1265 PKGCONF_FOREACH_LIST_ENTRY(testcase->define_variables.head, iter) 1266 { 1267 pkgconf_bufferset_t *set = iter->data; 1268 pkgconf_tuple_define_global(&state.cli_state.pkg_client, pkgconf_buffer_str_or_empty(&set->buffer)); 1269 } 1270 1271 /* 1272 * Re-expand Query now that %PWD% is known (if we have a tmp_dir). 1273 * For tests without a tmp_dir this is a no-op since %PWD% won't appear. 1274 */ 1275 char query_cwd[PATH_MAX] = {0}; 1276 const char *query_pwd = getcwd(query_cwd, sizeof(query_cwd)); 1277 1278 pkgconf_buffer_t query_expanded = PKGCONF_BUFFER_INITIALIZER; 1279 handle_substs(&query_expanded, &testcase->query, query_pwd); 1280 1281 pkgconf_buffer_t arg_buf = PKGCONF_BUFFER_INITIALIZER; 1282 int test_argc = 0; 1283 char **test_argv = NULL; 1284 1285 if (pkgconf_buffer_len(&query_expanded)) 1286 pkgconf_buffer_append_fmt(&arg_buf, "pkgconf %s", pkgconf_buffer_str(&query_expanded)); 1287 else 1288 pkgconf_buffer_append(&arg_buf, "pkgconf"); 1289 1290 pkgconf_argv_split(pkgconf_buffer_str(&arg_buf), &test_argc, &test_argv); 1291 pkgconf_buffer_finalize(&arg_buf); 1292 pkgconf_buffer_finalize(&query_expanded); 1293 1294 pkgconf_client_set_warn_handler(&state.cli_state.pkg_client, error_handler, NULL); 1295 1296 #ifndef PKGCONF_LITE 1297 if (debug) 1298 pkgconf_client_set_trace_handler(&state.cli_state.pkg_client, debug_handler, NULL); 1299 #endif // PKGCONF_LITE 1300 1301 ret = pkgconf_cli_run(&state.cli_state, test_argc, test_argv, 1); 1302 pkgconf_argv_free(test_argv); 1303 } 1304 1305 if (pkgconf_buffer_len(&out->o_stdout)) 1306 pkgconf_buffer_trim_byte(&out->o_stdout); 1307 1308 if (pkgconf_buffer_len(&out->o_stderr)) 1309 pkgconf_buffer_trim_byte(&out->o_stderr); 1310 1311 pkgconf_node_t *iter; 1312 1313 PKGCONF_FOREACH_LIST_ENTRY(testcase->expected_stdout.head, iter) 1314 { 1315 pkgconf_bufferset_t *set = iter->data; 1316 1317 char expected_cwd[PATH_MAX] = {0}; 1318 const char *expected_pwd = getcwd(expected_cwd, sizeof(expected_cwd)); 1319 1320 pkgconf_buffer_t expected_expanded = PKGCONF_BUFFER_INITIALIZER; 1321 handle_substs(&expected_expanded, &set->buffer, expected_pwd); 1322 1323 if (!test_match_buffer(testcase->match_stdout, &expected_expanded, &out->o_stdout, "stdout")) 1324 passed = false; 1325 1326 pkgconf_buffer_finalize(&expected_expanded); 1327 } 1328 1329 // ExpectedStdoutFile: load file relative to the .test file's directory 1330 if (pkgconf_buffer_len(&testcase->expected_stdout_file)) 1331 { 1332 pkgconf_buffer_t filepath = PKGCONF_BUFFER_INITIALIZER; 1333 pkgconf_buffer_append(&filepath, testcase->testfile_dir); 1334 pkgconf_buffer_push_byte(&filepath, '/'); 1335 pkgconf_buffer_append(&filepath, pkgconf_buffer_str(&testcase->expected_stdout_file)); 1336 1337 pkgconf_buffer_t file_contents = PKGCONF_BUFFER_INITIALIZER; 1338 if (!open_file_into_buffer(pkgconf_buffer_str(&filepath), &file_contents)) 1339 { 1340 fprintf(stderr, "ExpectedStdoutFile: failed to open '%s': %s\n", pkgconf_buffer_str(&filepath), strerror(errno)); 1341 passed = false; 1342 } 1343 else 1344 { 1345 if (pkgconf_buffer_len(&file_contents)) 1346 pkgconf_buffer_trim_byte(&file_contents); 1347 1348 if (!test_match_buffer(testcase->match_stdout, &file_contents, &out->o_stdout, "stdout (file)")) 1349 passed = false; 1350 } 1351 1352 pkgconf_buffer_finalize(&file_contents); 1353 pkgconf_buffer_finalize(&filepath); 1354 } 1355 1356 PKGCONF_FOREACH_LIST_ENTRY(testcase->expected_stderr.head, iter) 1357 { 1358 pkgconf_bufferset_t *set = iter->data; 1359 1360 if (!test_match_buffer(testcase->match_stderr, &set->buffer, &out->o_stderr, "stderr")) 1361 passed = false; 1362 } 1363 1364 if (ret != testcase->exitcode) 1365 { 1366 fprintf(stderr, "exitcode %d does not match expected %d\n", ret, testcase->exitcode); 1367 passed = false; 1368 } 1369 1370 printf("%s: %s\n", passed ? "PASS" : "FAIL", testcase->name); 1371 1372 if (!passed) 1373 annotate_result(testcase, ret, out); 1374 1375 test_output_reset(out); 1376 1377 // Restore cwd and clean up tmp_dir if we created one 1378 if (tmp_dir && strcmp(tmp_dir, original_cwd) != 0) 1379 { 1380 chdir(original_cwd); 1381 rmdir_recursive(tmp_dir); 1382 } 1383 1384 free(tmp_dir); 1385 return passed; 1386 } 1387 1388 static void 1389 free_test_case(pkgconf_test_case_t *testcase) 1390 { 1391 pkgconf_bufferset_free(&testcase->define_variables); 1392 pkgconf_bufferset_free(&testcase->expected_stderr); 1393 pkgconf_bufferset_free(&testcase->expected_stdout); 1394 pkgconf_bufferset_free(&testcase->mkdirs); 1395 #ifndef _WIN32 1396 pkgconf_bufferset_free(&testcase->symlinks); 1397 #endif // _WIN32 1398 pkgconf_bufferset_free(&testcase->copies); 1399 1400 test_environment_free(&testcase->env_vars); 1401 pkgconf_path_free(&testcase->search_path); 1402 1403 pkgconf_buffer_finalize(&testcase->query); 1404 pkgconf_buffer_finalize(&testcase->want_env_prefix); 1405 pkgconf_buffer_finalize(&testcase->want_variable); 1406 pkgconf_buffer_finalize(&testcase->fragment_filter); 1407 pkgconf_buffer_finalize(&testcase->skip_platforms); 1408 pkgconf_buffer_finalize(&testcase->atleast_version); 1409 pkgconf_buffer_finalize(&testcase->exact_version); 1410 pkgconf_buffer_finalize(&testcase->max_version); 1411 pkgconf_buffer_finalize(&testcase->expected_stdout_file); 1412 pkgconf_buffer_finalize(&testcase->tool); 1413 pkgconf_buffer_finalize(&testcase->tool_args); 1414 1415 #ifndef PKGCONF_LITE 1416 pkgconf_buffer_finalize(&testcase->want_personality); 1417 #endif 1418 1419 free(testcase->name); 1420 free(testcase->testfile_dir); 1421 free(testcase); 1422 } 1423 1424 static bool 1425 process_test_case(char *testcase_file) 1426 { 1427 pkgconf_test_case_t *testcase = load_test_case(testcase_file); 1428 bool ret; 1429 1430 if (testcase == NULL) 1431 { 1432 fprintf(stderr, "test %s failed to load\n", testcase_file); 1433 return false; 1434 } 1435 1436 ret = run_test_case(testcase); 1437 free_test_case(testcase); 1438 1439 return ret; 1440 } 1441 1442 static inline bool 1443 str_has_suffix(const char *str, const char *suffix) 1444 { 1445 size_t str_len = strlen(str); 1446 size_t suf_len = strlen(suffix); 1447 1448 if (str_len < suf_len) 1449 return false; 1450 1451 return !strncasecmp(str + str_len - suf_len, suffix, suf_len); 1452 } 1453 1454 static int 1455 path_sort_cmp(const void *a, const void *b) 1456 { 1457 return strcmp(*(const char **) a, *(const char **) b); 1458 } 1459 1460 static bool 1461 process_test_directory(char *dirpath) 1462 { 1463 bool ret = true; 1464 DIR *dir = opendir(dirpath); 1465 if (dir == NULL) 1466 { 1467 fprintf(stderr, "failed to open test directory %s\n", dirpath); 1468 return false; 1469 } 1470 char **paths = NULL; 1471 size_t numpaths = 0; 1472 1473 struct dirent *dirent; 1474 for (dirent = readdir(dir); dirent != NULL; dirent = readdir(dir)) 1475 { 1476 pkgconf_buffer_t pathbuf = PKGCONF_BUFFER_INITIALIZER; 1477 1478 pkgconf_buffer_append(&pathbuf, dirpath); 1479 pkgconf_buffer_push_byte(&pathbuf, '/'); 1480 pkgconf_buffer_append(&pathbuf, dirent->d_name); 1481 1482 char *pathstr = pkgconf_buffer_freeze(&pathbuf); 1483 if (pathstr == NULL) 1484 continue; 1485 1486 if (!str_has_suffix(pathstr, ".test")) 1487 { 1488 free(pathstr); 1489 continue; 1490 } 1491 1492 paths = pkgconf_reallocarray(paths, ++numpaths, sizeof(void *)); 1493 paths[numpaths - 1] = pathstr; 1494 } 1495 1496 qsort(paths, numpaths, sizeof(void *), path_sort_cmp); 1497 1498 for (size_t i = 0; i < numpaths; i++) 1499 { 1500 char *pathstr = paths[i]; 1501 1502 ret = process_test_case(pathstr); 1503 if (!ret) 1504 break; 1505 } 1506 1507 for (size_t i = 0; i < numpaths; i++) 1508 free(paths[i]); 1509 1510 free(paths); 1511 closedir(dir); 1512 return ret; 1513 } 1514 1515 static void 1516 usage(void) 1517 { 1518 fprintf(stderr, "usage: test-runner --test-fixtures <path-to-fixtures> [--tool-dir <path>] <path-to-tests>\n"); 1519 exit(EXIT_FAILURE); 1520 } 1521 1522 int 1523 main(int argc, char *argv[]) 1524 { 1525 int ret; 1526 char *test_fixtures_dir_arg = NULL; 1527 char *test_tool_dir_arg = NULL; 1528 1529 struct pkg_option options[] = 1530 { 1531 {"test-fixtures", required_argument, NULL, 1}, 1532 {"debug", no_argument, NULL, 2}, 1533 {"test-case", required_argument, NULL, 3}, 1534 {"tool-dir", required_argument, NULL, 4}, 1535 {NULL, 0, NULL, 0}, 1536 }; 1537 char *testcase = NULL; 1538 1539 while ((ret = pkg_getopt_long_only(argc, argv, "", options, NULL)) != -1) 1540 { 1541 switch (ret) 1542 { 1543 case 1: 1544 test_fixtures_dir_arg = pkg_optarg; 1545 break; 1546 case 2: 1547 debug = true; 1548 break; 1549 case 3: 1550 testcase = pkg_optarg; 1551 break; 1552 case 4: 1553 test_tool_dir_arg = pkg_optarg; 1554 break; 1555 } 1556 } 1557 1558 if (test_fixtures_dir_arg == NULL) 1559 usage(); 1560 1561 { 1562 char test_fixtures_dir_abs[PATH_MAX] = {0}; 1563 if (!realpath(test_fixtures_dir_arg, test_fixtures_dir_abs)) 1564 { 1565 fprintf(stderr, "realpath failed: %s\n", strerror(errno)); 1566 return EXIT_FAILURE; 1567 } 1568 const pkgconf_buffer_t *test_fixtures_dir_arg_buf = PKGCONF_BUFFER_FROM_STR_NONNULL(test_fixtures_dir_abs); 1569 pkgconf_buffer_subst(&test_fixtures_dir, test_fixtures_dir_arg_buf, "\\", "/"); 1570 } 1571 1572 if (test_tool_dir_arg != NULL) 1573 { 1574 const pkgconf_buffer_t *test_tool_dir_arg_buf = PKGCONF_BUFFER_FROM_STR(test_tool_dir_arg); 1575 pkgconf_buffer_subst(&test_tool_dir, test_tool_dir_arg_buf, "\\", "/"); 1576 } 1577 1578 if (testcase != NULL) 1579 return process_test_case(testcase) ? EXIT_SUCCESS : EXIT_FAILURE; 1580 1581 if (argv[pkg_optind] == NULL) 1582 usage(); 1583 1584 return process_test_directory(argv[pkg_optind]) ? EXIT_SUCCESS : EXIT_FAILURE; 1585 } 1586