1 /*
2 * test-tuple.c
3 * Tests for the public libpkgconf tuple API.
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/stdinc.h>
19 #include <libpkgconf/libpkgconf.h>
20 #include "test-api.h"
21
22 static void
test_tuple_add_and_find(void)23 test_tuple_add_and_find(void)
24 {
25 pkgconf_client_t *client = test_client_new();
26 pkgconf_list_t tuples = PKGCONF_LIST_INITIALIZER;
27
28 /* parse=false means store the value verbatim, no bytecode compile.
29 * That's the right mode for storing already-evaluated values. */
30 pkgconf_tuple_t *t = pkgconf_tuple_add(client, &tuples, "prefix", "/opt/foo", false, 0);
31 TEST_ASSERT_NONNULL(t);
32
33 const char *found = pkgconf_tuple_find(client, &tuples, "prefix");
34 TEST_ASSERT_NONNULL(found);
35 TEST_ASSERT_STRCMP_EQ(found, "/opt/foo");
36
37 pkgconf_tuple_free(&tuples);
38 pkgconf_client_free(client);
39 }
40
41 static void
test_tuple_find_absent(void)42 test_tuple_find_absent(void)
43 {
44 pkgconf_client_t *client = test_client_new();
45 pkgconf_list_t tuples = PKGCONF_LIST_INITIALIZER;
46
47 const char *found = pkgconf_tuple_find(client, &tuples, "nonexistent");
48 TEST_ASSERT_EMPTY_STRING(found);
49
50 pkgconf_tuple_free(&tuples);
51 pkgconf_client_free(client);
52 }
53
54 static void
test_tuple_add_multiple(void)55 test_tuple_add_multiple(void)
56 {
57 pkgconf_client_t *client = test_client_new();
58 pkgconf_list_t tuples = PKGCONF_LIST_INITIALIZER;
59
60 pkgconf_tuple_add(client, &tuples, "prefix", "/usr", false, 0);
61 pkgconf_tuple_add(client, &tuples, "exec_prefix", "/usr/local", false, 0);
62 pkgconf_tuple_add(client, &tuples, "libdir", "/usr/lib", false, 0);
63
64 TEST_ASSERT_STRCMP_EQ(pkgconf_tuple_find(client, &tuples, "prefix"), "/usr");
65 TEST_ASSERT_STRCMP_EQ(pkgconf_tuple_find(client, &tuples, "exec_prefix"), "/usr/local");
66 TEST_ASSERT_STRCMP_EQ(pkgconf_tuple_find(client, &tuples, "libdir"), "/usr/lib");
67 TEST_ASSERT_EMPTY_STRING(pkgconf_tuple_find(client, &tuples, "datadir"));
68
69 pkgconf_tuple_free(&tuples);
70 pkgconf_client_free(client);
71 }
72
73 static void
test_tuple_global_add_and_find(void)74 test_tuple_global_add_and_find(void)
75 {
76 pkgconf_client_t *client = test_client_new();
77
78 pkgconf_tuple_add_global(client, "PKG_TEST_KEY", "test_value");
79
80 const char *found = pkgconf_tuple_find_global(client, "PKG_TEST_KEY");
81 TEST_ASSERT_NONNULL(found);
82 TEST_ASSERT_STRCMP_EQ(found, "test_value");
83
84 pkgconf_tuple_free_global(client);
85 pkgconf_client_free(client);
86 }
87
88 static void
test_tuple_global_find_absent(void)89 test_tuple_global_find_absent(void)
90 {
91 pkgconf_client_t *client = test_client_new();
92
93 const char *found = pkgconf_tuple_find_global(client, "DOES_NOT_EXIST");
94 TEST_ASSERT_EMPTY_STRING(found);
95
96 pkgconf_client_free(client);
97 }
98
99 static void
test_tuple_define_global_kv_form(void)100 test_tuple_define_global_kv_form(void)
101 {
102 pkgconf_client_t *client = test_client_new();
103
104 /* This is the exact path --define-variable=KEY=VALUE takes
105 * pkgconf_tuple_define_global parses the "key=value" form and inserts it as a global tuple. */
106 pkgconf_tuple_define_global(client, "myvar=myvalue");
107
108 const char *found = pkgconf_tuple_find_global(client, "myvar");
109 TEST_ASSERT_NONNULL(found);
110 TEST_ASSERT_STRCMP_EQ(found, "myvalue");
111
112 pkgconf_tuple_free_global(client);
113 pkgconf_client_free(client);
114 }
115
116 static void
test_tuple_define_global_with_equals_in_value(void)117 test_tuple_define_global_with_equals_in_value(void)
118 {
119 pkgconf_client_t *client = test_client_new();
120
121 // Only the first '=' should split key from value; subsequent '=' characters belong to the value.
122 pkgconf_tuple_define_global(client, "CFLAGS=-DFOO=bar");
123
124 const char *found = pkgconf_tuple_find_global(client, "CFLAGS");
125 TEST_ASSERT_NONNULL(found);
126 TEST_ASSERT_STRCMP_EQ(found, "-DFOO=bar");
127
128 pkgconf_tuple_free_global(client);
129 pkgconf_client_free(client);
130 }
131
132 static void
test_tuple_global_multiple(void)133 test_tuple_global_multiple(void)
134 {
135 pkgconf_client_t *client = test_client_new();
136
137 pkgconf_tuple_define_global(client, "a=1");
138 pkgconf_tuple_define_global(client, "b=2");
139 pkgconf_tuple_define_global(client, "c=3");
140
141 TEST_ASSERT_STRCMP_EQ(pkgconf_tuple_find_global(client, "a"), "1");
142 TEST_ASSERT_STRCMP_EQ(pkgconf_tuple_find_global(client, "b"), "2");
143 TEST_ASSERT_STRCMP_EQ(pkgconf_tuple_find_global(client, "c"), "3");
144
145 pkgconf_tuple_free_global(client);
146 pkgconf_client_free(client);
147 }
148
149 static void
test_tuple_global_free_resets(void)150 test_tuple_global_free_resets(void)
151 {
152 pkgconf_client_t *client = test_client_new();
153
154 pkgconf_tuple_define_global(client, "temp=value");
155 TEST_ASSERT_NONNULL(pkgconf_tuple_find_global(client, "temp"));
156
157 pkgconf_tuple_free_global(client);
158 TEST_ASSERT_EMPTY_STRING(pkgconf_tuple_find_global(client, "temp"));
159
160 pkgconf_client_free(client);
161 }
162
163 static void
test_tuple_free_empty(void)164 test_tuple_free_empty(void)
165 {
166 pkgconf_list_t tuples = PKGCONF_LIST_INITIALIZER;
167
168 // Freeing an empty list should be a no-op. Mostly an ASan/leak check smoke test.
169 pkgconf_tuple_free(&tuples);
170 }
171
172 static void
test_tuple_define_variable_end_to_end(void)173 test_tuple_define_variable_end_to_end(void)
174 {
175 pkgconf_client_t *client = test_client_new();
176 pkgconf_list_t vars = PKGCONF_LIST_INITIALIZER;
177 bool saw_sysroot = false;
178
179 pkgconf_tuple_define_global(client, "myprefix=/opt/custom");
180
181 char *out = pkgconf_bytecode_eval_str(client, &vars, "-I${myprefix}/include", &saw_sysroot);
182
183 TEST_ASSERT_NONNULL(out);
184 TEST_ASSERT_STRCMP_EQ(out, "-I/opt/custom/include");
185
186 free(out);
187 pkgconf_variable_list_free(&vars);
188 pkgconf_tuple_free_global(client);
189 pkgconf_client_free(client);
190 }
191
192 static void
test_tuple_define_variable_overrides_local(void)193 test_tuple_define_variable_overrides_local(void)
194 {
195 pkgconf_client_t *client = test_client_new();
196 pkgconf_list_t vars = PKGCONF_LIST_INITIALIZER;
197 bool saw_sysroot = false;
198
199 pkgconf_variable_t *v = pkgconf_variable_get_or_create(&vars, "prefix");
200 TEST_ASSERT_NONNULL(v);
201 pkgconf_buffer_reset(&v->bcbuf);
202 pkgconf_bytecode_compile(&v->bcbuf, "/usr");
203 pkgconf_bytecode_from_buffer(&v->bc, &v->bcbuf);
204
205 // Simulate user passing --define-variable=prefix=/custom.
206 pkgconf_tuple_define_global(client, "prefix=/custom");
207
208 char *out = pkgconf_bytecode_eval_str(client, &vars, "${prefix}/lib", &saw_sysroot);
209
210 TEST_ASSERT_NONNULL(out);
211 TEST_ASSERT_STRCMP_EQ(out, "/custom/lib");
212
213 free(out);
214 pkgconf_variable_list_free(&vars);
215 pkgconf_tuple_free_global(client);
216 pkgconf_client_free(client);
217 }
218
219 static void
test_tuple_escaped_quote(void)220 test_tuple_escaped_quote(void)
221 {
222 pkgconf_client_t *client = test_client_new();
223 pkgconf_list_t tuples = PKGCONF_LIST_INITIALIZER;
224
225 // A double-quoted value containing an escaped double-quote
226 pkgconf_tuple_t *t = pkgconf_tuple_add(client, &tuples, "key", "\"a\\\"b\"", true, 0);
227 TEST_ASSERT_NONNULL(t);
228
229 const char *found = pkgconf_tuple_find(client, &tuples, "key");
230 TEST_ASSERT_NONNULL(found);
231 TEST_ASSERT_STRCMP_EQ(found, "a\"b");
232
233 pkgconf_tuple_free(&tuples);
234 pkgconf_client_free(client);
235 }
236
237 int
main(int argc,char * argv[])238 main(int argc, char *argv[])
239 {
240 (void) argc;
241 const char *basename = pkgconf_path_find_basename(argv[0]);
242
243 TEST_RUN(basename, test_tuple_add_and_find);
244 TEST_RUN(basename, test_tuple_find_absent);
245 TEST_RUN(basename, test_tuple_add_multiple);
246 TEST_RUN(basename, test_tuple_free_empty);
247 TEST_RUN(basename, test_tuple_global_add_and_find);
248 TEST_RUN(basename, test_tuple_global_find_absent);
249 TEST_RUN(basename, test_tuple_define_global_kv_form);
250 TEST_RUN(basename, test_tuple_define_global_with_equals_in_value);
251 TEST_RUN(basename, test_tuple_global_multiple);
252 TEST_RUN(basename, test_tuple_global_free_resets);
253 TEST_RUN(basename, test_tuple_define_variable_end_to_end);
254 TEST_RUN(basename, test_tuple_define_variable_overrides_local);
255 TEST_RUN(basename, test_tuple_escaped_quote);
256
257 return EXIT_SUCCESS;
258 }
259