xref: /freebsd/contrib/pkgconf/tests/api/test-bytecode.c (revision 592efe252472a3385acf36b1f49ecf710a7f3d9c)
1 /*
2  * test-bytecode.c
3  * Tests for the public libpkgconf bytecode API:
4  * pkgconf_bytecode_compile and pkgconf_bytecode_eval_str.
5  *
6  * SPDX-License-Identifier: pkgconf
7  *
8  * Copyright (c) 2025 pkgconf authors (see AUTHORS).
9  *
10  * Permission to use, copy, modify, and/or distribute this software for any
11  * purpose with or without fee is hereby granted, provided that the above
12  * copyright notice and this permission notice appear in all copies.
13  *
14  * This software is provided 'as is' and without any warranty, express or
15  * implied.  In no event shall the authors be liable for any damages arising
16  * from the use of this software.
17  */
18 
19 #include <libpkgconf/stdinc.h>
20 #include <libpkgconf/libpkgconf.h>
21 #include "test-api.h"
22 
23 /*
24  * Build a variable list with the given key/value pairs. Caller frees
25  * with pkgconf_variable_list_free().
26  *
27  * The key/value pairs are stored by compiling `value` as bytecode and
28  * stashing the result on a pkgconf_variable_t inside the list, which
29  * is how the parser builds variable scopes for real .pc files.
30  */
31 static void
seed_variable(pkgconf_list_t * vars,const char * key,const char * value)32 seed_variable(pkgconf_list_t *vars, const char *key, const char *value)
33 {
34 	pkgconf_variable_t *v = pkgconf_variable_get_or_create(vars, key);
35 	TEST_ASSERT_NONNULL(v);
36 
37 	pkgconf_buffer_reset(&v->bcbuf);
38 	pkgconf_bytecode_compile(&v->bcbuf, value);
39 	pkgconf_bytecode_from_buffer(&v->bc, &v->bcbuf);
40 }
41 
42 static void
test_emit_text_and_eval(void)43 test_emit_text_and_eval(void)
44 {
45 	pkgconf_client_t *client = test_client_new();
46 	pkgconf_list_t vars = PKGCONF_LIST_INITIALIZER;
47 	pkgconf_buffer_t bcbuf = PKGCONF_BUFFER_INITIALIZER;
48 	pkgconf_bytecode_t bc;
49 	bool saw_sysroot = false;
50 
51 	TEST_ASSERT_TRUE(pkgconf_bytecode_emit_text(&bcbuf, "plain", 5));
52 	pkgconf_bytecode_from_buffer(&bc, &bcbuf);
53 
54 	pkgconf_buffer_t out = PKGCONF_BUFFER_INITIALIZER;
55 	TEST_ASSERT_TRUE(pkgconf_bytecode_eval(client, &vars, &bc, &out, &saw_sysroot));
56 	TEST_ASSERT_STRCMP_EQ(pkgconf_buffer_str_or_empty(&out), "plain");
57 
58 	pkgconf_buffer_finalize(&out);
59 	pkgconf_buffer_finalize(&bcbuf);
60 	pkgconf_variable_list_free(&vars);
61 	pkgconf_client_free(client);
62 }
63 
64 static void
test_emit_guards(void)65 test_emit_guards(void)
66 {
67 	pkgconf_buffer_t bcbuf = PKGCONF_BUFFER_INITIALIZER;
68 
69 	TEST_ASSERT_TRUE(pkgconf_bytecode_emit_text(&bcbuf, NULL, 5));
70 	TEST_ASSERT_TRUE(pkgconf_bytecode_emit_text(&bcbuf, "x", 0));
71 	TEST_ASSERT_TRUE(pkgconf_bytecode_emit_var(&bcbuf, NULL, 3));
72 	TEST_ASSERT_TRUE(pkgconf_bytecode_emit_var(&bcbuf, "x", 0));
73 
74 	TEST_ASSERT_EQ(pkgconf_buffer_len(&bcbuf), 0);
75 
76 	pkgconf_buffer_finalize(&bcbuf);
77 }
78 
79 static void
test_emit_var_and_eval(void)80 test_emit_var_and_eval(void)
81 {
82 	pkgconf_client_t *client = test_client_new();
83 	pkgconf_list_t vars = PKGCONF_LIST_INITIALIZER;
84 	pkgconf_buffer_t bcbuf = PKGCONF_BUFFER_INITIALIZER;
85 	pkgconf_bytecode_t bc;
86 	bool saw_sysroot = false;
87 
88 	seed_variable(&vars, "name", "world");
89 
90 	TEST_ASSERT_TRUE(pkgconf_bytecode_emit_text(&bcbuf, "hello ", 6));
91 	TEST_ASSERT_TRUE(pkgconf_bytecode_emit_var(&bcbuf, "name", 4));
92 	pkgconf_bytecode_from_buffer(&bc, &bcbuf);
93 
94 	pkgconf_buffer_t out = PKGCONF_BUFFER_INITIALIZER;
95 	TEST_ASSERT_TRUE(pkgconf_bytecode_eval(client, &vars, &bc, &out, &saw_sysroot));
96 	TEST_ASSERT_STRCMP_EQ(pkgconf_buffer_str_or_empty(&out), "hello world");
97 
98 	pkgconf_buffer_finalize(&out);
99 	pkgconf_buffer_finalize(&bcbuf);
100 	pkgconf_variable_list_free(&vars);
101 	pkgconf_client_free(client);
102 }
103 
104 static void
test_emit_sysroot_and_eval(void)105 test_emit_sysroot_and_eval(void)
106 {
107 	pkgconf_client_t *client = test_client_new();
108 	pkgconf_list_t vars = PKGCONF_LIST_INITIALIZER;
109 	pkgconf_buffer_t bcbuf = PKGCONF_BUFFER_INITIALIZER;
110 	pkgconf_bytecode_t bc;
111 	bool saw_sysroot = false;
112 
113 	pkgconf_client_set_sysroot_dir(client, "/sysroot");
114 
115 	TEST_ASSERT_TRUE(pkgconf_bytecode_emit_sysroot(&bcbuf));
116 	TEST_ASSERT_TRUE(pkgconf_bytecode_emit_text(&bcbuf, "/usr/include", 12));
117 	pkgconf_bytecode_from_buffer(&bc, &bcbuf);
118 
119 	pkgconf_buffer_t out = PKGCONF_BUFFER_INITIALIZER;
120 	TEST_ASSERT_TRUE(pkgconf_bytecode_eval(client, &vars, &bc, &out, &saw_sysroot));
121 	TEST_ASSERT_TRUE(saw_sysroot);
122 	TEST_ASSERT_STRCMP_EQ(pkgconf_buffer_str_or_empty(&out), "/sysroot/usr/include");
123 
124 	pkgconf_buffer_finalize(&out);
125 	pkgconf_buffer_finalize(&bcbuf);
126 	pkgconf_variable_list_free(&vars);
127 	pkgconf_client_free(client);
128 }
129 
130 static void
test_eval_null_args(void)131 test_eval_null_args(void)
132 {
133 	pkgconf_client_t *client = test_client_new();
134 	pkgconf_list_t vars = PKGCONF_LIST_INITIALIZER;
135 	pkgconf_buffer_t out = PKGCONF_BUFFER_INITIALIZER;
136 	pkgconf_bytecode_t bc = { NULL, 0 };
137 
138 	// NULL client, bc, or out all return false.
139 	TEST_ASSERT_FALSE(pkgconf_bytecode_eval(NULL, &vars, &bc, &out, NULL));
140 	TEST_ASSERT_FALSE(pkgconf_bytecode_eval(client, &vars, NULL, &out, NULL));
141 	TEST_ASSERT_FALSE(pkgconf_bytecode_eval(client, &vars, &bc, NULL, NULL));
142 
143 	pkgconf_buffer_finalize(&out);
144 	pkgconf_variable_list_free(&vars);
145 	pkgconf_client_free(client);
146 }
147 
148 static void
test_compile_null_args(void)149 test_compile_null_args(void)
150 {
151 	pkgconf_buffer_t out = PKGCONF_BUFFER_INITIALIZER;
152 
153 	TEST_ASSERT_FALSE(pkgconf_bytecode_compile(NULL, "x"));
154 	TEST_ASSERT_FALSE(pkgconf_bytecode_compile(&out, NULL));
155 
156 	pkgconf_buffer_finalize(&out);
157 }
158 
159 static void
test_dollar_escape(void)160 test_dollar_escape(void)
161 {
162 	pkgconf_client_t *client = test_client_new();
163 	pkgconf_list_t vars = PKGCONF_LIST_INITIALIZER;
164 	bool saw_sysroot = false;
165 
166 	// $$ collapses to a literal $; the following text is NOT treated as a variable reference
167 	char *out = pkgconf_bytecode_eval_str(client, &vars, "price: $$5 and $${notavar}", &saw_sysroot);
168 	TEST_ASSERT_NONNULL(out);
169 	TEST_ASSERT_STRCMP_EQ(out, "price: $5 and ${notavar}");
170 
171 	free(out);
172 	pkgconf_variable_list_free(&vars);
173 	pkgconf_client_free(client);
174 }
175 
176 static void
test_malformed_unclosed(void)177 test_malformed_unclosed(void)
178 {
179 	pkgconf_client_t *client = test_client_new();
180 	pkgconf_list_t vars = PKGCONF_LIST_INITIALIZER;
181 	bool saw_sysroot = false;
182 
183 	// A ${ with no closing } is emitted as literal text
184 	char *out = pkgconf_bytecode_eval_str(client, &vars, "broken ${unclosed", &saw_sysroot);
185 	TEST_ASSERT_NONNULL(out);
186 	TEST_ASSERT_STRCMP_EQ(out, "broken ${unclosed");
187 
188 	free(out);
189 	pkgconf_variable_list_free(&vars);
190 	pkgconf_client_free(client);
191 }
192 
193 static void
test_empty_braces(void)194 test_empty_braces(void)
195 {
196 	pkgconf_client_t *client = test_client_new();
197 	pkgconf_list_t vars = PKGCONF_LIST_INITIALIZER;
198 	bool saw_sysroot = false;
199 
200 	// ${} has a zero-length name; it's emitted as literal text rather than treated as a variable
201 	char *out = pkgconf_bytecode_eval_str(client, &vars, "empty ${} here", &saw_sysroot);
202 	TEST_ASSERT_NONNULL(out);
203 	TEST_ASSERT_STRCMP_EQ(out, "empty ${} here");
204 
205 	free(out);
206 	pkgconf_variable_list_free(&vars);
207 	pkgconf_client_free(client);
208 }
209 
210 static void
test_compile_time_sysroot(void)211 test_compile_time_sysroot(void)
212 {
213 	pkgconf_client_t *client = test_client_new();
214 	pkgconf_list_t vars = PKGCONF_LIST_INITIALIZER;
215 	bool saw_sysroot = false;
216 
217 	pkgconf_client_set_sysroot_dir(client, "/sysroot");
218 
219 	// ${pc_sysrootdir} is special-cased at compile time into an OP_SYSROOT op rather than a variable lookup
220 	char *out = pkgconf_bytecode_eval_str(client, &vars, "${pc_sysrootdir}/usr/lib", &saw_sysroot);
221 	TEST_ASSERT_NONNULL(out);
222 	TEST_ASSERT_TRUE(saw_sysroot);
223 	TEST_ASSERT_STRCMP_EQ(out, "/sysroot/usr/lib");
224 
225 	free(out);
226 	pkgconf_variable_list_free(&vars);
227 	pkgconf_client_free(client);
228 }
229 
230 static void
test_sysroot_dot_disables(void)231 test_sysroot_dot_disables(void)
232 {
233 	pkgconf_client_t *client = test_client_new();
234 	pkgconf_list_t vars = PKGCONF_LIST_INITIALIZER;
235 	bool saw_sysroot = false;
236 
237 	// A sysroot of "." disables sysroot rewriting: ${pc_sysrootdir} expands to empty
238 	pkgconf_client_set_sysroot_dir(client, ".");
239 
240 	char *out = pkgconf_bytecode_eval_str(client, &vars, "${pc_sysrootdir}/usr/lib", &saw_sysroot);
241 	TEST_ASSERT_NONNULL(out);
242 	TEST_ASSERT_STRCMP_EQ(out, "/usr/lib");
243 
244 	free(out);
245 	pkgconf_variable_list_free(&vars);
246 	pkgconf_client_free(client);
247 }
248 
249 static void
test_sysroot_root_disables(void)250 test_sysroot_root_disables(void)
251 {
252 	pkgconf_client_t *client = test_client_new();
253 	pkgconf_list_t vars = PKGCONF_LIST_INITIALIZER;
254 	bool saw_sysroot = false;
255 
256 	// A sysroot of "/" (the root dir) disables rewriting.
257 	pkgconf_client_set_sysroot_dir(client, "/");
258 
259 	char *out = pkgconf_bytecode_eval_str(client, &vars, "${pc_sysrootdir}/usr/lib", &saw_sysroot);
260 	TEST_ASSERT_NONNULL(out);
261 	TEST_ASSERT_STRCMP_EQ(out, "/usr/lib");
262 
263 	free(out);
264 	pkgconf_variable_list_free(&vars);
265 	pkgconf_client_free(client);
266 }
267 
268 static void
test_sysroot_trailing_slash_trimmed(void)269 test_sysroot_trailing_slash_trimmed(void)
270 {
271 	pkgconf_client_t *client = test_client_new();
272 	pkgconf_list_t vars = PKGCONF_LIST_INITIALIZER;
273 	bool saw_sysroot = false;
274 
275 	// Trailing slashes on the sysroot are normalized away, so the result doesn't get a doubled slash
276 	pkgconf_client_set_sysroot_dir(client, "/sysroot///");
277 
278 	char *out = pkgconf_bytecode_eval_str(client, &vars, "${pc_sysrootdir}/usr/lib", &saw_sysroot);
279 	TEST_ASSERT_NONNULL(out);
280 	TEST_ASSERT_TRUE(saw_sysroot);
281 	TEST_ASSERT_STRCMP_EQ(out, "/sysroot/usr/lib");
282 
283 	free(out);
284 	pkgconf_variable_list_free(&vars);
285 	pkgconf_client_free(client);
286 }
287 
288 static void
test_sysroot_normalizes_to_root_disables(void)289 test_sysroot_normalizes_to_root_disables(void)
290 {
291 	pkgconf_client_t *client = test_client_new();
292 	pkgconf_list_t vars = PKGCONF_LIST_INITIALIZER;
293 	bool saw_sysroot = false;
294 
295 	/* A sysroot of "//" survives the bare-"/" early check but normalizes down to "/" after
296 	 * trailing-slash trimming, which then disables rewriting (empty sysroot) */
297 	pkgconf_client_set_sysroot_dir(client, "//");
298 
299 	char *out = pkgconf_bytecode_eval_str(client, &vars, "${pc_sysrootdir}/usr/lib", &saw_sysroot);
300 	TEST_ASSERT_NONNULL(out);
301 	TEST_ASSERT_STRCMP_EQ(out, "/usr/lib");
302 
303 	free(out);
304 	pkgconf_variable_list_free(&vars);
305 	pkgconf_client_free(client);
306 }
307 
308 static void
test_circular_reference(void)309 test_circular_reference(void)
310 {
311 	pkgconf_client_t *client = test_client_new();
312 	pkgconf_list_t vars = PKGCONF_LIST_INITIALIZER;
313 	bool saw_sysroot = false;
314 
315 	// A variable that references itself must not infinite-loop: the `expanding` guard breaks the cycle
316 	seed_variable(&vars, "loop", "${loop}");
317 
318 	char *out = pkgconf_bytecode_eval_str(client, &vars, "${loop}", &saw_sysroot);
319 
320 	/* The self-reference is broken; behavior is "expands to nothing further"
321 	 * We mostly care that it returns rather than hanging or crashing */
322 	if (out != NULL)
323 		free(out);
324 
325 	pkgconf_variable_list_free(&vars);
326 	pkgconf_client_free(client);
327 }
328 
329 static void
test_references_var(void)330 test_references_var(void)
331 {
332 	pkgconf_buffer_t bcbuf = PKGCONF_BUFFER_INITIALIZER;
333 
334 	pkgconf_bytecode_compile(&bcbuf, "-I${includedir} -L${libdir}");
335 
336 	TEST_ASSERT_TRUE(pkgconf_bytecode_references_var(&bcbuf, "includedir"));
337 	TEST_ASSERT_TRUE(pkgconf_bytecode_references_var(&bcbuf, "libdir"));
338 	TEST_ASSERT_FALSE(pkgconf_bytecode_references_var(&bcbuf, "prefix"));
339 
340 	TEST_ASSERT_FALSE(pkgconf_bytecode_references_var(NULL, "x"));
341 	TEST_ASSERT_FALSE(pkgconf_bytecode_references_var(&bcbuf, NULL));
342 
343 	pkgconf_buffer_finalize(&bcbuf);
344 }
345 
346 static void
test_references_var_text_only(void)347 test_references_var_text_only(void)
348 {
349 	pkgconf_buffer_t bcbuf = PKGCONF_BUFFER_INITIALIZER;
350 
351 	pkgconf_bytecode_compile(&bcbuf, "just plain text");
352 
353 	TEST_ASSERT_FALSE(pkgconf_bytecode_references_var(&bcbuf, "anything"));
354 
355 	pkgconf_buffer_finalize(&bcbuf);
356 }
357 
358 static void
test_rewrite_selfrefs(void)359 test_rewrite_selfrefs(void)
360 {
361 	pkgconf_client_t *client = test_client_new();
362 	pkgconf_list_t vars = PKGCONF_LIST_INITIALIZER;
363 	bool saw_sysroot = false;
364 
365 	pkgconf_buffer_t prev = PKGCONF_BUFFER_INITIALIZER;
366 	pkgconf_bytecode_compile(&prev, "old");
367 
368 	pkgconf_buffer_t rhs = PKGCONF_BUFFER_INITIALIZER;
369 	pkgconf_bytecode_compile(&rhs, "${foo} new");
370 
371 	pkgconf_buffer_t rewritten = PKGCONF_BUFFER_INITIALIZER;
372 	TEST_ASSERT_TRUE(pkgconf_bytecode_rewrite_selfrefs(&rewritten, &rhs, "foo", &prev));
373 
374 	pkgconf_bytecode_t bc;
375 	pkgconf_bytecode_from_buffer(&bc, &rewritten);
376 
377 	pkgconf_buffer_t out = PKGCONF_BUFFER_INITIALIZER;
378 	TEST_ASSERT_TRUE(pkgconf_bytecode_eval(client, &vars, &bc, &out, &saw_sysroot));
379 	TEST_ASSERT_STRCMP_EQ(pkgconf_buffer_str_or_empty(&out), "old new");
380 
381 	pkgconf_buffer_finalize(&out);
382 	pkgconf_buffer_finalize(&rewritten);
383 	pkgconf_buffer_finalize(&rhs);
384 	pkgconf_buffer_finalize(&prev);
385 	pkgconf_variable_list_free(&vars);
386 	pkgconf_client_free(client);
387 }
388 
389 static void
test_rewrite_selfrefs_no_match(void)390 test_rewrite_selfrefs_no_match(void)
391 {
392 	pkgconf_client_t *client = test_client_new();
393 	pkgconf_list_t vars = PKGCONF_LIST_INITIALIZER;
394 	bool saw_sysroot = false;
395 
396 	pkgconf_buffer_t prev = PKGCONF_BUFFER_INITIALIZER;
397 	pkgconf_bytecode_compile(&prev, "PREV");
398 
399 	pkgconf_buffer_t rhs = PKGCONF_BUFFER_INITIALIZER;
400 	pkgconf_bytecode_compile(&rhs, "${other} tail");
401 
402 	pkgconf_buffer_t rewritten = PKGCONF_BUFFER_INITIALIZER;
403 	TEST_ASSERT_TRUE(pkgconf_bytecode_rewrite_selfrefs(&rewritten, &rhs, "foo", &prev));
404 
405 	// ${other} survives; seed it so eval can resolve it
406 	seed_variable(&vars, "other", "OTHER");
407 
408 	pkgconf_bytecode_t bc;
409 	pkgconf_bytecode_from_buffer(&bc, &rewritten);
410 
411 	pkgconf_buffer_t out = PKGCONF_BUFFER_INITIALIZER;
412 	TEST_ASSERT_TRUE(pkgconf_bytecode_eval(client, &vars, &bc, &out, &saw_sysroot));
413 	TEST_ASSERT_STRCMP_EQ(pkgconf_buffer_str_or_empty(&out), "OTHER tail");
414 
415 	pkgconf_buffer_finalize(&out);
416 	pkgconf_buffer_finalize(&rewritten);
417 	pkgconf_buffer_finalize(&rhs);
418 	pkgconf_buffer_finalize(&prev);
419 	pkgconf_variable_list_free(&vars);
420 	pkgconf_client_free(client);
421 }
422 
423 static void
test_eval_plain_text(void)424 test_eval_plain_text(void)
425 {
426 	pkgconf_client_t *client = test_client_new();
427 	pkgconf_list_t vars = PKGCONF_LIST_INITIALIZER;
428 	bool saw_sysroot = false;
429 
430 	// A string with no variable references should round-trip unchanged.
431 	char *out = pkgconf_bytecode_eval_str(client, &vars, "plain text value", &saw_sysroot);
432 
433 	TEST_ASSERT_NONNULL(out);
434 	TEST_ASSERT_STRCMP_EQ(out, "plain text value");
435 	TEST_ASSERT_FALSE(saw_sysroot);
436 
437 	free(out);
438 	pkgconf_variable_list_free(&vars);
439 	pkgconf_client_free(client);
440 }
441 
442 static void
test_eval_variable_substitution(void)443 test_eval_variable_substitution(void)
444 {
445 	pkgconf_client_t *client = test_client_new();
446 	pkgconf_list_t vars = PKGCONF_LIST_INITIALIZER;
447 	bool saw_sysroot = false;
448 
449 	seed_variable(&vars, "prefix", "/opt/foo");
450 
451 	char *out = pkgconf_bytecode_eval_str(client, &vars, "${prefix}/lib", &saw_sysroot);
452 
453 	TEST_ASSERT_NONNULL(out);
454 	TEST_ASSERT_STRCMP_EQ(out, "/opt/foo/lib");
455 	TEST_ASSERT_FALSE(saw_sysroot);
456 
457 	free(out);
458 	pkgconf_variable_list_free(&vars);
459 	pkgconf_client_free(client);
460 }
461 
462 static void
test_eval_nested_variables(void)463 test_eval_nested_variables(void)
464 {
465 	pkgconf_client_t *client = test_client_new();
466 	pkgconf_list_t vars = PKGCONF_LIST_INITIALIZER;
467 	bool saw_sysroot = false;
468 
469 	// Recursive expansion: libdir references prefix.
470 	seed_variable(&vars, "prefix", "/usr/local");
471 	seed_variable(&vars, "libdir", "${prefix}/lib");
472 
473 	char *out = pkgconf_bytecode_eval_str(client, &vars, "-L${libdir}", &saw_sysroot);
474 
475 	TEST_ASSERT_NONNULL(out);
476 	TEST_ASSERT_STRCMP_EQ(out, "-L/usr/local/lib");
477 
478 	free(out);
479 	pkgconf_variable_list_free(&vars);
480 	pkgconf_client_free(client);
481 }
482 
483 static void
test_eval_undefined_variable(void)484 test_eval_undefined_variable(void)
485 {
486 	pkgconf_client_t *client = test_client_new();
487 	pkgconf_list_t vars = PKGCONF_LIST_INITIALIZER;
488 	bool saw_sysroot = false;
489 
490 	// Referencing an undefined variable should produce empty substitution, not failure
491 	char *out = pkgconf_bytecode_eval_str(client, &vars, "prefix=${nonexistent}/end", &saw_sysroot);
492 
493 	TEST_ASSERT_NONNULL(out);
494 	TEST_ASSERT_STRCMP_EQ(out, "prefix=/end");
495 
496 	free(out);
497 	pkgconf_variable_list_free(&vars);
498 	pkgconf_client_free(client);
499 }
500 
501 static void
test_eval_multiple_variables(void)502 test_eval_multiple_variables(void)
503 {
504 	pkgconf_client_t *client = test_client_new();
505 	pkgconf_list_t vars = PKGCONF_LIST_INITIALIZER;
506 	bool saw_sysroot = false;
507 
508 	seed_variable(&vars, "prefix", "/usr");
509 	seed_variable(&vars, "exec_prefix", "/usr/local");
510 
511 	char *out = pkgconf_bytecode_eval_str(client, &vars, "-I${prefix}/include -L${exec_prefix}/lib", &saw_sysroot);
512 
513 	TEST_ASSERT_NONNULL(out);
514 	TEST_ASSERT_STRCMP_EQ(out, "-I/usr/include -L/usr/local/lib");
515 
516 	free(out);
517 	pkgconf_variable_list_free(&vars);
518 	pkgconf_client_free(client);
519 }
520 
521 static void
test_eval_empty_input(void)522 test_eval_empty_input(void)
523 {
524 	pkgconf_client_t *client = test_client_new();
525 	pkgconf_list_t vars = PKGCONF_LIST_INITIALIZER;
526 	bool saw_sysroot = false;
527 
528 	char *out = pkgconf_bytecode_eval_str(client, &vars, "", &saw_sysroot);
529 
530 	// An empty input may evaluate to either NULL or an empty string
531 	if (out != NULL)
532 	{
533 		TEST_ASSERT_STRCMP_EQ(out, "");
534 		free(out);
535 	}
536 
537 	pkgconf_variable_list_free(&vars);
538 	pkgconf_client_free(client);
539 }
540 
541 static void
test_eval_sysroot_detection(void)542 test_eval_sysroot_detection(void)
543 {
544 	pkgconf_client_t *client = test_client_new();
545 	pkgconf_list_t vars = PKGCONF_LIST_INITIALIZER;
546 	bool saw_sysroot = false;
547 
548 	pkgconf_client_set_sysroot_dir(client, "/sysroot");
549 
550 	seed_variable(&vars, "pc_sysrootdir", "/sysroot");
551 	seed_variable(&vars, "includedir", "${pc_sysrootdir}/usr/include");
552 
553 	char *out = pkgconf_bytecode_eval_str(client, &vars, "${includedir}", &saw_sysroot);
554 
555 	TEST_ASSERT_NONNULL(out);
556 	TEST_ASSERT_TRUE(saw_sysroot);
557 
558 	free(out);
559 	pkgconf_variable_list_free(&vars);
560 	pkgconf_client_free(client);
561 }
562 
563 static void
test_compile_produces_nonempty_buffer(void)564 test_compile_produces_nonempty_buffer(void)
565 {
566 	pkgconf_buffer_t bc = PKGCONF_BUFFER_INITIALIZER;
567 
568 	pkgconf_bytecode_compile(&bc, "hello ${world}");
569 
570 	// The compiled bytecode buffer should be non-empty
571 	TEST_ASSERT_NE(pkgconf_buffer_len(&bc), 0);
572 
573 	pkgconf_buffer_finalize(&bc);
574 }
575 
576 static void
test_compile_eval_roundtrip(void)577 test_compile_eval_roundtrip(void)
578 {
579 	pkgconf_client_t *client = test_client_new();
580 	pkgconf_list_t vars = PKGCONF_LIST_INITIALIZER;
581 	bool saw_sysroot = false;
582 
583 	// Compile directly, then evaluate via the bc field on a variable
584 	seed_variable(&vars, "name", "world");
585 
586 	pkgconf_variable_t *v = pkgconf_variable_get_or_create(&vars, "greeting");
587 	TEST_ASSERT_NONNULL(v);
588 	pkgconf_buffer_reset(&v->bcbuf);
589 	pkgconf_bytecode_compile(&v->bcbuf, "hello ${name}");
590 	pkgconf_bytecode_from_buffer(&v->bc, &v->bcbuf);
591 
592 	char *out = pkgconf_variable_eval_str(client, &vars, v, &saw_sysroot);
593 	TEST_ASSERT_NONNULL(out);
594 	TEST_ASSERT_STRCMP_EQ(out, "hello world");
595 
596 	free(out);
597 	pkgconf_variable_list_free(&vars);
598 	pkgconf_client_free(client);
599 }
600 
601 int
main(int argc,char * argv[])602 main(int argc, char *argv[])
603 {
604 	(void) argc;
605 	const char *basename = pkgconf_path_find_basename(argv[0]);
606 
607 	TEST_RUN(basename, test_eval_plain_text);
608 	TEST_RUN(basename, test_eval_empty_input);
609 	TEST_RUN(basename, test_eval_variable_substitution);
610 	TEST_RUN(basename, test_eval_nested_variables);
611 	TEST_RUN(basename, test_eval_multiple_variables);
612 	TEST_RUN(basename, test_eval_undefined_variable);
613 	TEST_RUN(basename, test_eval_sysroot_detection);
614 	TEST_RUN(basename, test_eval_null_args);
615 
616 	TEST_RUN(basename, test_emit_guards);
617 	TEST_RUN(basename, test_emit_text_and_eval);
618 	TEST_RUN(basename, test_emit_var_and_eval);
619 	TEST_RUN(basename, test_emit_sysroot_and_eval);
620 
621 	TEST_RUN(basename, test_dollar_escape);
622 	TEST_RUN(basename, test_malformed_unclosed);
623 	TEST_RUN(basename, test_empty_braces);
624 
625 	TEST_RUN(basename, test_circular_reference);
626 	TEST_RUN(basename, test_references_var);
627 	TEST_RUN(basename, test_references_var_text_only);
628 
629 	TEST_RUN(basename, test_compile_time_sysroot);
630 	TEST_RUN(basename, test_sysroot_dot_disables);
631 	TEST_RUN(basename, test_sysroot_root_disables);
632 	TEST_RUN(basename, test_sysroot_trailing_slash_trimmed);
633 	TEST_RUN(basename, test_sysroot_normalizes_to_root_disables);
634 
635 	TEST_RUN(basename, test_rewrite_selfrefs);
636 	TEST_RUN(basename, test_rewrite_selfrefs_no_match);
637 
638 	TEST_RUN(basename, test_compile_eval_roundtrip);
639 	TEST_RUN(basename, test_compile_produces_nonempty_buffer);
640 	TEST_RUN(basename, test_compile_null_args);
641 
642 	return EXIT_SUCCESS;
643 }
644