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
test_environment_push(pkgconf_test_case_t * testcase,const char * key,const char * value)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
test_environment_free(pkgconf_list_t * env_list)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 *
environ_lookup_handler(const pkgconf_client_t * client,const char * key)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
debug_handler(const char * msg,const pkgconf_client_t * client,void * data)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
error_handler(const char * msg,const pkgconf_client_t * client,void * data)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
write_handler(pkgconf_output_t * output,pkgconf_output_stream_t stream,const pkgconf_buffer_t * buffer)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 *
test_output(void)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
test_output_reset(pkgconf_test_output_t * out)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
handle_substs(pkgconf_buffer_t * dest,const pkgconf_buffer_t * src,const char * pwd)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
test_keyword_pair_cmp(const void * key,const void * ptr)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
test_keyword_set_int(pkgconf_test_case_t * testcase,const char * keyword,const char * warnprefix,const ptrdiff_t offset,const char * value)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
test_keyword_set_bool(pkgconf_test_case_t * testcase,const char * keyword,const char * warnprefix,const ptrdiff_t offset,const char * value)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
test_keyword_set_buffer(pkgconf_test_case_t * testcase,const char * keyword,const char * warnprefix,const ptrdiff_t offset,const char * value)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
test_keyword_extend_bufferset(pkgconf_test_case_t * testcase,const char * keyword,const char * warnprefix,const ptrdiff_t offset,const char * value)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
test_flag_pair_cmp(const void * key,const void * ptr)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
test_keyword_set_wanted_flags(pkgconf_test_case_t * testcase,const char * keyword,const char * warnprefix,const ptrdiff_t offset,const char * value)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
prefixed_path_split(const char * text,pkgconf_list_t * dirlist,const char * prefix)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
test_keyword_set_path_list(pkgconf_test_case_t * testcase,const char * keyword,const char * warnprefix,const ptrdiff_t offset,const char * value)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
test_keyword_set_match_strategy(pkgconf_test_case_t * testcase,const char * keyword,const char * warnprefix,const ptrdiff_t offset,const char * value)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
test_keyword_set_environment(pkgconf_test_case_t * testcase,const char * keyword,const char * warnprefix,const ptrdiff_t offset,const char * value)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
test_keyword_disabled(pkgconf_test_case_t * testcase,const char * keyword,const char * warnprefix,const ptrdiff_t offset,const char * value)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
test_keyword_set(void * data,const char * warnprefix,const char * keyword,const char * value)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
test_parser_warn(void * p,const char * fmt,...)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 *
load_test_case(char * testfile)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 *
personality_for_test(const pkgconf_test_case_t * testcase)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
report_failure(pkgconf_test_match_strategy_t match,const pkgconf_buffer_t * expected,const pkgconf_buffer_t * actual,const char * buffername)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
test_match_buffer(pkgconf_test_match_strategy_t match,const pkgconf_buffer_t * expected,const pkgconf_buffer_t * actual,const char * buffername)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
read_file_into_buffer(FILE * f,pkgconf_buffer_t * buf)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
open_file_into_buffer(const char * path,pkgconf_buffer_t * buf)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
copy_file(const char * dst,const char * src)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
rmdir_recursive(const char * path)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
mkdir_recursive(const char * path)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
needs_tmp_dir(const pkgconf_test_case_t * testcase)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
run_tool(const pkgconf_test_case_t * testcase,pkgconf_buffer_t * o_stdout,pkgconf_buffer_t * o_stderr)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
split_pair(const char * entry,char ** left_out,char ** right_out)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
run_setup(const pkgconf_test_case_t * testcase,const char * pwd)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
annotate_result(const pkgconf_test_case_t * testcase,int ret,const pkgconf_test_output_t * out)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
run_test_case(const pkgconf_test_case_t * testcase)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
free_test_case(pkgconf_test_case_t * testcase)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
process_test_case(char * testcase_file)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
str_has_suffix(const char * str,const char * suffix)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
path_sort_cmp(const void * a,const void * b)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
process_test_directory(char * dirpath)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
usage(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
main(int argc,char * argv[])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