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